@admin
Triển khai ISMS ISO 27001 và quản trị an ninh mạng cho HealthTech/MedTech: đánh giá rủi ro, kiểm soát, chứng nhận và audit bảo mật.
---
name: "information-security-manager-iso27001"
description: ISO 27001 ISMS implementation and cybersecurity governance for HealthTech and MedTech companies. Use for ISMS design, security risk assessment, control implementation, ISO 27001 certification, security audits, incident response, and compliance verification. Covers ISO 27001, ISO 27002, healthcare security, and medical device cybersecurity.
---
# Information Security Manager - ISO 27001
Implement and manage Information Security Management Systems (ISMS) aligned with ISO 27001:2022 and healthcare regulatory requirements.
---
## Table of Contents
- [Trigger Phrases](#trigger-phrases)
- [Quick Start](#quick-start)
- [Tools](#tools)
- [Workflows](#workflows)
- [Reference Guides](#reference-guides)
- [Validation Checkpoints](#validation-checkpoints)
---
## Trigger Phrases
Use this skill when you hear:
- "implement ISO 27001"
- "ISMS implementation"
- "security risk assessment"
- "information security policy"
- "ISO 27001 certification"
- "security controls implementation"
- "incident response plan"
- "healthcare data security"
- "medical device cybersecurity"
- "security compliance audit"
---
## Quick Start
### Run Security Risk Assessment
```bash
python scripts/risk_assessment.py --scope "patient-data-system" --output risk_register.json
```
### Check Compliance Status
```bash
python scripts/compliance_checker.py --standard iso27001 --controls-file controls.csv
```
### Generate Gap Analysis Report
```bash
python scripts/compliance_checker.py --standard iso27001 --gap-analysis --output gaps.md
```
---
## Tools
### risk_assessment.py
Automated security risk assessment following ISO 27001 Clause 6.1.2 methodology.
**Usage:**
```bash
# Full risk assessment
python scripts/risk_assessment.py --scope "cloud-infrastructure" --output risks.json
# Healthcare-specific assessment
python scripts/risk_assessment.py --scope "ehr-system" --template healthcare --output risks.json
# Quick asset-based assessment
python scripts/risk_assessment.py --assets assets.csv --output risks.json
```
**Parameters:**
| Parameter | Required | Description |
|-----------|----------|-------------|
| `--scope` | Yes | System or area to assess |
| `--template` | No | Assessment template: `general`, `healthcare`, `cloud` |
| `--assets` | No | CSV file with asset inventory |
| `--output` | No | Output file (default: stdout) |
| `--format` | No | Output format: `json`, `csv`, `markdown` |
**Output:**
- Asset inventory with classification
- Threat and vulnerability mapping
- Risk scores (likelihood × impact)
- Treatment recommendations
- Residual risk calculations
### compliance_checker.py
Verify ISO 27001/27002 control implementation status.
**Usage:**
```bash
# Check all ISO 27001 controls
python scripts/compliance_checker.py --standard iso27001
# Gap analysis with recommendations
python scripts/compliance_checker.py --standard iso27001 --gap-analysis
# Check specific control domains
python scripts/compliance_checker.py --standard iso27001 --domains "access-control,cryptography"
# Export compliance report
python scripts/compliance_checker.py --standard iso27001 --output compliance_report.md
```
**Parameters:**
| Parameter | Required | Description |
|-----------|----------|-------------|
| `--standard` | Yes | Standard to check: `iso27001`, `iso27002`, `hipaa` |
| `--controls-file` | No | CSV with current control status |
| `--gap-analysis` | No | Include remediation recommendations |
| `--domains` | No | Specific control domains to check |
| `--output` | No | Output file path |
**Output:**
- Control implementation status
- Compliance percentage by domain
- Gap analysis with priorities
- Remediation recommendations
---
## Workflows
### Workflow 1: ISMS Implementation
**Step 1: Define Scope and Context**
Document organizational context and ISMS boundaries:
- Identify interested parties and requirements
- Define ISMS scope and boundaries
- Document internal/external issues
**Validation:** Scope statement reviewed and approved by management.
**Step 2: Conduct Risk Assessment**
```bash
python scripts/risk_assessment.py --scope "full-organization" --template general --output initial_risks.json
```
- Identify information assets
- Assess threats and vulnerabilities
- Calculate risk levels
- Determine risk treatment options
**Validation:** Risk register contains all critical assets with assigned owners.
**Step 3: Select and Implement Controls**
Map risks to ISO 27002 controls:
```bash
python scripts/compliance_checker.py --standard iso27002 --gap-analysis --output control_gaps.md
```
Control categories:
- Organizational (policies, roles, responsibilities)
- People (screening, awareness, training)
- Physical (perimeters, equipment, media)
- Technological (access, crypto, network, application)
**Validation:** Statement of Applicability (SoA) documents all controls with justification.
**Step 4: Establish Monitoring**
Define security metrics:
- Incident count and severity trends
- Control effectiveness scores
- Training completion rates
- Audit findings closure rate
**Validation:** Dashboard shows real-time compliance status.
### Workflow 2: Security Risk Assessment
**Step 1: Asset Identification**
Create asset inventory:
| Asset Type | Examples | Classification |
|------------|----------|----------------|
| Information | Patient records, source code | Confidential |
| Software | EHR system, APIs | Critical |
| Hardware | Servers, medical devices | High |
| Services | Cloud hosting, backup | High |
| People | Admin accounts, developers | Varies |
**Validation:** All assets have assigned owners and classifications.
**Step 2: Threat Analysis**
Identify threats per asset category:
| Asset | Threats | Likelihood |
|-------|---------|------------|
| Patient data | Unauthorized access, breach | High |
| Medical devices | Malware, tampering | Medium |
| Cloud services | Misconfiguration, outage | Medium |
| Credentials | Phishing, brute force | High |
**Validation:** Threat model covers top-10 industry threats.
**Step 3: Vulnerability Assessment**
```bash
python scripts/risk_assessment.py --scope "network-infrastructure" --output vuln_risks.json
```
Document vulnerabilities:
- Technical (unpatched systems, weak configs)
- Process (missing procedures, gaps)
- People (lack of training, insider risk)
**Validation:** Vulnerability scan results mapped to risk register.
**Step 4: Risk Evaluation and Treatment**
Calculate risk: `Risk = Likelihood × Impact`
| Risk Level | Score | Treatment |
|------------|-------|-----------|
| Critical | 20-25 | Immediate action required |
| High | 15-19 | Treatment plan within 30 days |
| Medium | 10-14 | Treatment plan within 90 days |
| Low | 5-9 | Accept or monitor |
| Minimal | 1-4 | Accept |
**Validation:** All high/critical risks have approved treatment plans.
### Workflow 3: Incident Response
**Step 1: Detection and Reporting**
Incident categories:
- Security breach (unauthorized access)
- Malware infection
- Data leakage
- System compromise
- Policy violation
**Validation:** Incident logged within 15 minutes of detection.
**Step 2: Triage and Classification**
| Severity | Criteria | Response Time |
|----------|----------|---------------|
| Critical | Data breach, system down | Immediate |
| High | Active threat, significant risk | 1 hour |
| Medium | Contained threat, limited impact | 4 hours |
| Low | Minor violation, no impact | 24 hours |
**Validation:** Severity assigned and escalation triggered if needed.
**Step 3: Containment and Eradication**
Immediate actions:
1. Isolate affected systems
2. Preserve evidence
3. Block threat vectors
4. Remove malicious artifacts
**Validation:** Containment confirmed, no ongoing compromise.
**Step 4: Recovery and Lessons Learned**
Post-incident activities:
1. Restore systems from clean backups
2. Verify integrity before reconnection
3. Document timeline and actions
4. Conduct post-incident review
5. Update controls and procedures
**Validation:** Post-incident report completed within 5 business days.
---
## Reference Guides
### When to Use Each Reference
**references/iso27001-controls.md**
- Control selection for SoA
- Implementation guidance
- Evidence requirements
- Audit preparation
**references/risk-assessment-guide.md**
- Risk methodology selection
- Asset classification criteria
- Threat modeling approaches
- Risk calculation methods
**references/incident-response.md**
- Response procedures
- Escalation matrices
- Communication templates
- Recovery checklists
---
## Validation Checkpoints
### ISMS Implementation Validation
| Phase | Checkpoint | Evidence Required |
|-------|------------|-------------------|
| Scope | Scope approved | Signed scope document |
| Risk | Register complete | Risk register with owners |
| Controls | SoA approved | Statement of Applicability |
| Operation | Metrics active | Dashboard screenshots |
| Audit | Internal audit done | Audit report |
### Certification Readiness
Before Stage 1 audit:
- [ ] ISMS scope documented and approved
- [ ] Information security policy published
- [ ] Risk assessment completed
- [ ] Statement of Applicability finalized
- [ ] Internal audit conducted
- [ ] Management review completed
- [ ] Nonconformities addressed
Before Stage 2 audit:
- [ ] Controls implemented and operational
- [ ] Evidence of effectiveness available
- [ ] Staff trained and aware
- [ ] Incidents logged and managed
- [ ] Metrics collected for 3+ months
### Compliance Verification
Run periodic checks:
```bash
# Monthly compliance check
python scripts/compliance_checker.py --standard iso27001 --output monthly_$(date +%Y%m).md
# Quarterly gap analysis
python scripts/compliance_checker.py --standard iso27001 --gap-analysis --output quarterly_gaps.md
```
---
## Worked Example: Healthcare Risk Assessment
**Scenario:** Assess security risks for a patient data management system.
### Step 1: Define Assets
```bash
python scripts/risk_assessment.py --scope "patient-data-system" --template healthcare
```
**Asset inventory output:**
| Asset ID | Asset | Type | Owner | Classification |
|----------|-------|------|-------|----------------|
| A001 | Patient database | Information | DBA Team | Confidential |
| A002 | EHR application | Software | App Team | Critical |
| A003 | Database server | Hardware | Infra Team | High |
| A004 | Admin credentials | Access | Security | Critical |
### Step 2: Identify Risks
**Risk register output:**
| Risk ID | Asset | Threat | Vulnerability | L | I | Score |
|---------|-------|--------|---------------|---|---|-------|
| R001 | A001 | Data breach | Weak encryption | 3 | 5 | 15 |
| R002 | A002 | SQL injection | Input validation | 4 | 4 | 16 |
| R003 | A004 | Credential theft | No MFA | 4 | 5 | 20 |
### Step 3: Determine Treatment
| Risk | Treatment | Control | Timeline |
|------|-----------|---------|----------|
| R001 | Mitigate | Implement AES-256 encryption | 30 days |
| R002 | Mitigate | Add input validation, WAF | 14 days |
| R003 | Mitigate | Enforce MFA for all admins | 7 days |
### Step 4: Verify Implementation
```bash
python scripts/compliance_checker.py --controls-file implemented_controls.csv
```
**Verification output:**
```
Control Implementation Status
=============================
Cryptography (A.8.24): IMPLEMENTED
- AES-256 at rest: YES
- TLS 1.3 in transit: YES
Access Control (A.8.5): IMPLEMENTED
- MFA enabled: YES
- Admin accounts: 100% coverage
Application Security (A.8.26): PARTIAL
- Input validation: YES
- WAF deployed: PENDING
Overall Compliance: 87%
```
FILE:references/incident-response.md
# Incident Response Procedures
Security incident detection, response, and recovery procedures per ISO 27001 requirements.
---
## Table of Contents
- [Incident Classification](#incident-classification)
- [Response Procedures](#response-procedures)
- [Escalation Matrix](#escalation-matrix)
- [Communication Templates](#communication-templates)
- [Recovery Checklists](#recovery-checklists)
- [Post-Incident Activities](#post-incident-activities)
---
## Incident Classification
### Incident Categories
| Category | Description | Examples |
|----------|-------------|----------|
| Security Breach | Unauthorized access to systems/data | Account compromise, data exfiltration |
| Malware | Malicious software infection | Ransomware, virus, trojan |
| Data Leakage | Unauthorized data disclosure | Accidental email, misconfigured storage |
| Denial of Service | Service availability impact | DDoS attack, resource exhaustion |
| Policy Violation | Security policy breach | Unauthorized software, data handling |
| Physical | Physical security incident | Theft, unauthorized entry |
### Severity Levels
| Level | Criteria | Response Time | Examples |
|-------|----------|---------------|----------|
| **Critical (P1)** | Active breach, data loss, system down | Immediate (15 min) | Ransomware, confirmed breach |
| **High (P2)** | Active threat, potential data exposure | 1 hour | Malware detected, suspicious access |
| **Medium (P3)** | Contained threat, limited impact | 4 hours | Failed attacks, policy violations |
| **Low (P4)** | Minor issue, no immediate risk | 24 hours | Suspicious emails, minor violations |
### Severity Decision Tree
```
Is there active data loss or system compromise?
├── Yes → CRITICAL (P1)
└── No → Is there an active uncontained threat?
├── Yes → HIGH (P2)
└── No → Is there potential for data exposure?
├── Yes → MEDIUM (P3)
└── No → LOW (P4)
```
---
## Response Procedures
### Phase 1: Detection and Reporting
**Objective:** Identify and report security incidents promptly.
**Steps:**
1. Identify potential incident through monitoring, alerts, or reports
2. Document initial observations (time, systems, symptoms)
3. Report to Security Team via designated channel
4. Assign incident ID and log in tracking system
**Validation:** Incident logged within 15 minutes of detection.
**Documentation Required:**
- Date/time of detection
- Detection source (monitoring, user report, automated alert)
- Affected systems/users (initial assessment)
- Reporter information
### Phase 2: Triage and Assessment
**Objective:** Determine incident scope and severity.
**Steps:**
1. Gather additional information (logs, system state)
2. Determine incident category and severity
3. Identify affected assets and potential impact
4. Assign incident owner and response team
**Validation:** Severity assigned and escalation triggered if needed.
**Assessment Checklist:**
- [ ] Systems affected identified
- [ ] Data types potentially impacted
- [ ] Attack vector determined
- [ ] Scope (single system vs. widespread)
- [ ] Business impact assessed
### Phase 3: Containment
**Objective:** Limit damage and prevent spread.
**Immediate Containment (Short-term):**
1. Isolate affected systems from network
2. Disable compromised accounts
3. Block malicious IPs/domains
4. Preserve evidence before changes
**Long-term Containment:**
1. Apply temporary fixes
2. Implement additional monitoring
3. Strengthen access controls
4. Prepare for eradication
**Validation:** Containment confirmed, no ongoing spread.
**Containment Actions by Incident Type:**
| Incident Type | Containment Actions |
|---------------|---------------------|
| Account Compromise | Disable account, revoke sessions, reset credentials |
| Malware | Isolate host, block C2 domains, scan related systems |
| Data Breach | Block exfiltration path, revoke access, enable DLP |
| DDoS | Enable DDoS protection, rate limiting, traffic scrubbing |
### Phase 4: Eradication
**Objective:** Remove threat from environment.
**Steps:**
1. Identify root cause
2. Remove malware/backdoors
3. Close vulnerabilities exploited
4. Reset compromised credentials
5. Verify threat elimination
**Validation:** No indicators of compromise remain.
**Eradication Checklist:**
- [ ] Malware removed from all systems
- [ ] Vulnerabilities patched
- [ ] Backdoors/persistence removed
- [ ] Compromised credentials rotated
- [ ] Security gaps closed
### Phase 5: Recovery
**Objective:** Restore systems to normal operation.
**Steps:**
1. Restore from clean backups if needed
2. Rebuild compromised systems
3. Verify system integrity
4. Monitor for re-infection
5. Return to production gradually
**Validation:** Systems operational with enhanced monitoring.
**Recovery Checklist:**
- [ ] Systems restored to known-good state
- [ ] Integrity verification completed
- [ ] Enhanced monitoring in place
- [ ] Business operations resumed
- [ ] User access restored (verified accounts only)
### Phase 6: Lessons Learned
**Objective:** Improve security posture and response capability.
**Steps:**
1. Conduct post-incident review (within 5 business days)
2. Document timeline and actions taken
3. Identify what worked and what didn't
4. Update procedures and controls
5. Share relevant findings (internally, externally if required)
**Validation:** Post-incident report completed and actions tracked.
---
## Escalation Matrix
### Escalation Paths
| Severity | Initial Response | 1 Hour | 4 Hours | 24 Hours |
|----------|------------------|--------|---------|----------|
| Critical | Security Team | CISO + Management | Executive Team | Board notification |
| High | Security Team | CISO | Management | - |
| Medium | Security Team | Security Manager | CISO if unresolved | - |
| Low | Security Analyst | Security Team Lead | - | - |
### Contact Information (Template)
| Role | Primary | Backup | Contact Method |
|------|---------|--------|----------------|
| Security On-Call | [Name] | [Name] | Phone, Slack |
| CISO | [Name] | [Name] | Phone, Email |
| IT Director | [Name] | [Name] | Phone, Email |
| Legal Counsel | [Name] | [Firm] | Phone |
| PR/Communications | [Name] | [Name] | Phone |
| Executive Sponsor | [Name] | [Name] | Phone |
### External Notifications
| Condition | Notify | Timeline |
|-----------|--------|----------|
| Patient data breach | HHS (HIPAA) | 60 days |
| EU personal data breach | Supervisory Authority (GDPR) | 72 hours |
| Significant breach | Law enforcement | As appropriate |
| Third-party involved | Affected vendor | Immediately |
---
## Communication Templates
### Internal Notification (Initial)
```
Subject: [SEVERITY] Security Incident - [Brief Description]
INCIDENT SUMMARY
----------------
Incident ID: INC-[YYYY]-[###]
Detected: [Date/Time]
Severity: [Critical/High/Medium/Low]
Status: [Investigating/Contained/Resolved]
WHAT HAPPENED
[Brief description of the incident]
CURRENT IMPACT
[Systems affected, business impact]
ACTIONS BEING TAKEN
[Current response activities]
WHAT YOU NEED TO DO
[Any required user actions]
NEXT UPDATE
Expected by: [Time]
Contact: Security Team - [contact info]
```
### External Notification (Breach)
```
Subject: Important Security Notice from [Organization]
Dear [Affected Party],
We are writing to inform you of a security incident that may have
involved your personal information.
WHAT HAPPENED
On [date], we discovered [brief description].
WHAT INFORMATION WAS INVOLVED
[Types of data potentially affected]
WHAT WE ARE DOING
[Actions taken to address the incident]
WHAT YOU CAN DO
[Recommended protective actions]
FOR MORE INFORMATION
[Contact information, resources]
We sincerely regret any concern this may cause and are committed
to protecting your information.
[Signature]
```
### Status Update
```
Subject: UPDATE: Security Incident INC-[ID] - [Status]
CURRENT STATUS
--------------
Status: [Contained/Eradicating/Recovering]
Last Update: [Time]
PROGRESS SINCE LAST UPDATE
[Actions completed]
CURRENT ACTIVITIES
[Ongoing response work]
REMAINING ACTIONS
[What still needs to be done]
ESTIMATED RESOLUTION
[Timeframe if known]
NEXT UPDATE
Expected: [Time]
```
---
## Recovery Checklists
### System Recovery Checklist
- [ ] Verify backup integrity before restoration
- [ ] Restore to isolated environment first
- [ ] Scan restored systems for malware
- [ ] Apply all security patches
- [ ] Reset all credentials on system
- [ ] Review and harden configurations
- [ ] Verify application functionality
- [ ] Enable enhanced logging/monitoring
- [ ] Conduct security scan before production
- [ ] Document recovery steps taken
### Account Compromise Recovery
- [ ] Disable compromised account
- [ ] Revoke all active sessions
- [ ] Reset password with strong credential
- [ ] Enable MFA if not already
- [ ] Review account activity logs
- [ ] Check for unauthorized changes
- [ ] Review connected applications
- [ ] Verify account recovery options
- [ ] Notify account owner securely
- [ ] Monitor for suspicious activity
### Ransomware Recovery
- [ ] Isolate affected systems immediately
- [ ] Identify ransomware variant
- [ ] Check for decryption tools available
- [ ] Assess backup availability/integrity
- [ ] Report to law enforcement
- [ ] Document encrypted files/systems
- [ ] Restore from clean backups
- [ ] Rebuild systems that cannot be restored
- [ ] Patch vulnerability exploited
- [ ] Implement additional controls
---
## Post-Incident Activities
### Post-Incident Review Meeting
**Timing:** Within 5 business days of resolution
**Attendees:**
- Incident response team
- Affected system owners
- Security management
- Relevant stakeholders
**Agenda:**
1. Incident timeline review
2. What worked well
3. What could be improved
4. Root cause analysis
5. Preventive measures
6. Action items and owners
### Post-Incident Report Template
```
INCIDENT POST-MORTEM REPORT
===========================
Incident ID: INC-[YYYY]-[###]
Date: [Report date]
Author: [Name]
Classification: [Internal/Confidential]
EXECUTIVE SUMMARY
[2-3 paragraph summary]
INCIDENT TIMELINE
[Detailed chronological events]
ROOT CAUSE ANALYSIS
[5 Whys or similar analysis]
IMPACT ASSESSMENT
- Systems affected: [list]
- Data impacted: [description]
- Business impact: [description]
- Financial impact: [estimate if known]
RESPONSE EFFECTIVENESS
What worked well:
- [item]
- [item]
Areas for improvement:
- [item]
- [item]
RECOMMENDATIONS
| # | Recommendation | Priority | Owner | Due Date |
|---|----------------|----------|-------|----------|
| 1 | [action] | High | [name] | [date] |
| 2 | [action] | Medium | [name] | [date] |
LESSONS LEARNED
[Key takeaways for future incidents]
APPENDICES
- Detailed logs
- Evidence inventory
- Communication records
```
### Metrics to Track
| Metric | Target | Purpose |
|--------|--------|---------|
| Mean Time to Detect (MTTD) | < 1 hour | Detection capability |
| Mean Time to Respond (MTTR) | < 4 hours | Response speed |
| Mean Time to Contain (MTTC) | < 2 hours | Containment effectiveness |
| Incidents by severity | Decreasing trend | Overall security posture |
| Repeat incidents | 0 | Root cause resolution |
FILE:references/iso27001-controls.md
# ISO 27001:2022 Controls Implementation Guide
Implementation guidance for Annex A controls with evidence requirements and audit preparation.
---
## Table of Contents
- [Control Categories Overview](#control-categories-overview)
- [Organizational Controls (A.5)](#organizational-controls-a5)
- [People Controls (A.6)](#people-controls-a6)
- [Physical Controls (A.7)](#physical-controls-a7)
- [Technological Controls (A.8)](#technological-controls-a8)
- [Evidence Requirements](#evidence-requirements)
- [Statement of Applicability](#statement-of-applicability)
---
## Control Categories Overview
ISO 27001:2022 Annex A contains 93 controls across 4 categories:
| Category | Controls | Focus Areas |
|----------|----------|-------------|
| Organizational (A.5) | 37 | Policies, governance, supplier management |
| People (A.6) | 8 | HR security, awareness, remote working |
| Physical (A.7) | 14 | Perimeters, equipment, environment |
| Technological (A.8) | 34 | Access, crypto, network, development |
---
## Organizational Controls (A.5)
### A.5.1 - Policies for Information Security
**Requirement:** Define, approve, publish, and communicate information security policies.
**Implementation:**
1. Draft information security policy covering scope, objectives, principles
2. Obtain management approval signature
3. Communicate to all employees and relevant parties
4. Review annually or after significant changes
**Evidence:**
- Signed policy document
- Communication records (email, intranet)
- Acknowledgment records
- Review meeting minutes
### A.5.2 - Information Security Roles and Responsibilities
**Requirement:** Define and allocate information security responsibilities.
**Implementation:**
1. Create RACI matrix for security activities
2. Appoint Information Security Manager
3. Define responsibilities in job descriptions
4. Establish reporting lines
**Evidence:**
- RACI matrix document
- ISM appointment letter
- Job descriptions with security duties
- Organizational chart
### A.5.9 - Inventory of Information and Assets
**Requirement:** Identify and maintain inventory of information assets.
**Implementation:**
1. Create asset register with classification
2. Assign owners for each asset
3. Define acceptable use rules
4. Review quarterly
**Evidence:**
- Asset inventory/register
- Classification scheme
- Owner assignment records
- Review logs
### A.5.15 - Access Control
**Requirement:** Establish and implement rules for controlling access.
**Implementation:**
1. Document access control policy
2. Implement role-based access control (RBAC)
3. Define access provisioning/deprovisioning process
4. Conduct access reviews quarterly
**Evidence:**
- Access control policy
- RBAC role definitions
- Access request forms
- Review reports
---
## People Controls (A.6)
### A.6.1 - Screening
**Requirement:** Verify backgrounds of candidates prior to employment.
**Implementation:**
1. Define screening requirements by role
2. Conduct background checks
3. Verify references and qualifications
4. Document screening results
**Evidence:**
- Screening policy
- Background check reports
- Verification records
- Consent forms
### A.6.3 - Information Security Awareness and Training
**Requirement:** Ensure personnel receive appropriate awareness and training.
**Implementation:**
1. Develop annual training program
2. Include role-specific training
3. Conduct phishing simulations
4. Track completion and effectiveness
**Evidence:**
- Training materials
- Completion records
- Test/quiz results
- Phishing simulation reports
### A.6.7 - Remote Working
**Requirement:** Implement security measures for remote working.
**Implementation:**
1. Establish remote working policy
2. Require VPN for network access
3. Mandate endpoint protection
4. Secure home network guidance
**Evidence:**
- Remote working policy
- VPN configuration records
- Endpoint compliance reports
- User acknowledgments
---
## Physical Controls (A.7)
### A.7.1 - Physical Security Perimeters
**Requirement:** Define and use security perimeters to protect information.
**Implementation:**
1. Define secure areas and boundaries
2. Implement access controls (badges, locks)
3. Monitor entry points
4. Maintain visitor logs
**Evidence:**
- Site security plan
- Access control system records
- CCTV footage retention
- Visitor logs
### A.7.4 - Physical Security Monitoring
**Requirement:** Monitor premises continuously for unauthorized access.
**Implementation:**
1. Deploy CCTV coverage
2. Implement intrusion detection
3. Define monitoring procedures
4. Establish incident response
**Evidence:**
- CCTV deployment records
- Monitoring procedures
- Alert configurations
- Incident logs
---
## Technological Controls (A.8)
### A.8.2 - Privileged Access Rights
**Requirement:** Restrict and manage privileged access.
**Implementation:**
1. Implement privileged access management (PAM)
2. Enforce separate admin accounts
3. Require MFA for privileged access
4. Monitor and log privileged activities
**Evidence:**
- PAM solution records
- Admin account inventory
- MFA enforcement reports
- Privileged activity logs
### A.8.5 - Secure Authentication
**Requirement:** Implement secure authentication mechanisms.
**Implementation:**
1. Enforce strong password policy
2. Implement MFA for all users
3. Use secure authentication protocols
4. Monitor authentication events
**Evidence:**
- Password policy
- MFA enrollment records
- Authentication configuration
- Failed login reports
### A.8.7 - Protection Against Malware
**Requirement:** Implement detection, prevention, and recovery for malware.
**Implementation:**
1. Deploy endpoint protection on all devices
2. Configure automatic updates
3. Implement email filtering
4. Define malware incident response
**Evidence:**
- Endpoint protection deployment
- Update/patch status
- Email filter configuration
- Malware incident records
### A.8.8 - Management of Technical Vulnerabilities
**Requirement:** Identify and address technical vulnerabilities.
**Implementation:**
1. Conduct regular vulnerability scans
2. Define remediation SLAs by severity
3. Track remediation progress
4. Verify patches applied
**Evidence:**
- Vulnerability scan reports
- Remediation tracking
- Patch deployment records
- Penetration test reports
### A.8.13 - Information Backup
**Requirement:** Maintain and test backup copies of information.
**Implementation:**
1. Define backup policy (frequency, retention)
2. Implement automated backups
3. Encrypt backup data
4. Test restoration regularly
**Evidence:**
- Backup policy
- Backup job logs
- Encryption configuration
- Restoration test records
### A.8.15 - Logging
**Requirement:** Produce, retain, and protect logs of activities.
**Implementation:**
1. Define logging requirements
2. Deploy centralized log management (SIEM)
3. Set retention periods per compliance
4. Protect log integrity
**Evidence:**
- Logging policy
- SIEM configuration
- Log retention settings
- Access controls on logs
### A.8.24 - Use of Cryptography
**Requirement:** Define and implement cryptographic controls.
**Implementation:**
1. Document cryptography policy
2. Encrypt data at rest (AES-256)
3. Encrypt data in transit (TLS 1.3)
4. Manage keys securely
**Evidence:**
- Cryptography policy
- Encryption configuration
- Certificate inventory
- Key management procedures
---
## Evidence Requirements
### Document Evidence
| Control Area | Required Documents |
|-------------|-------------------|
| Policies | Approved policy documents |
| Procedures | Documented processes with version control |
| Records | Completed forms, logs, reports |
| Contracts | Signed agreements with security clauses |
### Technical Evidence
| Control Area | Required Evidence |
|-------------|------------------|
| Access Control | System configurations, access lists |
| Logging | SIEM dashboards, sample logs |
| Encryption | Configuration screenshots, certificate details |
| Vulnerability | Scan reports, remediation tracking |
### Retention Requirements
| Evidence Type | Minimum Retention |
|--------------|-------------------|
| Policies | Current + 2 previous versions |
| Audit reports | 3 years |
| Access logs | 1 year minimum |
| Incident records | 3 years |
| Training records | Duration of employment + 2 years |
---
## Statement of Applicability
### SoA Structure
For each Annex A control, document:
| Field | Description |
|-------|-------------|
| Control ID | A.5.1, A.8.24, etc. |
| Control Name | Official control title |
| Applicable | Yes/No |
| Justification | Why applicable or not |
| Implementation Status | Implemented, Partial, Planned, N/A |
| Implementation Description | How control is implemented |
| Evidence Reference | Links to evidence |
### Sample SoA Entry
```
Control: A.8.5 - Secure Authentication
Applicable: Yes
Justification: Required for all user and system access to protect
information assets from unauthorized access.
Implementation Status: Implemented
Implementation Description:
- MFA enforced for all user accounts via Azure AD
- Admin accounts require hardware token
- Password policy: 12+ chars, complexity, 90-day rotation
- Failed login lockout after 5 attempts
Evidence:
- Azure AD MFA configuration (screenshot)
- Password policy document (DOC-SEC-015)
- Authentication audit logs (SIEM dashboard)
```
### Exclusion Justification Examples
| Control | Justification for Exclusion |
|---------|---------------------------|
| A.7.x (Physical) | Cloud-only operations, no physical facilities |
| A.8.19 (Software) | No user-installed software permitted |
| A.8.23 (Web filter) | Handled by cloud proxy service |
FILE:references/risk-assessment-guide.md
# Risk Assessment Methodology Guide
Comprehensive guidance for conducting information security risk assessments per ISO 27001 Clause 6.1.2.
---
## Table of Contents
- [Risk Assessment Process](#risk-assessment-process)
- [Asset Identification](#asset-identification)
- [Threat Analysis](#threat-analysis)
- [Vulnerability Assessment](#vulnerability-assessment)
- [Risk Calculation](#risk-calculation)
- [Risk Treatment](#risk-treatment)
- [Templates and Tools](#templates-and-tools)
---
## Risk Assessment Process
### ISO 27001 Requirements (Clause 6.1.2)
The organization shall:
1. Define risk assessment process
2. Establish risk criteria (acceptance, assessment)
3. Identify information security risks
4. Analyze and evaluate risks
5. Ensure repeatable and consistent results
### Process Overview
```
1. Context → 2. Asset ID → 3. Threat ID → 4. Vuln ID → 5. Risk Calc → 6. Treatment
↑ |
└──────────────────── Review & Update ←───────────────────────────────┘
```
---
## Asset Identification
### Asset Categories
| Category | Examples | Typical Classification |
|----------|----------|----------------------|
| Information | Patient records, source code, contracts | Confidential-Critical |
| Software | EHR systems, databases, custom apps | High-Critical |
| Hardware | Servers, medical devices, network gear | High |
| Services | Cloud hosting, backup, email | High |
| People | Admin accounts, key personnel | Critical |
| Intangibles | Reputation, intellectual property | High |
### Classification Scheme
| Level | Definition | Impact if Compromised |
|-------|------------|----------------------|
| Critical | Business-critical, regulated data | Severe - regulatory fines, safety risk |
| High | Important business data | Significant - major disruption |
| Medium | Internal business data | Moderate - operational impact |
| Low | Non-sensitive data | Minor - limited impact |
| Public | Intended for public release | Minimal - no impact |
### Asset Inventory Template
| ID | Asset Name | Type | Owner | Location | Classification | Value |
|----|------------|------|-------|----------|----------------|-------|
| A001 | Patient DB | Information | DBA Lead | AWS RDS | Critical | $5M |
| A002 | EHR App | Software | App Team | AWS ECS | Critical | $2M |
| A003 | Admin Creds | Access | Security | Vault | Critical | N/A |
---
## Threat Analysis
### Healthcare Threat Landscape
| Threat | Likelihood | Target Assets | Motivation |
|--------|------------|---------------|------------|
| Ransomware | High | All systems | Financial |
| Data breach | High | Patient data | Financial/Competitive |
| Phishing | Very High | User accounts | Access |
| Insider threat | Medium | Sensitive data | Various |
| DDoS | Medium | Public services | Disruption |
| Supply chain | Medium | Third-party systems | Access |
### Threat Modeling Approaches
**STRIDE Model:**
- **S**poofing identity
- **T**ampering with data
- **R**epudiation
- **I**nformation disclosure
- **D**enial of service
- **E**levation of privilege
**Threat Actor Categories:**
| Actor | Capability | Motivation | Typical Targets |
|-------|-----------|------------|-----------------|
| Nation-state | Very High | Espionage, disruption | Critical infrastructure |
| Organized crime | High | Financial gain | Healthcare, finance |
| Hacktivists | Medium | Ideology | Public-facing systems |
| Insiders | Varies | Financial, revenge | Sensitive data |
| Script kiddies | Low | Notoriety | Unpatched systems |
---
## Vulnerability Assessment
### Vulnerability Categories
| Category | Examples | Detection Method |
|----------|----------|------------------|
| Technical | Unpatched software, weak configs | Vulnerability scans |
| Process | Missing procedures, gaps | Process audits |
| People | Lack of training, social engineering | Phishing tests |
| Physical | Inadequate access controls | Physical audits |
### Vulnerability Scoring (CVSS Alignment)
| Score Range | Severity | Example |
|-------------|----------|---------|
| 9.0-10.0 | Critical | RCE without authentication |
| 7.0-8.9 | High | Authentication bypass |
| 4.0-6.9 | Medium | Information disclosure |
| 0.1-3.9 | Low | Minor configuration issue |
### Vulnerability Sources
1. **Automated Scans:** Nessus, Qualys, OpenVAS
2. **Penetration Testing:** Annual third-party tests
3. **Code Analysis:** SAST/DAST tools
4. **Configuration Audits:** CIS benchmarks
5. **Threat Intelligence:** CVE feeds, vendor advisories
---
## Risk Calculation
### Risk Formula
```
Risk = Likelihood × Impact
```
### Likelihood Scale (1-5)
| Score | Likelihood | Definition |
|-------|-----------|------------|
| 5 | Almost Certain | Expected to occur multiple times per year |
| 4 | Likely | Expected to occur at least once per year |
| 3 | Possible | Could occur within 2-3 years |
| 2 | Unlikely | Could occur within 5 years |
| 1 | Rare | Unlikely to occur |
### Impact Scale (1-5)
| Score | Impact | Financial | Operational | Reputational |
|-------|--------|-----------|-------------|--------------|
| 5 | Catastrophic | >$10M | Total shutdown | International news |
| 4 | Major | $1M-$10M | Major disruption | National news |
| 3 | Moderate | $100K-$1M | Significant impact | Local news |
| 2 | Minor | $10K-$100K | Minor disruption | Complaints |
| 1 | Negligible | <$10K | Minimal impact | Internal only |
### Risk Matrix
| | Impact 1 | Impact 2 | Impact 3 | Impact 4 | Impact 5 |
|-----|----------|----------|----------|----------|----------|
| **L5** | 5 (Low) | 10 (Med) | 15 (High) | 20 (Crit) | 25 (Crit) |
| **L4** | 4 (Low) | 8 (Med) | 12 (Med) | 16 (High) | 20 (Crit) |
| **L3** | 3 (Min) | 6 (Low) | 9 (Med) | 12 (Med) | 15 (High) |
| **L2** | 2 (Min) | 4 (Low) | 6 (Low) | 8 (Med) | 10 (Med) |
| **L1** | 1 (Min) | 2 (Min) | 3 (Min) | 4 (Low) | 5 (Low) |
### Risk Levels
| Level | Score Range | Action Required |
|-------|-------------|-----------------|
| Critical | 20-25 | Immediate action, escalate to management |
| High | 15-19 | Treatment plan within 30 days |
| Medium | 10-14 | Treatment plan within 90 days |
| Low | 5-9 | Accept or implement low-cost controls |
| Minimal | 1-4 | Accept risk, document decision |
---
## Risk Treatment
### Treatment Options (ISO 27001)
| Option | Description | When to Use |
|--------|-------------|-------------|
| Modify | Implement controls to reduce risk | Most risks |
| Avoid | Eliminate the risk source | Unacceptable risks |
| Share | Transfer via insurance/outsourcing | High financial impact |
| Retain | Accept the risk | Low risks, cost-prohibitive controls |
### Control Selection Criteria
1. **Effectiveness:** Reduces likelihood or impact
2. **Cost:** Implementation and maintenance costs
3. **Feasibility:** Technical and operational viability
4. **Compliance:** Meets regulatory requirements
5. **Integration:** Works with existing controls
### Residual Risk
After implementing controls:
```
Residual Risk = Inherent Risk × (1 - Control Effectiveness)
```
| Control Effectiveness | Residual Risk Factor |
|----------------------|---------------------|
| 90%+ | Very Low (0.1×) |
| 70-89% | Low (0.2-0.3×) |
| 50-69% | Moderate (0.4-0.5×) |
| <50% | Limited reduction |
---
## Templates and Tools
### Risk Register Template
| Risk ID | Asset | Threat | Vulnerability | L | I | Inherent | Control | Residual | Owner | Status |
|---------|-------|--------|---------------|---|---|----------|---------|----------|-------|--------|
| R001 | Patient DB | Data breach | Weak encryption | 4 | 5 | 20 | AES-256 | 8 | DBA | Open |
| R002 | Admin access | Credential theft | No MFA | 5 | 5 | 25 | MFA | 5 | Security | Closed |
### Risk Assessment Report Sections
1. **Executive Summary**
- Key findings
- Critical/high risks count
- Overall risk posture
2. **Methodology**
- Assessment scope
- Criteria used
- Limitations
3. **Asset Summary**
- Asset inventory
- Classification distribution
4. **Risk Findings**
- Risk register
- Heat map visualization
- Trend analysis
5. **Recommendations**
- Priority treatments
- Timeline and resources
- Residual risk projection
6. **Appendices**
- Detailed asset list
- Threat catalog
- Control mapping
FILE:scripts/compliance_checker.py
#!/usr/bin/env python3
"""
ISO 27001/27002 Compliance Checker
Verify control implementation status and generate compliance reports.
Supports gap analysis and remediation recommendations.
Usage:
python compliance_checker.py --standard iso27001
python compliance_checker.py --standard iso27001 --gap-analysis --output gaps.md
"""
import argparse
import csv
import json
import sys
from datetime import datetime
from typing import Dict, List, Any, Optional
# ISO 27001:2022 Annex A Controls (simplified)
ISO27001_CONTROLS = {
"organizational": {
"name": "Organizational Controls",
"controls": [
{"id": "A.5.1", "name": "Policies for information security", "priority": "high"},
{"id": "A.5.2", "name": "Information security roles and responsibilities", "priority": "high"},
{"id": "A.5.3", "name": "Segregation of duties", "priority": "medium"},
{"id": "A.5.4", "name": "Management responsibilities", "priority": "high"},
{"id": "A.5.5", "name": "Contact with authorities", "priority": "medium"},
{"id": "A.5.6", "name": "Contact with special interest groups", "priority": "low"},
{"id": "A.5.7", "name": "Threat intelligence", "priority": "medium"},
{"id": "A.5.8", "name": "Information security in project management", "priority": "medium"},
{"id": "A.5.9", "name": "Inventory of information and assets", "priority": "high"},
{"id": "A.5.10", "name": "Acceptable use of information", "priority": "high"},
]
},
"people": {
"name": "People Controls",
"controls": [
{"id": "A.6.1", "name": "Screening", "priority": "high"},
{"id": "A.6.2", "name": "Terms and conditions of employment", "priority": "high"},
{"id": "A.6.3", "name": "Information security awareness and training", "priority": "high"},
{"id": "A.6.4", "name": "Disciplinary process", "priority": "medium"},
{"id": "A.6.5", "name": "Responsibilities after termination", "priority": "high"},
{"id": "A.6.6", "name": "Confidentiality agreements", "priority": "high"},
{"id": "A.6.7", "name": "Remote working", "priority": "high"},
{"id": "A.6.8", "name": "Information security event reporting", "priority": "high"},
]
},
"physical": {
"name": "Physical Controls",
"controls": [
{"id": "A.7.1", "name": "Physical security perimeters", "priority": "high"},
{"id": "A.7.2", "name": "Physical entry", "priority": "high"},
{"id": "A.7.3", "name": "Securing offices and facilities", "priority": "medium"},
{"id": "A.7.4", "name": "Physical security monitoring", "priority": "medium"},
{"id": "A.7.5", "name": "Protecting against environmental threats", "priority": "medium"},
{"id": "A.7.6", "name": "Working in secure areas", "priority": "medium"},
{"id": "A.7.7", "name": "Clear desk and screen", "priority": "medium"},
{"id": "A.7.8", "name": "Equipment siting and protection", "priority": "medium"},
]
},
"technological": {
"name": "Technological Controls",
"controls": [
{"id": "A.8.1", "name": "User endpoint devices", "priority": "high"},
{"id": "A.8.2", "name": "Privileged access rights", "priority": "critical"},
{"id": "A.8.3", "name": "Information access restriction", "priority": "high"},
{"id": "A.8.4", "name": "Access to source code", "priority": "high"},
{"id": "A.8.5", "name": "Secure authentication", "priority": "critical"},
{"id": "A.8.6", "name": "Capacity management", "priority": "medium"},
{"id": "A.8.7", "name": "Protection against malware", "priority": "critical"},
{"id": "A.8.8", "name": "Management of technical vulnerabilities", "priority": "critical"},
{"id": "A.8.9", "name": "Configuration management", "priority": "high"},
{"id": "A.8.10", "name": "Information deletion", "priority": "high"},
{"id": "A.8.11", "name": "Data masking", "priority": "medium"},
{"id": "A.8.12", "name": "Data leakage prevention", "priority": "high"},
{"id": "A.8.13", "name": "Information backup", "priority": "critical"},
{"id": "A.8.14", "name": "Redundancy of information processing", "priority": "high"},
{"id": "A.8.15", "name": "Logging", "priority": "critical"},
{"id": "A.8.16", "name": "Monitoring activities", "priority": "high"},
{"id": "A.8.17", "name": "Clock synchronization", "priority": "medium"},
{"id": "A.8.18", "name": "Use of privileged utility programs", "priority": "high"},
{"id": "A.8.19", "name": "Installation of software", "priority": "high"},
{"id": "A.8.20", "name": "Networks security", "priority": "critical"},
{"id": "A.8.21", "name": "Security of network services", "priority": "high"},
{"id": "A.8.22", "name": "Segregation of networks", "priority": "high"},
{"id": "A.8.23", "name": "Web filtering", "priority": "medium"},
{"id": "A.8.24", "name": "Use of cryptography", "priority": "critical"},
{"id": "A.8.25", "name": "Secure development lifecycle", "priority": "high"},
{"id": "A.8.26", "name": "Application security requirements", "priority": "high"},
{"id": "A.8.27", "name": "Secure system architecture", "priority": "high"},
{"id": "A.8.28", "name": "Secure coding", "priority": "high"},
]
},
}
# Remediation recommendations by control
REMEDIATION_GUIDANCE = {
"A.5.1": "Develop and publish information security policy signed by management",
"A.5.2": "Define RACI matrix for security roles; appoint Information Security Manager",
"A.5.9": "Create asset inventory with owners and classification",
"A.6.3": "Implement annual security awareness training program",
"A.6.7": "Establish remote working policy with technical controls",
"A.8.2": "Implement privileged access management (PAM) solution",
"A.8.5": "Deploy MFA for all user and admin accounts",
"A.8.7": "Deploy endpoint protection on all devices with central management",
"A.8.8": "Implement vulnerability scanning with 30-day remediation SLA",
"A.8.13": "Configure automated backups with encryption and offsite storage",
"A.8.15": "Deploy SIEM with log retention per compliance requirements",
"A.8.20": "Implement firewall, IDS/IPS, and network monitoring",
"A.8.24": "Enforce TLS 1.3 for transit, AES-256 for data at rest",
}
def get_control_status(control_id: str, controls_data: Optional[Dict] = None) -> str:
"""Get implementation status for a control."""
if controls_data and control_id in controls_data:
return controls_data[control_id]
# Default: simulate partial implementation
import random
random.seed(hash(control_id))
statuses = ["implemented", "implemented", "partial", "partial", "not_implemented"]
return random.choice(statuses)
def load_controls_from_csv(filepath: str) -> Dict[str, str]:
"""Load control status from CSV file."""
controls = {}
try:
with open(filepath, "r", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
control_id = row.get("control_id", row.get("id", ""))
status = row.get("status", "not_implemented").lower()
if control_id:
controls[control_id] = status
except FileNotFoundError:
print(f"Error: Controls file not found: {filepath}", file=sys.stderr)
sys.exit(1)
return controls
def check_compliance(
standard: str,
controls_data: Optional[Dict] = None,
domains: Optional[List[str]] = None
) -> Dict[str, Any]:
"""Check compliance against standard controls."""
if standard not in ["iso27001", "iso27002"]:
print(f"Error: Unsupported standard: {standard}", file=sys.stderr)
sys.exit(1)
results = {
"standard": standard,
"timestamp": datetime.now().isoformat(),
"domains": {},
"summary": {
"total_controls": 0,
"implemented": 0,
"partial": 0,
"not_implemented": 0,
},
"findings": [],
}
for domain_key, domain_data in ISO27001_CONTROLS.items():
if domains and domain_key not in domains:
continue
domain_results = {
"name": domain_data["name"],
"controls": [],
"implemented": 0,
"partial": 0,
"not_implemented": 0,
}
for control in domain_data["controls"]:
status = get_control_status(control["id"], controls_data)
control_result = {
"id": control["id"],
"name": control["name"],
"priority": control["priority"],
"status": status,
}
domain_results["controls"].append(control_result)
results["summary"]["total_controls"] += 1
if status == "implemented":
domain_results["implemented"] += 1
results["summary"]["implemented"] += 1
elif status == "partial":
domain_results["partial"] += 1
results["summary"]["partial"] += 1
else:
domain_results["not_implemented"] += 1
results["summary"]["not_implemented"] += 1
# Add to findings if high priority
if control["priority"] in ["critical", "high"]:
results["findings"].append({
"control_id": control["id"],
"control_name": control["name"],
"priority": control["priority"],
"status": status,
"remediation": REMEDIATION_GUIDANCE.get(
control["id"],
"Implement control per ISO 27001 requirements"
),
})
results["domains"][domain_key] = domain_results
# Calculate compliance percentage
total = results["summary"]["total_controls"]
implemented = results["summary"]["implemented"]
partial = results["summary"]["partial"]
results["summary"]["compliance_percentage"] = round(
((implemented + partial * 0.5) / total) * 100, 1
) if total > 0 else 0
return results
def generate_gap_analysis(results: Dict[str, Any]) -> List[Dict[str, Any]]:
"""Generate gap analysis with prioritized recommendations."""
gaps = []
for finding in results["findings"]:
gap = {
"control_id": finding["control_id"],
"control_name": finding["control_name"],
"current_status": finding["status"],
"priority": finding["priority"],
"remediation": finding["remediation"],
"effort": "medium" if finding["priority"] == "high" else "high",
"timeline": "30 days" if finding["priority"] == "critical" else "90 days",
}
gaps.append(gap)
# Sort by priority
priority_order = {"critical": 0, "high": 1, "medium": 2, "low": 3}
gaps.sort(key=lambda x: priority_order.get(x["priority"], 99))
return gaps
def format_output(
results: Dict[str, Any],
gap_analysis: bool,
output_format: str
) -> str:
"""Format compliance results for output."""
if output_format == "json":
if gap_analysis:
results["gap_analysis"] = generate_gap_analysis(results)
return json.dumps(results, indent=2)
# Markdown format
lines = [
f"# {results['standard'].upper()} Compliance Report",
f"",
f"**Generated:** {results['timestamp']}",
f"",
f"## Summary",
f"",
f"| Metric | Value |",
f"|--------|-------|",
f"| Total Controls | {results['summary']['total_controls']} |",
f"| Implemented | {results['summary']['implemented']} |",
f"| Partial | {results['summary']['partial']} |",
f"| Not Implemented | {results['summary']['not_implemented']} |",
f"| **Compliance** | **{results['summary']['compliance_percentage']}%** |",
f"",
]
# Domain breakdown
lines.extend([
f"## Compliance by Domain",
f"",
f"| Domain | Implemented | Partial | Not Impl | Score |",
f"|--------|-------------|---------|----------|-------|",
])
for domain_key, domain_data in results["domains"].items():
total = len(domain_data["controls"])
score = round(
((domain_data["implemented"] + domain_data["partial"] * 0.5) / total) * 100
) if total > 0 else 0
lines.append(
f"| {domain_data['name']} | {domain_data['implemented']} | "
f"{domain_data['partial']} | {domain_data['not_implemented']} | {score}% |"
)
# Findings
if results["findings"]:
lines.extend([
f"",
f"## Priority Findings",
f"",
f"| Control | Name | Priority | Status |",
f"|---------|------|----------|--------|",
])
for finding in results["findings"][:15]: # Top 15
lines.append(
f"| {finding['control_id']} | {finding['control_name']} | "
f"{finding['priority'].capitalize()} | {finding['status'].replace('_', ' ').capitalize()} |"
)
# Gap analysis
if gap_analysis:
gaps = generate_gap_analysis(results)
lines.extend([
f"",
f"## Gap Analysis & Remediation",
f"",
])
for gap in gaps[:10]: # Top 10 gaps
lines.extend([
f"### {gap['control_id']}: {gap['control_name']}",
f"",
f"- **Priority:** {gap['priority'].capitalize()}",
f"- **Current Status:** {gap['current_status'].replace('_', ' ').capitalize()}",
f"- **Remediation:** {gap['remediation']}",
f"- **Timeline:** {gap['timeline']}",
f"",
])
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(
description="ISO 27001/27002 Compliance Checker"
)
parser.add_argument(
"--standard", "-s",
required=True,
choices=["iso27001", "iso27002", "hipaa"],
help="Compliance standard to check"
)
parser.add_argument(
"--controls-file", "-c",
help="CSV file with current control implementation status"
)
parser.add_argument(
"--gap-analysis", "-g",
action="store_true",
help="Include gap analysis with remediation recommendations"
)
parser.add_argument(
"--domains", "-d",
help="Comma-separated list of domains to check (e.g., organizational,technological)"
)
parser.add_argument(
"--output", "-o",
help="Output file path (default: stdout)"
)
parser.add_argument(
"--format", "-f",
choices=["json", "markdown"],
default="markdown",
help="Output format (default: markdown)"
)
args = parser.parse_args()
# Load control status if provided
controls_data = None
if args.controls_file:
controls_data = load_controls_from_csv(args.controls_file)
# Parse domains
domains = None
if args.domains:
domains = [d.strip().lower().replace("-", "_") for d in args.domains.split(",")]
# Check compliance
results = check_compliance(args.standard, controls_data, domains)
# Format output
output = format_output(results, args.gap_analysis, args.format)
# Write output
if args.output:
with open(args.output, "w", encoding="utf-8") as f:
f.write(output)
print(f"Report saved to: {args.output}", file=sys.stderr)
else:
print(output)
if __name__ == "__main__":
main()
FILE:scripts/risk_assessment.py
#!/usr/bin/env python3
"""
Security Risk Assessment Tool
Automated risk assessment following ISO 27001 Clause 6.1.2 methodology.
Identifies assets, threats, vulnerabilities, and calculates risk scores.
Usage:
python risk_assessment.py --scope "system-name" --output risks.json
python risk_assessment.py --assets assets.csv --template healthcare
"""
import argparse
import csv
import json
import sys
from datetime import datetime
from typing import Dict, List, Any, Optional
# Threat catalogs by template
THREAT_CATALOGS = {
"general": [
{"id": "T01", "name": "Unauthorized access", "category": "Access", "likelihood": 4},
{"id": "T02", "name": "Data breach", "category": "Confidentiality", "likelihood": 3},
{"id": "T03", "name": "Malware infection", "category": "Integrity", "likelihood": 4},
{"id": "T04", "name": "Phishing attack", "category": "Social Engineering", "likelihood": 5},
{"id": "T05", "name": "Denial of service", "category": "Availability", "likelihood": 3},
{"id": "T06", "name": "Insider threat", "category": "Personnel", "likelihood": 2},
{"id": "T07", "name": "Physical theft", "category": "Physical", "likelihood": 2},
{"id": "T08", "name": "System misconfiguration", "category": "Technical", "likelihood": 4},
{"id": "T09", "name": "Third-party compromise", "category": "Supply Chain", "likelihood": 3},
{"id": "T10", "name": "Natural disaster", "category": "Environmental", "likelihood": 1},
],
"healthcare": [
{"id": "T01", "name": "Patient data breach", "category": "Confidentiality", "likelihood": 4},
{"id": "T02", "name": "Ransomware attack", "category": "Availability", "likelihood": 4},
{"id": "T03", "name": "Medical device tampering", "category": "Integrity", "likelihood": 3},
{"id": "T04", "name": "EHR unauthorized access", "category": "Access", "likelihood": 4},
{"id": "T05", "name": "HIPAA violation", "category": "Compliance", "likelihood": 3},
{"id": "T06", "name": "Clinical data corruption", "category": "Integrity", "likelihood": 2},
{"id": "T07", "name": "Telemedicine interception", "category": "Confidentiality", "likelihood": 3},
{"id": "T08", "name": "Credential theft", "category": "Access", "likelihood": 5},
{"id": "T09", "name": "Third-party vendor breach", "category": "Supply Chain", "likelihood": 3},
{"id": "T10", "name": "Insider data theft", "category": "Personnel", "likelihood": 2},
],
"cloud": [
{"id": "T01", "name": "Cloud misconfiguration", "category": "Technical", "likelihood": 5},
{"id": "T02", "name": "API vulnerability exploit", "category": "Application", "likelihood": 4},
{"id": "T03", "name": "Account hijacking", "category": "Access", "likelihood": 4},
{"id": "T04", "name": "Data exfiltration", "category": "Confidentiality", "likelihood": 3},
{"id": "T05", "name": "Shared tenancy attack", "category": "Infrastructure", "likelihood": 2},
{"id": "T06", "name": "Service outage", "category": "Availability", "likelihood": 3},
{"id": "T07", "name": "Compliance violation", "category": "Compliance", "likelihood": 3},
{"id": "T08", "name": "Shadow IT exposure", "category": "Governance", "likelihood": 4},
{"id": "T09", "name": "Encryption key exposure", "category": "Cryptography", "likelihood": 2},
{"id": "T10", "name": "CSP vendor lock-in", "category": "Strategic", "likelihood": 3},
],
}
# Vulnerability patterns
VULNERABILITY_PATTERNS = {
"access": ["No MFA", "Weak passwords", "Excessive privileges", "Shared accounts"],
"technical": ["Unpatched systems", "Weak encryption", "Missing logging", "Open ports"],
"process": ["No incident response", "Missing backups", "No change control", "Lack of monitoring"],
"people": ["Untrained staff", "No security awareness", "Social engineering susceptibility"],
}
# Asset classification criteria
CLASSIFICATION_CRITERIA = {
"critical": {"description": "Business-critical, severe impact if compromised", "impact": 5},
"high": {"description": "Important assets, significant impact", "impact": 4},
"medium": {"description": "Standard business assets, moderate impact", "impact": 3},
"low": {"description": "Limited business value, minor impact", "impact": 2},
"minimal": {"description": "Public or non-sensitive, negligible impact", "impact": 1},
}
# Risk treatment options
TREATMENT_OPTIONS = {
"critical": "Immediate mitigation required - implement controls within 7 days",
"high": "Priority mitigation - implement controls within 30 days",
"medium": "Planned mitigation - implement controls within 90 days",
"low": "Accept risk with monitoring or implement low-cost controls",
"minimal": "Accept risk - document acceptance decision",
}
def calculate_risk_score(likelihood: int, impact: int) -> int:
"""Calculate risk score as likelihood × impact."""
return likelihood * impact
def get_risk_level(score: int) -> str:
"""Determine risk level from score."""
if score >= 20:
return "critical"
elif score >= 15:
return "high"
elif score >= 10:
return "medium"
elif score >= 5:
return "low"
return "minimal"
def load_assets_from_csv(filepath: str) -> List[Dict[str, Any]]:
"""Load asset inventory from CSV file."""
assets = []
try:
with open(filepath, "r", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
asset = {
"id": row.get("id", f"A{len(assets)+1:03d}"),
"name": row.get("name", "Unknown"),
"type": row.get("type", "Information"),
"owner": row.get("owner", "Unassigned"),
"classification": row.get("classification", "medium").lower(),
}
assets.append(asset)
except FileNotFoundError:
print(f"Error: Asset file not found: {filepath}", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"Error reading asset file: {e}", file=sys.stderr)
sys.exit(1)
return assets
def generate_sample_assets(scope: str, template: str) -> List[Dict[str, Any]]:
"""Generate sample asset inventory based on scope and template."""
base_assets = []
if template == "healthcare":
base_assets = [
{"id": "A001", "name": "Patient Database", "type": "Information", "owner": "DBA Team", "classification": "critical"},
{"id": "A002", "name": "EHR Application", "type": "Software", "owner": "App Team", "classification": "critical"},
{"id": "A003", "name": "Medical Imaging System", "type": "Software", "owner": "Radiology", "classification": "high"},
{"id": "A004", "name": "Database Servers", "type": "Hardware", "owner": "Infrastructure", "classification": "high"},
{"id": "A005", "name": "Admin Credentials", "type": "Access", "owner": "Security", "classification": "critical"},
{"id": "A006", "name": "Backup Systems", "type": "Service", "owner": "IT Ops", "classification": "high"},
{"id": "A007", "name": "Network Infrastructure", "type": "Hardware", "owner": "Network Team", "classification": "high"},
{"id": "A008", "name": "API Gateway", "type": "Software", "owner": "Platform Team", "classification": "high"},
]
elif template == "cloud":
base_assets = [
{"id": "A001", "name": "Cloud Storage Buckets", "type": "Service", "owner": "Platform", "classification": "high"},
{"id": "A002", "name": "Container Registry", "type": "Service", "owner": "DevOps", "classification": "high"},
{"id": "A003", "name": "API Services", "type": "Software", "owner": "Engineering", "classification": "critical"},
{"id": "A004", "name": "Database Instances", "type": "Service", "owner": "DBA Team", "classification": "critical"},
{"id": "A005", "name": "IAM Configuration", "type": "Access", "owner": "Security", "classification": "critical"},
{"id": "A006", "name": "Secrets Manager", "type": "Service", "owner": "Security", "classification": "critical"},
{"id": "A007", "name": "Load Balancers", "type": "Infrastructure", "owner": "Platform", "classification": "high"},
{"id": "A008", "name": "Monitoring Systems", "type": "Service", "owner": "SRE", "classification": "medium"},
]
else: # general
base_assets = [
{"id": "A001", "name": "Corporate Data", "type": "Information", "owner": "Data Team", "classification": "high"},
{"id": "A002", "name": "Business Applications", "type": "Software", "owner": "IT", "classification": "high"},
{"id": "A003", "name": "Server Infrastructure", "type": "Hardware", "owner": "Infrastructure", "classification": "high"},
{"id": "A004", "name": "User Credentials", "type": "Access", "owner": "Security", "classification": "critical"},
{"id": "A005", "name": "Email System", "type": "Service", "owner": "IT", "classification": "medium"},
{"id": "A006", "name": "File Servers", "type": "Hardware", "owner": "Infrastructure", "classification": "medium"},
{"id": "A007", "name": "Network Equipment", "type": "Hardware", "owner": "Network", "classification": "high"},
{"id": "A008", "name": "Backup Infrastructure", "type": "Service", "owner": "IT Ops", "classification": "high"},
]
# Tag assets with scope
for asset in base_assets:
asset["scope"] = scope
return base_assets
def assess_risks(
assets: List[Dict[str, Any]],
template: str
) -> List[Dict[str, Any]]:
"""Perform risk assessment on assets."""
threats = THREAT_CATALOGS.get(template, THREAT_CATALOGS["general"])
risks = []
risk_id = 1
for asset in assets:
classification = asset.get("classification", "medium")
impact = CLASSIFICATION_CRITERIA.get(classification, {}).get("impact", 3)
# Map relevant threats to asset
relevant_threats = threats[:5] # Top 5 threats for each asset
for threat in relevant_threats:
likelihood = threat["likelihood"]
score = calculate_risk_score(likelihood, impact)
level = get_risk_level(score)
# Identify potential vulnerabilities
vuln_category = threat["category"].lower()
vulns = VULNERABILITY_PATTERNS.get("technical", ["Unknown vulnerability"])
if "access" in vuln_category:
vulns = VULNERABILITY_PATTERNS["access"]
elif "personnel" in vuln_category or "social" in vuln_category:
vulns = VULNERABILITY_PATTERNS["people"]
risk = {
"id": f"R{risk_id:03d}",
"asset_id": asset["id"],
"asset_name": asset["name"],
"threat_id": threat["id"],
"threat_name": threat["name"],
"threat_category": threat["category"],
"vulnerability": vulns[0] if vulns else "Unidentified",
"likelihood": likelihood,
"impact": impact,
"score": score,
"level": level,
"treatment": TREATMENT_OPTIONS.get(level, "Review required"),
}
risks.append(risk)
risk_id += 1
# Sort by risk score descending
risks.sort(key=lambda x: x["score"], reverse=True)
return risks
def calculate_residual_risk(risk: Dict[str, Any], control_effectiveness: float = 0.7) -> Dict[str, Any]:
"""Calculate residual risk after applying controls."""
residual_likelihood = max(1, int(risk["likelihood"] * (1 - control_effectiveness)))
residual_score = calculate_risk_score(residual_likelihood, risk["impact"])
return {
"risk_id": risk["id"],
"inherent_score": risk["score"],
"control_effectiveness": control_effectiveness,
"residual_likelihood": residual_likelihood,
"residual_score": residual_score,
"residual_level": get_risk_level(residual_score),
}
def generate_report(
scope: str,
template: str,
assets: List[Dict[str, Any]],
risks: List[Dict[str, Any]],
output_format: str
) -> str:
"""Generate risk assessment report."""
timestamp = datetime.now().isoformat()
# Calculate summary statistics
risk_counts = {"critical": 0, "high": 0, "medium": 0, "low": 0, "minimal": 0}
for risk in risks:
risk_counts[risk["level"]] += 1
report_data = {
"metadata": {
"scope": scope,
"template": template,
"timestamp": timestamp,
"methodology": "ISO 27001 Clause 6.1.2",
},
"summary": {
"total_assets": len(assets),
"total_risks": len(risks),
"risk_distribution": risk_counts,
"critical_risks": risk_counts["critical"],
"high_risks": risk_counts["high"],
},
"assets": assets,
"risks": risks,
"residual_risks": [calculate_residual_risk(r) for r in risks[:10]], # Top 10
}
if output_format == "json":
return json.dumps(report_data, indent=2)
elif output_format == "csv":
lines = ["risk_id,asset,threat,likelihood,impact,score,level,treatment"]
for risk in risks:
lines.append(
f"{risk['id']},{risk['asset_name']},{risk['threat_name']},"
f"{risk['likelihood']},{risk['impact']},{risk['score']},"
f"{risk['level']},{risk['treatment']}"
)
return "\n".join(lines)
else: # markdown
lines = [
f"# Security Risk Assessment Report",
f"",
f"**Scope:** {scope}",
f"**Template:** {template}",
f"**Date:** {timestamp}",
f"**Methodology:** ISO 27001 Clause 6.1.2",
f"",
f"## Summary",
f"",
f"| Metric | Value |",
f"|--------|-------|",
f"| Total Assets | {len(assets)} |",
f"| Total Risks | {len(risks)} |",
f"| Critical Risks | {risk_counts['critical']} |",
f"| High Risks | {risk_counts['high']} |",
f"| Medium Risks | {risk_counts['medium']} |",
f"",
f"## Asset Inventory",
f"",
f"| ID | Asset | Type | Owner | Classification |",
f"|----|-------|------|-------|----------------|",
]
for asset in assets:
lines.append(
f"| {asset['id']} | {asset['name']} | {asset['type']} | "
f"{asset['owner']} | {asset['classification'].capitalize()} |"
)
lines.extend([
f"",
f"## Risk Register",
f"",
f"| Risk ID | Asset | Threat | L | I | Score | Level |",
f"|---------|-------|--------|---|---|-------|-------|",
])
for risk in risks[:20]: # Top 20 risks
lines.append(
f"| {risk['id']} | {risk['asset_name']} | {risk['threat_name']} | "
f"{risk['likelihood']} | {risk['impact']} | {risk['score']} | "
f"{risk['level'].capitalize()} |"
)
lines.extend([
f"",
f"## Treatment Recommendations",
f"",
])
for level, treatment in TREATMENT_OPTIONS.items():
count = risk_counts[level]
if count > 0:
lines.append(f"**{level.capitalize()} ({count} risks):** {treatment}")
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(
description="Security Risk Assessment Tool - ISO 27001 Clause 6.1.2"
)
parser.add_argument(
"--scope", "-s",
required=True,
help="System or area to assess"
)
parser.add_argument(
"--template", "-t",
choices=["general", "healthcare", "cloud"],
default="general",
help="Assessment template (default: general)"
)
parser.add_argument(
"--assets", "-a",
help="CSV file with asset inventory"
)
parser.add_argument(
"--output", "-o",
help="Output file path (default: stdout)"
)
parser.add_argument(
"--format", "-f",
choices=["json", "csv", "markdown"],
default="markdown",
help="Output format (default: markdown)"
)
args = parser.parse_args()
# Load or generate assets
if args.assets:
assets = load_assets_from_csv(args.assets)
else:
assets = generate_sample_assets(args.scope, args.template)
# Perform risk assessment
risks = assess_risks(assets, args.template)
# Generate report
report = generate_report(
args.scope,
args.template,
assets,
risks,
args.format
)
# Output
if args.output:
with open(args.output, "w", encoding="utf-8") as f:
f.write(report)
print(f"Report saved to: {args.output}", file=sys.stderr)
else:
print(report)
if __name__ == "__main__":
main()
Kiểm tra vault LLM Wiki: trang mồ côi, link hỏng, trang cũ, mâu thuẫn, thiếu tham chiếu chéo; xuất báo cáo gợi ý hành động.
--- name: wiki-lint description: Run a health check on the LLM Wiki vault — mechanical checks (orphans, broken links, stale pages, missing frontmatter, log gap, duplicates) plus semantic checks (contradictions, cross-reference gaps, concepts missing their own page). Outputs a markdown report with suggested actions. Usage /wiki-lint [--stale-days N] [--log-gap-days N] --- # /wiki-lint Health-check the wiki. Surfaces orphan pages, broken wikilinks, stale claims, missing frontmatter, contradictions, and structural drift. **Reports, doesn't silently fix** — you decide what to change. Run this weekly, after batch ingests, and always before sharing the wiki. ## Usage ``` /wiki-lint /wiki-lint --stale-days 60 /wiki-lint --log-gap-days 7 ``` ## What happens ### Pass 1 — Mechanical (scripts) - `scripts/lint_wiki.py` — orphans, broken links, stale pages, missing frontmatter, duplicate titles, log gap - `scripts/graph_analyzer.py` — hubs, sinks, connected components, graph stats ### Pass 2 — Semantic (LLM reads and thinks) - Contradictions between recently-updated pages - Stale claims superseded by newer sources - Concepts mentioned in plain text across 3+ pages without their own page - Cross-reference gaps (entities mentioned but not wikilinked) - Index drift (index.md out of sync with wiki/) ### Pass 3 — Report A markdown report grouped by severity: ```markdown # Wiki lint — <date> **Total pages:** N **Components:** N **Last log:** <date> ## Found - ⚠️ <N> contradictions (list) - <N> orphans - <N> broken links - <N> stale pages - ... ## Suggested actions 1. Investigate contradiction between [[sources/a]] and [[sources/b]] 2. Create concept page for "<name>" 3. Fix broken link in [[concepts/x]] 4. Re-ingest [[sources/c]] — stale + contradicted 5. ... ``` Then appends a `lint` entry to `log.md`. ## Sub-agent Dispatches the `wiki-linter` sub-agent. See `agents/wiki-linter.md`. ## Scripts - `engineering/llm-wiki/scripts/lint_wiki.py` - `engineering/llm-wiki/scripts/graph_analyzer.py` - `engineering/llm-wiki/scripts/append_log.py` ## Frequency | Trigger | Pass | |---|---| | Weekly | Mechanical only — fast | | After batch ingest | Full (mechanical + semantic) | | Monthly | Full + structural review | | Before sharing | Full + extra review | ## Skill Reference → `engineering/llm-wiki/SKILL.md` → `engineering/llm-wiki/references/lint-workflow.md`
Chuẩn bị đánh giá QMS theo ISO 13485 bằng 6 câu hỏi chất vấn, tập trung kiểm soát thiết kế, CAPA và giám sát sau thị trường.
--- name: "iso13485-audit-prep" description: "/cs:iso13485-audit-prep <scope> — ISO 13485 QMS audit 6-question forcing interrogation. Design controls + CAPA + post-market focused. Use before Clause 8.2.4 internal audit, MDR / FDA QSR alignment review, or product-launch DHF closure audit." --- # /cs:iso13485-audit-prep — ISO 13485 QMS Forcing Questions **Command:** `/cs:iso13485-audit-prep <scope>` The ISO 13485 QMS auditor pressure-tests any medical-device QMS work. Six traceability-obsessed questions before any internal audit, MDR / FDA QSR review, or product launch. ## When to Run - Before annual Clause 8.2.4 internal audit - Before MDR / FDA QSR alignment review (substantially harmonized post Feb 2026) - Before new-device commercial launch (DHF closure audit) - After significant CAPA closure event (effectiveness verification audit) - Post-recall event (root cause + corrective action audit) - Quarterly during regulatory submission preparation ## The Six QMS Questions ### 1. Pull three random DHFs. Are design verification + validation evidence complete? **Most-cited finding area.** - DHF must include: design plan + inputs + outputs + verification + validation + transfer + changes - Sample stratified by product class (I, IIa, IIb, III per MDR) - Reference `iso13485_audit_playbook.md` for the per-DHF checklist - Verify traceability matrix from user needs through clinical evidence ### 2. Show me the last 5 CAPAs with effectiveness verification evidence. **Second-most-cited finding area.** - Containment / correction / corrective action distinction documented - Root cause analysis depth: 5 Why minimum - Effectiveness verification = measurable evidence, not "we updated the procedure" - Closure approved by appropriate authority - Repeat CAPAs across products = systemic issue trigger ### 3. When was process validation (IQ/OQ/PQ) last revalidated? **Clause 7.5.6 — often stale.** - Initial validation at process introduction - Revalidation triggers: process change, equipment change, material change, periodic schedule - Trend monitoring (SPC) where statistical techniques apply per Clause 8.4 - Cross-check with cs-fda-qsr-auditor for 21 CFR 820.75 alignment ### 4. Show me the risk management file for the highest-risk product. **Clause 7.1 + ISO 14971:2019.** - Risk management plan exists per product - Hazard identification covers reasonable foreseeable misuse - Risk control hierarchy applied: inherent safety > protective measures > information for safety - Residual risk evaluated + accepted with rationale - Post-production information feeds back into RMF - For AI-enabled medical devices: layer ISO 42001 A.5 impact assessment on top ### 5. Show me post-market surveillance evidence — last 6 months. **Clause 8.2.1 — high-stakes for MDR + FDA.** - Customer complaint log + investigation closure - Vigilance reports (serious incident / FSCA) submitted per applicable regulation - Trend analysis evidence + management review input - Post-market clinical follow-up (PMCF) for MDR high-risk devices - MDR reports per 21 CFR 803 for US-marketed devices (cross-check with cs-fda-qsr-auditor) ### 6. Where's the management review evidence covering all Clause 5.6 inputs? **Annual minimum; semi-annual for mature programs.** - Required inputs per Clause 5.6.2: audit results, customer feedback, process performance, product conformity, status of preventive + corrective actions, follow-up from prior reviews, changes that could affect QMS, recommendations for improvement, regulatory requirements - Outputs per Clause 5.6.3: improvement decisions, product requirement changes, resource needs - Integrated review across frameworks (per `multi_framework_audit_playbook.md`) preferred ## Workflow ```bash # 1. Audit programme optimization python ../../ra-qm-team/skills/qms-audit-expert/scripts/audit_schedule_optimizer.py audit_scope.json # 2. Mock audit for readiness check python ../../skills/compliance-os/scripts/audit_simulator.py iso13485_scope.json # 3. CAPA system review # Route to ra-qm-team/skills/capa-officer/ tools # 4. Risk management file review # Route to ra-qm-team/skills/risk-management-specialist/ tools ``` ## Output Format ```markdown # ISO 13485 Audit Prep: <scope> **Date:** YYYY-MM-DD ## The Decision Being Made [programme-plan | DHF-closure | CAPA-health | post-market-trend | pre-cert | MDR-FDA-alignment] ## Design Control Status (sampled DHFs) - DHFs sampled: <list product IDs> - Verification evidence: pass/fail per DHF - Validation evidence: pass/fail per DHF - Clinical evidence (per MDR Annex XIV / FDA 510(k)): pass/fail - Traceability matrix complete: yes/no per DHF ## CAPA Health - CAPAs sampled: N - Root cause analysis depth: adequate/inadequate per CAPA - Effectiveness verification: complete/incomplete per CAPA - Aging CAPAs > 90 days: N - Repeat issues across products: <list> ## Process Validation Status - Validations on schedule: % - Stale validations (> 12 months since revalidation): <list> - Statistical techniques applied per Clause 8.4: yes/no ## Risk Management File Status - Sampled product RMFs: <list> - Post-production updates in last 12 months: <count per product> - Residual risk acceptance signed: yes/no ## Post-Market Surveillance - Complaint trending: stable/rising - MDR / vigilance reports filed timely: % - PMCF on schedule (where required): yes/no ## Management Review Status - Last review date: YYYY-MM-DD - Required Clause 5.6.2 inputs present: yes/no - Open action items past due: N ## Cross-Framework Impact - EU MDR alignment: clean / gaps in <list> - FDA QSR alignment (post-Feb 2026): substantially harmonized; FDA-specific overlays per cs-fda-qsr-auditor - ISO 42001 AIMS overlay (if AI-enabled device): pass/fail per Annex A ## Verdict 🟢 READY | 🟡 CLOSE-DHF-GAPS-FIRST | 🔴 NOT-READY ## Top 3 Actions [3 concrete next steps with owner + corrective-action timeline] ``` ## Routing - `/cs:compliance-readiness` — for multi-framework view - `/cs:fda-qsr-audit-prep` — for FDA-specific overlay - `/cs:aims-audit` — for AI-enabled medical device ISO 42001 layer - `/cs:gdpr-audit-prep` — for personal-data overlap (clinical data, customer data) - `/cs:cpo-review` — for executive product strategy decisions - `/cs:decide` — to log the verdict ## Related - Agent: [`cs-cqm-iso13485`](../../agents/cs-cqm-iso13485.md) - Skill: [`qms-audit-expert`](../../../ra-qm-team/skills/qms-audit-expert/SKILL.md) - Playbook: [iso13485_audit_playbook.md](../../../ra-qm-team/skills/qms-audit-expert/references/iso13485_audit_playbook.md) - Adjacent: `../fda-qsr-audit-prep/`, `../aims-audit/`, `../compliance-readiness/` --- **Version:** 1.0.0
Làm nội dung do AI tạo nghe tự nhiên, có cá tính, bớt sáo rỗng và bớt cảm giác máy móc.
---
name: "content-humanizer"
description: "Makes AI-generated content sound genuinely human — not just cleaned up, but alive. Use when content feels robotic, uses too many AI clichés, lacks personality, or reads like it was written by committee. Triggers: 'this sounds like AI', 'make it more human', 'add personality', 'it feels generic', 'sounds robotic', 'fix AI writing', 'inject our voice'. NOT for initial content creation (use content-production). NOT for SEO optimization (use content-production Mode 3)."
license: MIT
metadata:
version: 1.0.0
author: Alireza Rezvani
category: marketing
updated: 2026-03-06
---
# Content Humanizer
You are an expert in authentic writing and brand voice. Your goal is to transform content that reads like it was generated by a machine — even when it technically was — into writing that sounds like a real person with real opinions, real experience, and real stakes in what they're saying.
This is not a cleaning service. You're not just removing "delve" and calling it a day. You're rebuilding the voice from the ground up.
## Before Starting
**Check for context first:**
If `marketing-context.md` exists, read it. It contains brand voice guidelines, writing examples, and the specific tone this brand uses. That context is your voice blueprint. Use it — don't improvise a voice when the brief already defines one.
Gather what you need before starting:
### What you need
- **The content** — paste the draft to humanize
- **Brand voice notes** — if no `marketing-context.md`, ask: "Is your voice direct/casual/technical/irreverent? Give me one example of writing you love."
- **Audience** — who reads this? (This changes what "human" sounds like)
- **Goal** — what should this piece do? (Knowing the goal tells you how much personality is appropriate)
One question if needed: "Before I rewrite this, give me an example of content you've written or read that felt right. Specific is better than descriptive."
## How This Skill Works
Three modes. Run them in sequence for a full transformation, or jump to the one you need:
### Mode 1: Detect — AI Pattern Analysis
Audit the content for AI tells. Name what's wrong and why before fixing anything. This is diagnostic — not editorial.
### Mode 2: Humanize — Pattern Removal and Rhythm Fix
Strip the AI patterns. Fix sentence rhythm. Replace generic with specific. The content starts sounding like a person.
### Mode 3: Voice Injection — Brand Character
Now that the generic is gone, inject the brand's specific personality. This is where "human" becomes *your brand's* human.
Run all three in one pass when you have enough context. Split them when the client needs to see the audit before you edit.
---
## Mode 1: Detect — AI Pattern Analysis
Scan the content for these categories. Score severity: 🔴 critical (kills credibility) / 🟡 medium (softens impact) / 🟢 minor (polish only).
See [references/ai-tells-checklist.md](references/ai-tells-checklist.md) for the comprehensive detection list.
### The Core AI Tell Categories
**1. Overused Filler Words** 🔴
The model loves certain words because they appear frequently in its training data. Flag these on sight:
- "delve," "delve into," "delve deeper"
- "landscape" (as in "the current AI landscape")
- "crucial," "vital," "pivotal"
- "leverage" (when "use" works fine)
- "furthermore," "moreover," "in addition"
- "navigate" (metaphorical: "navigate this challenge")
- "robust," "comprehensive," "holistic"
- "foster," "facilitate," "ensure"
**2. Hedging Chains** 🔴
AI hedges constantly. It hedges because it doesn't know if it's right. Humans hedge sometimes — but not in every sentence.
- "It's important to note that..."
- "It's worth mentioning that..."
- "One might argue that..."
- "In many cases," "In most scenarios,"
- "It goes without saying..."
- "Needless to say..."
**3. Em-Dash Overuse** 🟡
One or two em-dashes in a piece: fine. Em-dash in every other paragraph: AI fingerprint. The model uses em-dashes to add clauses the way humans add breath — but it does it compulsively.
**4. Identical Paragraph Structure** 🔴
Every paragraph: topic sentence → explanation → example → bridge to next. AI is remarkably consistent. Remarkably boring. Real writing has short paragraphs. Fragments. Asides. Digressions. Then it snaps back. The structure varies.
**5. Lack of Specificity** 🔴
AI replaces specific claims with vague ones because specific claims can be wrong. Look for:
- "Many companies" → which companies?
- "Studies show" → which studies?
- "Significantly improved" → improved by how much?
- "Leading brands" → name one
- "A lot of" → how many?
**6. False Certainty / False Authority** 🟡
AI asserts confidently about things no one can be certain about. "Companies that do X are more successful." According to what? This isn't humility — it's laziness dressed as confidence.
**7. The "In conclusion" Paragraph** 🟡
AI conclusions are often carbon copies of the intro. "In this article, we explored X, Y, and Z. By implementing these strategies, you can achieve..." No human concludes like this. Real conclusions either add something new or nail the exit line.
---
## Mode 2: Humanize — Pattern Removal and Rhythm Fix
After identifying what's wrong, fix it systematically.
### Replace Filler Words
**Rule:** Never just delete — always replace with something better.
| AI phrase | Human alternative |
|---|---|
| "delve into" | "look at," "dig into," "break down," or just: "here's what matters" |
| "the [X] landscape" | "how [X] works today," "the current state of [X]" |
| "leverage" | "use," "apply," "put to work" |
| "crucial" / "vital" | "the part that actually matters," "the one thing," or just state the thing — let it be self-evidently important |
| "furthermore" | nothing (just start the next sentence), or "and," or "also" |
| "robust" | specific: "handles 10,000 requests/sec," "covers 47 edge cases" |
| "facilitate" | "help," "make easier," "allow" |
| "navigate this challenge" | "handle this," "deal with this," "get through this" |
### Fix Sentence Rhythm
**The problem:** AI produces uniform sentence length. Every sentence is 18-22 words. The ear goes numb.
**The fix:** Deliberate variation. Read aloud. Then:
- Break long sentences into two
- Add a short sentence after a long one. Like this.
- Use fragments where they serve emphasis. Especially for emphasis.
- Let some sentences run longer when the thought needs to unwind and the reader has the context to follow it
**Rhythm patterns that feel human:**
- Long. Short. Long, long. Short.
- Question? Answer. Proof.
- Claim. Specific example. So what?
### Replace Generic with Specific
Every vague claim is an invitation to doubt. Replace:
**Before:** "Many companies have seen significant improvements by implementing this strategy."
**After:** "HubSpot published their onboarding funnel data in 2023 — companies that hit their first-value moment within 7 days showed 40% higher 90-day retention. That's not a rounding error."
If you don't have specific data, be honest: "I haven't seen controlled studies on this, but in my experience working with SaaS onboarding flows, the pattern is consistent: earlier activation = higher retention."
Personal experience beats vague authority. Every time.
### Vary Paragraph Structure
Break the uniform SEEB pattern (Statement → Explanation → Example → Bridge):
- **Single-sentence paragraph:** Use it. Emphasis needs air.
- **Question paragraph:** Pose a question. Then answer it.
- **List in the middle:** Drop a quick list when there are genuinely 3-5 parallel items. Then return to prose.
- **Aside / parenthetical paragraph:** A small digression that reveals personality. (Readers actually like these. It's the equivalent of a raised eyebrow mid-sentence.)
- **Confession:** "I got this wrong the first time." Instantly human.
### Add Friction and Imperfection
AI writing is too smooth. Too complete. Real people:
- Change direction mid-thought and acknowledge it: "Actually, let me back up..."
- Qualify things they're uncertain about without hiding the uncertainty
- Have opinions that might be wrong: "I might be wrong about this, but..."
- Notice things and say so: "What's interesting here is..."
- React: "Which, if you've ever tried to debug this, you know is maddening."
---
## Mode 3: Voice Injection — Brand Character
Humanizing removes AI. Voice injection makes it *yours*.
### Read the Voice Blueprint First
If `marketing-context.md` is available: read the brand voice section and writing examples. If not, ask for one example of content this brand loves. One. Then extract the patterns from it.
**What to extract from a voice example:**
- Sentence length preference (short punchy vs. longer flowing?)
- Formality level (contractions? slang? industry jargon?)
- Use of humor (dry wit? self-deprecating? none?)
- Relationship stance (peer-to-peer? expert-to-student? provocateur?)
- Signature phrases or patterns
See [references/voice-techniques.md](references/voice-techniques.md) for specific techniques for each voice type.
### Voice Injection Techniques
**1. Personal Anecdotes**
Even branded content gets more credible when grounded in experience. "We saw this firsthand when building X" is worth more than any study citation.
**2. Direct Address**
Talk to the reader as "you." Not "users" or "teams" or "organizations." You.
**3. Opinions Without Apology**
State your position. "We think the industry is wrong about this" is more credible than "there are various perspectives." Take the side.
**4. The Aside**
A brief parenthetical that shows the brand knows more than it's saying. "This also affects API performance, but that's a separate rabbit hole."
**5. Rhythm Signature**
Every brand has a rhythm. Some write in short staccato bursts. Some write long, winding sentences that spiral back on themselves. Find the rhythm from the examples and apply it consistently.
### Before / After Example
**Before (AI-generated):**
> It is crucial to leverage your existing customer data in order to effectively navigate the competitive landscape. Furthermore, by implementing a robust onboarding strategy, organizations can ensure that users achieve maximum value from the product and reduce churn significantly.
**After (humanized):**
> Here's the thing nobody says out loud: most SaaS companies have the data to fix their churn problem. They just don't look at it until after customers leave.
>
> Your activation funnel is in there. Your best cohorts, your worst, the moment the drop-off happens. You don't need another tool — you need someone to stop ignoring what the tool is already showing you.
>
> Nail onboarding first. Everything else is downstream.
What changed:
- Removed: "crucial," "leverage," "navigate," "robust," "ensure," "significantly," "furthermore"
- Added: direct address, specific accusation ("what the tool is already showing you"), short-sentence punch at the end
- Changed: passive recommendations → active point of view
---
## Proactive Triggers
Flag these without being asked:
- **AI fingerprint density too high** — If the piece has 10+ AI tells per 500 words, a patch job won't work. Flag that the piece needs a full rewrite, not an edit. Trying to polish a piece that's 80% AI patterns produces AI patterns with nicer words.
- **Voice context missing** — If `marketing-context.md` doesn't exist and the user hasn't given voice guidance, pause before injecting voice. Ask for one example. Guessing the voice and being wrong wastes everyone's time.
- **Specificity gap** — If the piece makes 5+ vague claims with zero data or attribution, flag it to the user. You can make the prose flow better, but you can't invent specific proof. They need to provide it.
- **Tone mismatch after humanizing** — If the piece is now genuinely human but sounds like a different brand than everything else the client publishes, flag it. Consistency matters as much as quality.
- **Over-editing risk** — If the original content has one or two genuinely good paragraphs buried in the AI mush, flag them before rewriting. Don't accidentally destroy the good parts.
---
## Output Artifacts
| When you ask for... | You get... |
|---|---|
| AI audit | Annotated version of the draft with each AI pattern flagged, severity score, and count by category |
| Humanized draft | Full rewrite with AI patterns removed, rhythm varied, specificity improved |
| Voice injection | Annotated draft with brand voice applied — specific changes called out so you can learn the pattern |
| Before/after comparison | Side-by-side view of key paragraphs showing what changed and why |
| Humanity score | Run `scripts/humanizer_scorer.py` — 0-100 score with breakdown by signal type |
---
## Communication
All output follows the structured standard:
- **Bottom line first** — answer before explanation
- **What + Why + How** — every finding includes all three
- **Actions have owners and deadlines** — no "you might want to consider"
- **Confidence tagging** — 🟢 verified pattern / 🟡 medium / 🔴 assumed based on limited voice context
When auditing: name the pattern → explain why it reads as AI → give the specific fix. Not "this sounds robotic." Say: "Paragraph 4 opens with 'It is important to note that' — this is a pure hedge. Cut it. Start with the actual note."
---
## Related Skills
- **content-production**: Use to produce the initial draft. Run content-humanizer after drafting, before the SEO optimization pass.
- **copywriting**: Use for conversion copy — landing pages, CTAs, headlines. content-humanizer works on longer-form pieces; copywriting handles short punchy copy with different principles.
- **content-strategy**: Use when deciding what content to create. NOT for voice or draft execution.
- **ai-seo**: Use after humanizing, to optimize for AI search citation. Human-sounding content gets cited more — but it still needs structure to get extracted.
FILE:references/ai-tells-checklist.md
# AI Tells Checklist
A comprehensive reference for detecting AI-generated or AI-assisted writing patterns. Use this during Mode 1 (Detect) to audit content before editing.
Rate each finding: 🔴 Critical (rewrites required) / 🟡 Medium (edits needed) / 🟢 Minor (polish)
---
## Category 1: Overused Vocabulary
These words appear in AI output at 5-20x the frequency of human writing. One instance is fine. Multiple instances in a single piece is a tell.
### Red-flag verbs (overused)
| Word | Problem | What humans say instead |
|---|---|---|
| delve / delve into | Pretentious filler | look at, dig into, explore, examine |
| leverage | Jargon for "use" | use, apply, put to work, capitalize on |
| foster | Formal filler | build, develop, encourage, create |
| facilitate | Bureaucratic filler | help, make easier, allow, support |
| navigate | Overused metaphor | handle, manage, work through, deal with |
| ensure | Empty guarantee | check that, make sure, verify |
| utilize | Formal for "use" | just say "use" |
| prioritize | Often redundant | just say what to do first |
| streamline | Vague improvement promise | be specific about what gets faster/easier |
### Red-flag adjectives (overused)
| Word | Problem | What humans say instead |
|---|---|---|
| crucial / vital / pivotal | Overloaded intensifiers | just show why it matters |
| robust | Vague positive | specific: "handles X load," "covers Y cases" |
| comprehensive | Overpromise | specific: "covers the 5 most common..." |
| innovative | Meaningless | say what it actually does differently |
| holistic | Buzzword | say what it covers |
| seamless | Overused product adjective | describe the actual experience |
| cutting-edge | Dated marketing | say what's new or different specifically |
| dynamic | Filler adjective | usually just delete it |
### Red-flag nouns (overused)
| Word | Problem | What humans say instead |
|---|---|---|
| landscape | Overused metaphor | "how X works today," "the state of X" |
| ecosystem | Overused for "industry" or "community" | be specific |
| framework | Often vague | name the specific framework or approach |
| paradigm | Academic filler | usually replaceable with "approach" or "way of thinking" |
| synergy | Corporate cliché | delete or replace with what actually happens |
---
## Category 2: Hedging and Qualification Patterns
Humans hedge. AI hedges compulsively. The difference is frequency and context.
### Opening hedges (usually safe to cut)
- "It's important to note that..."
- "It's worth mentioning that..."
- "It should be noted that..."
- "Needless to say..."
- "It goes without saying..."
- "Of course, ..."
- "Naturally, ..."
### Mid-sentence hedges (examine each)
- "In many cases" / "In most instances" / "In certain scenarios"
- "Generally speaking," / "For the most part,"
- "This may vary depending on..."
- "Results may differ based on..."
- "While this isn't always the case..."
**Diagnostic:** If the hedge is protecting a claim that should just be a claim, cut the hedge and state the claim. If the hedge reflects genuine uncertainty, keep it — but make the uncertainty specific: "I don't have data for this, but based on [context]..."
### Vague authority claims (replace with specifics)
- "Studies show..." → "A 2023 McKinsey study of 400 SaaS companies showed..."
- "Research suggests..." → same — name the research
- "Many companies..." → "HubSpot, Slack, and several bootstrapped SaaS founders we've talked to..."
- "Experts agree..." → name one expert
- "It has been shown that..." → by whom, when, where
---
## Category 3: Structural Patterns
### The SEEB paragraph (most common)
Pattern: Statement → Explanation → Example → Bridge to next topic
When every paragraph follows this exact structure, the writing reads like a machine assembled it. Because it was.
**Fix:** Mix in fragments. Questions. Short paragraphs. Asides. Let sections breathe differently.
### Parallelism overload
AI loves parallel structure. Three-item lists. Four-item lists. Everything in threes. Alliteration sometimes.
Occasional parallelism is powerful. Three consecutive bulleted lists of three items each is a tell.
**Fix:** Break the rhythm. Some sections need bullets. Most don't.
### The summary conclusion
AI conclusions restate the introduction. Paragraph by paragraph. "In this guide, we covered X, Y, and Z. By applying these strategies, you can achieve [thing from the intro]."
Human conclusions either add something (a new angle, an honest admission, a call to action that feels earned) or they nail the exit line and stop.
### Symmetric section lengths
Every H2 section is roughly the same length. ~300 words. Every one. That's a machine maintaining consistency. Humans have opinions — some things deserve more space, some less.
---
## Category 4: Punctuation and Formatting Tells
### Em-dash frequency 🟡
One or two em-dashes per piece: fine. Three per page: suspicion. Five+: AI fingerprint.
The em-dash is AI's favorite way to add subordinate clauses — like this — because it sounds sophisticated — and it learned this from a lot of well-written text — so now it can't stop.
**Fix:** Replace most with periods. Break into shorter sentences.
### Colon-then-list patterns 🟢
AI frequently: introduces a list: with: colons. Multiple times per section. Lists are useful. But if every section has a colon-introducing list, it's mechanical.
### Excessive bold 🟢
AI sometimes bolds every important-sounding phrase in a paragraph. **This results** in **too many** phrases being **highlighted for emphasis** when the emphasis **dilutes itself**.
Bold should be used sparingly — for the one thing that matters most in a section, not for three to five things per paragraph.
---
## Category 5: Tonal Tells
### False warmth 🟡
"We hope this guide has been helpful in your journey to..."
"We trust that you've found valuable insights in..."
"It's our sincere hope that these strategies will empower you to..."
No one talks like this. It's the corporate newsletter voice. Cut it.
### Emotional escalation without basis 🟡
AI sometimes starts clinical and then, near the end, gets unexpectedly warm and inspirational. "Now that you have these tools, you can transform your business and achieve your goals." The warmth wasn't earned by the preceding content.
### Artificial enthusiasm 🟢
"Exciting developments," "fascinating case study," "incredible opportunity" — used to simulate human engagement. Usually reads as hollow because it's disconnected from actual content that earns those descriptors.
---
## Quick Audit Scoring
For a 1,000-word piece, count:
| Signal | Count | Severity Threshold |
|---|---|---|
| Red-flag vocabulary words | | >5 = 🔴 |
| Opening hedges | | >2 = 🔴 |
| Vague authority claims ("studies show") | | >2 = 🔴 |
| Em-dashes | | >4 = 🟡 |
| SEEB paragraphs (all follow same structure) | | >60% of paragraphs = 🔴 |
| Instances of false warmth | | >1 = 🟡 |
**Scoring:**
- 0-5 total flags: Light edit (Mode 2 quick pass)
- 6-12 total flags: Full humanize pass (Mode 2 complete)
- 12+ total flags: Full rewrite recommended (Mode 1 audit → complete Mode 2 → Mode 3)
FILE:references/voice-techniques.md
# Voice Techniques Reference
Techniques for injecting authentic brand voice into content. This is the Mode 3 playbook — after you've removed AI patterns (Mode 2), these techniques put the brand's specific personality in.
---
## Step 1: Extract the Voice Profile
Before writing anything, extract the voice from examples. Ask for one piece of content the brand considers representative. Then answer these questions about it:
| Question | What to look for |
|---|---|
| Average sentence length? | Count words per sentence across 10 sentences. ≤15 = punchy. ≥25 = flowing. |
| Formality level? | Count contractions ("it's" vs "it is"). Count first-person. Count slang. |
| Use of humor? | Dry wit (unexpected juxtapositions). Self-deprecating (acknowledges own limitations). Provocateur (picks fights). None. |
| Relationship to reader? | Peer (we're both figuring this out). Expert (I know, you're learning). Challenger (you're probably wrong about this). |
| Signature phrases? | Phrases or constructions that appear more than once. That's the brand's verbal tic. |
| What do they avoid? | Listen for what's conspicuously absent. |
Document this as a voice profile before editing. Reference it throughout.
---
## The Core Voice Types
### Voice Type 1: The Direct Expert
**Profile:** Short sentences. Confident claims. No hedging. No qualifications unless the qualification is the point. Reads like advice from the smartest person in the room who also has the least patience.
**Natural habitat:** Developer tools, technical documentation, no-fluff B2B brands, opinionated founders.
**Techniques:**
- Lead every section with the conclusion, not the setup
- Make claims without softening them: "Most onboarding is broken" not "Many onboarding flows may have room for improvement"
- Use "you" relentlessly — direct address, always
- Short paragraphs. Often 1-2 sentences.
- When listing, use the smallest number of words possible
- No rhetorical questions unless you're about to immediately answer them
- Swear if the brand does (a well-placed "this is genuinely terrible" signals authenticity)
**Before:** "It's important to consider that implementing an effective onboarding strategy can have significant positive impacts on user retention rates."
**After:** "Fix your onboarding. It's where you're losing them."
---
### Voice Type 2: The Thoughtful Peer
**Profile:** Longer sentences when the thought requires it. Occasional vulnerability. Shares the thinking, not just the conclusion. Treats the reader as someone at roughly the same level.
**Natural habitat:** B2B SaaS with community focus, content brands, newsletter-first companies, founder blogs.
**Techniques:**
- Show the reasoning: "Here's how I think about this..." not just the conclusion
- Acknowledge what you don't know: "I don't have solid data on this, but my read is..."
- Share the mistake before the lesson: "We tried X first and it failed. Here's what we learned."
- Longer transitions: connect ideas, don't just jump to the next point
- Questions that earn their keep — rhetorical only when you're genuinely pointing at a tension
- First person plural ("we") where appropriate — but only if there actually is a "we"
**Before:** "Companies should invest in customer success to improve retention."
**After:** "I spent two years thinking retention was a marketing problem. It wasn't. Every company we talked to that had great retention had one thing in common: they treated customer success like a product team, not a support team. The product mindset made all the difference."
---
### Voice Type 3: The Provocateur
**Profile:** Challenges received wisdom. Takes positions most people avoid. Enjoys being right when everyone else was wrong. Not confrontational for its own sake — has actual conviction behind the provocation.
**Natural habitat:** Opinionated SaaS, contrarian analyst voices, certain agency brands, fast-growing startups trying to differentiate.
**Techniques:**
- Open with the unpopular claim: "Most SEO advice is wrong" not "There are many perspectives on SEO"
- Name the thing people don't say: "The reason nobody talks about X is because..."
- Acknowledge the argument against your position before making yours stronger
- Use "actually" strategically: "What actually happens is..."
- When everyone agrees on something, be specific about what they're getting wrong
- Let the evidence land without fanfare — state the uncomfortable number, then wait
**Before:** "There are various approaches to content marketing that teams may find valuable."
**After:** "Your competitors' content marketing budgets are wasted. The average B2B blog post gets 300 views and zero conversions. The top 10% of pieces generate 90% of the value. Most teams don't know which 10% they have — which means they're spending 90% of their budget on content that will never pay back."
---
### Voice Type 4: The Enthusiastic Practitioner
**Profile:** Genuine excitement about the craft. Energized by the detail. The person who could talk about their domain for hours and you'd actually enjoy it.
**Natural habitat:** Creative tools, marketing tech, community platforms, brands with a strong practitioner user base.
**Techniques:**
- Allow enthusiasm to come through without forcing it: "This is the part I love about cohort analysis..."
- Share the unexpected detail that only a practitioner would notice
- Use specific terminology confidently — don't over-explain to practitioners
- "The thing is..." as a transition — signals you're about to share the real insight
- Parenthetical asides that reveal depth: "(And yes, I know about [edge case] — that's a separate problem)"
- Let the complexity show when the complexity is interesting
**Before:** "Segmenting your audience allows for more targeted communication."
**After:** "Here's where audience segmentation gets genuinely interesting. Most people segment by company size or industry. That's fine. But the cohort that always outperforms is behavioral — people who hit your activation event within 72 hours. That cohort has 2-3x the LTV of the same-company-size cohort that took two weeks. The behavior tells you more than the firmographic ever will."
---
## Specific Techniques Regardless of Voice Type
### The Personal Anecdote Injection
Every brand can use a personal anecdote — even B2B technical brands. The format:
**Setup:** "When we [did X thing in context]..."
**What happened:** "[Specific unexpected thing occurred]."
**The lesson:** "[The principle that explains it]."
The anecdote doesn't have to be dramatic. It just has to be real and specific. "When we rebuilt our onboarding flow in 2023, we expected the copy to be the problem. It was the loading time."
### The Honest Admission
Instantly humanizing. Rarely done by brands because it feels like weakness. But readers trust brands that admit:
- What they got wrong
- What they don't know
- What they tried that failed
- What surprised them
The format: "We thought X. We were wrong. What actually happened was Y."
### The Contrarian Setup
Works for any voice type. Setup: state the conventional wisdom. Then undercut it.
"Everyone says [thing]. The data shows [opposite thing]."
Or more subtle: "The conventional wisdom is [X]. That's mostly true. Except when [specific condition] — and that's where it gets interesting."
### Sentence Rhythm Signature
After analyzing the brand voice, identify the rhythm pattern and apply it:
- **Staccato brand:** Short sentence. Shorter. Done.
- **Flowing brand:** Content that allows ideas to build on each other, connect, and arrive somewhere richer than where they started.
- **Mixed cadence (most natural):** Long sentence establishing context. Short punch. Then another long one that goes somewhere. Then: done.
Once you know the pattern, apply it consistently throughout the piece.
### The Earned Ending
The last sentence of a piece should land. Not summarize. Not encourage. Land.
Techniques:
- **The hard cut:** State the core truth one final time, nakedly. No wrap-up.
- **The reversal:** Setup expectation, then subvert it in the last line.
- **The action:** Name the single most important thing to do next. One thing.
- **The honest admission:** "We're still figuring this out too."
Avoid: "By following these steps, you'll be well on your way to achieving your goals." Nobody reads that. Nobody trusts it.
---
## Voice Injection Quality Check
After applying voice:
- [ ] Read the piece aloud — does it sound like a person you'd actually follow?
- [ ] Is every paragraph distinguishable from the others? (Not just same rhythm, same length, same pattern)
- [ ] Is there at least one moment where the brand's specific personality is unmistakable?
- [ ] Would someone who knows this brand's other content recognize this as theirs?
- [ ] Is there at least one place where the brand says something most brands wouldn't?
FILE:scripts/humanizer_scorer.py
#!/usr/bin/env python3
"""humanizer_scorer.py — scores content 0-100 on 'humanity' by detecting AI writing patterns."""
import sys
import re
import json
import math
from collections import Counter
# ── Sample content for zero-config demo ──────────────────────────────────────
SAMPLE_HUMAN = """
We tried to fix our churn problem the wrong way for about a year.
We threw money at marketing, assumed acquisition would outpace loss, and avoided looking at the actual numbers. It didn't work. Churn stayed flat at 8% monthly, which sounds manageable until you realize that's 65% annual churn. We were filling a leaky bucket with a garden hose.
The breakthrough — if you can call it that — was embarrassingly simple: we actually talked to the customers who left.
Not the ones who complained. The ones who quietly disappeared. We called 30 churned accounts over two weeks. You know what most of them said? They didn't hate the product. They just... forgot about it. It was solving a problem they cared about once, and then stopped caring about.
So we rebuilt our onboarding around one question: what would make this impossible to ignore? Not "valuable" — people know it's valuable. Impossible to ignore.
Three months later, 30-day activation was up 40%. Churn dropped to 4.5%.
The lesson wasn't about product or pricing. It was about habit formation. And we were terrible at it.
"""
SAMPLE_AI = """
It is crucial to leverage data-driven insights in order to effectively navigate the challenges of customer retention in the competitive SaaS landscape. Furthermore, by implementing robust onboarding strategies, organizations can ensure that users achieve maximum value from the product, thereby significantly reducing churn rates.
To facilitate this process, it's important to note that companies should delve into their customer behavior data to identify patterns and trends. Moreover, by fostering meaningful connections with customers and ensuring comprehensive support throughout their journey, businesses can cultivate lasting relationships that drive long-term success.
In conclusion, the implementation of these holistic strategies will empower organizations to streamline their customer success operations and achieve sustainable growth in an increasingly competitive marketplace.
"""
# ── AI vocabulary signals ─────────────────────────────────────────────────────
AI_VOCABULARY = [
# The notorious list
"delve", "delve into", "delves", "delving",
"landscape",
"crucial", "vital", "pivotal",
"leverage", "leveraging", "leveraged",
"robust",
"comprehensive",
"holistic",
"foster", "fosters", "fostering",
"facilitate", "facilitates", "facilitating",
"navigate", "navigating",
"ensure", "ensures", "ensuring",
"utilize", "utilizing", "utilizes",
"furthermore", "moreover",
"innovative", "cutting-edge",
"seamless", "seamlessly",
"empower", "empowers", "empowering",
"streamline", "streamlines", "streamlining",
"cultivate", "cultivating",
"paradigm",
"ecosystem",
"synergy",
"in conclusion",
"in summary",
"to summarize",
]
HEDGING_PHRASES = [
"it is important to note",
"it's important to note",
"it should be noted",
"it is worth mentioning",
"it's worth mentioning",
"it goes without saying",
"needless to say",
"in many cases",
"in most cases",
"in certain cases",
"in most instances",
"in many instances",
"generally speaking",
"for the most part",
"this may vary",
"results may differ",
"one might argue",
"it can be argued",
"there are various",
"there are many",
"it is crucial to",
"it's crucial to",
]
PASSIVE_PATTERNS = [
r'\b(is|are|was|were|be|been|being)\s+(being\s+)?\w+ed\b',
r'\b(can|could|should|would|may|might|must)\s+be\s+\w+ed\b',
]
VAGUE_AUTHORITY = [
"studies show",
"research suggests",
"research shows",
"experts agree",
"experts say",
"many companies",
"leading brands",
"it has been shown",
"according to research",
"data suggests",
"evidence suggests",
]
# ── Scoring functions ─────────────────────────────────────────────────────────
def score_ai_vocabulary(text: str) -> dict:
"""Score 0-25: fewer AI words = higher score."""
text_lower = text.lower()
words_total = max(1, len(re.findall(r'\b\w+\b', text)))
hits = []
for phrase in AI_VOCABULARY:
count = text_lower.count(phrase)
if count > 0:
hits.append((phrase, count))
total_hits = sum(c for _, c in hits)
density = total_hits / (words_total / 100) # per 100 words
# Score: 0 hits = 25, scales down
if total_hits == 0:
score = 25
elif total_hits <= 2:
score = 20
elif total_hits <= 5:
score = 14
elif total_hits <= 10:
score = 8
elif total_hits <= 15:
score = 3
else:
score = 0
return {
"score": score,
"max": 25,
"ai_word_hits": total_hits,
"density_per_100_words": round(density, 2),
"flagged_terms": [f for f, _ in hits[:10]], # top 10 for display
}
def score_sentence_variance(text: str) -> dict:
"""Score 0-20: high variance = more human (robots use uniform length)."""
sentences = re.split(r'[.!?]+', text)
sentences = [s.strip() for s in sentences if len(s.split()) >= 3]
if len(sentences) < 3:
return {"score": 10, "max": 20, "std_dev": 0, "avg_length": 0, "note": "too few sentences to score"}
lengths = [len(s.split()) for s in sentences]
avg = sum(lengths) / len(lengths)
variance = sum((l - avg) ** 2 for l in lengths) / len(lengths)
std_dev = math.sqrt(variance)
# Good human writing has std_dev of 8-15 for mixed content
if std_dev >= 12:
score = 20
elif std_dev >= 8:
score = 16
elif std_dev >= 5:
score = 10
elif std_dev >= 3:
score = 5
else:
score = 0 # very robotic: all sentences same length
return {
"score": score,
"max": 20,
"std_dev": round(std_dev, 1),
"avg_length": round(avg, 1),
"min_length": min(lengths),
"max_length": max(lengths),
}
def score_passive_voice(text: str) -> dict:
"""Score 0-20: less passive = more human."""
sentences = re.split(r'[.!?]+', text)
sentences = [s.strip() for s in sentences if s.strip()]
n_sentences = max(1, len(sentences))
passive_count = 0
for pattern in PASSIVE_PATTERNS:
passive_count += len(re.findall(pattern, text, re.IGNORECASE))
passive_ratio = passive_count / n_sentences
if passive_ratio < 0.1:
score = 20
elif passive_ratio < 0.2:
score = 16
elif passive_ratio < 0.3:
score = 10
elif passive_ratio < 0.4:
score = 5
else:
score = 0
return {
"score": score,
"max": 20,
"passive_count": passive_count,
"passive_ratio": round(passive_ratio, 2),
"passive_pct": f"{round(passive_ratio * 100)}%",
}
def score_hedging(text: str) -> dict:
"""Score 0-15: fewer hedges = more direct = more human."""
text_lower = text.lower()
hits = []
for phrase in HEDGING_PHRASES:
count = text_lower.count(phrase)
if count > 0:
hits.append((phrase, count))
total_hedges = sum(c for _, c in hits)
if total_hedges == 0:
score = 15
elif total_hedges == 1:
score = 12
elif total_hedges == 2:
score = 8
elif total_hedges == 3:
score = 4
else:
score = 0
vague_hits = sum(text_lower.count(p) for p in VAGUE_AUTHORITY)
return {
"score": score,
"max": 15,
"hedge_count": total_hedges,
"vague_authority_count": vague_hits,
"flagged_phrases": [f for f, _ in hits],
}
def score_em_dashes(text: str) -> dict:
"""Score 0-10: moderate em-dash use is fine; overuse is a tell."""
# Count em-dashes (—) and double-hyphen (--) used as em-dash
em_count = text.count('—') + text.count('--')
word_count = max(1, len(re.findall(r'\b\w+\b', text)))
per_100 = em_count / (word_count / 100)
if per_100 < 0.5:
score = 10 # none or very rare: fine
elif per_100 < 1.5:
score = 8 # occasional: good
elif per_100 < 3:
score = 5 # frequent: suspicious
elif per_100 < 5:
score = 2 # overuse: likely AI
else:
score = 0 # compulsive: AI fingerprint
return {
"score": score,
"max": 10,
"em_dash_count": em_count,
"per_100_words": round(per_100, 2),
}
def score_paragraph_variety(text: str) -> dict:
"""Score 0-10: varied paragraph lengths = more human."""
paragraphs = [p.strip() for p in text.split('\n\n') if p.strip() and not p.startswith('#')]
if len(paragraphs) < 3:
return {"score": 5, "max": 10, "note": "too few paragraphs to score"}
lengths = [len(p.split()) for p in paragraphs]
avg = sum(lengths) / len(lengths)
variance = sum((l - avg) ** 2 for l in lengths) / len(lengths)
std_dev = math.sqrt(variance)
# Has any single-sentence paragraphs? (hallmark of human writing)
has_short = any(l <= 15 for l in lengths)
has_long = any(l >= 80 for l in lengths)
score = 0
if std_dev >= 30:
score += 5
elif std_dev >= 15:
score += 3
elif std_dev >= 5:
score += 1
if has_short:
score += 3
if has_long and std_dev >= 15:
score += 2
score = min(10, score)
return {
"score": score,
"max": 10,
"paragraph_count": len(paragraphs),
"paragraph_std_dev": round(std_dev, 1),
"has_short_paragraphs": has_short,
"avg_paragraph_words": round(avg, 1),
}
# ── Main scoring ──────────────────────────────────────────────────────────────
def score_humanity(text: str) -> dict:
vocab = score_ai_vocabulary(text)
variance = score_sentence_variance(text)
passive = score_passive_voice(text)
hedging = score_hedging(text)
em = score_em_dashes(text)
paragraphs = score_paragraph_variety(text)
total = vocab["score"] + variance["score"] + passive["score"] + hedging["score"] + em["score"] + paragraphs["score"]
if total >= 85:
label = "Sounds human ✅"
elif total >= 70:
label = "Mostly human — light edits needed"
elif total >= 50:
label = "Mixed — AI patterns detectable"
elif total >= 30:
label = "Robotic — significant rewrite needed"
else:
label = "AI fingerprint — full rewrite required 🔴"
return {
"humanity_score": total,
"label": label,
"sections": {
"ai_vocabulary": vocab,
"sentence_variance": variance,
"passive_voice": passive,
"hedging": hedging,
"em_dashes": em,
"paragraph_variety": paragraphs,
}
}
def print_report(result: dict, label: str = "") -> None:
total = result["humanity_score"]
verdict = result["label"]
s = result["sections"]
bar_filled = int(total / 5)
bar = "█" * bar_filled + "░" * (20 - bar_filled)
print()
print("╔══════════════════════════════════════════╗")
print("║ HUMANIZER SCORER — REPORT ║")
print("╚══════════════════════════════════════════╝")
if label:
print(f" Input: {label}")
print()
print(f" HUMANITY SCORE: {total}/100")
print(f" [{bar}]")
print(f" Verdict: {verdict}")
print()
print(" ── Section Breakdown ──────────────────────")
sections = [
("AI Vocabulary", s["ai_vocabulary"], 25),
("Sentence Variance", s["sentence_variance"], 20),
("Passive Voice", s["passive_voice"], 20),
("Hedging Phrases", s["hedging"], 15),
("Em-Dash Use", s["em_dashes"], 10),
("Paragraph Variety", s["paragraph_variety"], 10),
]
for name, sec, mx in sections:
sc = sec["score"]
bar2 = "█" * int(sc / mx * 10) + "░" * (10 - int(sc / mx * 10))
print(f" {name:<20} {sc:>2}/{mx} [{bar2}]")
print()
print(" ── Detected Issues ────────────────────────")
v = s["ai_vocabulary"]
if v["ai_word_hits"] > 0:
terms = ", ".join(v["flagged_terms"][:5])
print(f" 🔴 AI vocabulary: {v['ai_word_hits']} hits — [{terms}]")
else:
print(" ✅ No AI vocabulary detected")
sv = s["sentence_variance"]
if sv["std_dev"] < 5:
print(f" 🔴 Sentence rhythm robotic — std dev only {sv['std_dev']} (target: 8+)")
elif sv["std_dev"] < 8:
print(f" 🟡 Sentence variance low — {sv['std_dev']} (target: 8+)")
else:
print(f" ✅ Sentence variance good — {sv['std_dev']}")
pv = s["passive_voice"]
if pv["passive_ratio"] > 0.3:
print(f" 🔴 Passive voice overuse — {pv['passive_pct']} of sentences")
elif pv["passive_ratio"] > 0.2:
print(f" 🟡 Passive voice elevated — {pv['passive_pct']}")
else:
print(f" ✅ Passive voice in range — {pv['passive_pct']}")
hg = s["hedging"]
if hg["hedge_count"] > 2:
terms = ", ".join(hg["flagged_phrases"][:3])
print(f" 🔴 Hedging overload — {hg['hedge_count']} phrases: [{terms}]")
elif hg["hedge_count"] > 0:
print(f" 🟡 Hedging present — {hg['hedge_count']} phrase(s): {hg['flagged_phrases']}")
else:
print(" ✅ No hedging detected")
if hg["vague_authority_count"] > 0:
print(f" 🟡 Vague authority claims: {hg['vague_authority_count']} (e.g. 'studies show') — add citations")
em = s["em_dashes"]
if em["per_100_words"] > 3:
print(f" 🟡 Em-dash overuse — {em['em_dash_count']} in piece ({em['per_100_words']}/100 words)")
pg = s["paragraph_variety"]
if not pg.get("has_short_paragraphs"):
print(" 🟡 No short paragraphs found — add some 1-2 sentence paragraphs for rhythm")
print()
print(" ── Priority Fixes ─────────────────────────")
if v["ai_word_hits"] > 5:
print(" 1. Replace AI vocabulary (biggest impact)")
if sv["std_dev"] < 8:
print(" 2. Vary sentence length — mix short punchy sentences with longer ones")
if pv["passive_ratio"] > 0.25:
print(" 3. Flip passive sentences to active voice")
if hg["hedge_count"] > 2:
print(" 4. Cut hedging phrases — state claims directly")
if not pg.get("has_short_paragraphs"):
print(" 5. Add short paragraphs — even 1-sentence paragraphs help rhythm")
if total >= 85:
print(" ✅ No priority fixes — content reads as human")
print()
def main():
import argparse
parser = argparse.ArgumentParser(
description="Scores content 0-100 on 'humanity' by detecting AI writing patterns. "
"Checks AI vocabulary, sentence variance, passive voice, hedging, "
"em-dash overuse, and paragraph variety."
)
parser.add_argument(
"file", nargs="?", default=None,
help="Path to a text file to analyze. If omitted, runs demo comparing "
"human vs AI sample content."
)
parser.add_argument(
"--json", action="store_true",
help="Also output results as JSON."
)
args = parser.parse_args()
if args.file is None:
# Demo mode: compare human vs AI sample
print("[Demo mode — comparing human vs AI sample content]")
print()
print("═" * 50)
print("SAMPLE 1: Human-written content")
print("═" * 50)
r1 = score_humanity(SAMPLE_HUMAN)
print_report(r1, "Human sample")
print("═" * 50)
print("SAMPLE 2: AI-generated content")
print("═" * 50)
r2 = score_humanity(SAMPLE_AI)
print_report(r2, "AI sample")
print(f" Delta: Human scored {r1['humanity_score']}, AI scored {r2['humanity_score']}")
print(f" Difference: {r1['humanity_score'] - r2['humanity_score']} points")
print()
else:
try:
with open(args.file, 'r', encoding='utf-8') as f:
text = f.read()
except FileNotFoundError:
print(f"Error: file not found: {args.file}", file=sys.stderr)
sys.exit(1)
result = score_humanity(text)
print_report(result, args.file)
if args.json:
print(json.dumps(result, indent=2))
if __name__ == "__main__":
main()
Bộ 6 skill quản lý dự án: PM cấp cao, scrum master, chuyên gia Jira (JQL), Confluence, quản trị Atlassian, tạo template, tích hợp MCP với Jira/Confluence.
--- name: "pm-skills" description: "6 project management agent skills and plugins for Claude Code, Codex, Gemini CLI, Cursor, OpenClaw. Senior PM, scrum master, Jira expert (JQL), Confluence expert, Atlassian admin, template creator. MCP integration for live Jira/Confluence automation." version: 2.9.0 author: Alireza Rezvani license: MIT tags: - project-management - jira - confluence - atlassian - scrum - agile agents: - claude-code - codex-cli - openclaw --- # Project Management Skills 6 production-ready project management skills with Atlassian MCP integration. ## Quick Start ### Claude Code ``` /read project-management/jira-expert/SKILL.md ``` ### Codex CLI ```bash npx agent-skills-cli add alirezarezvani/claude-skills/project-management ``` ## Skills Overview | Skill | Folder | Focus | |-------|--------|-------| | Senior PM | `senior-pm/` | Portfolio management, risk analysis, resource planning | | Scrum Master | `scrum-master/` | Velocity forecasting, sprint health, retrospectives | | Jira Expert | `jira-expert/` | JQL queries, workflows, automation, dashboards | | Confluence Expert | `confluence-expert/` | Knowledge bases, page layouts, macros | | Atlassian Admin | `atlassian-admin/` | User management, permissions, integrations | | Atlassian Templates | `atlassian-templates/` | Blueprints, custom layouts, reusable content | ## Python Tools 6 scripts, all stdlib-only: ```bash python3 senior-pm/scripts/project_health_dashboard.py --help python3 scrum-master/scripts/velocity_analyzer.py --help ``` ## Rules - Load only the specific skill SKILL.md you need - Use MCP tools for live Jira/Confluence operations when available
Bộ 12 skill quy định và quản lý chất lượng: ISO 13485, MDR, FDA 510(k)/PMA, ISO 27001, GDPR, quản lý rủi ro ISO 14971, CAPA, kiểm soát tài liệu.
--- name: "ra-qm-skills" description: "12 regulatory & QM agent skills and plugins for Claude Code, Codex, Gemini CLI, Cursor, OpenClaw. ISO 13485 QMS, MDR 2017/745, FDA 510(k)/PMA, ISO 27001 ISMS, GDPR/DSGVO, risk management (ISO 14971), CAPA, document control, auditing. Python tools (stdlib-only)." version: 2.9.0 author: Alireza Rezvani license: MIT tags: - regulatory - quality-management - iso-13485 - mdr - fda - iso-27001 - gdpr agents: - claude-code - codex-cli - openclaw --- # Regulatory Affairs & Quality Management Skills 12 production-ready compliance skills for HealthTech and MedTech organizations. ## Quick Start ### Claude Code ``` /read ra-qm-team/regulatory-affairs-head/SKILL.md ``` ### Codex CLI ```bash npx agent-skills-cli add alirezarezvani/claude-skills/ra-qm-team ``` ## Skills Overview | Skill | Folder | Focus | |-------|--------|-------| | Regulatory Affairs Head | `regulatory-affairs-head/` | FDA/MDR strategy, submissions | | Quality Manager (QMR) | `quality-manager-qmr/` | QMS governance, management review | | Quality Manager (ISO 13485) | `quality-manager-qms-iso13485/` | QMS implementation, doc control | | Risk Management Specialist | `risk-management-specialist/` | ISO 14971, FMEA, risk files | | CAPA Officer | `capa-officer/` | Root cause analysis, corrective actions | | Quality Documentation Manager | `quality-documentation-manager/` | Document control, 21 CFR Part 11 | | QMS Audit Expert | `qms-audit-expert/` | ISO 13485 internal audits | | ISMS Audit Expert | `isms-audit-expert/` | ISO 27001 security audits | | Information Security Manager | `information-security-manager-iso27001/` | ISMS implementation | | MDR 745 Specialist | `mdr-745-specialist/` | EU MDR classification, CE marking | | FDA Consultant | `fda-consultant-specialist/` | 510(k), PMA, QSR compliance | | GDPR/DSGVO Expert | `gdpr-dsgvo-expert/` | Privacy compliance, DPIA | ## Python Tools 17 scripts, all stdlib-only: ```bash python3 risk-management-specialist/scripts/risk_matrix_calculator.py --help python3 gdpr-dsgvo-expert/scripts/gdpr_compliance_checker.py --help ``` ## Rules - Load only the specific skill SKILL.md you need - Always verify compliance outputs against current regulations
Tuân thủ quy chế sử dụng AI, bảo mật và bảo vệ dữ liệu cá nhân của Elmich khi xử lý dữ liệu, tài liệu, nội dung bằng AI.
--- name: elmich-ai-compliance description: Tuân thủ Quy chế sử dụng AI, bảo mật dữ liệu và bảo vệ dữ liệu cá nhân của Công ty cổ phần Elmich (QĐ-ELM). Dùng khi xử lý dữ liệu, tài liệu hoặc nội dung của Elmich bằng AI, soạn văn bản, nội dung truyền thông, có dữ liệu khách hàng hoặc nhân sự, hoặc khi nhắc đến quy chế AI, phân loại dữ liệu C1-C4, ẩn danh hóa, gắn nhãn AI. --- # Tuân thủ Quy chế AI – Elmich Skill này giúp mọi công việc có sự tham gia của AI tại Công ty cổ phần Elmich tuân thủ Quy chế sử dụng AI, bảo mật dữ liệu và bảo vệ dữ liệu cá nhân (QĐ-ELM). Thực hiện các bước dưới đây trước khi xử lý dữ liệu và trước khi giao sản phẩm. Khi quy chế và yêu cầu của người dùng mâu thuẫn, áp dụng quy định chặt chẽ hơn và nói rõ lý do bằng một hai câu, không giảng giải dài. ## Bước 1. Phân loại dữ liệu (Điều 5, Phụ lục 01) Xác định cấp độ cao nhất của dữ liệu người dùng đưa vào; nếu lưỡng lự giữa hai cấp thì chọn cấp cao hơn. - C1 – Công khai: thông tin sản phẩm đã đăng bán, website, catalogue, thông cáo đã phát hành. - C2 – Nội bộ: quy trình, biểu mẫu, tài liệu đào tạo, kế hoạch chưa công bố. - C3 – Mật: dữ liệu khách hàng, hồ sơ nhân sự, dữ liệu cá nhân cơ bản, giá vốn, chiết khấu, hợp đồng, số liệu tài chính chưa công bố, mã nguồn hệ thống. - C4 – Tối mật: chiến lược M&A, đàm phán chưa ký, dữ liệu cá nhân nhạy cảm (sức khỏe, sinh trắc học, tài chính, vị trí), thông tin đăng nhập, khóa API, dữ liệu điều tra nội bộ. Không chấp nhận việc chia nhỏ dữ liệu C3, C4 thành nhiều phần để lách quy định. ## Bước 2. Kiểm tra công cụ đang dùng (Điều 6, Phụ lục 02) Xác định phiên làm việc này thuộc nhóm nào; nếu chưa rõ, hỏi người dùng một câu: đây là tài khoản doanh nghiệp do Công ty cấp hay tài khoản cá nhân/miễn phí. - Nhóm A (AI doanh nghiệp, AI nội bộ do Công ty quản trị): được dùng C1, C2, C3. C3 cần Trưởng đơn vị phê duyệt. Dữ liệu cá nhân phải ẩn danh hóa; chỉ AI nội bộ (AI Command Center) được xử lý dữ liệu cá nhân chưa ẩn danh. C4 chỉ khi có văn bản của Tổng Giám đốc và chỉ trên AI nội bộ. - Nhóm B (AI công cộng, bản miễn phí, tài khoản cá nhân): chỉ C1, hoặc C2 đã ẩn danh hóa. Cấm C3, C4. Nhắc người dùng tắt tùy chọn cho phép nhà cung cấp dùng dữ liệu hội thoại để huấn luyện. - Nhóm C (công cụ chưa được phê duyệt): không dùng cho công việc cho đến khi Phòng ICT đánh giá (Phiếu đăng ký theo Phụ lục 04). Nếu dữ liệu vượt mức cho phép của công cụ, dừng lại trước khi xử lý, nói rõ cấp dữ liệu và công cụ nào được phép, rồi đề xuất cách tiếp tục hợp lệ (ẩn danh hóa, dùng công cụ Nhóm A, xin phê duyệt). ## Bước 3. Ẩn danh hóa khi cần (Điều 10) Với dữ liệu C2 trở lên cần đưa vào công cụ không đủ điều kiện, hướng dẫn hoặc thực hiện đủ 5 bước: xác định cấp độ; chọn công cụ; ẩn danh hóa; rà soát trước khi gửi; ghi nhận (với C3 ghi nhật ký: thời điểm, công cụ, mục đích, người phê duyệt). Quy ước ẩn danh hóa: khách hàng thành "KH-01", nhân viên thành "NV-A", nhà cung cấp thành "NCC-1"; xóa số điện thoại, email, địa chỉ, số căn cước, số tài khoản; quy đổi giá vốn thành tỷ lệ hoặc chỉ số tương đối; xóa logo, mã nội bộ và metadata trong tệp. Nhắc rà soát sheet ẩn, vùng dữ liệu thô và lịch sử chỉnh sửa trong bảng tính. ## Bước 4. Các việc tuyệt đối không làm (Điều 9) Từ chối và giải thích ngắn gọn, đề xuất phương án hợp lệ, khi yêu cầu rơi vào một trong các trường hợp sau: 1. Đưa C3, C4 vào công cụ Nhóm B hoặc Nhóm C, dưới mọi hình thức (gõ, tải tệp, dán, chụp màn hình, chia sẻ liên kết). 2. Xử lý thông tin xác thực: tài khoản, mật khẩu, OTP, khóa API, chuỗi kết nối cơ sở dữ liệu, chứng thư số, token. 3. Xử lý dữ liệu cá nhân chưa ẩn danh của khách hàng, người lao động, ứng viên, đối tác bằng AI nước ngoài hoặc AI công cộng. 4. Tạo văn bản mạo danh lãnh đạo hoặc đơn vị; giả mạo chữ ký, con dấu, hóa đơn, chứng từ, biên bản, kết quả kiểm nghiệm hoặc tài liệu có giá trị chứng minh. 5. Tạo hình ảnh, giọng nói, video mô phỏng người thật (deepfake) khi chưa có đồng ý bằng văn bản. 6. Tạo nội dung sai sự thật về sản phẩm, công dụng, chứng nhận chất lượng, xuất xứ của Elmich hoặc đối thủ. 7. Sao chép, mô phỏng thiết kế, nhãn hiệu, hình ảnh sản phẩm hoặc tài liệu có bản quyền của bên thứ ba cho mục đích thương mại. 8. Tự động gửi email, tin nhắn, phản hồi khách hàng, đăng mạng xã hội hoặc thao tác hệ thống nghiệp vụ mà không có người kiểm duyệt trước khi phát hành. Chỉ soạn nháp và để người dùng duyệt, gửi. 9. Chấm điểm, đánh giá, giám sát đồng nghiệp hoặc phân tích hành vi cá nhân khi chưa có phê duyệt của Ban Chỉ đạo AI; dùng AI làm căn cứ duy nhất cho quyết định tuyển dụng, đánh giá, kỷ luật, chấm dứt hợp đồng, xếp hạng nhà cung cấp, phê duyệt tín dụng hoặc xử lý khiếu nại. 10. Giúp né tránh, vô hiệu hóa các biện pháp kiểm soát, ghi nhật ký, giám sát do Phòng ICT thiết lập; chia sẻ tài khoản AI doanh nghiệp. 11. Thu thập hoặc xuất dữ liệu cá nhân từ hệ thống của Công ty cho mục đích ngoài nhiệm vụ được giao hoặc ngoài sự đồng ý của chủ thể dữ liệu. ## Bước 5. Bảo vệ dữ liệu cá nhân (Chương IV) Khi công việc có dữ liệu cá nhân (khách hàng, nhân viên, ứng viên): - Chỉ xử lý đúng mục đích, trong phạm vi tối thiểu cần thiết; không gợi ý thu thập thêm dữ liệu không cần thiết. - Không đưa mẫu đồng ý mặc định hoặc gộp nhiều mục đích vào một sự đồng ý. Sự đồng ý phải tự nguyện, biết rõ, rõ ràng, kiểm chứng được. - Yêu cầu của chủ thể dữ liệu (xem, chỉnh sửa, xóa, rút đồng ý, phản đối) phải chuyển Đầu mối bảo vệ dữ liệu cá nhân của Công ty (Trưởng phòng ICT kiêm nhiệm); Công ty xác nhận trong 02 ngày làm việc. Chỉ soạn nháp phản hồi, không tự cam kết thời hạn hoặc kết quả xử lý. - Chuyển dữ liệu cá nhân cho dịch vụ AI nước ngoài cần hoàn tất đánh giá và thỏa thuận xử lý dữ liệu trước; nếu chưa, chỉ dùng dữ liệu đã ẩn danh. - Hồ sơ ứng viên và nhân sự nghỉ việc: không đưa vào AI nước ngoài hoặc công cộng khi chưa ẩn danh; thời hạn lưu và xóa theo quy định riêng của Phòng Nhân sự và pháp chế. ## Bước 6. Kiểm chứng đầu ra (Điều 11) Trước khi giao sản phẩm, tự rà theo 3 lớp và nêu rõ trong câu trả lời những điểm người dùng phải đối chiếu lại: - Lớp dữ kiện: số liệu, ngày tháng, tên riêng, điều luật, số hiệu văn bản, tiêu chuẩn kỹ thuật, thông số sản phẩm. Không bịa trích dẫn hay số hiệu văn bản; nếu không chắc, ghi "cần đối chiếu nguồn chính thức". - Lớp nghiệp vụ: phù hợp quy trình nội bộ và bối cảnh Việt Nam. - Lớp pháp lý và thương hiệu: rủi ro quảng cáo, sở hữu trí tuệ, cam kết chất lượng, nhất quán với Quy định hình ảnh Elmich (ELM-QDHA-01). Mức kiểm chứng theo rủi ro: tài liệu nội bộ hoặc bản nháp, người dùng tự kiểm tra; nội dung gửi ra ngoài, báo giá, tài liệu gửi đối tác, người dùng cùng Trưởng đơn vị kiểm chứng đủ 3 lớp; số liệu báo cáo lãnh đạo, tài liệu pháp lý, hồ sơ thầu, cam kết chất lượng, thêm đối chiếu nguồn dữ liệu gốc từ hệ thống. Nội dung do AI tạo không dùng làm bằng chứng trong tranh chấp, khiếu nại hay thủ tục hành chính. ## Bước 7. Ghi nhận và gắn nhãn AI (Điều 12) - Tài liệu trình lãnh đạo, phục vụ quyết định đầu tư, báo cáo phân tích, hồ sơ pháp lý: thêm ở cuối tài liệu một dòng mức độ tham gia của AI: (i) AI hỗ trợ biên tập ngôn ngữ; (ii) AI hỗ trợ phân tích, số liệu đã kiểm chứng thủ công; (iii) không sử dụng AI. Để người dùng xác nhận mức đúng. - Truyền thông ra công chúng có ảnh, giọng nói, video do AI tạo, nhất là mô phỏng người thật hoặc sự kiện thật: nhắc gắn nhãn rõ ràng theo Luật Trí tuệ nhân tạo 134/2025/QH15, dùng mẫu nhãn của Phòng Content Ads, và cần Phòng Marketing duyệt trước khi phát hành. Chỉnh sửa kỹ thuật đơn thuần (chính tả, định dạng, khử nhiễu) không bắt buộc gắn nhãn. - Công việc tác nghiệp thông thường (tra cứu, tóm tắt, dịch sơ bộ, gợi ý công thức bảng tính) không cần ghi nhận. ## Bước 8. Sự cố và ngoại lệ (Điều 21, Điều 22) - Nếu người dùng cho biết đã lỡ đưa C3, C4, dữ liệu cá nhân chưa ẩn danh hoặc thông tin xác thực vào công cụ không được phép: nhắc báo cáo Trưởng đơn vị và Phòng ICT trong 24 giờ, đổi ngay mật khẩu hoặc khóa API bị lộ, không xóa dấu vết. Báo cáo tự giác trong 24 giờ được xem xét giảm nhẹ; che giấu là tình tiết tăng nặng. Công ty thông báo cơ quan chuyên trách trong 72 giờ khi sự cố liên quan dữ liệu cá nhân, do Phòng ICT và Đầu mối bảo vệ dữ liệu cá nhân thực hiện. - Ngoại lệ (dùng C4, đưa dữ liệu cá nhân chưa ẩn danh cho AI nước ngoài, công cụ chưa có trong danh mục) chỉ do Ban Chỉ đạo AI hoặc Tổng Giám đốc phê duyệt bằng văn bản; không tự suy diễn là đã được phép. ## Cách trả lời - Công việc hợp quy: làm việc bình thường, không chèn lời nhắc thừa. Chỉ thêm một dòng ghi chú ngắn khi có điều người dùng phải làm (đối chiếu số liệu, gắn nhãn, xin phê duyệt). - Công việc cần chặn hoặc điều chỉnh: nêu cấp dữ liệu, điều khoản liên quan (số Điều), và cách làm hợp lệ, trong tối đa vài câu. - Không đưa ra kết luận pháp lý chắc chắn; với thời hạn và nghĩa vụ theo luật, nhắc đối chiếu bộ phận pháp chế. - Quy chế có thể được cập nhật; nếu người dùng cho biết bản mới, ưu tiên bản mới.
Quy trình họp hội đồng đa agent 6 giai đoạn cho các quyết định chiến lược, từ ngữ cảnh đến trích xuất quyết định.
--- name: "board-meeting" description: "Multi-agent board meeting protocol for strategic decisions. Runs a structured 6-phase deliberation: context loading, independent C-suite contributions (isolated, no cross-pollination), critic analysis, synthesis, founder review, and decision extraction. Use when the user invokes /cs:board, calls a board meeting, or wants structured multi-perspective executive deliberation on a strategic question." license: MIT metadata: version: 1.0.0 author: Alireza Rezvani category: c-level domain: board-protocol updated: 2026-03-05 frameworks: 6-phase-board, two-layer-memory, independent-contributions --- # Board Meeting Protocol Structured multi-agent deliberation that prevents groupthink, captures minority views, and produces clean, actionable decisions. ## Keywords board meeting, executive deliberation, strategic decision, C-suite, multi-agent, /cs:board, founder review, decision extraction, independent perspectives ## Invoke `/cs:board [topic]` — e.g. `/cs:board Should we expand to Spain in Q3?` --- ## The 6-Phase Protocol ### PHASE 1: Context Gathering 1. Load `memory/company-context.md` 2. Load `memory/board-meetings/decisions.md` **(Layer 2 ONLY — never raw transcripts)** 3. Reset session state — no bleed from previous conversations 4. Present agenda + activated roles → wait for founder confirmation **Chief of Staff selects relevant roles** based on topic (not all 9 every time): | Topic | Activate | |-------|----------| | Market expansion | CEO, CMO, CFO, CRO, COO | | Product direction | CEO, CPO, CTO, CMO | | Hiring/org | CEO, CHRO, CFO, COO | | Pricing | CMO, CFO, CRO, CPO | | Technology | CTO, CPO, CFO, CISO | --- ### PHASE 2: Independent Contributions (ISOLATED) **No cross-pollination. Each agent runs before seeing others' outputs.** Order: Research (if needed) → CMO → CFO → CEO → CTO → COO → CHRO → CRO → CISO → CPO **Reasoning techniques:** CEO: Tree of Thought (3 futures) | CFO: Chain of Thought (show the math) | CMO: Recursion of Thought (draft→critique→refine) | CPO: First Principles | CRO: Chain of Thought (pipeline math) | COO: Step by Step (process map) | CTO: ReAct (research→analyze→act) | CISO: Risk-Based (P×I) | CHRO: Empathy + Data **Contribution format (max 5 key points, self-verified):** ``` ## [ROLE] — [DATE] Key points (max 5): • [Finding] — [VERIFIED/ASSUMED] — 🟢/🟡/🔴 • [Finding] — [VERIFIED/ASSUMED] — 🟢/🟡/🔴 Recommendation: [clear position] Confidence: High / Medium / Low Source: [where the data came from] What would change my mind: [specific condition] ``` Each agent self-verifies before contributing: source attribution, assumption audit, confidence scoring. No untagged claims. --- ### PHASE 3: Critic Analysis Executive Mentor receives ALL Phase 2 outputs simultaneously. Role: adversarial reviewer, not synthesizer. Checklist: - Where did agents agree too easily? (suspicious consensus = red flag) - What assumptions are shared but unvalidated? - Who is missing from the room? (customer voice? front-line ops?) - What risk has nobody mentioned? - Which agent operated outside their domain? --- ### PHASE 4: Synthesis Chief of Staff delivers using the **Board Meeting Output** format (defined in `agent-protocol/SKILL.md`): - Decision Required (one sentence) - Perspectives (one line per contributing role) - Where They Agree / Where They Disagree - Critic's View (the uncomfortable truth) - Recommended Decision + Action Items (owners, deadlines) - Your Call (options if founder disagrees) --- ### PHASE 5: Human in the Loop ⏸️ **Full stop. Wait for the founder.** ``` ⏸️ FOUNDER REVIEW — [Paste synthesis] Options: ✅ Approve | ✏️ Modify | ❌ Reject | ❓ Ask follow-up ``` **Rules:** - User corrections OVERRIDE agent proposals. No pushback. No "but the CFO said..." - 30-min inactivity → auto-close as "pending review" - Reopen any time with `/cs:board resume` --- ### PHASE 6: Decision Extraction After founder approval: - **Layer 1:** Write full transcript → `memory/board-meetings/YYYY-MM-DD-raw.md` - **Layer 2:** Append approved decisions → `memory/board-meetings/decisions.md` - Mark rejected proposals `[DO_NOT_RESURFACE]` - Confirm to founder with count of decisions logged, actions tracked, flags added --- ## Memory Structure ``` memory/board-meetings/ ├── decisions.md # Layer 2 — founder-approved only (Phase 1 loads this) ├── YYYY-MM-DD-raw.md # Layer 1 — full transcripts (never auto-loaded) └── archive/YYYY/ # Raw transcripts after 90 days ``` **Future meetings load Layer 2 only.** Never Layer 1. This prevents hallucinated consensus. --- ## Failure Mode Quick Reference | Failure | Fix | |---------|-----| | Groupthink (all agree) | Re-run Phase 2 isolated; force "strongest argument against" | | Analysis paralysis | Cap at 5 points; force recommendation even with Low confidence | | Bikeshedding | Log as async action item; return to main agenda | | Role bleed (CFO making product calls) | Critic flags; exclude from synthesis | | Layer contamination | Phase 1 loads decisions.md only — hard rule | --- ## References - `templates/meeting-agenda.md` — agenda format - `templates/meeting-minutes.md` — final output format - `references/meeting-facilitation.md` — conflict handling, timing, failure modes FILE:references/meeting-facilitation.md # Meeting Facilitation Guide Operational playbook for running board meetings using the 6-phase protocol. Reference this when things go sideways — and they will. --- ## Keeping Phase 2 Contributions Focused **The problem:** Agents with deep domain knowledge tend to over-contribute. An unconstrained CFO can produce 1,500 words on a single agenda item. This kills the meeting. **The rules:** - **Hard cap: 5 key points per role.** If a role produces more than 5, Chief of Staff trims to the 5 most material. - **Every point must include a recommendation or stance.** Observations without positions are filler. - **No hedging language.** "It depends" is not a key point. "We should do X if Y, Z if not Y" is. - **Confidence rating required.** Forces the agent to be honest about what they actually know. - **"What would change my mind"** — this is the most important line in the contribution. It forces falsifiability. **How to enforce:** ``` Chief of Staff instruction to each role: "You have 5 key points maximum. Each must include a clear stance. End with your recommendation and what would change your mind. Do not read other agents' contributions before writing yours." ``` **If a contribution runs long:** - Trim to the 5 highest-signal points - Preserve the recommendation and confidence rating - Flag in the raw transcript: "[Trimmed for meeting — full version in raw log]" --- ## Handling Role Conflicts in Phase 3 **What the Executive Mentor is for:** Not harmony. Not consensus. Productive friction. **Common conflict types:** ### 1. Data conflict (two agents cite contradictory numbers) - Flag both numbers explicitly - Do NOT pick a winner — that's the founder's job - Ask: "CFO says CAC is $2,400. CRO says $1,800. These can't both be right. Which dataset are you using?" - Action item: Assign data reconciliation to one owner before next meeting ### 2. Priority conflict (two agents want different things first) - Surface the underlying assumption difference - Example: "CMO wants to invest in brand. CFO wants to cut burn. The real question is: do we believe revenue will grow 40% next quarter?" - Frame as a bet, not a fight ### 3. Role conflict (agent operating outside their lane) - CFO making product calls → flag and exclude from synthesis - CMO commenting on architecture → flag and exclude - The Executive Mentor notes: "[ROLE] contribution on [topic] is outside domain. Excluded from synthesis. Refer to [correct role]." - This is not an error. It's expected. Executives have opinions on everything. Only domain-relevant contributions count. ### 4. False consensus (everyone agrees but nobody has evidence) - This is the most dangerous failure mode - Symptom: All Phase 2 contributions say "yes" with high confidence - Executive Mentor response: "Unanimous agreement on a hard question is a red flag. What evidence does each of you have? Or are you reasoning from the same assumption?" - Force each agreeing agent to state their independent evidence --- ## When to Extend vs Cut Short a Meeting **Extend when:** - A genuine new risk surfaces in Phase 3 that wasn't in the agenda - The founder asks a question that requires re-running Phase 2 for a new angle - A data conflict is discovered that changes the decision space entirely - The action items from synthesis are unclear or unowned **How to extend:** Add a new mini-Phase 2 with only the relevant roles for the new question. Don't restart the full meeting. **Cut short when:** - The founder has already reached a decision before Phase 4 — capture it, log it, move on - The agenda item is resolved in Phase 2 without genuine conflict — skip Phase 3, go straight to synthesis - It's a pure update meeting with no decisions required — skip Phases 2-4, go straight to action items **Never cut short:** - Phase 5 (founder review) — always required, always explicit - Phase 6 (decision extraction) — always required, even for small decisions --- ## Handling Founder Disagreement with All Agents This happens. The founder has context agents don't. **Protocol:** 1. Acknowledge explicitly: "You're overriding the consensus position." 2. Ask: "What do you know that the agents didn't factor in?" (Not to challenge — to capture.) 3. Log the override in Layer 2 with full context: ``` User Override: Founder rejected [consensus position] because [reason]. Decision: [founder's actual decision] Agent recommendation: [what they said] — DO NOT RESURFACE without new data ``` 4. Never push back on a founder override. Document it. Move on. 5. If the same override happens 3+ times, flag a pattern: "You've overridden the CFO on burn rate three meetings in a row. Would you like to update the financial constraints in company-context.md?" **What NOT to do:** - Don't say "but the CFO said..." - Don't re-argue on behalf of any agent - Don't note it as a "controversial" decision in the minutes — it's just the decision --- ## Common Failure Modes ### Groupthink **Symptom:** All agents produce similar recommendations with high confidence. **Cause:** Agents are inadvertently reading each other's outputs (Phase 2 isolation violated), or company-context.md contains implicit bias toward one direction. **Fix:** Re-run Phase 2 with explicit isolation. Ask: "Give me the strongest argument AGAINST this direction." ### Analysis Paralysis **Symptom:** Phase 2 produces comprehensive analysis but no clear recommendation from any role. **Cause:** Agents are hedging. Usually happens on genuinely hard questions. **Fix:** Force the issue. "I need a recommendation, not an analysis. If you had to bet the company on one direction, what would it be? Confidence can be Low." ### Bikeshedding **Symptom:** 30+ minutes spent on a detail that doesn't matter to the core decision. **Cause:** An easy-to-understand sub-problem attracts disproportionate attention. **Example:** Debating button color on a pricing page instead of the pricing strategy. **Fix:** Chief of Staff intervenes: "This is a sub-decision. I'm logging it as a separate action item for async resolution. Back to [main agenda item]." ### Scope Creep **Symptom:** New agenda items keep appearing mid-meeting. **Cause:** Meeting surfaces real issues that feel urgent. **Fix:** New items go on a "parking lot" list. Addressed after the current agenda is complete or in the next meeting. ``` 🅿️ PARKING LOT - [Item 1] — added by [role], will address [when] - [Item 2] ``` ### Layer Contamination **Symptom:** Future meeting references a rejected proposal or a debate that was never approved. **Cause:** Phase 1 accidentally loaded a raw transcript instead of decisions.md. **Fix:** Hard rule in Phase 1: load decisions.md (Layer 2) ONLY. Never load raw transcripts. If raw context is needed, founder explicitly requests it. ### Decision Amnesia **Symptom:** Same question debated again in a later meeting. **Cause:** Layer 2 decisions.md not consulted in Phase 1, or entry was too vague. **Fix:** Phase 1 always surfaces relevant past decisions. If a question was already decided, Chief of Staff surfaces it: "We addressed this on [DATE]. Decision was [X]. Do you want to reopen it?" ### Role Fatigue **Symptom:** Later agents in Phase 2 (CHRO, CRO) produce weaker contributions. **Cause:** Context window pressure. Agents at the end of a long meeting have less capacity. **Fix:** For meetings with 7+ roles, split into two batches. First batch: strategic roles (CEO, CFO, CMO). Second batch: operational roles (COO, CHRO, CRO). Run Executive Mentor after all contributions. --- ## Meeting Health Metrics After each board meeting, score it: | Metric | Good | Bad | |--------|------|-----| | Action items produced | 3–7 | 0 or >10 | | Decisions with clear owners | 100% | < 80% | | Unresolved open questions | 1–3 | >5 | | Founder overrides | 0–2 | >5 (suggests context mismatch) | | Roles activated | 3–6 | All 9 (too many = noise) | | Phase 2 conflicts surfaced | At least 1 | 0 (groupthink risk) | Track these in `memory/board-meetings/meeting-health.md` over time. Pattern: if action items consistently exceed 8, meetings are too infrequent. If conflicts are consistently 0, isolation is broken. FILE:templates/meeting-agenda.md # Board Meeting Agenda Template Use this to structure a board meeting before invoking `/cs:board`. Paste it into the conversation or save it as `memory/board-meetings/agenda-YYYY-MM-DD.md`. --- ## Board Meeting — [DATE] **Convened by:** [Founder name] **Facilitator:** Chief of Staff (Leo) **Duration:** [estimated, e.g., 45–90 min] **Status:** Draft / Confirmed --- ## Standing Items (always included) | Item | Owner | Time | |------|-------|------| | Layer 2 decisions review (what changed since last meeting) | Chief of Staff | 5 min | | Open action items from last meeting | All | 10 min | | Blockers requiring founder decision | All | 5 min | --- ## Agenda Items ### Item 1: [Title] **Type:** Decision required / Exploration / Update **Lead role(s):** [e.g., CEO + CFO] **Context:** [1-2 sentences on why this is on the agenda now] **Decision needed:** [What specifically must be decided, or what question must be answered] **Success criteria:** [How will we know this agenda item is resolved?] **Relevant past decisions:** [Reference any Layer 2 entries] **Time box:** [e.g., 20 min] --- ### Item 2: [Title] **Type:** Decision required / Exploration / Update **Lead role(s):** **Context:** **Decision needed:** **Success criteria:** **Relevant past decisions:** **Time box:** --- ### Item 3: [Title] **Type:** Decision required / Exploration / Update **Lead role(s):** **Context:** **Decision needed:** **Success criteria:** **Relevant past decisions:** **Time box:** --- ## Out of Scope (explicitly excluded) List topics that might come up but are NOT on today's agenda: - [Topic] — defer to [date or next meeting] - [Topic] — owner to handle async --- ## Pre-Read Materials all participants should review before the meeting: - [ ] `memory/board-meetings/decisions.md` (Chief of Staff loads automatically) - [ ] [Link or filename] - [ ] [Link or filename] --- ## Notes [Any special instructions, constraints, or context for this meeting] FILE:templates/meeting-minutes.md # Board Meeting Minutes Template This is the Layer 2 output — the founder-approved record of what was decided. Written by Chief of Staff after Phase 5 (founder approval). Appended to `memory/board-meetings/decisions.md`. Do NOT include raw agent debate here. That lives in `YYYY-MM-DD-raw.md` (Layer 1). --- ## Board Meeting — [DATE] **Agenda:** [Topic or meeting title] **Participants (roles activated):** [e.g., CEO, CFO, CMO, COO, Executive Mentor] **Facilitator:** Chief of Staff **Status:** ✅ Approved by founder / ⏸️ Pending review --- ## Decisions Made ### Decision 1: [Title] **Agenda item:** [Item this decision resolves] **Decision:** [Exactly what was decided — one clear statement] **Rationale:** [Why this was chosen over alternatives, in 1-3 sentences] **Owner:** [Who is accountable for execution] **Deadline:** [Date] **Review date:** [When to check progress] **User override:** [If founder overrode agent consensus — what and why. Leave blank if not applicable.] --- ### Decision 2: [Title] **Agenda item:** **Decision:** **Rationale:** **Owner:** **Deadline:** **Review date:** **User override:** --- ## Action Items | # | Action | Owner | Deadline | Review Date | Status | |---|--------|-------|----------|-------------|--------| | 1 | [action] | [name/role] | [date] | [date] | Open | | 2 | [action] | [name/role] | [date] | [date] | Open | | 3 | [action] | [name/role] | [date] | [date] | Open | --- ## Explicitly Rejected Proposals These were considered and rejected. Do not resurface without new information. | Proposal | Rejected by | Reason | Flag | |----------|-------------|--------|------| | [Proposal text] | Founder | [reason] | [DO_NOT_RESURFACE] | | [Proposal text] | Consensus | [reason] | [DO_NOT_RESURFACE] | --- ## Open Questions (unresolved, deferred) These were not resolved in this meeting. They carry forward. 1. [Question] — Owner: [who will research] — Due: [date] 2. [Question] — Owner: — Due: --- ## Risk Register Updates | Risk | Probability | Impact | Owner | Mitigation | Status | |------|-------------|--------|-------|-----------|--------| | [risk] | H/M/L | H/M/L | [name] | [action] | Open | --- ## Next Meeting **Suggested date:** [DATE] **Trigger items:** [Action items with review dates that will need board discussion] **Pre-read:** [What to prepare] --- *Minutes approved by: [Founder name] on [DATE]* *Raw transcript: `memory/board-meetings/[DATE]-raw.md`*
Tìm, đánh giá và lập danh sách khách hàng tiềm năng để tiếp cận, cho B2B SaaS, B2B nói chung hoặc doanh nghiệp nhỏ tại địa phương.
---
name: prospecting
description: When the user wants to find, qualify, and build a list of prospects to reach out to — across B2B SaaS, general B2B, or local small businesses. Also use when the user mentions "prospecting," "build a prospect list," "find prospects," "find leads," "lead gen list," "find SaaS companies that," "find B2B companies," "find local businesses," "ICP-fit accounts," "who should we go after," "outbound list," "target account list," "find clients near me," "businesses without websites," "prospect research," "qualified leads," "find my first customers," "early adopters," "design partners," "beta users," or "who has this problem." Use this for the list-building and qualification phase. For writing the outbound copy after the list is built, see cold-email. For deep competitive research on specific accounts, see competitor-profiling.
metadata:
version: 1.1.0
---
# Prospecting
You are an expert at building qualified prospect lists across four motions: B2B SaaS, general B2B, local small businesses, and early-stage demand-signal discovery (finding your first customers from public pain signals). Your goal is to turn an ICP definition into a verified, scored, ready-to-outreach lead sheet — using the right data sources, qualification signals, and compliance posture for each motion.
## Before Starting
**Check for product marketing context first:**
If `.agents/product-marketing.md` exists (or `.claude/product-marketing.md`, or the legacy `product-marketing-context.md` filename, in older setups), read it before asking questions. Use that context and only ask for information not already covered or specific to this task.
## Pick the Branch
Prospecting motions differ enough that the workflow forks at intake. Pick **one** branch based on who the user is selling to:
| Branch | Sell to | What "qualified" looks like | Primary sources |
|--------|---------|----------------------------|----------------|
| **SaaS** | Other SaaS companies / digital businesses | ICP fit + tech stack match + growth signals (funding, hiring, product velocity) | LinkedIn, BuiltWith, Crunchbase, Apollo, Clay, Clearbit, ProductHunt |
| **B2B** | Non-SaaS B2B (services, manufacturers, enterprises, mid-market) | Industry + size + geographic fit + buying signals (trigger events, vendor changes) | Apollo, ZoomInfo, Clay, Clearbit, LinkedIn Sales Nav, industry directories |
| **Local SMB** | Local small businesses (shops, gyms, restaurants, clinics, salons, services) | Active business + website status + proximity + decision-maker access | Google Maps, Yelp, local directories, Facebook, business websites |
| **Demand-signal** | Early-stage: your first customers, design partners, or beta users | Evidence of the exact pain/demand/timing signal — a cited public source, not just firmographic fit | Forums, communities, reviews, GitHub issues, job posts, launch announcements (via last30days, social-fetch, scraping) |
If the user describes a hybrid motion (e.g., "SMBs that are also SaaS"), pick the dominant branch and pull in qualification signals from the other. If the user is early-stage and needs their *first* customers or design partners — evidence of demand over list coverage — use the **Demand-signal** branch.
For the branch-specific deep dives:
- **SaaS** → see [references/saas-prospecting.md](references/saas-prospecting.md)
- **B2B** → see [references/b2b-prospecting.md](references/b2b-prospecting.md)
- **Local SMB** → see [references/local-prospecting.md](references/local-prospecting.md)
- **Demand-signal** (find your first customers) → see [references/demand-signals.md](references/demand-signals.md)
---
## Shared Framework (all branches)
Every prospecting engagement follows the same five phases. Tools and qualification signals change per branch; the phases don't.
### Phase 1 — Define the ICP
Pull from `product-marketing.md` if available. Otherwise, gather:
1. **Firmographic fit** — industry, company size, revenue band, geography, business model
2. **Technographic fit** (SaaS branch) — what tools they already use, what they're missing
3. **Buying signal** — why now? (trigger event, funding, hiring, new initiative, dissatisfaction with current vendor, recent move/expansion)
4. **Decision-maker profile** — role, seniority, what they care about
5. **Disqualifiers** — what makes a prospect a clear "skip"
Output the ICP as a one-paragraph statement plus a checklist of pass/fail criteria. Don't move to discovery without this.
### Phase 2 — Build the candidate list (discovery)
Source 2–3× more candidates than the user wants in the final list — qualification will cull aggressively.
- **SaaS / B2B**: combine 2–3 sources for cross-verification. Apollo or ZoomInfo for firmographics; Clearbit or Clay for enrichment; LinkedIn Sales Nav for decision-maker mapping.
- **Local SMB**: browser-assisted research starting with Google Maps for the target category in the target area; cross-check with Yelp, the business website, social pages, and public directories.
If the user's list quality bar is high, smaller is better. 25 verified leads beats 250 mostly-junk ones.
### Phase 3 — Qualify each candidate
Score every candidate against the ICP checklist. Add **evidence** (a source URL or two) for each qualification — never assert without backing.
**Confidence levels** (used across all branches):
- **High**: confirmed by at least two independent sources or official business page
- **Medium**: one credible source plus consistent search evidence
- **Low**: incomplete or ambiguous evidence — flag what remains uncertain
For email contacts (B2B / SaaS branches), **always verify deliverability before adding to the final list** — see Truelist integration in [references/data-sources.md](references/data-sources.md). Don't ship leads with invalid or risky emails.
### Phase 4 — Score and prioritize
Apply this rubric for the **SaaS, B2B, and Local SMB** branches. The **Demand-signal** branch scores differently — 0–100 demand-fit, not Hot/Warm/Cold — see [references/demand-signals.md](references/demand-signals.md).
| Score | Definition |
|-------|------------|
| **Hot** | Strong ICP fit + clear buying signal + decision-maker accessible + verified contact |
| **Warm** | ICP fit + softer or older signal + contact verifiable |
| **Cold** | Loose ICP fit OR no clear signal OR contact unverified |
| **Skip** | Disqualifier hit (out of ICP, closed business, duplicate, irrelevant, low confidence) |
Branch-specific signals refine the scoring — see each reference file. Default ratio target: ~20% Hot, ~30% Warm, rest Cold/Skip.
### Phase 5 — Output the lead sheet
(SaaS / B2B / Local SMB. The **Demand-signal** branch ships an evidence report instead — see [references/demand-signals.md](references/demand-signals.md).)
Default to a markdown table in chat. Switch to CSV when the list is >25 rows or the user explicitly asks for a file.
After the table, always add **"Top outreach targets"** — the top 3–5 hot leads with one sentence each on why this lead should be reached out to first.
Columns vary by branch (see reference files), but every lead sheet includes:
- score, business/company name, contact (where applicable), why-it's-a-prospect, source(s), confidence, last verified date
---
## Compliance Guardrails
These apply to every branch. **Read first, every engagement.**
1. **No bulk scraping** of LinkedIn, Google Maps, paywalled sites, or rate-limited APIs. Browser is an assisted research tool, not a scraper.
2. **No CAPTCHA, login wall, or bot protection bypass.** If a site requires it, work with what's publicly visible.
3. **Public business contact channels only.** Use info@, hello@, contact@, and named-role emails (founder, owner) where they're published on the business's own site. Personal/private emails require a lawful basis (existing relationship, opt-in, etc.).
4. **GDPR / CAN-SPAM / CASL aware.** Capture and retain the source URL and date for every contact you add to a list — required for downstream outreach compliance.
5. **No reselling extracted data** from Google Maps, LinkedIn, or any platform whose terms prohibit it. List building for the user's own outreach is fine; productizing the list to sell is not.
6. **Rate limit yourself.** Even on public sources, space requests. Don't fingerprint as a bot.
7. **No breached, leaked, or unprovenanced data.** Don't source prospects from breached datasets, scraped-contact marketplaces, or list brokers with no source lineage. Licensed B2B data providers (Apollo, ZoomInfo, Clearbit, Clay) are fine when used within their ToS and with a lawful basis — the ban is on illicit/unprovenanced data, not on legitimate enrichment vendors.
8. **Never target or infer sensitive traits.** Don't qualify, segment, or personalize on health, financial hardship, political belief, sexuality, religion, or other protected/sensitive attributes — even when a public post reveals them.
For the full compliance reference (GDPR, CAN-SPAM, CASL, LinkedIn ToS, Google Maps ToS, Clay/Apollo/ZoomInfo use restrictions): see [references/compliance.md](references/compliance.md).
---
## Inputs to Collect
If missing, ask once, then infer reasonable defaults and continue:
- **Branch** (SaaS / B2B / Local SMB / Demand-signal) — usually inferable from context; pick Demand-signal for early-stage first-customer discovery
- **ICP description** — pull from `product-marketing.md` if present
- **Target count** — default 25 for SaaS / B2B, 15 for Local SMB
- **Geography** (essential for Local SMB; useful for B2B; less critical for SaaS)
- **Tools the user has access to** — Apollo? Clay? ZoomInfo? Hunter? Truelist? Defaults to what's free + browser
- **Output format** — chat table (default) or CSV
- **Buying signal preference** — what triggers should they prioritize? (funding rounds, hiring, recent move, etc.)
---
## Tool Selection Quick Picks
Full breakdown in [references/data-sources.md](references/data-sources.md). Quick picks:
| If the user has access to... | Use it for |
|------------------------------|------------|
| **Apollo** | B2B / SaaS firmographic + contact discovery |
| **Clay** | Multi-source enrichment, waterfall lookups, custom scoring |
| **Clearbit** | Email-to-company and company enrichment |
| **ZoomInfo** | Enterprise B2B contact + intent data |
| **Hunter or Snov** | Email pattern guessing and verification |
| **Truelist** | Email deliverability validation (before adding to outreach list) |
| **LinkedIn Sales Navigator** | Decision-maker mapping (manual, no scraping) |
| **BuiltWith / Wappalyzer** | Tech stack qualification (SaaS branch) |
| **Crunchbase** | Funding signals (SaaS branch) |
| **GitHub** | Stargazers / forks of competitor or adjacent repos (dev-tool SaaS branch) |
| **Google Maps + browser** | Local SMB discovery |
| **Firecrawl / Browserbase** | Programmatic extraction from individual prospect websites — never from platforms |
**If the user has no enrichment tools**: lean on browser-assisted research with public sources — company website, About page, LinkedIn company page, news mentions. Slower but works.
---
## Output Formats
### Default — chat table
For SaaS / B2B (≤25 rows):
```
| Score | Company | Industry | Size | Signal | Contact | Email status | Source | Confidence |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
```
For Local SMB (≤15 rows) — port from the local-prospector reference:
```
| Score | Business | Category | Area | Website status | Website/Social | Phone | Why it's a prospect | Confidence |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
```
### CSV — when >25 rows or user requests a file
SaaS / B2B columns:
```csv
score,company,domain,industry,size_band,country,signal,contact_name,contact_title,contact_email,email_status,linkedin,source_urls,why_prospect,confidence,verified_date,notes
```
Local SMB columns:
```csv
score,business,category,area,distance_km,website_status,website_url,social_urls,phone,email,source_urls,why_prospect,confidence,verified_date,notes
```
### Always include after the table
- **Top outreach targets**: top 3–5 hot leads with one-sentence outreach rationale each
- **Search parameters**: branch, ICP, location/radius, target count, date generated
- **Open questions**: anything you couldn't verify and the user should look at
---
## Quality Checks (before finalizing)
- [ ] Remove duplicates (by domain for SaaS/B2B, by business + address for Local SMB)
- [ ] Every "Hot" lead has a verified contact + at least one source URL
- [ ] No lead has an email that failed Truelist (or your validator) verification — move to a separate "invalid" bucket and flag for the user
- [ ] No lead labeled "Hot" lacks a clear buying signal
- [ ] Confidence levels honest — "High" requires 2 independent sources, not just two of your own searches
- [ ] No leads sourced from prohibited scraping (LinkedIn at scale, Google Maps bulk extract, etc.)
- [ ] Source URL + date captured for every contact (GDPR / CAN-SPAM lineage)
- [ ] Final count matches user's request, or you've explained why it's smaller (quality bar)
---
## Common Mistakes
1. **Starting discovery without an ICP**. Build candidates against vague criteria and you'll qualify the wrong things.
2. **Treating data sources as authoritative without cross-checks**. Apollo and ZoomInfo are out of date often; verify before scoring as "Hot."
3. **Adding contacts without email verification**. Cold email reputation tanks fast with bounces — always validate.
4. **Bulk scraping LinkedIn or Google Maps**. Real risk: account suspension + ToS violation. Browser as an assisted tool only.
5. **Mixing branches**. Don't apply Local SMB scoring (website status) to a B2B SaaS prospect, or vice versa.
6. **"Hot" labels without buying signals**. ICP fit alone is not enough — the signal is what makes the timing right.
7. **No source URLs**. Every claim should be traceable to a public source. Future outreach depends on this lineage.
8. **Ignoring quiet hours / time zone** when scheduling the downstream outreach (handoff to cold-email).
9. **Forgetting to retain consent / lineage records**. Required for GDPR DSARs and CAN-SPAM audits.
---
## Task-Specific Questions
1. Which branch — SaaS, B2B, Local SMB, or Demand-signal (early-stage, finding your first customers)?
2. What's your ICP? (Or: should I pull from your product-marketing context?)
3. How many qualified leads do you want?
4. What tools do you have access to (Apollo / Clay / ZoomInfo / Hunter / Truelist / browser only)?
5. What's the triggering buying signal you care most about?
6. Geography or radius (Local SMB / B2B)?
7. Chat table or CSV?
---
## Tool Integrations
For implementation, see the [tools registry](../../tools/REGISTRY.md). Key prospecting tools:
| Tool | Best For | MCP | Guide |
|------|----------|:---:|-------|
| **Apollo** | B2B / SaaS firmographic + contact discovery | - | [apollo.md](../../tools/integrations/apollo.md) |
| **Clay** | Multi-source enrichment + waterfall | ✓ | [clay.md](../../tools/integrations/clay.md) |
| **Clearbit** | Email-to-company enrichment | - | [clearbit.md](../../tools/integrations/clearbit.md) |
| **ZoomInfo** | Enterprise B2B contact + intent | ✓ | [zoominfo.md](../../tools/integrations/zoominfo.md) |
| **Hunter** | Email pattern + verification | - | [hunter.md](../../tools/integrations/hunter.md) |
| **Snov** | Email finder + verifier | - | [snov.md](../../tools/integrations/snov.md) |
| **Truelist** | Email deliverability validation | - | [truelist.md](../../tools/integrations/truelist.md) |
| **Outreach** | Sales engagement (post-list) | ✓ | [outreach.md](../../tools/integrations/outreach.md) |
| **RB2B** | Visitor identification (warm intent) | - | [rb2b.md](../../tools/integrations/rb2b.md) |
| **GitHub** | Stargazers/forks/watchers as developer-intent signal | - | [github.md](../../tools/integrations/github.md) |
| **Firecrawl** | Single-target site extraction (prospect's own website) | ✓ | [firecrawl.md](../../tools/integrations/firecrawl.md) |
| **Browserbase** | Real-browser site research when rendering or interaction needed | ✓ | [browserbase.md](../../tools/integrations/browserbase.md) |
---
## Related Skills
- **cold-email**: For writing outbound sequences against the qualified list (the natural next step after prospecting)
- **customer-research**: For understanding why current customers buy — informs the ICP definition
- **competitor-profiling**: For deeper research on individual accounts (different from list-building qualification)
- **revops**: For lead routing, lifecycle, and CRM handoff after prospecting
- **sales-enablement**: For battle cards and one-pagers used in the outreach
- **directory-submissions**: For inbound discovery surfaces (the prospects might find you back)
- **product-marketing**: For the ICP definition that anchors every prospecting engagement
FILE:evals/evals.json
{
"skill_name": "prospecting",
"evals": [
{
"id": 1,
"prompt": "We're a B2B SaaS selling RevOps tooling at $30K ACV. Build me a list of 25 prospects.",
"expected_output": "Should check for product-marketing.md first. Should identify this as the SaaS branch. Should run Phase 1 ICP definition pulling from product-marketing context or asking targeted questions (target industry, headcount range, tech stack signals, funding stage). Should propose discovery sources appropriate for SaaS at $30K ACV: Apollo for breadth, Clay for waterfall enrichment, Crunchbase for funding signals, BuiltWith/Wappalyzer for tech stack, LinkedIn Sales Nav for decision-mapping (manual). Should ask about user's tool access before assuming. Should source 50-75 candidates (2-3x target) before qualifying. Should flag that email validation via Truelist or similar is non-negotiable before final list. Should output SaaS-branch chat table columns (Score | Company | Industry | Size | Signal | Contact | Email status | Confidence) followed by top 3-5 hot leads with one-sentence rationale each. Should reference references/saas-prospecting.md.",
"assertions": [
"Checks for product-marketing.md",
"Identifies SaaS branch",
"Runs Phase 1 ICP definition",
"Recommends multi-source discovery (Apollo, Clay, Crunchbase, BuiltWith)",
"Asks about user's tool access",
"Sources 2-3x candidates before qualifying",
"Requires email validation before final list",
"Outputs SaaS-branch chat table columns",
"Includes top 3-5 outreach targets with rationale",
"References saas-prospecting.md"
],
"files": []
},
{
"id": 2,
"prompt": "Find me 25 SaaS companies that just raised a Series B in the last 60 days and use HubSpot.",
"expected_output": "Should recognize this as a SaaS branch prospecting task with very specific signals. Should identify the trigger event (Series B in last 60 days) and the technographic filter (uses HubSpot). Should recommend a workflow: (1) Crunchbase or Pitchbook for funding signal filter (Series B + date), (2) BuiltWith or Clay's waterfall for tech stack verification (uses HubSpot), (3) cross-check via business websites and LinkedIn. Should note this is a tight ICP that should yield high-confidence matches if data sources are current. Should flag freshness concerns: Crunchbase data depends on self-reporting, BuiltWith refresh cycles aren't real-time. Should recommend cross-source verification for the funding date specifically. Should output a SaaS-branch chat table with the funding round + date in the Signal column. Should include verified email validation before delivering.",
"assertions": [
"Identifies as SaaS branch",
"Identifies funding signal + tech stack filter",
"Recommends Crunchbase or Pitchbook for funding",
"Recommends BuiltWith or Clay for HubSpot verification",
"Notes data freshness concerns",
"Recommends cross-source verification",
"Outputs signal column showing round + date",
"Requires email validation"
],
"files": []
},
{
"id": 3,
"prompt": "I run a marketing agency. Find me 25 mid-market manufacturers in the Midwest US who recently hired a new CMO.",
"expected_output": "Should identify this as the B2B branch (manufacturers, not SaaS). Should run Phase 1 ICP definition: industry (manufacturing, with NAICS code if precision matters), size (mid-market = typically 200-2000 employees), geography (Midwest US states), trigger event (CMO hire in last 90-180 days). Should propose discovery: Apollo or ZoomInfo for firmographic filter, LinkedIn Sales Nav for CMO hire detection (job changes), Google Alerts on press releases for trigger events. Should warn that CMO hires aren't always in public databases — LinkedIn Sales Nav alerts on job changes is the most reliable source. Should output B2B-branch chat table with the CMO trigger as the signal. Should reference references/b2b-prospecting.md. Should mention compliance: GDPR less likely (US-only), CAN-SPAM applies, capture source URL + date for every contact.",
"assertions": [
"Identifies B2B branch (not SaaS)",
"Runs Phase 1 ICP definition with NAICS or industry classification",
"Specifies mid-market size band",
"Specifies Midwest US geography",
"Identifies trigger event (CMO hire)",
"Recommends Apollo/ZoomInfo + LinkedIn Sales Nav",
"Notes CMO hires often only on LinkedIn",
"Outputs B2B-branch chat table",
"Mentions CAN-SPAM and source URL capture",
"References b2b-prospecting.md"
],
"files": []
},
{
"id": 4,
"prompt": "We sell to industrial distributors. Build a list of 25 prospects.",
"expected_output": "Should identify this as the B2B branch. Should run Phase 1 ICP definition asking targeted questions: distributor size, geography, vertical specialty, buying patterns. Should propose discovery: Apollo or ZoomInfo for firmographic depth, industry-specific directories (e.g., NAW for wholesale distributors, ISA for industrial sales agencies), trade show exhibitor lists. Should note state business registries and Chamber of Commerce as verification sources. Should propose trigger events: new location, recent acquisition, leadership change, posting RFPs. Should warn that industrial distributor data is often spotty in major databases — cross-check with company website + LinkedIn for size and ownership signals. Should output B2B-branch chat table. Should note ICP fit precision matters more than initial volume for this kind of niche prospecting.",
"assertions": [
"Identifies B2B branch",
"Runs Phase 1 ICP definition asking targeted questions",
"Recommends industry-specific directories beyond Apollo/ZoomInfo",
"Mentions trade show exhibitor lists",
"Identifies relevant trigger events",
"Warns about data spottiness for industrial",
"Recommends cross-verification with business websites + LinkedIn",
"Notes ICP fit precision over volume"
],
"files": []
},
{
"id": 5,
"prompt": "I build websites for local businesses. Find me 15 prospects near Austin, TX who don't have a website.",
"expected_output": "Should identify as Local SMB branch. Should run Phase 1 ICP definition: business category (ask user — gyms, restaurants, salons, etc. matter), radius (default 20 km from Austin), target count (15). Should run the browser research workflow: search Google Maps for category + Austin, build candidate list from visible results, cross-check via business name + city web search to verify website status. Should apply the 4-tier website status classification (No site found / Social only / Weak site / Has site) — prioritize No site + Social only as Hot. Should score: Hot (no site + active + phone + within radius), Warm (weak site), Cold (has site), Skip (closed/duplicate/out of scope). Should output Local SMB chat table (Score | Business | Category | Area | Distance | Website status | Website/Social | Phone | Why prospect | Confidence). Should add 'Best first outreach targets' top 3 with reasoning. Should reference references/local-prospecting.md. Should warn against bulk-scraping Google Maps (ToS violation) — browser-assisted research only.",
"assertions": [
"Identifies Local SMB branch",
"Asks about business category if not specified",
"Defaults radius to 20km",
"Runs browser research workflow",
"Applies 4-tier website status classification",
"Uses Hot/Warm/Cold/Skip scoring",
"Outputs Local SMB chat table columns",
"Adds top 3 outreach targets",
"References local-prospecting.md",
"Warns against bulk-scraping Google Maps"
],
"files": []
},
{
"id": 6,
"prompt": "I have a list of 200 prospect emails from Apollo. How do I know which ones are deliverable before I start outreach?",
"expected_output": "Should explain the deliverability validation step in Phase 3. Should recommend Truelist (the integration in this pack) for bulk validation. Should explain the email_state classification output: ok (deliverable), email_invalid (bounces, exclude), risky (deliverable with risk like role or disposable, include cautiously), unknown (couldn't determine, skip or re-verify), accept_all (catch-all domain, include cautiously). Should warn that Apollo data accuracy is typically 60-80% — sending without validation will tank sender reputation (bounce rate >2% triggers ISP throttling and reputation damage). Should recommend the workflow: bulk POST to /api/v1/verify or CSV upload → keep ok, include risky/accept_all cautiously, exclude email_invalid, re-verify unknown → hand off to outreach. Should note Truelist also has an official MCP server for agent-driven validation. Should note cold email reputation is hard to recover once damaged — validation is non-negotiable, not optional. Should mention Hunter and Snov as alternatives with built-in verification. Should reference truelist.md integration guide.",
"assertions": [
"Recommends Truelist for bulk validation",
"Explains email_state values (ok, email_invalid, risky, unknown, accept_all)",
"Warns Apollo accuracy is 60-80%",
"Cites 2% bounce rate threshold for reputation damage",
"Recommends workflow: validate, keep ok, exclude email_invalid",
"Mentions Truelist MCP server for agent workflows",
"Mentions cold email reputation is hard to recover",
"References truelist.md or data-sources.md"
],
"files": []
},
{
"id": 7,
"prompt": "I just built a tool that automates failed-payment follow-up for gym owners. I have no customers yet. Help me find my first ten — the people who are actually dealing with this problem right now.",
"expected_output": "Should select the Demand-signal branch (early-stage, first customers, evidence-of-demand) and load references/demand-signals.md — NOT the SMB/B2B list-building branches. Should start with a product brief, then mine the five signal buckets (explicit demand / pain / workaround / switching / timing) across public discourse (forums, communities, reviews, GitHub issues, job posts) — using last30days for recency, social-fetch/scraping to read original pages, not qualifying from snippets. Should score prospects on demand-fit (pain 25 / product fit 25 / timing 20 / reachability 15 / evidence quality 15, 0-100 with bands) rather than ICP-fit Hot/Warm/Cold, and require a cited public signal for every primary-shortlist prospect. Should draft source-based openers but never auto-send. Should produce an evidence report (verdict → ICP → top prospect → shortlist with sources+scores → repeated patterns → 7-day manual outreach plan → limits) and label prospects as 'potential customers based on public signals,' not confirmed buyers. Should honor the compliance guardrails including no data brokers/leaked data and no sensitive-trait targeting.",
"assertions": [
"Selects the Demand-signal branch, not the SMB/B2B/SaaS list-building branches",
"Mines the five signal buckets from public discourse rather than contact databases",
"Uses recency/original-source tooling (last30days, social-fetch, scraping) and does not qualify from snippets",
"Scores on the demand-fit rubric (0-100 weighted), not ICP-fit Hot/Warm/Cold",
"Requires a cited public signal for every primary-shortlist prospect",
"Drafts openers but never auto-sends; labels prospects as potential-based-on-public-signals",
"Produces the evidence report structure with a 7-day manual outreach plan and limits"
],
"files": []
}
]
}
FILE:references/b2b-prospecting.md
# B2B Prospecting Reference
For when the user sells to non-SaaS B2B — services, agencies, manufacturers, mid-market and enterprise companies, professional services firms.
---
## ICP Signals That Matter (B2B branch)
### Firmographic signals
- **Industry / vertical** — NAICS or SIC codes if precision matters
- **Company size** — headcount band, revenue band, location count
- **Geography** — relevant for time zones, regulations, on-site requirements
- **Business model** — service vs product vs distribution; B2B vs B2B2C
- **Ownership** — independent, PE-backed, public, family-owned — affects buying motion
### Buying signals
- **Trigger events**: new C-level hire, recent acquisition or divestiture, IPO/funding, opening a new location, recent rebrand, expansion announcement
- **Vendor signals**: posting RFPs publicly, switching costs in last quarterly report, contract renewal windows
- **Operational signals**: recent layoffs (cost pressure) or rapid hiring (capacity pressure)
- **News mentions**: launching new initiative, entering new market, regulatory change forcing action
- **PR / press**: anything that signals "this company is changing right now"
### Decay signals
- Multiple bankruptcies or PE-stripped operations
- Negative growth + cost-cutting headlines
- Ownership stagnation (small family-owned, no growth incentive)
- Buyer turnover (3+ Marketing Directors in 2 years)
---
## Discovery Sources (B2B branch)
### Tier 1 — primary discovery
- **Apollo**: best general B2B firmographic + contact discovery
- **ZoomInfo**: enterprise B2B + intent signals (mid-market+)
- **LinkedIn Sales Navigator**: industry + role + signal search; the gold standard for decision-maker mapping (manual)
- **Clay**: when you need custom waterfall lookups (e.g., enrich Apollo records with Hunter + Clearbit)
### Tier 2 — industry-specific directories
- **Crunchbase / Pitchbook**: funded businesses
- **D&B Hoovers**: large traditional B2B firmographics
- **State / national business registries**: for verified incorporation data
- **Industry association membership rosters**: trade groups often publish member lists
- **Trade show exhibitor lists**: signals active participation in a vertical
- **Procurement databases** (Procore for construction, e.g.): vertical-specific signals
### Tier 3 — trigger event monitoring
- **Google Alerts / Feedly**: trigger keywords ("acquired," "hires," "expansion," "raises," "announces")
- **PR Newswire / Business Wire**: company-controlled announcements
- **SEC filings** (public companies): material change disclosures
- **State filings**: new entity formation, dissolution
---
## Qualification Checklist (B2B branch)
- [ ] Industry / vertical matches ICP (use a recognized classification if possible)
- [ ] Company size within range (employees or revenue)
- [ ] Geography fits
- [ ] At least one trigger event in last 90–180 days
- [ ] Decision-maker role exists (CEO, COO, VP Operations, Director of X — match buyer profile)
- [ ] Email contact verifiable (named role > info@ catchall)
- [ ] Source URLs captured for firmographic claims
- [ ] No disqualifiers (closed, acquired-paused, multi-bankrupt, off-ICP)
---
## Output Columns (B2B branch)
Recommended CSV columns:
```csv
score,company,domain,industry,naics_code,size_band,revenue_band,country,city,trigger_event,trigger_date,contact_name,contact_title,contact_email,email_status,linkedin_url,source_urls,why_prospect,confidence,verified_date,notes
```
For chat table, condense to: Score | Company | Industry | Size | Trigger | Contact | Email status | Confidence.
---
## Top Outreach Targets Selection (B2B)
Prioritize for the top 3–5 hot leads:
1. **Trigger event recency** — 30 days beats 6 months
2. **Trigger event specificity** — new CMO hire in your buyer's role beats "company in the news"
3. **Decision-maker access** — named contact with verified email + LinkedIn beats role-only
4. **Vertical fit precision** — exact NAICS match beats "adjacent industry"
Each top target rationale names the trigger and decision-maker: "Hired new VP of Marketing 14 days ago; verified email; mid-market manufacturer matching ICP."
---
## Common Mistakes (B2B)
1. **Treating B2B like SaaS** — funding rounds matter less; PE ownership and acquisition activity matter more.
2. **Trying to verify private company revenue precisely** — most public databases approximate. Use size bands, not point estimates.
3. **Ignoring procurement complexity** at enterprise scale — your prospect contact list may not include the actual approver.
4. **Cold-emailing executive assistants** — they're not the buyer and they will flag your outreach as spam.
5. **Source URL hygiene** — without source lineage, you can't defend a contact under GDPR DSAR or CAN-SPAM challenge.
6. **Stopping at one source** — Apollo can be 60% accurate on small businesses. Cross-verify with LinkedIn or the business website.
FILE:references/compliance.md
# Prospecting Compliance Reference
The legal and platform-ToS constraints that apply to prospect list building. Read first, every engagement.
> Operational guidance, not legal advice. For high-volume programs or programs touching EU/UK residents, run your setup past a privacy attorney.
---
## United States — CAN-SPAM (downstream)
CAN-SPAM regulates the cold email **send**, not the list build. But the list build matters because:
- You must be able to identify the source of every email address you contact (required if challenged)
- The "from" line and email content rules apply at send time — but you can't lie about how you got the contact
- Opt-out requests must be honored within 10 business days and tracked
**For prospecting specifically**: capture and retain the source URL + date for every contact you add to a list. CAN-SPAM doesn't require it explicitly, but defending your sender practices does.
---
## EU / UK — GDPR
The strictest applicable framework. Triggers when:
- Your prospect resides in EU/UK
- You're processing personal data (any identifiable info, including business emails tied to a named person)
### Lawful bases for cold B2B outreach
You have three credible options:
1. **Legitimate interest** (most common for B2B). Requires:
- The contact is in a business role likely to be interested in your offer
- The data was collected from a public, business-context source
- You provide a clear opt-out
- You can articulate the legitimate interest test in writing
2. **Consent** — typically not feasible for cold outreach (you don't have consent before first contact)
3. **Existing customer relationship** — only applies to current customers, not prospects
### What you must do
- Capture **source + date + lawful basis** for every contact
- Honor data subject access requests (DSARs) — you must be able to disclose, correct, or delete on request
- Include a privacy notice / opt-out in the first outreach
- Don't store personal data longer than necessary for the legitimate interest
### What disqualifies a list
- Bulk-scraped LinkedIn data — explicit ToS violation + GDPR risk
- Email addresses purchased from a list broker without source provenance
- "Anyone @ this domain" guessed emails sent without verification (multiplies risk + bounces)
---
## Canada — CASL
Stricter than CAN-SPAM. Cold B2B outreach requires:
- **Express consent** (explicit opt-in) — typically not present for cold prospecting
- **OR implied consent** — existing business relationship within 24 months, OR business address publicly published on the company's own site for the purpose of receiving such communications
**Practical implication for Canadian prospects**: relying on the publicly-published-address exception is the most defensible cold prospecting basis in Canada. You must include sender identification, mailing address, and an unsubscribe mechanism in every message.
---
## Platform Terms of Service
### LinkedIn
- **Sales Navigator** as a research tool: fine
- **Scraping LinkedIn at any scale**: explicit ToS violation. Banned accounts are permanent. Don't.
- **Apollo, Clay, and ZoomInfo** claim LinkedIn-overlap data through various legitimate channels — verify their data sources before assuming compliance
- **InMail and Connection Requests**: governed by LinkedIn's own messaging rules, not by CAN-SPAM/GDPR (because LinkedIn-internal)
### Google Maps
- ToS prohibits bulk extraction or productizing Maps data
- Browser-assisted research as a discovery aid: acceptable
- Storing Place IDs or large structured Maps data in your CRM: explicit ToS prohibition
- Use Maps to **find** local businesses, then cross-source from the business's own site for the data you retain
### Apollo / ZoomInfo / Clearbit
- All have their own ToS limiting reselling, downstream sharing, and use cases
- Read your contract — typically you can use the data for your own outreach but not productize it
- Don't share extracts publicly (e.g., on a leaderboard, in a public report)
### Crunchbase
- Free tier is read-only for personal use
- Paid tier permits broader use within contractual scope
- API access requires paid Pro+ tier
---
## Anti-Patterns (Don't Do These)
1. **Bulk-scraping LinkedIn / Google Maps / Yelp**. Browser-assisted research is OK; automated scrapers pointed at these platforms are not. **Firecrawl and Browserbase are fine for an individual prospect's own website** (the URL you found through manual discovery) — not for the platforms hosting prospects.
2. **Buying lists from random vendors** without source provenance. You inherit their legal exposure.
3. **Guessing emails and sending unverified**. Bounce rates over 2% destroy sender reputation; legally, you can't claim a "legitimate interest" basis for an email you fabricated.
4. **Harvesting personal email addresses** (Gmail, personal Outlook, etc.) from public profiles. Personal addresses raise GDPR risk significantly.
5. **Storing data you don't need**. Minimize retention. Don't keep prospect lists forever — GDPR right to deletion applies.
6. **Skipping the lawful basis documentation**. If challenged, you need to show your work. Capture source URL + collection date for every contact.
7. **Reselling prospect lists**. You may not have the right to share them downstream. Read your data provider contracts.
8. **CAPTCHA bypass / login wall bypass**. Even if technically possible, this signals bot behavior and violates virtually every ToS.
---
## Quick Audit Checklist
Before shipping a list to the user (or downstream to cold-email):
- [ ] Every contact has a source URL + collection date
- [ ] No contacts sourced from scraped LinkedIn data
- [ ] No Google Maps Place IDs or large Maps-structured data retained
- [ ] Lawful basis documented (legitimate interest test for B2B, or relevant alternative)
- [ ] Email addresses validated (deliverability check before outreach)
- [ ] Personal addresses (Gmail, etc.) flagged or excluded
- [ ] Source provider contracts permit the intended use case
- [ ] Retention plan documented (when to delete)
- [ ] First outreach will include unsubscribe + privacy notice (downstream concern for cold-email skill, but mention it now)
FILE:references/data-sources.md
# Prospecting Data Sources
Tool selection guide for prospecting across all three branches.
---
## Tool selection by goal
| Goal | Primary tools | Notes |
|------|--------------|-------|
| **Build initial firmographic list (B2B / SaaS)** | Apollo, ZoomInfo, Clay | Apollo for breadth, ZoomInfo for enterprise + intent, Clay for custom workflows |
| **Decision-maker mapping** | LinkedIn Sales Navigator (manual), Apollo, ZoomInfo | Sales Nav is the gold standard. Never bulk scrape it. |
| **Tech stack qualification (SaaS)** | BuiltWith, Wappalyzer | BuiltWith has wider coverage + paid plans for bulk; Wappalyzer is lighter + free for small use |
| **Funding signals (SaaS)** | Crunchbase, Pitchbook | Crunchbase free tier sufficient for early signals; Pitchbook for deeper investor data |
| **Email pattern discovery** | Hunter, Snov, Apollo | Pattern guessing — followed by verification |
| **Email deliverability verification** | Truelist, Hunter, NeverBounce, ZeroBounce | Always verify before adding to outreach lists |
| **Visitor identification (warm intent)** | RB2B, Clearbit Reveal | Anonymous traffic → company identification |
| **Intent data** | ZoomInfo Intent, 6sense, Bombora | Pre-warmed signals; mid-market+ pricing |
| **Trigger event monitoring** | Google Alerts, Feedly, LinkedIn Sales Nav alerts | Free options are sufficient for most |
| **Local business discovery** | Google Maps (manual), Yelp, Facebook Pages | Browser-assisted, not bulk-extracted |
---
## Apollo
**Use for**: General B2B / SaaS firmographic + contact data. Best starting point if you don't already have a list.
**Strengths**:
- Large database (>200M contacts, >60M companies)
- Strong filtering UI (industry, size, technologies, signals)
- Integrated email + LinkedIn finder
- Pay-as-you-go and tiered plans
**Watch out for**:
- Data freshness varies — re-verify before scoring as "Hot"
- Email accuracy ~60–80% — always validate
- Bulk export limits apply
**Integration**: see [apollo.md](../../../tools/integrations/apollo.md)
---
## Clay
**Use for**: Multi-source enrichment, waterfall lookups, custom scoring logic. When list quality matters more than list size.
**Strengths**:
- Waterfall logic: try Apollo first → fallback to ZoomInfo → fallback to Clearbit
- 100+ data provider integrations
- AI-powered enrichment (LLM-driven extraction from URLs)
- Custom columns + scoring formulas
- Native MCP server
**Watch out for**:
- Per-credit pricing can spike on large lists
- Complexity overhead — easy to over-engineer workflows
**Integration**: see [clay.md](../../../tools/integrations/clay.md)
---
## ZoomInfo
**Use for**: Enterprise B2B + intent data. Mid-market+ buyer profiles.
**Strengths**:
- Enterprise-grade firmographic depth
- Intent signals (companies searching topics relevant to your offer)
- Best-in-class for >$50K ACV B2B sales
- Native MCP server
**Watch out for**:
- Expensive ($15K+/yr starter)
- Overkill for SMB prospecting
- Locked into multi-year contracts typically
**Integration**: see [zoominfo.md](../../../tools/integrations/zoominfo.md)
---
## Clearbit
**Use for**: Email → company enrichment, anonymous visitor identification (Clearbit Reveal).
**Strengths**:
- Strong company enrichment (industry, size, funding, tech stack)
- Email lookup by domain
- Reveal: identify anonymous site visitors at company level
- API-first
**Watch out for**:
- HubSpot acquisition (2023) — bundled into HubSpot Breeze Intelligence now
- Standalone API still available but pricing/access depends on tier
**Integration**: see [clearbit.md](../../../tools/integrations/clearbit.md)
---
## Hunter / Snov
**Use for**: Email pattern discovery + lightweight verification on small lists.
**Hunter strengths**:
- Domain-based email discovery
- Built-in deliverability verification
- Free tier reasonable for occasional use
**Snov strengths**:
- Email finder + drip campaigns (overlap with outreach tooling)
- Bulk verification
- Cheaper than Hunter at scale
**Watch out for**:
- Both are pattern-guessing tools — accuracy depends on the target company's email pattern being inferable
- Always run results through a dedicated validator (Truelist or similar) before outreach
**Integrations**: see [hunter.md](../../../tools/integrations/hunter.md), [snov.md](../../../tools/integrations/snov.md)
---
## Truelist
**Use for**: Email deliverability validation before adding contacts to outreach lists. Critical safety step.
**Strengths**:
- Single-email sync verification (`/api/v1/verify_inline`) + bulk async (`/api/v1/verify`)
- Returns `email_state` (ok / email_invalid / risky / unknown / accept_all) + `email_sub_state` (email_ok / is_disposable / is_role / unknown_error / failed_smtp_check) + did-you-mean typo suggestions
- Catches catch-all domains, role accounts, spam traps, disposable providers
- Official MCP server for agent-driven workflows (Claude, Cursor, VS Code)
- Official SDKs in 7 languages + framework integrations (Django, Laravel, Next.js, Rails, React, Svelte, Vue, WordPress)
- Native integrations with Mailchimp, Klaviyo, HubSpot, Zapier, Make, n8n, Clay, Salesforce, more
- Pay-per-email pricing
**Why this matters**: Cold email reputation craters when bounce rates exceed 2%. Validating before sending is non-negotiable. Apollo/ZoomInfo/Hunter data is often 60–80% accurate — Truelist catches the rest.
**Integration**: see [truelist.md](../../../tools/integrations/truelist.md)
---
## LinkedIn Sales Navigator
**Use for**: Manual decision-maker discovery. The gold standard for B2B / SaaS prospecting but only when used as a research tool.
**Strengths**:
- Most accurate decision-maker data in the industry
- Real-time job changes, posts, signals
- Lead lists, alerts, saved searches
- Inmail credits (separate channel from cold email)
**Hard rules**:
- **Never bulk scrape**. LinkedIn aggressively bans scrapers. Account ban risk is real and permanent.
- Use Sales Nav as a research interface — open profiles, read, take notes, capture key data manually.
- Apollo and other tools claim LinkedIn data via partnerships / public mirroring — verify the source legitimacy before assuming compliance.
**Integration**: no MCP or API access at consumer level. Manual research only.
---
## BuiltWith / Wappalyzer
**Use for**: Tech stack qualification (SaaS branch).
**BuiltWith**:
- ~50K+ technologies tracked
- API + bulk lookups (paid)
- Historical data (when stack changed)
**Wappalyzer**:
- Free browser extension; paid API
- Lighter coverage than BuiltWith
- Faster for one-off lookups
Cross-reference both for high-confidence tech stack signals.
---
## Crunchbase
**Use for**: Funding signals (SaaS branch).
**Strengths**:
- Free tier shows recent funding events
- Paid (Pro / Enterprise) unlocks alerts and deep history
- API access for paid users
**Watch out for**:
- Coverage is best for VC-backed companies; bootstrapped + small businesses underrepresented
- Self-reported data — verify funding amounts independently
---
## GitHub (stargazers / forks / watchers)
**Use for**: Developer-intent prospecting. Especially powerful for dev-tool SaaS — stargazers of competitor or category-defining repos are in-market signal.
**Strengths**:
- Public API, no scraping concerns
- High signal quality (a starred repo = explicit interest)
- Forks are an even stronger signal (intent to modify, not just bookmark)
- Bundled `github-prospects.js` CLI handles pagination + enrichment + CSV output
- Free with 5,000 req/hr authenticated rate limit
**Watch out for**:
- Only ~5–20% of users publish email — pair with Apollo/Clay/Hunter for enrichment
- Very-popular repos (100K+ stars) are mostly noise; smaller targeted repos (5K–25K) give better signal density
- Most prospects are individuals, not company contacts directly — need to figure out their company from `company` field or LinkedIn
**Integration**: see [github.md](../../../tools/integrations/github.md)
---
## Firecrawl / Browserbase (single-target site research)
**Use for**: Programmatically extracting content from a **prospect's own website** that you already found via discovery on platforms like Google Maps, Yelp, or LinkedIn. Not for scraping those platforms themselves.
### Firecrawl
- **Best for**: "Just give me the page as markdown" — Local SMB website status checks, B2B company about/team page extraction, structured field extraction
- **Strengths**: Low overhead, returns clean LLM-ready markdown, handles most JS-rendered sites, has an MCP server
- **API + MCP + SDKs**: Node, Python, Go, Rust
### Browserbase
- **Best for**: When you need real Chromium — JS-heavy pages, cookie consent dialogs, form submission to reach a contact page, session state
- **Strengths**: Full browser control via Playwright/Puppeteer; Stagehand provides AI-friendly natural-language extraction; session recordings for debugging
- **API + MCP (Stagehand) + SDKs**: Node, Python
### Critical compliance line
Both tools can technically point at any URL. The hard rule:
- ✓ **OK**: extracting content from a single business's own website (`joescoffeeshop.com`) that you found through manual discovery
- ✗ **NOT OK**: pointing them at `google.com/maps`, LinkedIn search results, Yelp listings, or any platform whose ToS prohibits bulk extraction
Discovery happens on platforms (manual browser-assisted research). Extraction happens on individual public business sites.
**Integrations**: see [firecrawl.md](../../../tools/integrations/firecrawl.md), [browserbase.md](../../../tools/integrations/browserbase.md)
---
## RB2B / Clearbit Reveal
**Use for**: Identifying anonymous site visitors as warm intent signals.
**Strengths**:
- Pixel-based visitor → company identification
- High-intent: they came to your site, they're already in research mode
- Slack / email alerts on key visits
**Watch out for**:
- Privacy/GDPR considerations — verify your privacy policy disclosures
- Person-level identification raises higher concerns than company-level
**Integration**: see [rb2b.md](../../../tools/integrations/rb2b.md)
---
## Free / browser-only fallbacks
When the user has no paid tools, lean on:
- **Google Search** — exact business name + city + role searches
- **LinkedIn** (manual, no scraping) — company pages, employee lookups
- **Crunchbase free tier** — funding events
- **Wappalyzer browser extension** — tech stack at a glance
- **Hunter.io free tier** — 25 lookups/month
- **Google Maps** — for Local SMB discovery
- **Business websites + About pages** — primary source for any claim
- **News sites + press releases** — trigger event monitoring via Google Alerts
Slower than tooled-up workflows, but produces high-quality smaller lists if the user is willing to do the work.
---
## Sequencing recommendations
A typical full-stack prospecting workflow:
1. **Define ICP** from product-marketing context (no tools needed)
2. **Initial list** from Apollo or ZoomInfo (firmographic filter)
3. **Enrich** with Clay (waterfall: tech stack, funding, trigger events)
4. **Decision-maker mapping** in LinkedIn Sales Nav (manual)
5. **Email pattern discovery** with Hunter or Apollo's built-in
6. **Email validation** with Truelist before final list
7. **Hand off** to cold-email skill for outreach copy
Adapt this sequence based on which tools the user actually has.
FILE:references/demand-signals.md
# Demand-Signal Discovery (Find Your First Customers)
The other three branches build a list from who *fits* (firmographics, technographics, proximity). This branch builds a list from who is *already showing the pain* — the early-stage motion where you have a product and a hunch but no customer base yet, and you need your first ten real conversations. You are not filtering a database; you are mining recent public discourse for people describing the exact problem you solve, then linking every prospect to the evidence.
Use this branch when the user is pre-product-market-fit, launching something new, or looking for **design partners, beta users, or first customers** rather than a scaled outbound list. It reuses the shared five phases and every compliance guardrail in SKILL.md; what changes is where you look, how you score, and what you ship.
Pattern credit: the framework here is re-expressed from the open-source `first-customer-finder` Codex skill (Kappaemme, MIT), extended with our live-recency tooling.
## What makes this branch different
| | List-building branches (SaaS / B2B / SMB) | Demand-signal discovery |
|---|---|---|
| Starts from | A firmographic ICP | A described problem |
| Sources | Contact databases (Apollo, ZoomInfo, Clay) | Public discourse (forums, reviews, issues, posts) |
| Contact step | Enrich + verify email deliverability | None — reach them where they already posted |
| Wins on | Coverage at scale | 10 strong evidence-backed matches over a long list |
| Output | A scored lead sheet | An evidence report + manual outreach plan |
A prospect here without a cited pain, need, or timing signal is a speculative fit — it does **not** belong in the primary shortlist. Evidence is the entry ticket.
## Step 1 — Product brief (before any searching)
Define, specifically enough to *reject* weak matches:
- product and the promised outcome
- primary user and the economic buyer (often different)
- the urgent job to be done
- the current alternative or workaround being replaced
- the likely adoption trigger (what makes now the moment)
- geography / language constraint
- clear disqualifiers
Don't start broad collection until the brief is sharp. Pull from `.agents/product-marketing.md` if it exists.
## Step 2 — Mine the five signal buckets
Search several angles, not one query repeated. Adapt wording to how the audience actually talks (mine their vocabulary from organic content first — see the ad-creative hook-system's organic-language note for the same idea).
1. **Explicit demand** — "looking for," "recommend a tool for," "alternative to [X]," "does anything exist that," "how do you all handle."
2. **Pain** — "takes hours," "so manual," "hate that," "keeps breaking," "biggest frustration with," "why is there no."
3. **Workaround** — spreadsheets, copy-paste, a VA, a Zapier chain, a script, a template, any repeated manual step that your product would replace.
4. **Switching** — cancellation, migration, "moving off [competitor]," a missing feature, a pricing complaint, competitor frustration.
5. **Timing** — a public launch, a new hire for the relevant function, expansion, a new workflow or regulation, an integration announcement — a *current* event that makes the product relevant now.
**Use our live-recency edge.** A generic skill relies on whatever a web search surfaces; you have better:
- **last30days** — Reddit, Hacker News, X, YouTube, and web signals from the last 30 days. This is the single highest-value tool for this branch: recency *is* the timing signal.
- **social-fetch** — pull the full content of a specific post/thread you find, normalized.
- **scraping** / **Firecrawl** / **Browserbase** — read the original public page (a forum thread, a GitHub issue, a review), never qualify from a search snippet alone.
- **deep-research** — for a multi-source sweep with adversarial verification when the wedge is broad.
- **competitor-profiling** / **customer-research** — competitor switching signals and review-mining for the pain language.
## Step 3 — Source mix (public only)
Forums and public community threads · public social posts and replies · product and app-marketplace reviews · GitHub issues and feature requests · public company pages, job posts, changelogs, launch announcements · "looking for a tool" posts and directories.
Avoid private groups, gated communities, data brokers, leaked datasets, and any source whose terms prohibit access — the same compliance guardrails as every other branch (see SKILL.md), including the no-sensitive-traits rule.
**Business/professional context only.** Qualify and reach out only where someone is posting in a professional or business capacity about a work problem (a founder in an indie-hackers thread, a developer in a GitHub issue, an ops lead in a subreddit for their role). Exclude personal-distress contexts entirely — health, financial hardship, addiction, grief, or any consumer support forum where people are venting personal problems, even if your product is tangentially relevant. When the motion is genuinely consumer (B2C), a public pain post is not on its own a lawful basis for cold outreach — reach people through the channel's own norms (reply publicly where replying is expected) and never DM a stranger off a personal post.
Quote minimally, paraphrase by default, and link every material pain or timing signal.
## Step 4 — Score on demand-fit (not ICP-fit)
The list-building branches score Hot/Warm/Cold on ICP fit. This branch scores 0–100 on **demand fit** — how strongly the evidence says this specific prospect wants this specific thing now. Score each dimension 0–5:
| Dimension | Weight | What it measures |
|---|---|---|
| **Pain strength** | 25% | Directness, severity, repetition, and cost of the stated problem |
| **Product fit** | 25% | How directly your product solves the evidenced job |
| **Timing** | 20% | Freshness + a current trigger present |
| **Public reachability** | 15% | A natural, relevant public/professional contact path exists |
| **Evidence quality** | 15% | Specificity, source reliability, confidence the signal is really theirs |
```
score = pain/5*25 + fit/5*25 + timing/5*20 + reachability/5*15 + evidence/5*15
```
| Band | Meaning |
|---|---|
| **80–100** | Strong first-customer candidate |
| **65–79** | Promising — validate fast |
| **50–64** | Plausible but missing a material signal |
| **Below 50** | Do not include in the primary shortlist |
An old explicit request can still count — but lower the timing score and label the date. A company that merely matches the industry with no evidenced trigger is *not* a qualified prospect here.
### Prospect stages
- **High intent** — publicly requesting a solution or actively switching
- **Problem aware** — clearly describing the pain or an expensive workaround
- **Trigger present** — a current business event makes the product relevant
- **Potential fit** — ICP match, incomplete evidence → keep *outside* the primary shortlist
### Evidence ledger (per qualified prospect)
Displayed name (company/project/public professional) · source title + URL · visible publication date or "date unavailable" · source type · the concise pain/timing signal · observed evidence vs. inference (label which) · score breakdown · freshness warning when the signal is stale.
## Step 5 — Draft outreach, never send it
Recommend the most natural channel *already associated with the source*, and only where a reply is a normal part of that channel (reply in the public thread, respond via a public professional profile). Don't turn a public post into a private DM the poster didn't invite, and never contact someone off a personal-distress post. Draft one opener, under ~90 words, in this shape:
1. mention the public context naturally
2. connect it to the exact problem
3. explain the product in one sentence
4. ask one low-friction question
Never claim familiarity you don't have, never fabricate personal details, and never auto-send: no messages, connects, follows, comments, form submissions, or CRM records unless the user separately authorizes that action. This is the manual/gated posture from the marketing-loops guardrails.
## Step 6 — Ship the evidence report
Lead with the most actionable evidence, in this order:
1. **Verdict** — does the product have reachable early-customer signal, or not yet? (An honest "not yet, here's why" is a valid answer.)
2. **ICP** — buyer, job, trigger, disqualifiers.
3. **Top prospect** — the single strongest evidence-backed candidate and why now.
4. **Prospect shortlist** — per prospect: source, pain signal, demand-fit score, stage, why-now, channel, opener.
5. **Repeated patterns** — pains and triggers recurring across prospects (these are your positioning and messaging gold).
6. **Seven-day manual outreach plan** — a low-volume validation sequence (e.g., contact the top 3 with one source-based question; share a mockup only after they confirm the pain; target three conversations and one design-partner commitment).
7. **Limits** — what evidence is missing and what must be confirmed through real conversations.
For a shareable standalone HTML version of this report, the JSON→HTML generator pattern in ad-creative's [creative-review-page.md](../../ad-creative/references/creative-review-page.md) is the model (escape every value; keep it self-contained).
## The honesty rules (non-negotiable)
- Every primary prospect links to at least one real public signal. No signal, no shortlist.
- Label the output **"potential customer based on public signals"** — never "interested," "will buy," or "has consented."
- Prefer ten strong matches over a long generic list. Make uncertainty and stale evidence visible.
- Personalize from the cited source, not from invented assumptions.
- Treat the shortlist as a research hypothesis to validate through conversations, not a customer database.
FILE:references/local-prospecting.md
# Local SMB Prospecting Reference
For when the user sells to local small businesses — shops, gyms, restaurants, salons, clinics, professional services, contractors, real estate, fitness studios, dental practices.
Adapted from and generalized beyond the local-client-prospector pattern (browser-assisted discovery + website status classification + proximity scoring).
---
## ICP Signals That Matter (Local SMB branch)
### Operational signals
- **Active business** — Google Business Profile updated, recent reviews, recent hours updates
- **Recent activity** — open right now, regular hours posted, recent photos uploaded by owner
- **Customer engagement** — owner responding to reviews, posts on social, active calendar (for service businesses)
### Online presence signals (the core SMB qualification axis)
The reference local-client-prospector skill uses **website status** as the primary qualification — port this directly. Four classifications:
| Status | Definition | Typical outcome |
|--------|-----------|-----------------|
| **No site found** | No credible standalone website after cross-checked search | **Hot prospect** for web/marketing service |
| **Social only** | Facebook, Instagram, WhatsApp, Linktree, booking portal, marketplace page only — no standalone site | **Hot prospect** for web/marketing service |
| **Weak site** | Standalone site exists but outdated, broken, very thin, non-mobile-friendly, or missing clear contact/conversion flow | **Warm prospect** for refresh / rebuild service |
| **Has site** | Credible, modern standalone site exists | **Low prospect** unless other signals apply (e.g., poor SEO, weak conversion design) |
### Proximity signals
- **Distance** from the user's location or service area
- **Density** — clusters of similar businesses in one area = neighborhood targeting opportunity
- **Travel time** — useful when in-person discovery, install, or service delivery is required
### Decay signals
- Closed permanently (Google Maps banner)
- Reviews paused or business listing reported as closed
- Last activity (review, post) >12 months ago
---
## Discovery Sources (Local SMB branch)
### Primary
- **Google Maps** (browser, manual) — search "category near [location]" and walk the visible results. Cross-check details. Don't bulk-extract.
- **Yelp** — secondary verification; complementary categories
- **Bing Local / Apple Maps** — different coverage on smaller businesses
- **Facebook Pages search** — many SMBs are Facebook-only
### Cross-verification
- **Business's own website** (if any)
- **Industry directories** (e.g., Healthgrades for medical, OpenTable for restaurants, Avvo for legal)
- **Local Chamber of Commerce listings**
- **State business registries** for incorporation status
- **Search results for "[business name] [city]"** to discover non-Maps presence
---
## Browser Research Workflow
1. Open a browser and search Google Maps for the category near `base_location`
2. Build a candidate list from visible local results, search results, and public directories
3. For each candidate, inspect public sources to fill required fields
4. Search the exact business name plus city/town to check whether a standalone website exists
5. Classify website status per the table above
6. Mark confidence: High (2+ sources), Medium (1 source + consistent evidence), Low (incomplete/ambiguous)
When the user explicitly asks for subagents AND subagents are available, split candidates into non-overlapping batches and ask each subagent to verify only website/social/contact status. Don't use subagents for the primary search if it slows progress.
### Optional: programmatic verification with Firecrawl or Browserbase
Once you have a candidate's website URL (found via manual Maps/Yelp discovery), you can speed up website-status classification by hitting the URL programmatically:
- **Firecrawl** for simple "is this site live, modern, mobile-friendly, conversion-flow-equipped" reads — returns clean markdown you can inspect
- **Browserbase** when the candidate site requires JS rendering, has a cookie consent dialog, or you need session state
**Strict line**: use these on the individual business's URL. **Don't** point them at Google Maps, Yelp, or any platform whose ToS prohibits bulk extraction — discovery stays manual.
See [data-sources.md](data-sources.md) for setup details.
---
## Qualification Checklist (Local SMB branch)
- [ ] Business is active (recent reviews or activity in last 6 months)
- [ ] Category matches user's service offering
- [ ] Distance / proximity within target radius
- [ ] Website status classified
- [ ] Phone or contact channel verified
- [ ] At least one cross-source confirms business operates at the listed address
- [ ] Not a duplicate / chain location / out-of-scope category
- [ ] Not closed permanently
---
## Lead Scoring (Local SMB)
Use this simple rubric (matches local-client-prospector pattern):
| Score | Criteria |
|-------|----------|
| **Hot** | No site found OR social-only + phone present + active business + within target radius |
| **Warm** | Weak site, poor online presentation, or marketplace/booking-page only |
| **Cold** | Good website already present OR low confidence |
| **Skip** | Closed, duplicate, outside radius, irrelevant category, or not a business prospect |
---
## Output Columns (Local SMB branch)
Chat table (≤15 rows):
```
| Score | Business | Category | Area | Distance | Website status | Website/Social | Phone | Why it's a prospect | Confidence |
```
CSV:
```csv
score,business,category,area,distance_km,website_status,website_url,social_urls,phone,email,source_urls,why_prospect,confidence,verified_date,notes
```
Rules:
- Keep "Why it's a prospect" short and actionable
- Use `Not found` instead of leaving blank fields
- Include source links sparingly, not all of them
- After the table, add **Best first outreach targets** with the top 3 leads and one practical reason each
- If confidence is low, state exactly what remains uncertain
---
## Top Outreach Targets Selection (Local SMB)
Prioritize for the top 3 hot leads:
1. **No site / social only + phone present** = clearest service opportunity
2. **High review count** = active, established business with real customers
3. **Owner-responded reviews** = engaged owner = more likely to evaluate a vendor
4. **Industry alignment with your service specialty** beats generic category match
Each top target rationale should be one sentence naming the gap and the signal: "No standalone website (cross-checked); 80+ Google reviews with owner replies; 2 km from target area."
---
## Compliance Notes (Local SMB-specific)
The local branch is the most scraping-sensitive of the three motions. Specifically:
- **Google Maps Terms of Service** prohibit bulk extraction. Treat browser visits as research, not as data acquisition.
- **Don't store full Google Maps Place IDs in your CRM** — the ToS limits storage of Maps data.
- **Public business contact channels only**: published phone, contact form, info@ email. Don't reach individual employees through their personal channels.
- **Owner/operator name when published on the business's own site** is OK to use. If you only got it from LinkedIn, mark the source.
---
## Common Mistakes (Local SMB)
1. **Bulk-scraping Google Maps** — fastest way to violate ToS and lose the research channel.
2. **Treating Google Maps data as truth** — listings go stale. Cross-check hours, status, and reviews.
3. **Skipping the website status cross-check** — finding "no site" on Maps doesn't mean no site exists; do an exact-name web search before classifying.
4. **Targeting only the largest businesses** — they're already covered by other providers. The 2–5 employee SMBs are the under-served opportunity.
5. **Generic outreach to all hot leads** — local SMBs respond better to outreach that names their specific gap ("I noticed your menu isn't visible on mobile") than generic pitches.
6. **Ignoring chains and franchises** as Skip — sometimes the franchisee is the buyer and they have local marketing authority. Verify before skipping.
FILE:references/saas-prospecting.md
# SaaS Prospecting Reference
For when the user sells SaaS or digital services to other SaaS companies / digital businesses.
---
## ICP Signals That Matter (SaaS branch)
Beyond standard firmographics (industry, size, geography), SaaS prospects are qualified by:
### Technographic signals
- **Tech stack** — do they use complementary tools (your integration target) or competing tools (a switch opportunity)?
- **Recent stack changes** — adding/removing tools signals active vendor evaluation
- **Custom-built vs off-the-shelf** — DIY tooling often means a buyer who'd benefit from your product
- **Free/freemium plan signals** — using a free competitor means they may be ready to upgrade
### Growth signals
- **Funding round** — Series A / B / C in last 6 months = budget + new hires + tool needs
- **Headcount growth** — 10%+ growth in last quarter signals scaling pressure
- **Hiring signals** — specific role openings (e.g., "Head of RevOps" → ICP for revops tooling)
- **Product velocity** — frequent shipping, new features, blog posts = healthy growth motion
- **Open positions for your buyer's role** — if you sell to Marketing Ops and they're hiring one, that's a signal
### Decay signals (downgrade scoring)
- Layoffs in target department
- Funding round >2 years ago with no follow-up
- Product hasn't shipped in 6+ months
- Team page shows founders only (very early — may not have budget)
---
## Discovery Sources (SaaS branch)
Combine 2+ sources for cross-verification.
### Tier 1 — primary discovery
- **Apollo**: firmographic + technographic + contact data. Good for building large initial lists.
- **Clay**: waterfall enrichment, custom scoring, multi-source merges. Best for high-quality smaller lists.
- **ZoomInfo**: enterprise-grade firmographic + intent signals. Expensive; mid-market+.
- **LinkedIn Sales Navigator**: decision-maker mapping. Use manually, never bulk scrape.
### Tier 2 — technographic / growth signals
- **BuiltWith**: tech stack lookups, find sites using specific tools
- **Wappalyzer**: free browser extension + API; lighter tech stack signal
- **Crunchbase**: funding rounds, headcount, founders
- **Pitchbook**: deeper investor data (enterprise/paid)
- **ProductHunt**: recent launches, builder audience
- **Hacker News / Show HN**: technical builders launching products
### Tier 3 — buying signals
- **Job boards** (LinkedIn Jobs, Indeed, AngelList): role openings as signals
- **RB2B / Clearbit Reveal**: visitor identification (warm anonymous traffic)
- **GitHub stars/forks of competitor or adjacent repos**: developer-level intent signal (see `tools/integrations/github.md` and the `github-prospects.js` CLI). Especially strong for dev-tool SaaS — a developer who starred `vercel/next.js` last week is in-market for adjacent Next.js infrastructure.
- **Recent blog posts / changelog**: product direction signals
- **G2 reviews mentioning competitor switches**: explicit dissatisfaction signal
#### GitHub prospecting pattern (when audience is developers)
For dev-tool SaaS, GitHub is one of the highest-quality discovery channels:
1. Identify 3–5 "anchor" repos: your direct competitors, your category leader, complementary tools your buyer uses
2. Pull stargazers (or forks for stronger intent) via `node tools/clis/github-prospects.js stargazers <owner/repo> --enrich --with-company --format csv`
3. Filter to users with `company` set — these are the easiest to enrich downstream
4. Pair with Apollo/Clay/Hunter to lookup email by name + company
5. Validate with Truelist before adding to outreach list
Tradeoffs: GitHub yields email for only ~5–20% of users directly. The strength is the signal quality — a stargazer of a niche dev tool is genuinely in-market in a way Apollo firmographics alone can't tell you.
---
## Qualification Checklist (SaaS branch)
For each candidate, verify:
- [ ] Industry vertical matches ICP
- [ ] Company size (headcount) within range
- [ ] Tech stack includes (or notably excludes) a target technology
- [ ] Funding stage matches buyer maturity
- [ ] At least one growth signal in last 90 days (funding, hiring, product velocity)
- [ ] Decision-maker role exists at the company (named or inferable from job listings)
- [ ] Email contact verifiable
- [ ] No disqualifiers (closed, acquired-and-paused, layoffs, ICP miss)
---
## Output Columns (SaaS branch)
Recommended CSV columns:
```csv
score,company,domain,industry,size_band,country,funding_stage,last_round_date,tech_stack_match,signal,signal_date,contact_name,contact_title,contact_email,email_status,linkedin_url,source_urls,why_prospect,confidence,verified_date,notes
```
For chat table, condense to: Score | Company | Industry | Size | Signal | Contact | Email status | Confidence.
---
## Top Outreach Targets Selection (SaaS)
Prioritize for the top 3–5 hot leads:
1. **Strongest signal recency** — funding 30 days ago beats funding 9 months ago
2. **Tech stack match strength** — known integration partner beats inferred fit
3. **Decision-maker named with verified email** — beats role-pattern-guessed email
4. **Multi-source confidence** — both Apollo + Crunchbase agree beats one source
Each top target gets a one-sentence outreach rationale that names the specific signal: "Raised Series B 30 days ago; hiring Head of RevOps; verified VP of Ops email."
---
## Common Mistakes (SaaS)
1. **Buying lists from Apollo wholesale** without re-verifying email and re-checking firmographics. Stale data is the norm.
2. **Treating tech stack data as 100% accurate**. BuiltWith and Wappalyzer miss things; Clay's waterfalls miss things. Cross-check.
3. **Targeting Series C+ for early-stage SaaS sellers**. The buyer profile is wrong — too many procurement hoops, too much red tape.
4. **Targeting Series Pre-Seed seed** for products requiring meaningful budget. They have neither budget nor evaluator bandwidth.
5. **Ignoring intent data when it exists** (ZoomInfo Intent, 6sense, etc.) — pre-warm signals beat cold every time.
Nộp sản phẩm lên các danh bạ startup, SaaS, AI, MCP, no-code, đánh giá để lấy backlink, tăng domain rating và được khám phá.
---
name: directory-submissions
description: When the user wants to submit their product to startup, SaaS, AI, agent, MCP, no-code, or review directories for backlinks, domain rating, and discovery. Also use when the user mentions "directory submissions," "submit to directories," "backlinks from directories," "list my product," "submit to Product Hunt," "BetaList," "TAAFT," "Futurepedia," "G2 listing," "Capterra listing," "AlternativeTo," "SaaSHub," "AI directories," "MCP registry," "agent directory," "dofollow backlinks," "launch directories," or "directory tracker." Use this whenever someone is planning the directory layer of a product launch or an ongoing backlink campaign. For the broader launch moment, see launch. For programmatic SEO pages that should live behind these backlinks, see programmatic-seo. For AI citation optimization, see ai-seo.
metadata:
version: 2.0.0
---
# Directory Submissions
You are an expert in directory-driven distribution for software products. Your goal is to help the user build a compounding backlink + discovery foundation by submitting to the right directories, in the right order, with the right positioning — and to make sure that foundation actually produces leads instead of vanity backlinks.
## Before Starting
**Check for product marketing context first:**
If `.agents/product-marketing.md` exists (or `.claude/product-marketing.md`, or the legacy `product-marketing-context.md` filename, in older setups), read it before asking questions. Use that context and only ask for information not already covered or specific to this task.
---
## Core Philosophy
Directory submissions are the **foundation layer** of distribution — never the whole strategy. They do three things well:
1. **Pass dofollow backlinks** from high domain-rating sites into your marketing pages. This raises your DR, which makes your entire site easier to rank for competitive keywords.
2. **Create discovery surface area** — people browsing AI/SaaS directories are in-market buyers, not random traffic.
3. **Get cited by AI engines** — ChatGPT, Claude, Perplexity, and Google AI Overviews all pull heavily from high-DR directories when answering "what's the best [category]?" queries. AI-referred traffic converts **6–27× higher** than traditional search traffic.
But directories alone will not generate meaningful leads. They exist to pass link equity into the pages that DO generate leads — template galleries, comparison pages, alternative pages, blog posts. **Build the destination pages first, then submit to directories so the link equity has somewhere useful to land.**
The full directory catalog lives in `references/directory-list.md`. The positioning variant library lives in `references/positioning-variations.md`. The submission tracker template lives in `references/submission-tracker-template.csv`.
---
## The Three Hard Rules
### Rule 1: Foundation before submission
Never submit to a directory until the landing page it will link to is live, indexed, and has:
- A single `<h1>` and sequential heading hierarchy — pages with clean hierarchy have **2.8× higher AI citation rates**, and 87% of ChatGPT-cited pages use a single H1.
- A real pricing page (even "free while in beta" counts — most Tier 1 directories require one).
- Privacy policy + terms.
- Logo assets in PNG + SVG + square 1024×1024 + favicon.
- 5–8 real product screenshots at 1920×1080 (not marketing mockups).
- A 60–90 second demo video — products with video on Product Hunt get **2.7× more upvotes**.
- FAQ schema markup (AI engines heavily weight `FAQPage` JSON-LD for answer extraction).
- Structured data: `Organization`, `Product`, `SoftwareApplication`.
### Rule 2: Destination pages before directories
Directories are the *source* of link equity. You need *destinations* that can convert the resulting traffic. Minimum destinations before submitting to anything:
- 3–5 competitor alternative pages (`/alternatives/[competitor]`) targeting "[competitor] alternative" keywords. Comparison/alternative pages convert at **5–15%** vs 0.5–2% for generic content.
- 3–5 use-case pages (`/for/[audience]` or `/use-cases/[use-case]`).
- Template gallery with 20+ entries (if applicable — this was Typeform's largest SEO growth driver, generating 30K non-branded signups and $3M/year LTV).
- 1 "best of" blog post you wrote yourself about your own category, including honest coverage of competitors.
### Rule 3: Positioning varies by directory type
Never copy-paste the same description everywhere. AI engines penalize duplicate content, and each directory audience responds to different framing. See `references/positioning-variations.md` for the full variant library. Short version:
| Surface | Lead with | Why |
|---|---|---|
| Startup directories | **Outcome** | Audience is other founders. They care what it does. |
| SaaS directories | **Alternative framing** | People search "[competitor] alternative" — meet them there. |
| AI directories | **AI-first architecture** | TAAFT/Futurepedia audiences explicitly want AI tools. |
| Agent/MCP directories | **Agent/MCP angle** | Niche but high-intent. A real moat. |
| No-code directories | **Ease + power** | Audience values speed-to-build over depth. |
| Dev directories | **Technical depth** | Dev audiences reward technical substance. |
| B2B review sites | **ROI + use case** | Buyers want outcomes and case studies. |
---
## Workflow
### Step 1: Readiness assessment (Phase 0)
Ask the user these 9 questions. If any are "no", they're not ready — help them build the missing piece first.
1. Is the product publicly accessible (no password wall)?
2. Is there a pricing page (even "free while in beta")?
3. Are privacy policy + terms live?
4. Logo assets in PNG + SVG + square + favicon?
5. 5–8 real screenshots + 60–90s demo video?
6. Landing pages GEO-ready (single H1, sequential hierarchy, FAQ schema, structured data)?
7. At least 3 alternative pages and 3 use-case pages live and indexed?
8. Template gallery or lead magnet asset (if applicable to category)?
9. At least 20 beta/early users who could leave a review on G2?
A "no" on any of 1–7 is a hard block. A "no" on 8–9 is a soft block: you can launch but will lose Tier 2 review value and Typeform-style compounding.
### Step 2: Choose the tiers
Full catalog in `references/directory-list.md`. Summary:
| Tier | When | Examples | Typical count |
|---|---|---|---|
| **Tier 1 — Flagship launch** | Launch week only | Product Hunt (anchor), BetaList, HN Show HN, Fazier, DevHunt | ~15 |
| **Tier 2 — Startup/SaaS** | Week 1 + rolling | AlternativeTo, SaaSHub, G2, Capterra, F6S, SourceForge, Slashdot | ~50 |
| **Tier 3 — AI directories** | Week 1–3 | TAAFT, Futurepedia, Toolify, Future Tools, aitools.inc, AIStage | ~40 |
| **Tier 4 — Agent/MCP registries** | Week 1–3 (if MCP) | Glama, APITracker, LF MCP Registry, AI Agents List | ~10 |
| **Tier 5 — No-code directories** | Week 1–3 (if no-code) | NoCodeFinder, No Code MBA, We Are No Code, MakerPad | ~8 |
| **Tier 6 — "Best of" listicles** | Rolling outreach | Cold outreach to DR 40+ blog posts | ~10 inclusions |
| **Tier 7 — Integration marketplaces** | When integrations ship | Zapier, HubSpot, Slack, Airtable, Notion | ~5 |
| **Tier 8 — Profile & content platforms** | Rolling | GitHub, WordPress.com, Substack, Dev.to, SlideShare, Behance | ~50 |
| **Tier 9 — Local business directories** | Rolling (if applicable) | Manta, Hotfrog, Locanto, MerchantCircle | ~20 |
| **Tier 10 — Forums & communities** | Rolling (participate first) | SitePoint, GrowthHackers, Warrior Forum, Designer News | ~13 |
| **Tier 11 — Press release & article sites** | Launch + milestones | PRLog, PR.com, EzineArticles, Feedspot | ~25 |
| **Tier 12 — Social bookmarking** | Rolling | Scoop.it, Diigo, Pearltrees | ~5 |
| **Tier 13 — Niche vertical directories** | When vertical fits | Justia (legal), Porch (home), LandBook (design), etc. | ~20 |
**Triage rule:** Only submit where the product is a genuine fit. Forcing a listing into the wrong category burns the first-submission advantage and gets rejected by moderators.
### Step 3: Prepare asset variations
For each tier, prep a distinct description variant (pulled from `references/positioning-variations.md`):
- **Tagline** under 10 words
- **Short description** at 60 chars
- **Long description** at 150 words
- **5–8 category tags**
- **Logo** assets
- **Screenshots** + demo video URL
- **Founder story** (2–3 sentences)
**Critical:** Don't copy-paste the same long description into every directory. Vary the opening sentence, the feature emphasis, and the audience framing per tier. AI engines cross-reference and down-weight duplicate content.
### Step 4: Batch submit
Set up the tracker spreadsheet (`references/submission-tracker-template.csv`). Work left-to-right through it. 2–3 hours per batch is realistic.
Per submission:
1. Copy the tier-appropriate positioning variant.
2. Fill in the form.
3. Upload assets.
4. Submit.
5. Log: date, URL, status, moderator notes.
6. Once live, verify the backlink exists and is dofollow: `curl -sIL https://directory.com/your-listing | grep -i rel=`. If absent, the link is dofollow.
---
## Product Hunt Deep Dive (The Anchor Event)
Product Hunt is the single highest-leverage submission but also the most easily wasted. The 2026 PH algorithm weights **comment quality** more than upvote count — a post with 50 upvotes + 30 genuine comments ranks above one with 200 upvotes + 5 comments. **80% of failed launches** fail because they launched without a warm audience OR asked for upvotes instead of feedback.
### 3-week prep timeline
- **Day -21 to -14:** Warm up hunter account. Upvote + thoughtfully comment on 3 launches/day. Follow 100+ active makers. Build history so your account looks real to the algorithm.
- **Day -14:** Create "Upcoming" page on PH. Drive traffic to it to collect "notify on launch" subscribers.
- **Day -10:** (Optional) book a hunter. Don't pay cash — trade a feature, shoutout, or intro. A known hunter adds ~15% to day-one momentum but isn't required.
- **Day -7:** Draft launch-day assets: gallery images (1270×760), tagline, 260-char description, first comment from you, first comment from a customer.
- **Day -3:** Email list warm-up. "We're launching Tuesday. Here's what to expect. Reply if you want a heads up."
- **Day -1:** Final check — product works in incognito, video autoplays, CTA goes to signup, PH listing preview looks right.
### Launch day execution
- **Launch at 12:01 AM Pacific Time.** Tuesday, Wednesday, or Thursday only — weekend launches get 60–70% less traffic. The 12:01 AM PT start maximizes your 24-hour window.
- **First 2 hours are everything.** Need 50+ supporters in the first 2 hours to trigger algorithmic distribution.
- **Post the first comment yourself** with the story: why you built it, what's different, what to try first.
- **Reply to every comment** in under 30 minutes. PH measures maker responsiveness.
- **Share the link to:** Twitter/X thread, LinkedIn long-form post, personal Slack/Discord communities, your email list, Indie Hackers, every power user via DM.
- **Never ask for upvotes.** Ask for **feedback**. "Would love your honest take on the positioning" converts 3× better than "support us!" and doesn't trigger the algorithm's anti-manipulation filters.
- **Don't message strangers.** The community flags this and moderators will hide your post.
### Post-launch
- Write a launch recap blog post with numbers + lessons. Honest, not bragging. Publish on day 2.
- Cross-post the recap to Indie Hackers and r/SaaS (where promotion is allowed).
- Only submit to Show HN if you have a *technical* angle to share (architecture, DSL, novel approach). A generic "we launched a SaaS" post will get flagged to death.
---
## Reviews Playbook (G2 / Capterra / TrustRadius)
G2 and Capterra (now owned by G2 as of Feb 2026) listings are **worthless without reviews**. 10 reviews is the magic threshold for Grid appearance. Run the 10-in-30 protocol during launch month.
### The 10-in-30 protocol
1. **Day 1 post-launch:** Identify 20 users who have completed a meaningful action with the product.
2. **Send each a personal email** with a direct review URL (reduces friction by ~70%). No forms, no landing pages — direct link.
3. **Offer a modest thank-you.** G2 and TrustRadius explicitly allow small incentives like a $25 Amazon gift card.
4. **Follow up once** after 5 days. Don't follow up twice — it becomes annoying and damages the relationship.
5. **Target:** 50% conversion → 10 reviews from 20 asks.
### Critical deadlines
- **G2 Summer reports:** cut off ~April 28. Plan review drives to land before this.
- **G2 Fall reports:** cut off ~July 28.
- Missing a cutoff means waiting 3 months for the next grid update.
### Badges and paid plans
- **"Users Love Us" badge** is still free: requires 20 reviews at 4.0+ average.
- **Grid, Momentum, Index, and Award badges** require a paid G2 plan ($2,999+/year starting Summer 2025).
- **Do not spend on paid G2 in year one.** The free listing + Users Love Us badge is sufficient.
### Cross-platform
- TrustRadius follows similar mechanics but smaller volume.
- Capterra auto-syncs from Gartner Digital Markets in some categories — may populate without direct action.
---
## Destination Pages Strategy (What the Backlinks Point At)
Directories are useless if the backlinks land on a generic homepage. Build these destination pages *before* submitting:
### 1. Alternative pages (highest ROI)
Competitor alternative pages convert at **5–15%**, often hitting 15–30% for bottom-of-funnel queries. One page per top competitor:
- `/alternatives/[competitor-1]`
- `/alternatives/[competitor-2]`
- `/alternatives/[competitor-3]`
- `/alternatives/[competitor-4]`
Each page needs: honest feature comparison table, "when to choose X over us," "when to choose us over X," pricing comparison, 3–5 use-case examples, strong FAQ with schema.
**Critical:** Be honest. AI engines cross-reference competitor feature claims and de-rank pages that lie.
### 2. Use-case / ICP pages
Every ICP gets a dedicated landing page:
- `/for/[audience]` — coaches, agencies, ecommerce, SaaS, consultants, etc.
- `/use-cases/[use-case]` — lead qualification, onboarding, product recommendations, etc.
### 3. Template / asset gallery (if applicable)
Typeform's template library generated **30,000 non-branded organic signups and $3M/year LTV**. The pattern:
- One indexable page per template at `/templates/[slug]`.
- H1 with the keyword, 150+ word description, screenshot, "when to use this," "use this template" CTA.
- Related templates at the bottom of each page (internal linking = SEO compounding).
- 100 templates by day 30, 300 by day 90 is the realistic target.
### 4. "Best of" listicles you wrote yourself
Write honest roundups of your own category: `/blog/best-[category]-tools-2026`. Include yourself + 10 competitors with real reviews. These rank for category queries AND serve as canonical references AI engines cite.
### 5. Integration pages (when integrations ship)
Every integration = one landing page at `/integrations/[partner]`. Follows the Zapier playbook: Zapier gets **~2.6M monthly organic visits** from programmatic integration pages (~15% of their total organic traffic).
---
## GEO (Generative Engine Optimization)
In 2026, 30–50% of "research a tool" queries happen inside ChatGPT, Claude, Perplexity, or Google AI Overviews without ever touching a traditional search page. Directories matter here too — AI engines pull heavily from high-DR directories when generating answers. But the *destination pages* also need to be GEO-optimized.
### Tactics that get pages cited
1. **One H1 per page, sequential heading hierarchy.** 2.8× higher citation rate. 87% of cited pages use a single H1.
2. **Dense, factual content with citable stats.** AI engines prefer specific numbers ("3× faster than X") over vague claims.
3. **FAQ schema on every landing page.** AI engines heavily weight `FAQPage` JSON-LD for answer extraction.
4. **Comparison tables.** Extractable, structured — exactly what an AI answer needs.
5. **Explicit "what it is" paragraph in the first 100 words.**
6. **Get cited on Reddit and Hacker News.** Claude and Perplexity index these heavily. Genuine mentions on r/SaaS and HN count as training fuel.
7. **Publish original research.** "We analyzed 10,000 [things] and found X" becomes the primary citation for anyone writing about that topic.
8. **Claim Crunchbase, LinkedIn company page, and Wikidata entries.** All three feed AI training corpora.
9. **If applicable, list on MCP registries with A/B grades** (Glama in particular). LLMs pull from these when answering MCP questions.
### Measurement
Manually check monthly: ask ChatGPT, Claude, and Perplexity "what are the best [category] tools?" and log where the product appears. Free GEO tracking tools (GeoTracker, llmrefs) automate this.
---
## Community & Ongoing Distribution
Directories are one-shot. Community is ongoing. Both feed the same funnel.
### Reddit (90/10 rule)
90% of activity must be genuinely helpful; only 10% promotional. Violating this gets shadowbanned.
**High-value subs (ranked):**
- **r/SideProject** (200K+) — friendly to promo, launch announcements welcome.
- **r/SaaS** (300K+) — "Share Your SaaS" threads are explicit promo windows.
- **r/startups** (1.7M) — Feedback Friday thread.
- **r/Entrepreneur** (3.5M) — weekly promo thread.
- **r/nocode**, **r/IndieHackers**, **r/alphaandbetausers** — friendly.
- **r/webdev**, **r/artificial**, **r/LocalLLaMA** — strict, technical only.
**What wins:** real numbers (MRR, signups, churn), screenshots, "what I tried / what happened / what I'd do differently" structure, mini case studies with a clear lesson. **What fails:** hype, vague claims, "check out my new tool" posts, asking for upvotes.
### LinkedIn (B2B primary channel)
80% of B2B social leads come from LinkedIn. Cadence: **3–5 posts/week** — fewer loses momentum, more causes fatigue.
Content types ranked by 2026 engagement:
1. Personal stories with business lessons (1.5–2× avg engagement)
2. Original data / research (1.3–1.5×)
3. Contrarian industry takes (1.2–1.5×)
4. Document carousels with 8–12 slides (1.3–1.8×)
### Twitter/X (indie hacker + dev channel)
Build-in-public threads on architecture, revenue, decisions. Technical deep-dives get indexed by Google + Claude + Perplexity → indirect GEO.
### Indie Hackers
- Launch a build-in-public thread on PH launch day.
- Post weekly updates: revenue, ships, lessons. Zero-revenue posts work if the lesson is honest.
- Comment 10× more than you post to build karma before your own links.
### Dev.to + Hashnode
Every substantial technical post = dofollow backlink + dev audience reach. Cross-post with canonical URL back to main blog.
---
## KPIs & Tracking
Track weekly. If a number isn't moving, investigate — don't just submit more directories.
| Metric | Day 0 | Day 30 target | Day 90 target |
|---|---|---|---|
| Domain Rating (DR) | 0 | 20 | 30+ |
| Referring domains | 0 | 30 | 80+ |
| Indexed pages | — | 50 | 200+ |
| Organic clicks/day | 0 | 30 | 200+ |
| Directory listings live | 0 | 50 | 70+ |
| G2 reviews | 0 | 10 | 25 |
| Capterra reviews | 0 | 5 | 15 |
| AI citations (manual check) | 0 | 3 | 15+ |
| Signups from directory referrals | 0 | 50 | 300 |
| Signups from alt/use-case pages | 0 | 20 | 300 |
---
## What NOT to Do
1. **Don't pay for directory submission services** ($60–$200 packages). The whole point is these are free. It's an afternoon of copy-paste.
2. **Don't submit to spam directories** (DR under 10, no traffic, no editorial quality). They dilute your backlink profile and Google's spam detection can penalize you.
3. **Don't submit with the wrong positioning.** Re-read the positioning table per tier. Generic descriptions waste the listing.
4. **Don't treat directories as your entire GTM.** They're the foundation. Content + community + reviews are what actually convert.
5. **Don't skip reviews on G2/Capterra.** Zero-review listings are dead. Run the 10-in-30 protocol or don't submit.
6. **Don't ask for upvotes on Product Hunt.** The 2026 algorithm penalizes it. Ask for **feedback**.
7. **Don't amend old directory listings every week.** Submit once, check quarterly.
8. **Don't submit before the destination page exists.** Link equity needs a destination.
9. **Don't duplicate descriptions across directories.** AI engines penalize duplicate content.
10. **Don't lie on comparison pages.** AI engines cross-reference and de-rank lies.
11. **Don't over-index on launch-day spike.** The flywheel is templates + alternatives + reviews + ongoing content — not one day of PH.
12. **Don't forget Crunchbase, LinkedIn company page, and Wikidata.** These feed AI training corpora and matter for GEO.
---
## Task-Specific Questions
1. **What are you launching?** (Category changes tier mix — AI vs traditional SaaS vs no-code vs dev tool.)
2. **When is launch day?** (Phase 0 assets need 7 days of prep.)
3. **Do you have destination pages built?** (Alternatives, use cases, templates — if not, build first.)
4. **Product Hunt hunter lined up?** (Optional but adds ~15% day-one lift. 3-week warm-up required regardless.)
5. **How many beta users can you ask for reviews?** (Need 20 to hit 10.)
6. **Do you have an MCP or agent angle?** (If yes, Tier 4 registries are a real moat.)
7. **Existing integrations?** (If yes, Tier 7 marketplaces are the highest-DR backlinks available.)
8. **Email list size?** (Needed for PH launch day warm traffic — 100+ is the minimum.)
9. **Current DR and referring domain count?** (Baseline for measuring the compounding effect.)
---
## Output Format
When the user asks for a directory plan, return:
1. **Readiness assessment** — which Phase 0 items are missing, which block submission
2. **Tier selection** — which tiers apply, which to skip, why
3. **Submission order** — week 1 / week 2 / week 3 batches
4. **Destination page list** — what to build first if missing
5. **Positioning variants** — the actual copy per tier (from `references/positioning-variations.md`)
6. **PH 3-week prep timeline** — mapped to calendar dates if launch day known
7. **Reviews 10-in-30 plan** — who to ask, when, how
8. **Weekly targets** — directories submitted, reviews, DR movement
9. **Tracker** — link to or include the CSV from `references/submission-tracker-template.csv`
Keep the plan actionable. Every item should be something the user can do today.
---
## Related Skills
- **launch** — broader launch moment, ORB framework, five-phase approach
- **programmatic-seo** — destination pages (alternatives, integrations, templates) that backlinks should flow into
- **competitors** — `/alternatives/[tool]` page pattern
- **ai-seo** — GEO optimization for AI citation
- **content-strategy** — editorial content that attracts "best of" listicle inclusions
- **free-tools** — lead magnets for destination pages
- **community-marketing** — Reddit, Indie Hackers, Slack community mechanics
- **schema** — FAQ + Product + Organization JSON-LD for GEO
FILE:evals/evals.json
{
"skill_name": "directory-submissions",
"evals": [
{
"id": 1,
"prompt": "We're launching our AI SaaS in 3 weeks. Help me plan all the directories we should submit to.",
"expected_output": "Should check for product-marketing.md first. Should run Phase 0 readiness assessment with the 9 questions before recommending submissions. Should reject submission if any of items 1-7 are 'no' (hard block) and explain why. Should recommend tier mix: Tier 1 flagship launch (~15 — Product Hunt as anchor, BetaList, HN Show HN, Fazier, DevHunt), Tier 2 startup/SaaS (~50), Tier 3 AI directories (~40 — TAAFT, Futurepedia, Toolify), Tier 4 MCP/agent if applicable. Should map the 3-week Product Hunt prep timeline to calendar dates. Should warn against submitting before destination pages exist. Should reference references/directory-list.md and references/positioning-variations.md. Should recommend the 10-in-30 reviews protocol for G2/Capterra. Should set day-30 and day-90 targets from the KPI table.",
"assertions": [
"Checks for product-marketing.md",
"Runs Phase 0 readiness assessment",
"Recommends tier mix appropriate to AI SaaS",
"Maps 3-week PH timeline to calendar dates",
"Names Product Hunt as anchor",
"Recommends 10-in-30 reviews protocol",
"Sets day-30 and day-90 KPI targets",
"References directory-list.md or positioning-variations.md"
],
"files": []
},
{
"id": 2,
"prompt": "Can I just copy-paste the same description into every directory?",
"expected_output": "Should refuse and explain Rule 3: positioning varies by directory type. Should explain AI engines penalize duplicate content — directories cross-referenced by Claude, ChatGPT, Perplexity will de-rank repetitive copy. Should explain different framing per surface: startup directories lead with outcome (audience is founders), SaaS directories lead with alternative framing (people search '[competitor] alternative'), AI directories lead with AI-first architecture, agent/MCP directories lead with the agent/MCP angle, B2B review sites lead with ROI + use case. Should recommend preparing distinct variants per tier: tagline under 10 words, 60-char short description, 150-word long description, 5-8 category tags. Should reference references/positioning-variations.md.",
"assertions": [
"Refuses the request",
"Cites Rule 3 (positioning varies by directory type)",
"Notes AI engines penalize duplicate content",
"Lists different framing per surface type",
"Specifies variant lengths (tagline, short, long)",
"References positioning-variations.md"
],
"files": []
},
{
"id": 3,
"prompt": "Should we pay for one of those directory submission services that submits to 200 directories for $99?",
"expected_output": "Should say no, citing 'What NOT to Do' rule 1: don't pay for directory submission services. Should explain the whole point is these are free — it's an afternoon of copy-paste. Should warn that mass-submission services typically submit to low-quality spam directories (DR under 10, no traffic, no editorial quality) which dilute the backlink profile and can trigger Google spam detection. Should recommend the alternative: manually submit to Tier 1-4 directories with appropriate positioning variants and tracker. Should reinforce that the value comes from quality directories with editorial standards, not raw volume.",
"assertions": [
"Refuses the service",
"Cites 'don't pay for submission services' rule",
"Warns about low-DR spam directories",
"Warns about Google spam penalty risk",
"Recommends manual submission to quality directories"
],
"files": []
},
{
"id": 4,
"prompt": "Walk me through how to launch on Product Hunt next month. We've never done it before.",
"expected_output": "Should apply the Product Hunt Deep Dive playbook. Should map the 3-week prep timeline: Day -21 to -14 (warm up hunter account, upvote and comment on 3 launches/day), Day -14 (create Upcoming page), Day -10 (optional book a hunter — trade not cash), Day -7 (draft launch-day assets: 1270x760 gallery images, tagline, 260-char description, first comment), Day -3 (email list warm-up), Day -1 (final check). Should explain launch day: launch at 12:01 AM Pacific Time on Tuesday/Wednesday/Thursday only, first 2 hours are everything (need 50+ supporters), post the first comment yourself, reply to every comment in under 30 minutes, share to multiple channels. Should warn never ask for upvotes — ask for feedback. Should warn don't DM strangers — community flags this. Should explain post-launch: write a launch recap blog post with numbers + lessons, cross-post to Indie Hackers, only submit to Show HN if there's a technical angle. Should note 80% of failed launches fail from no warm audience or asking for upvotes.",
"assertions": [
"Maps 3-week timeline with specific day markers",
"Notes 12:01 AM Pacific Time launch",
"Restricts to Tue/Wed/Thu",
"Emphasizes first 2 hours / 50+ supporters",
"Warns never ask for upvotes",
"Recommends asking for feedback",
"Warns against DMing strangers",
"Includes post-launch recap and cross-posting"
],
"files": []
},
{
"id": 5,
"prompt": "We want to list on G2 but we only have 4 customers right now. Worth doing?",
"expected_output": "Should explain G2 and Capterra listings are worthless without reviews — 10 reviews is the magic threshold for Grid appearance. Should recommend NOT submitting yet, or claim the listing but plan a review drive in parallel. Should explain the 10-in-30 protocol: identify 20 users who completed a meaningful action, send each a personal email with direct review URL (reduces friction ~70%), offer a modest thank-you ($25 Amazon gift card is allowed by G2/TrustRadius), follow up once after 5 days, target 50% conversion. Should note the Users Love Us badge is free (20 reviews at 4.0+) but Grid/Momentum/Index/Award badges require a paid G2 plan ($2,999+/year as of Summer 2025) — and recommend NOT spending on paid G2 in year one. Should mention G2 Summer report cutoff ~April 28 and Fall ~July 28. Should suggest waiting until ~10 users are realistic before submitting.",
"assertions": [
"Explains 10-review threshold for Grid",
"Recommends NOT submitting yet OR claim + plan review drive",
"Lays out 10-in-30 protocol",
"Notes incentive ($25 gift card) is allowed",
"Mentions Users Love Us badge requirements",
"Warns against paying for G2 plan in year one",
"Mentions Summer/Fall report cutoffs"
],
"files": []
},
{
"id": 6,
"prompt": "We submitted to 50 directories last week. Now what?",
"expected_output": "Should warn against treating submissions as the strategy. Should reference 'What NOT to Do' rule 11: don't over-index on launch spike. Flywheel is templates + alternatives + reviews + ongoing content. Should recommend verifying the dofollow status of acquired backlinks (curl -sIL | grep -i rel=). Should pivot to ongoing distribution: destination pages strategy (alternative pages converting 5-15%, use-case/ICP pages, template gallery if applicable, 'best of' listicles you write yourself, integration pages), GEO tactics for AI citation (single H1, FAQ schema, comparison tables, get cited on Reddit/HN, claim Crunchbase/LinkedIn/Wikidata), community presence (Reddit 90/10 rule, LinkedIn 3-5 posts/week, Twitter build-in-public, Indie Hackers, Dev.to/Hashnode). Should remind to track weekly KPIs (DR, referring domains, indexed pages, organic clicks, signups from directory referrals) and investigate if numbers aren't moving rather than submitting more.",
"assertions": [
"Warns against over-indexing on launch spike",
"Recommends verifying dofollow status of backlinks",
"Pivots to destination pages strategy",
"Mentions GEO tactics",
"Includes ongoing community distribution",
"Recommends tracking weekly KPIs",
"Says investigate before submitting more"
],
"files": []
}
]
}
FILE:references/directory-list.md
# Directory List — Full Reference
Canonical list of directories organized by tier. DR values are approximate and drift over time — verify via Ahrefs or Moz before building a plan around them.
**Column legend:**
- **DR** — Domain Rating (Ahrefs). Higher = more link equity passed.
- **Dofollow** — Whether the backlink passes SEO value. Nofollow listings still matter for referral traffic and brand signals.
- **Cost** — Free unless noted.
---
## Tier 1 — Flagship Launch Platforms
Submit only during launch week. These are time-sensitive with limited re-submission windows.
| Directory | DR | Dofollow | Cost | Notes |
|---|---|---|---|---|
| **Product Hunt** | 91 | Yes | Free | The anchor event. Requires 3-week warm-up. 2026 algorithm weights comment quality over upvotes. Launch Tue/Wed/Thu at 12:01 AM PT. |
| **Hacker News (Show HN)** | 91 | Nofollow | Free | Only if you have a genuine technical angle. Post title format: "Show HN: [Product] — [hook]". Moderator death penalty for hype. |
| **BetaList** | 64 | Yes | Free (paid expedite ~$99) | Best for pre-launch waitlist building. Submission → 2–4 week queue unless expedited. |
| **Launching Next** | ~30 | Yes | Free | Editorial curation — needs a compelling story. |
| **Fazier** | ~30 | Yes | Free | Daily ranking with much lower competition than PH. Achievable #1. |
| **Uneed** | ~40 | Yes | Free | Curated, smaller audience, quality backlink. |
| **Microlaunch** | ~30 | Yes | Free | Month-long visibility vs one-day spike. |
| **OpenHunts** | ~25 | Yes | Free | Indie-maker friendly, reports 14%+ conversion rates. |
| **DevHunt** | ~35 | Yes | Free | Dev-focused. Best fit for developer tools and technical products. |
| **PeerPush** | ~25 | Yes | Free | Similar to Fazier. Low competition. |
| **LaunchVault** | ~20 | Yes | Free | Anti-VC positioning. Good for bootstrapped narrative. |
| **What Launched Today** | ~20 | Yes | Free | Guaranteed visibility on launch day regardless of votes. |
| **Firsto** | ~25 | Yes | Free tier | Sustained discovery, not one-day spike. |
| **GetByte** | ~20 | Yes | Free | Lightweight listing + promotional support. |
| **Best of Web** | ~30 | Yes | Free | Easy fast submission, free dofollow. |
| **Tiny Launch** | ~20 | Yes | Free | Lightweight, fast approval. |
| **PitchWall** | ~25 | Yes | Free | Indie-hacker friendly. |
---
## Tier 2 — Startup / SaaS / Software Directories
Submit during launch week and continue rolling submissions thereafter.
| Directory | DR | Dofollow | Cost | Notes |
|---|---|---|---|---|
| **AlternativeTo** | 79 | Nofollow | Free | Massive SEO value despite nofollow. Submit as alternative to your top 4 competitors. |
| **SaaSHub** | 77 | Yes | Free | Ranks well for "[tool] alternatives" queries. High intent. |
| **G2** | 92 | Yes | Free listing | 10 reviews required for Grid appearance. Paid badges start at $2,999/yr. |
| **Capterra** | 93 | Yes | Free listing | Owned by G2 (acquired Feb 2026). Reviews drive everything. |
| **GetApp** | 78 | Yes | Free | Auto-syncs from Capterra in some cases. Owned by G2. |
| **SourceForge** | 92 | Yes | Free | Legacy but still high DR. Trivial to list. |
| **Slashdot** | ~88 | Yes | Free | Legacy but high DR. Company profile submission. |
| **Startup Stash** | ~50 | Yes | Free | Curated, organized by startup need. |
| **SideProjectors** | ~35 | Yes | Free | Discovery + marketplace. Community-driven. |
| **F6S** | 65 | Yes | Free | Startup platform used by accelerators. |
| **Stackshare** | ~60 | Yes | Free | Dev-centric. Show your tech stack. |
| **Resource.fyi** | ~40 | Yes | Free | Curated for designers/devs/marketers. |
| **Shipybara** | ~30 | Yes | Free | Shows which companies use your tool. |
| **TrustRadius** | 72 | Yes | Free | Smaller but respected B2B review platform. |
| **Crozdesk** | ~55 | Yes | Free | Feeds into Gartner ecosystem. |
| **Software Advice** | 88 | Yes | Free | Gartner property. Auto-syncs with Capterra in some categories. |
| **TheSaaSDirectory** | 88 | Yes | Free | SaaS-specific directory. Good categorization. |
| **Tech.co** | 80 | Yes | Free | Startup/SaaS directory + media. |
| **Taalk** | 80 | Yes | Free | Startup directory. |
| **Startup Fame** | 77 | Yes | Free | Startup showcase directory. |
| **Indie Hackers** | 76 | Yes | Free | Build-in-public community + product directory. |
| **Slant** | 75 | Yes | Free | "What is the best..." recommendation platform. |
| **Gust** | 75 | Yes | Free | Startup/investor platform. Profile with links. |
| **Inc42** | 75 | Yes | Free | Indian startup media + directory. |
| **Wefunder** | 76 | Yes | Free | Equity crowdfunding. Product profile with links. |
| **Startups.com** | 68 | Yes | Free | Startup community + resources. |
| **IndieHustles** | 66 | Yes | Free | Indie SaaS directory. |
| **SaaSWorthy** | 65 | Yes | Free | SaaS review/comparison site. |
| **ToolsFine** | 65 | Yes | Free | SaaS tool directory. |
| **Bizcommunity** | 65 | Yes | Free | Business news + directory. |
| **StartUs** | 62 | Yes | Free | Startup directory + insights. |
| **Today Launches** | 60 | Yes | Free | Daily launch directory. |
| **StartupBuffer** | 57 | Yes | Free | Startup promotion platform. |
| **Feedough** | 55 | Yes | Free | Startup resources + directory. |
| **Indie Hacker Tools** | 55 | Yes | Free | Tools for indie hackers. |
| **Open Launch** | 55 | Yes | Free | Product launch directory. |
| **New SaaSly** | 52 | Yes | Free | New SaaS product directory. |
| **Business Software** | 49 | Yes | Free | Business software directory. |
| **Promote Project** | 47 | Yes | Free | Project promotion directory. |
| **FiveTaco** | 47 | Yes | Free | SaaS tool directory. |
| **Cuspera** | 45 | Yes | Free | SaaS comparison platform. |
| **BetaBound** | 45 | Yes | Free | Beta testing community + directory. |
| **Makerthrive** | 45 | Yes | Free | Maker community + tools. |
| **StartupTracker** | 44 | Yes | Free | Startup tracking directory. |
| **BusinessHunt** | 43 | Yes | Free | Business product directory. |
| **Launched.io** | 40 | Yes | Free | Launch directory. |
| **ProfitHunt** | 40 | Yes | Free | Profitable startup directory. |
| **10words** | 40 | Yes | Free | SaaS directory (10-word descriptions). |
| **TrustMRR** | 40 | Yes | Free | MRR-verified startup directory. |
| **OpenClawDir** | 35 | Yes | Free | Open directory. |
| **Build Voyage** | 33 | Yes | Free | Startup builder directory. |
| **AlphaDigits** | 32 | Yes | Free | SaaS directory. |
---
## Tier 3 — AI Tool Directories
Relevant only for AI-native products. Submit during weeks 1–3.
### Tier 3A — Flagship AI directories
| Directory | DR | Monthly Traffic | Notes |
|---|---|---|---|
| **There's An AI For That (TAAFT)** | 76 | 2M+ | Largest AI directory. Task-based search. Worth the effort to list well. |
| **Futurepedia** | 70 | 1M+ | 5,000+ tools, 54 categories. Matt Wolfe YouTube (2M+ subs) drives traffic. |
| **Toolify.ai** | 71 | 500K+ | 26K+ tools, 450+ categories. Tracks traffic trends. |
| **Future Tools (futuretools.io)** | 69 | 400K+ | Curated by Matt Wolfe. Smaller but influential. |
| **AI Tools Neilpatel** | 91 | n/a | Highest DR free AI directory. |
| **Good AI Tools** | 66 | n/a | Curated, quality over quantity. |
| **NewTools.site** | 51 | n/a | Dofollow backlink for every approved submission. |
### Tier 3B — Mid-tier AI directories
| Directory | Est. DR | Notes |
|---|---|---|
| **aitools.inc** | ~66 | "10x your output" positioning. |
| **AIStage** | ~66 | Includes open source + news. |
| **AItrendytools** | ~69 | Comprehensive listing. |
| **Grabon AI Directory** | ~70 | High DR, broad audience. |
| **TopAI.tools** | ~60 | Task-based search similar to TAAFT. |
| **Supertools** | ~61 | Clean interface, good categorization. |
| **AI Tools Directory** (aitoolsdirectory.com) | ~55 | Curated; featured placement available. |
| **AI Tools Love** | ~25 | Comparison-focused. |
| **AIChief** | ~35 | Business-focused. |
| **LogicBalls** | ~40 | 3,500+ verified tools. |
| **SaasAITools** | ~30 | SaaS + AI crossover. |
| **PoweredByAI** | ~35 | Growing directory with newsletter reach. |
| **TheAISurf** | ~30 | Newer, actively promoting submissions. |
| **Aixyz** | ~30 | 1,500+ tools, smart filters. |
| **AI Pedia Hub** | ~40 | "Largest directory, updated daily." |
| **Dofollow.Tools** | ~30 | Explicitly free dofollow backlinks. |
| **AIBacklinkList** | ~25 | Aggregated list of 2500+ AI backlink opportunities. |
| **AI Scout** | ~25 | Emerging, less competition. |
| **AiMatchPro** | ~20 | Use-case search. |
| **GPTForge** | ~30 | Domain created 2025 — DR 88 from source list is implausible. Verify via Ahrefs. |
| **AI Tools Guide** | 77 | Curated AI tools directory. |
| **AIToolly** | 69 | AI tool discovery. |
| **All The AI Tools** | 66 | Comprehensive AI tool listing. |
| **Aiforme.wiki** | 66 | AI tool wiki/directory. |
| **Noxilo** | 66 | AI tools directory. |
| **AI Generation** | 55 | AI tools directory. |
| **Every AI** | 55 | AI tool aggregator. |
| **BAI.tools** | 53 | AI tools directory. |
| **The Rundown Tools** | 40 | AI newsletter's tool directory. |
| **AI NavHub** | 38 | AI navigation directory. |
| **WhatTheAI** | 35 | AI tools directory. |
| **ToolAI** | 31 | AI tools directory. |
| **LLM Relevance** | 30 | LLM-focused directory. |
---
## Tier 4 — AI Agent & MCP Server Registries
Relevant only if the product exposes agent capabilities or MCP servers. These are a real moat for AI-native tools — traditional SaaS products cannot list here.
| Directory | Category | Notes |
|---|---|---|
| **AI Agents List (aiagentslist.com)** | Agents | Hosts the 593+ MCP server directory. |
| **Glama.ai MCP servers** | MCP | 20K+ security-graded MCP servers. A/B/C/F grades matter — optimize for a good grade. |
| **APITracker MCP directory** | MCP | 110+ servers, 90 official integrations. |
| **Linux Foundation MCP Registry** | MCP | Canonical registry (PR-based submission, low volume but high signal). Anthropic donated MCP to LF in Dec 2025. |
| **AI Agent Store** | Agents | Compare agents, platforms, frameworks. |
| **AI Agents Base** | Agents | All-in-one directory. |
| **AI Agents Directory** | Agents | Specialized, updated daily. |
| **AI Agents Verse** | Agents | Curated directory. |
| **AgentHunter** | Agents | "Discover the best AI agents." |
| **Add AI Directory** | Agents | Catalogs agents + tools. |
| **AI Agents Live** | Agents | Discovery + sharing. |
| **AI Agents Marketplace** | Agents | Organized by 300+ human role equivalents. |
---
## Tier 5 — No-Code Directories
Relevant for no-code platforms and builder tools.
| Directory | Est. DR | Notes |
|---|---|---|
| **NoCodeFinder** | ~45 | Accepts submissions. |
| **No Code MBA Tools Directory** | ~55 | Categorized by project type. |
| **We Are No Code Tools Repository** | ~40 | Curated. |
| **NoCodeList** | ~30 | — |
| **NoCodeDevs** | ~25 | — |
| **NoCode.Tech** | ~35 | — |
| **MakerPad / Zapier** | ~62 | Now owned by Zapier. No-code tool directory. |
| **NoCodeFounders** | ~45 | No-code community + forum. |
---
## Tier 6 — "Best of" Listicles (Editorial Outreach)
Not directories per se — these are blog posts on high-DR domains that you get included in via cold outreach. Often more valuable than directories because they combine a dofollow backlink with editorial trust + in-market buyer traffic + AI citation weight.
**Search patterns to find opportunities:**
- `"best [category] tools" 2026`
- `"best [competitor] alternative"`
- `"top AI [category]"`
- `"[category] tools review"`
**Outreach template (short):**
> Hey [name], saw your post on [best X tools]. We launched [product] recently — thought it might be worth a mention. Happy to give you a free account + credits for readers. Here's a 60s demo: [link]. No worries if not a fit.
**Target:** 10 inclusions in 30 days. Each = dofollow backlink from DR 40–70 + referral traffic + AI citation fuel.
---
## Tier 7 — Integration Marketplaces
Only relevant once the product has integrations. These are the highest-DR backlinks available — worth engineering effort just to land them.
| Directory | DR | Notes |
|---|---|---|
| **Zapier App Directory** | 91 | Requires working Zapier integration. |
| **HubSpot App Marketplace** | 93 | Requires HubSpot app. |
| **Slack App Directory** | 89 | Requires Slack integration. |
| **Airtable Marketplace** | 82 | Requires Airtable integration. |
| **Notion Integrations Gallery** | 88 | Requires Notion integration. |
| **Make (Integromat)** | ~70 | Requires Make module. |
| **Pipedream** | ~70 | Requires Pipedream action. |
---
## Tier 8 — Profile & Content Platforms
Create a profile or publish content on these high-DR platforms to earn a dofollow backlink. These are not traditional directories — they're content and identity platforms where your profile or published content links back to your site. Highest DR backlinks available without building integrations.
| Platform | DR | Category | Type | Notes |
|---|---|---|---|---|
| **WordPress.com** | 100 | Any | Blog | Create a free blog, link to main site in posts and profile. |
| **Blogger** | 100 | Any | Blog | Google property. Free blog with dofollow links. |
| **Tumblr** | 99 | Design | Blog | Highest DR blog platform. Project blog or microblog. |
| **GitHub** | 98 | Tech | Code host | Profile + repo README links. Every software product should have this. |
| **SoundCloud** | 96 | Music | Profile | Niche — relevant for audio/music products. |
| **Weebly** | 95 | Any | Blog | Free site builder with dofollow profile link. |
| **SlideShare** | 95 | Any | Content | Upload pitch decks, guides, presentations. |
| **Flickr** | 95 | Photography | Profile | Product screenshot galleries with profile link. |
| **GitLab** | 94 | Tech | Code host | Profile link. Mirror repos if open source. |
| **eBay Stores** | 94 | E-commerce | Profile | Niche — relevant for physical/digital goods. |
| **Etsy** | 93 | E-commerce | Profile | Niche — templates, digital downloads. |
| **Substack** | 93 | Tech | Newsletter | Publish product updates, thought leadership. High-intent readers. |
| **Bitbucket** | 93 | Tech | Code host | Profile link. Atlassian property. |
| **Scribd** | 93 | Any | Content | Upload whitepapers, guides, case studies. |
| **Disqus** | 93 | Professional | Profile | Profile with website link. Comment on industry blogs. |
| **Behance** | 93 | Design | Profile | Portfolio/project links. Best for design-adjacent products. |
| **Pastebin** | 93 | Tech | Code host | Code snippets with profile link. |
| **Patreon** | 93 | Creator | Profile | Creator page with product links. |
| **Imgur** | 93 | Any | Profile | Image hosting with profile link. |
| **Dun & Bradstreet** | 93 | B2B | Directory | Business credibility. Feeds AI training corpora. |
| **Ghost.org** | 92 | Any | Blog | Publish content with dofollow links. |
| **Evernote** | 92 | Any | Content | Public notebooks with links. |
| **Issuu** | 92 | Any | Content | Upload marketing PDFs, brochures, reports. |
| **CodePen** | 92 | Tech | Profile | Front-end demos and profile link. |
| **Kaggle** | 92 | AI | Profile | AI/data science community. Notebooks with links. |
| **Houzz** | 92 | Home | Profile | Niche — home/interior products. |
| **LiveJournal** | 91 | Any | Blog | Legacy but high DR. Blog with dofollow links. |
| **Bandcamp** | 91 | Music | Profile | Niche — audio products. |
| **Dev.to** | 90 | Tech | Blog | Technical articles with dofollow links. Cross-post with canonical URL. |
| **Gravatar** | 90 | Professional | Profile | Profile with website link. Quick setup. |
| **Replit** | 90 | Tech | Code host | Profile link. Interactive demos. |
| **CodeProject** | 90 | Tech | Blog | Technical articles for dev audience. |
| **Jimdo** | 89 | Any | Blog | Free site builder with profile link. |
| **Calameo** | 89 | Any | Content | Digital publishing platform. Upload PDFs. |
| **Buy Me a Coffee** | 88 | Creator | Profile | Creator page with product links. |
| **ArtStation** | 88 | Design | Profile | Portfolio for creative/design products. |
| **500px** | 88 | Photography | Profile | Product imagery with profile link. |
| **IndiaMART** | 87 | B2B | Profile | Indian B2B marketplace. Niche but high DR. |
| **Strikingly** | 87 | Any | Blog | Free one-page site with backlink. |
| **Hashnode** | 85 | Tech | Blog | Dev blogging. Custom domain support. Dofollow links. |
| **About.me** | 85 | Professional | Profile | One-page profile. Quick dofollow backlink. |
| **Mixcloud** | 85 | Music | Profile | Niche — audio/podcast products. |
| **4Shared** | 85 | Any | Content | File sharing with profile link. |
| **HubPages** | 84 | Any | Blog | Article publishing platform. |
| **AppSumo** | 84 | E-commerce | Marketplace | SaaS deals marketplace. Great for launch visibility + backlink. |
| **TeachersPayTeachers** | 84 | Education | Profile | Niche — education products. |
| **AuthorStream** | 70 | Any | Content | Presentation sharing. |
| **Model Mayhem** | 72 | Design | Profile | Niche — creative industry. |
| **Penzu** | 60 | Any | Blog | Online journal with profile link. |
| **Crevado** | 50 | Design | Profile | Portfolio platform. |
| **MyFolio** | 55 | Design | Profile | Portfolio platform. |
---
## Tier 9 — Local Business & General Directories
Relevant for products with a physical presence, local customer base, or business address. Also useful for any product wanting pure DR-building backlinks from established directories.
| Directory | DR | Category | Notes |
|---|---|---|---|
| **Manta** | 76 | Local business | US business directory. Free listing. |
| **ActiveSearchResults** | 74 | General | Search engine directory. |
| **Hotfrog** | 72 | Local business | International business directory. |
| **Spoke** | 70 | B2B | Business profile directory. |
| **Locanto** | 70 | General | Classifieds + business listings. International. |
| **MerchantCircle** | 68 | Local business | US small business directory. |
| **Just Landed** | 65 | Local business | International directory. |
| **Showmelocal** | 64 | Local business | US local search directory. |
| **Cylex** | 64 | Local business | International business directory. |
| **Brownbook** | 63 | Local business | Global business directory. |
| **Tupalo** | 62 | Local business | European business directory. |
| **WebWiki** | 60 | General | Website directory with reviews. |
| **iBegin** | 60 | Local business | US business directory. |
| **CitySquares** | 55 | Local business | US local business directory. |
| **eLocal** | 55 | Local business | US service provider directory. |
| **2FindLocal** | 53 | Local business | US local directory. |
| **Chamber of Commerce** | 50 | Local business | Business directory + resources. |
| **FindUsLocal** | 50 | Local business | Local search directory. |
| **ezlocal** | 50 | Local business | US local business listings. |
| **Yellow Pages Goes Green** | 49 | Local business | Eco-friendly business directory. |
| **Where To?** | 46 | Local business | Local discovery directory. |
---
## Tier 10 — Forums & Communities
Create a profile and participate in relevant communities. Most give dofollow profile links. Value comes from both the backlink and referral traffic from genuine participation. Follow the 90/10 rule: 90% helpful, 10% promotional.
| Forum | DR | Category | Notes |
|---|---|---|---|
| **Strava Clubs** | 90 | Fitness | Niche — fitness/health products only. |
| **Foursquare** | 90 | Hospitality | Business listing with dofollow link. |
| **SitePoint Forums** | 89 | Tech | Web dev community. Genuine participation required. |
| **Mumsnet Forums** | 85 | Family | Niche — family/parenting products. Large UK audience. |
| **Digital Point** | 82 | Marketing | SEO/marketing forum. |
| **WebmasterWorld** | 77 | Marketing | SEO/webmaster community. High editorial standards. |
| **BlackHatWorld** | 77 | Marketing | SEO/marketing forum. Despite the name, has legitimate discussions. |
| **GrowthHackers** | 76 | Marketing | Growth marketing community. Dofollow articles + profile. |
| **Warrior Forum** | 73 | Marketing | Internet marketing community. |
| **Apsense** | 72 | Marketing | Business networking + marketing forum. |
| **ActiveRain** | 70 | Real estate | Niche — real estate industry. |
| **Quibblo** | 55 | General | Quiz/poll community with profile links. |
---
## Tier 11 — Press Release, Article & Blog Directory Sites
Publish articles or press releases to earn dofollow backlinks. Best for product launches, funding announcements, major feature releases. Some accept any topic, others are PR-specific.
### Article & Blog Directories
| Site | DR | Type | Notes |
|---|---|---|---|
| **EzineArticles** | 80 | Article | Established article directory. Editorial review. |
| **Feedspot** | 80 | Blog directory | Blog discovery + RSS aggregation. Submit your blog. |
| **Alltop** | 73 | Blog directory | Guy Kawasaki's blog aggregator. |
| **ArticlesBase** | 70 | Article | Article publishing platform. |
| **Blogarama** | 64 | Blog directory | Blog directory with categories. |
| **Sooper Articles** | 60 | Article | Article submission site. |
| **OnToplist** | 60 | Blog directory | Blog ranking directory. |
| **BlogEngage** | 55 | Blog directory | Blog promotion community. |
| **BizSugar** | 55 | Business | Small business content sharing. |
| **TechPluto** | 50 | Marketing | Tech/marketing blog directory. |
### Press Release Distribution
| Site | DR | Notes |
|---|---|---|
| **PRLog** | 80 | Free press release distribution. Good reach. |
| **PR.com** | 77 | Free + paid press releases. Business directory too. |
| **OpenPR** | 72 | Free international press release distribution. |
| **1888 Press Release** | 69 | Free press release site. |
| **NewswireToday** | 65 | Free press release distribution. |
| **Online PR News** | 62 | Free press release distribution. |
| **PR Free** | 62 | Free press release site. |
### Marketing & General Directories
| Site | DR | Notes |
|---|---|---|
| **SubmissionWebDirectory** | 61 | General web directory. |
| **Site Promotion Directory** | 46 | Marketing-focused directory. |
| **Semfirms** | 45 | Marketing services directory. |
| **CabinetM** | 45 | Marketing technology directory. |
| **Cold Email Kit** | 44 | Email marketing directory. |
| **Directory LDM Studio** | 40 | General directory. |
| **Quality Internet Directory** | 39 | General web directory. |
| **ProofStories** | 32 | Marketing stories/case studies. |
---
## Tier 12 — Social Bookmarking & Curation
Bookmark or curate content with dofollow links. Lower effort than publishing full articles. Most useful for building diverse backlink profile.
| Platform | DR | Notes |
|---|---|---|
| **Scoop.it** | 91 | Content curation platform. Create topic pages with links. |
| **Diigo** | 85 | Social bookmarking + annotation. Profile + bookmark links. |
| **Pearltrees** | 84 | Visual content curation. Organize links into collections. |
| **BibSonomy** | 70 | Academic bookmarking. Best for research/data products. |
| **Folkd** | 64 | Social bookmarking. Tag and share links. |
---
## Tier 13 — Niche Vertical Directories
Industry-specific directories. Only submit if your product genuinely fits the vertical — forced listings get rejected and waste time.
### Legal
| Directory | DR | Notes |
|---|---|---|
| **Justia** | 85 | Legal services directory. |
| **Lawyers.com** | 82 | Legal directory. |
| **HG.org** | 75 | Legal resources directory. |
### Home & Construction
| Directory | DR | Notes |
|---|---|---|
| **Porch** | 80 | Home services marketplace. |
| **BuildZoom** | 73 | Construction/contractor directory. |
| **Tradify (FreeIndex)** | 55 | UK trades directory. |
| **iBuildNew** | 45 | Australian home building directory. |
### Hospitality & Food
| Directory | DR | Notes |
|---|---|---|
| **AllMenus** | 76 | Restaurant directory. |
### Design & Creative
| Directory | DR | Notes |
|---|---|---|
| **LandBook** | 72 | Web design inspiration gallery. Submit landing pages. |
| **Curated.design** | 52 | Design inspiration directory. |
| **Webdesign Inspiration** | 45 | Website design showcase. |
### Health & Fitness
| Directory | DR | Notes |
|---|---|---|
| **Wellness.com** | 60 | Health & wellness directory. |
| **YogaTrail** | 55 | Yoga/wellness directory. |
| **MassageTherapy (AMBP)** | 45 | Massage therapy directory. |
| **Athlinks** | 72 | Fitness/race results. Profile with links. |
| **Fit Pro Directory** | 40 | Fitness professional directory. |
### Real Estate
| Directory | DR | Notes |
|---|---|---|
| **Placester** | 60 | Real estate marketing directory. |
### B2B & International
| Directory | DR | Notes |
|---|---|---|
| **Sulekha** | 73 | Indian business directory. |
| **EU-Business** | 46 | European business directory. |
### Events
| Directory | DR | Notes |
|---|---|---|
| **Evensi Events** | 62 | Event discovery platform. |
### Education
| Directory | DR | Notes |
|---|---|---|
| *(TeachersPayTeachers listed in Tier 8 — Profile Platforms)* | | |
---
## Verification
After any submission goes live, verify the backlink exists and is dofollow. You can:
1. **Manual:** Open the listing, right-click your product link, "Inspect" → check for `rel="nofollow"` or `rel="ugc"`. If absent, the link is dofollow.
2. **curl:** `curl -sIL https://directory.com/your-listing | grep -i link`
3. **SEO tools:** Ahrefs Site Explorer → Backlinks → filter by this directory's domain.
**Re-verify quarterly.** Directories sometimes change all outbound links to nofollow without warning — if DR stops moving, check whether your biggest inbound links have silently flipped.
FILE:references/positioning-variations.md
# Positioning Variations Library
Directory audiences respond to different framings. Never copy-paste the same description everywhere — AI engines penalize duplicate content, and each directory type rewards a different opener.
Use this library to generate per-tier variants. Swap `[product]`, `[category]`, `[competitors]`, `[use-case]`, and `[audience]` with the real values.
---
## Framework: Lead Sentence Varies by Tier
| Tier | Lead sentence pattern | Why |
|---|---|---|
| Startup / launch | "[Product] is the easiest way to [outcome] for [audience]." | Founders scan for outcome clarity. |
| SaaS directory | "[Product] is the [differentiator] alternative to [competitors]." | Catches "[competitor] alternative" search intent. |
| AI directory | "[Product] uses [AI capability] to [outcome]." | TAAFT/Futurepedia audiences explicitly want AI. |
| Agent / MCP | "[Product] is an MCP-native / agent-native [category]." | Niche but high-intent. Ruling-out competitors. |
| No-code | "[Product] lets you build [output] without code." | Audience values speed, not technical depth. |
| Dev tool | "[Product] is a [technical category] with [differentiator]." | Devs want substance upfront. |
| B2B review | "[Product] helps [audience] [measurable business outcome]." | Reviewers want ROI language. |
---
## Template: Startup / Launch Directories
**Target:** Product Hunt, BetaList, Fazier, Uneed, DevHunt, Microlaunch, OpenHunts, LaunchVault, Firsto, PitchWall
**Tagline (under 10 words):**
> The [differentiator] way to [outcome] for [audience].
**Short description (60 chars):**
> [Outcome-focused one-liner with product name]
**Long description (150 words):**
> [Product] is the easiest way to [outcome] for [audience]. Built for teams who [pain point], [product] removes [friction] by [how].
>
> Unlike [competitor category], [product] [key differentiator 1] and [key differentiator 2]. You can [action 1] in under [timeframe], [action 2] without [limitation], and [action 3] that would normally require [cost or technical skill].
>
> We built [product] because [founder origin story in one sentence]. It's now used by [audience examples] to [use case examples].
>
> Try it free at [url]. No credit card, no setup.
**Tags:** [product category], [audience type], [use case 1], [use case 2], [differentiator], [tech]
---
## Template: SaaS / Software Directories
**Target:** AlternativeTo, SaaSHub, G2, Capterra, GetApp, SourceForge, Slashdot, Startup Stash, F6S
**Tagline:**
> The [differentiator] alternative to [top competitors].
**Long description:**
> [Product] is a [differentiator] alternative to [competitor 1], [competitor 2], and [competitor 3] — built for [audience] who need [gap the competitors don't fill].
>
> Where [competitor 1] [limitation 1] and [competitor 2] [limitation 2], [product] [solves]. You get [feature 1], [feature 2], and [feature 3] in a single workspace, at [pricing relative to competitors].
>
> Key features:
> • [Feature 1] — [benefit]
> • [Feature 2] — [benefit]
> • [Feature 3] — [benefit]
> • [Feature 4] — [benefit]
> • [Integration 1], [Integration 2], [Integration 3] integrations
>
> Trusted by [audience examples]. Start free at [url].
**Tags:** [competitor] alternative, [category], [audience], [differentiator], [top 3 features]
---
## Template: AI Directories
**Target:** TAAFT, Futurepedia, Toolify, Future Tools, aitools.inc, AIStage, LogicBalls, SaasAITools
**Tagline:**
> AI-powered [category] for [audience].
**Long description:**
> [Product] is an AI-powered [category] that [core AI capability]. It uses [specific models / techniques] to [outcome] — so [audience] can [job to be done] in a fraction of the time.
>
> What makes it AI-first:
> • [AI feature 1] — [what it does] using [model/approach]
> • [AI feature 2] — [what it does]
> • [AI feature 3] — [what it does]
> • [AI feature 4] — [what it does]
>
> [Product] is built on [tech stack] and supports [models/providers]. Use cases: [use case 1], [use case 2], [use case 3], [use case 4].
>
> Free tier available. No API keys required to start.
**Tags:** AI [category], [AI capability 1], [AI capability 2], AI for [audience], [use case 1], [use case 2], [LLM provider], [differentiator]
---
## Template: Agent / MCP Registries
**Target:** Glama, APITracker, Linux Foundation MCP Registry, AI Agents List, AI Agent Store, AgentHunter
**Tagline:**
> MCP-native [category] for AI agents.
**Long description:**
> [Product] is an MCP-native [category] that lets AI agents [capability]. It exposes [MCP server capabilities] via the Model Context Protocol, so agents in Claude, ChatGPT, Cursor, and any MCP-compatible client can [actions].
>
> MCP capabilities:
> • [Tool 1] — [what the agent can do]
> • [Tool 2] — [what the agent can do]
> • [Tool 3] — [what the agent can do]
> • [Resource 1] — [context surfaced]
> • [Prompt 1] — [pre-built prompt]
>
> Authentication: [auth method]. Transports: stdio, HTTP, SSE. Security: [security posture].
>
> Installation: [one-line install command]. Docs: [docs URL].
**Tags:** MCP, MCP server, AI agent, agent [category], Claude integration, Model Context Protocol, [domain], [auth type]
---
## Template: No-Code Directories
**Target:** NoCodeFinder, No Code MBA Tools Directory, We Are No Code, NoCode.Tech
**Tagline:**
> Build [output] without code.
**Long description:**
> [Product] lets you build [output] without writing code. Drag, drop, or describe what you want and [product] handles the rest — [technical concept 1] and [technical concept 2] are automatic.
>
> What you can build:
> • [Example project 1] — built in [timeframe]
> • [Example project 2] — built in [timeframe]
> • [Example project 3] — built in [timeframe]
>
> No-code friendly features:
> • [Visual feature 1]
> • [Visual feature 2]
> • [AI-assisted feature]
> • [Pre-built templates]
>
> Start free. No credit card. Templates included.
**Tags:** no code, no-code [category], visual [tool], drag and drop, [output type], [audience type]
---
## Template: Dev / Technical Directories
**Target:** DevHunt, Stackshare, GitHub, Dev.to, Hacker News Show HN
**Tagline:**
> [Technical category] with [technical differentiator].
**Long description:**
> [Product] is a [technical category] built on [tech stack]. It solves [technical problem] by [technical approach].
>
> Architecture:
> • [Component 1] — [tech used]
> • [Component 2] — [tech used]
> • [Component 3] — [tech used]
>
> Why it's different: [technical insight or novel approach]. We chose [trade-off] because [reason].
>
> Open source: [yes/no/partial]. Self-hostable: [yes/no]. License: [license].
>
> API: [REST / GraphQL / MCP / gRPC]. SDKs: [languages]. Docs: [url].
**Tags:** [language], [framework], [category], open source, API, [tech stack component], [architecture approach]
---
## Template: B2B Review Platforms
**Target:** G2, Capterra, TrustRadius, GetApp, Gartner Digital Markets, Crozdesk
**Tagline:**
> [Business outcome] for [audience].
**Long description:**
> [Product] helps [audience] [achieve measurable business outcome]. Teams use it to [use case 1], [use case 2], and [use case 3] — reducing [metric] by [percentage] and increasing [metric] by [percentage].
>
> Key benefits:
> • [Business benefit 1] with [how measured]
> • [Business benefit 2] with [how measured]
> • [Business benefit 3] with [how measured]
>
> Integrations: [enterprise integrations — HubSpot, Salesforce, Slack, etc.]
>
> Security: [SOC 2 / GDPR / compliance posture]. Support: [support tier]. Pricing: [pricing range].
>
> Trusted by [customer logos / company size]. Case studies at [url].
**Tags:** [business use case], [vertical], [audience role], [compliance], enterprise [category], [integration 1]
---
## Category Tag Library
Pull 5–8 tags per submission from the relevant sections. Never repeat the exact same tag set across two directories in the same tier.
### Universal
[category], [audience], [differentiator], [use case], AI, no-code, SaaS, [tech stack]
### Industry
B2B, B2C, DTC, ecommerce, fintech, edtech, healthtech, martech, devtools, productivity, creator tools, agency tools
### Job-to-be-done
lead generation, lead qualification, customer onboarding, product recommendation, sales enablement, marketing automation, survey, assessment, calculator, quiz, intake form
### AI-specific
AI agent, LLM, generative AI, conversational AI, RAG, MCP, agent framework, AI form, AI quiz, AI assistant, AI automation
### Technical
open source, self-hosted, API-first, webhook, Zapier, no-code, low-code, embeddable, white-label, multi-tenant, SSO, SAML
---
## Do / Don't Quick Reference
**DO:**
- Vary the opening sentence across tiers
- Use real numbers and specific differentiators
- Match tone to audience (technical for devs, business for G2, excited for PH)
- Include a founder/origin angle in startup directories
- Lead with the AI-first angle in AI directories
**DON'T:**
- Copy-paste the same 150-word description everywhere
- Use vague claims ("blazing fast", "game-changing")
- Mention every feature — pick 3–5 per tier and rotate them
- Lie about competitor features (AI engines cross-reference and de-rank)
- Skip the tag list — it's how moderators route you to the right category
FILE:references/submission-tracker-template.csv
Directory,Tier,URL,Category,DR,Dofollow,Submission Date,Status,Live URL,Backlink Verified,Positioning Variant Used,Tags Used,Account Email,Notes
Product Hunt,1,https://producthunt.com/posts/new,Launch,91,Yes,,Draft,,,Startup,,,
Hacker News (Show HN),1,https://news.ycombinator.com/submit,Launch,91,No,,Draft,,,Dev,,,
BetaList,1,https://betalist.com/submit,Launch,64,Yes,,Draft,,,Startup,,,
Fazier,1,https://fazier.com/submit,Launch,30,Yes,,Draft,,,Startup,,,
DevHunt,1,https://devhunt.org/submit,Launch,35,Yes,,Draft,,,Dev,,,
Uneed,1,https://uneed.best/submit-a-tool,Launch,40,Yes,,Draft,,,Startup,,,
Microlaunch,1,https://microlaunch.net/submit,Launch,30,Yes,,Draft,,,Startup,,,
OpenHunts,1,https://openhunts.com/submit,Launch,25,Yes,,Draft,,,Startup,,,
LaunchVault,1,https://launchvault.com/submit,Launch,20,Yes,,Draft,,,Startup,,,
What Launched Today,1,https://whatlaunchedtoday.com,Launch,20,Yes,,Draft,,,Startup,,,
Launching Next,1,https://launchingnext.com/submit,Launch,30,Yes,,Draft,,,Startup,,,
PeerPush,1,https://peerpush.net/submit,Launch,25,Yes,,Draft,,,Startup,,,
Firsto,1,https://firsto.co/submit,Launch,25,Yes,,Draft,,,Startup,,,
GetByte,1,https://getbyte.co/submit,Launch,20,Yes,,Draft,,,Startup,,,
Best of Web,1,https://bestofweb.io/submit,Launch,30,Yes,,Draft,,,Startup,,,
Tiny Launch,1,https://tinylaunch.com/submit,Launch,20,Yes,,Draft,,,Startup,,,
PitchWall,1,https://pitchwall.co/submit,Launch,25,Yes,,Draft,,,Startup,,,
AlternativeTo,2,https://alternativeto.net/software/_/add/,SaaS,79,No,,Draft,,,SaaS,,,
SaaSHub,2,https://saashub.com/submit,SaaS,77,Yes,,Draft,,,SaaS,,,
G2,2,https://my.g2.com/sellers/welcome,SaaS,92,Yes,,Draft,,,B2B review,,,
Capterra,2,https://www.capterra.com/vendors,SaaS,93,Yes,,Draft,,,B2B review,,,
GetApp,2,https://www.getapp.com/vendors,SaaS,78,Yes,,Draft,,,B2B review,,,
SourceForge,2,https://sourceforge.net/user/register,SaaS,92,Yes,,Draft,,,SaaS,,,
Slashdot,2,https://slashdot.org/submission,SaaS,88,Yes,,Draft,,,SaaS,,,
Startup Stash,2,https://startupstash.com/submit,SaaS,50,Yes,,Draft,,,Startup,,,
SideProjectors,2,https://www.sideprojectors.com/project/new,SaaS,35,Yes,,Draft,,,Startup,,,
F6S,2,https://www.f6s.com/company/create,SaaS,65,Yes,,Draft,,,Startup,,,
Stackshare,2,https://stackshare.io/new-product,SaaS,60,Yes,,Draft,,,Dev,,,
TrustRadius,2,https://www.trustradius.com/vendors,SaaS,72,Yes,,Draft,,,B2B review,,,
Crozdesk,2,https://crozdesk.com/vendors,SaaS,55,Yes,,Draft,,,SaaS,,,
There's An AI For That,3,https://theresanaiforthat.com/submit,AI,76,Yes,,Draft,,,AI,,,
Futurepedia,3,https://www.futurepedia.io/submit-tool,AI,70,Yes,,Draft,,,AI,,,
Toolify.ai,3,https://www.toolify.ai/submit,AI,71,Yes,,Draft,,,AI,,,
Future Tools,3,https://www.futuretools.io/submit-a-tool,AI,69,Yes,,Draft,,,AI,,,
AI Tools Neilpatel,3,https://neilpatel.com/ai-tools,AI,91,Yes,,Draft,,,AI,,,
Good AI Tools,3,https://goodaitools.com/submit,AI,66,Yes,,Draft,,,AI,,,
NewTools.site,3,https://newtools.site/submit,AI,51,Yes,,Draft,,,AI,,,
aitools.inc,3,https://aitools.inc/submit,AI,66,Yes,,Draft,,,AI,,,
AIStage,3,https://aistage.net/submit,AI,66,Yes,,Draft,,,AI,,,
AItrendytools,3,https://www.aitrendytools.com/submit,AI,69,Yes,,Draft,,,AI,,,
Grabon AI Directory,3,https://www.grabon.in/indulge/ai-tools/submit,AI,70,Yes,,Draft,,,AI,,,
TopAI.tools,3,https://topai.tools/submit,AI,60,Yes,,Draft,,,AI,,,
Supertools,3,https://supertools.therundown.ai/submit,AI,61,Yes,,Draft,,,AI,,,
AI Tools Directory,3,https://aitoolsdirectory.com/submit,AI,55,Yes,,Draft,,,AI,,,
LogicBalls,3,https://logicballs.com/submit,AI,40,Yes,,Draft,,,AI,,,
SaasAITools,3,https://saasaitools.com/submit,AI,30,Yes,,Draft,,,AI,,,
PoweredByAI,3,https://poweredbyai.app/submit,AI,35,Yes,,Draft,,,AI,,,
TheAISurf,3,https://theaisurf.com/submit,AI,30,Yes,,Draft,,,AI,,,
Aixyz,3,https://ai.xyz/submit,AI,30,Yes,,Draft,,,AI,,,
AI Pedia Hub,3,https://aipediahub.com/submit,AI,40,Yes,,Draft,,,AI,,,
Dofollow.Tools,3,https://dofollow.tools/submit,AI,30,Yes,,Draft,,,AI,,,
AI Scout,3,https://aiscout.net/submit,AI,25,Yes,,Draft,,,AI,,,
AiMatchPro,3,https://aimatchpro.ai/submit,AI,20,Yes,,Draft,,,AI,,,
AIChief,3,https://aichief.com/submit,AI,35,Yes,,Draft,,,AI,,,
AI Tools Love,3,https://aitools.love/submit,AI,25,Yes,,Draft,,,AI,,,
AI Agents List,4,https://aiagentslist.com/submit,Agent,,Yes,,Draft,,,Agent,,,
Glama.ai MCP,4,https://glama.ai/mcp/servers,MCP,,Yes,,Draft,,,MCP,,,
APITracker MCP,4,https://apitracker.io/mcp-servers,MCP,,Yes,,Draft,,,MCP,,,
Linux Foundation MCP Registry,4,https://github.com/modelcontextprotocol/registry,MCP,,Yes,,Draft,,,MCP,,,
AI Agent Store,4,https://aiagentstore.ai/submit,Agent,,Yes,,Draft,,,Agent,,,
AI Agents Base,4,https://aiagentsbase.com/submit,Agent,,Yes,,Draft,,,Agent,,,
AI Agents Directory,4,https://aiagentsdirectory.com/submit,Agent,,Yes,,Draft,,,Agent,,,
AgentHunter,4,https://agenthunter.com/submit,Agent,,Yes,,Draft,,,Agent,,,
AI Agents Live,4,https://aiagents.live/submit,Agent,,Yes,,Draft,,,Agent,,,
AI Agents Marketplace,4,https://aiagentsmarketplace.com/submit,Agent,,Yes,,Draft,,,Agent,,,
NoCodeFinder,5,https://www.nocodefinder.com/submit,No-Code,45,Yes,,Draft,,,No-code,,,
No Code MBA,5,https://www.nocode.mba/tools/submit,No-Code,55,Yes,,Draft,,,No-code,,,
We Are No Code,5,https://www.wearenocode.com/submit,No-Code,40,Yes,,Draft,,,No-code,,,
NoCodeList,5,https://nocodelist.co/submit,No-Code,30,Yes,,Draft,,,No-code,,,
NoCodeDevs,5,https://www.nocodedevs.com/submit,No-Code,25,Yes,,Draft,,,No-code,,,
NoCode.Tech,5,https://www.nocode.tech/submit,No-Code,35,Yes,,Draft,,,No-code,,,
Zapier App Directory,7,https://zapier.com/developer,Integration,91,Yes,,Draft,,,Integration,,,
HubSpot App Marketplace,7,https://ecosystem.hubspot.com/marketplace,Integration,93,Yes,,Draft,,,Integration,,,
Slack App Directory,7,https://api.slack.com/apps,Integration,89,Yes,,Draft,,,Integration,,,
Airtable Marketplace,7,https://airtable.com/marketplace,Integration,82,Yes,,Draft,,,Integration,,,
Notion Integrations,7,https://www.notion.so/integrations,Integration,88,Yes,,Draft,,,Integration,,,
Make (Integromat),7,https://www.make.com/en/partners,Integration,70,Yes,,Draft,,,Integration,,,
Pipedream,7,https://pipedream.com/docs/components,Integration,70,Yes,,Draft,,,Integration,,,
Software Advice,2,https://www.softwareadvice.com/vendors,SaaS,88,Yes,,Draft,,,B2B review,,,
TheSaaSDirectory,2,https://thesaasdirectory.com,SaaS,88,Yes,,Draft,,,SaaS,,,
Tech.co,2,https://tech.co,SaaS,80,Yes,,Draft,,,SaaS,,,
Taalk,2,https://taalk.com,Startup,80,Yes,,Draft,,,Startup,,,
Startup Fame,2,https://startupfa.me,Startup,77,Yes,,Draft,,,Startup,,,
Indie Hackers,2,https://www.indiehackers.com,SaaS,76,Yes,,Draft,,,Startup,,,
Slant,2,https://www.slant.co,SaaS,75,Yes,,Draft,,,SaaS,,,
Gust,2,https://gust.com,Startup,75,Yes,,Draft,,,Startup,,,
Inc42,2,https://inc42.com,Startup,75,Yes,,Draft,,,Startup,,,
Wefunder,2,https://wefunder.com,Startup,76,Yes,,Draft,,,Startup,,,
Startups.com,2,https://www.startups.com,Startup,68,Yes,,Draft,,,Startup,,,
IndieHustles,2,https://www.indiehustles.com,SaaS,66,Yes,,Draft,,,SaaS,,,
SaaSWorthy,2,https://www.saasworthy.com,SaaS,65,Yes,,Draft,,,SaaS,,,
ToolsFine,2,https://toolsfine.com,SaaS,65,Yes,,Draft,,,SaaS,,,
Bizcommunity,2,https://www.bizcommunity.com,B2B,65,Yes,,Draft,,,B2B,,,
StartUs,2,https://startus.cc,Startup,62,Yes,,Draft,,,Startup,,,
Today Launches,2,https://todaylaunches.com,Startup,60,Yes,,Draft,,,Startup,,,
StartupBuffer,2,https://startupbuffer.com,Startup,57,Yes,,Draft,,,Startup,,,
Feedough,2,https://www.feedough.com,Startup,55,Yes,,Draft,,,Startup,,,
Indie Hacker Tools,2,https://www.indiehacker.tools,Startup,55,Yes,,Draft,,,Startup,,,
Open Launch,2,https://open-launch.com,Startup,55,Yes,,Draft,,,Startup,,,
New SaaSly,2,https://newsaasly.com,SaaS,52,Yes,,Draft,,,SaaS,,,
Business Software,2,https://www.business-software.com,SaaS,49,Yes,,Draft,,,SaaS,,,
Promote Project,2,https://www.promoteproject.com,Startup,47,Yes,,Draft,,,Startup,,,
FiveTaco,2,https://fivetaco.com,SaaS,47,Yes,,Draft,,,SaaS,,,
Cuspera,2,https://www.cuspera.com,SaaS,45,Yes,,Draft,,,SaaS,,,
BetaBound,2,https://betabound.com,Startup,45,Yes,,Draft,,,Startup,,,
Makerthrive,2,https://makerthrive.com,Startup,45,Yes,,Draft,,,Startup,,,
StartupTracker,2,https://startuptracker.io,Startup,44,Yes,,Draft,,,Startup,,,
BusinessHunt,2,https://businesshunt.co,SaaS,43,Yes,,Draft,,,SaaS,,,
Launched.io,2,https://launched.io,Startup,40,Yes,,Draft,,,Startup,,,
ProfitHunt,2,https://profithunt.co,Startup,40,Yes,,Draft,,,Startup,,,
10words,2,https://10words.io,SaaS,40,Yes,,Draft,,,SaaS,,,
TrustMRR,2,https://trustmrr.com,Startup,40,Yes,,Draft,,,Startup,,,
OpenClawDir,2,https://openclawdir.com,Tech,35,Yes,,Draft,,,Dev,,,
Build Voyage,2,https://buildvoyage.com,Startup,33,Yes,,Draft,,,Startup,,,
AlphaDigits,2,https://alphadigits.com,SaaS,32,Yes,,Draft,,,SaaS,,,
GPTForge,3,https://gptforge.net,AI,30,Yes,,Draft,,,AI,,,Domain created 2025 — DR 88 from source list is implausible
AI Tools Guide,3,https://aitoolsguide.com,AI,77,Yes,,Draft,,,AI,,,
AIToolly,3,https://aitoolly.com,AI,69,Yes,,Draft,,,AI,,,
All The AI Tools,3,https://alltheaitools.com,AI,66,Yes,,Draft,,,AI,,,
Aiforme.wiki,3,https://aiforme.wiki,AI,66,Yes,,Draft,,,AI,,,
Noxilo,3,https://noxilo.com,AI,66,Yes,,Draft,,,AI,,,
AI Generation,3,https://www.theaigeneration.com,AI,55,Yes,,Draft,,,AI,,,
Every AI,3,https://every-ai.com,AI,55,Yes,,Draft,,,AI,,,
BAI.tools,3,https://bai.tools,AI,53,Yes,,Draft,,,AI,,,
The Rundown Tools,3,https://www.rundown.ai/tools,AI,40,Yes,,Draft,,,AI,,,
AI NavHub,3,https://ainavhub.com,AI,38,Yes,,Draft,,,AI,,,
WhatTheAI,3,https://whattheai.tech,AI,35,Yes,,Draft,,,AI,,,
ToolAI,3,https://toolai.io,AI,31,Yes,,Draft,,,AI,,,
LLM Relevance,3,https://www.llmrelevance.com,AI,30,Yes,,Draft,,,AI,,,
MakerPad / Zapier,5,https://www.makerpad.co,No-Code,62,Yes,,Draft,,,No-code,,,
NoCodeFounders,5,https://www.nocodefounders.com,No-Code,45,Yes,,Draft,,,No-code,,,
WordPress.com,8,https://wordpress.com,Blog,100,Yes,,Draft,,,Profile,,,
Blogger,8,https://www.blogger.com,Blog,100,Yes,,Draft,,,Profile,,,
Tumblr,8,https://www.tumblr.com,Blog,99,Yes,,Draft,,,Profile,,,
GitHub,8,https://github.com,Tech,98,Yes,,Draft,,,Profile,,,
SoundCloud,8,https://soundcloud.com,Music,96,Yes,,Draft,,,Profile,,,
Weebly,8,https://www.weebly.com,Blog,95,Yes,,Draft,,,Profile,,,
SlideShare,8,https://www.slideshare.net,Content,95,Yes,,Draft,,,Profile,,,
Flickr,8,https://www.flickr.com,Photography,95,Yes,,Draft,,,Profile,,,
GitLab,8,https://gitlab.com,Tech,94,Yes,,Draft,,,Profile,,,
eBay Stores,8,https://www.ebay.com,E-commerce,94,Yes,,Draft,,,Profile,,,
Etsy,8,https://www.etsy.com,E-commerce,93,Yes,,Draft,,,Profile,,,
Substack,8,https://substack.com,Newsletter,93,Yes,,Draft,,,Profile,,,
Bitbucket,8,https://bitbucket.org,Tech,93,Yes,,Draft,,,Profile,,,
Scribd,8,https://www.scribd.com,Content,93,Yes,,Draft,,,Profile,,,
Disqus,8,https://disqus.com,Professional,93,Yes,,Draft,,,Profile,,,
Behance,8,https://www.behance.net,Design,93,Yes,,Draft,,,Profile,,,
Pastebin,8,https://pastebin.com,Tech,93,Yes,,Draft,,,Profile,,,
Patreon,8,https://www.patreon.com,Creator,93,Yes,,Draft,,,Profile,,,
Imgur,8,https://imgur.com,Content,93,Yes,,Draft,,,Profile,,,
Dun & Bradstreet,8,https://www.dnb.com,B2B,93,Yes,,Draft,,,Profile,,,
Ghost.org,8,https://ghost.org,Blog,92,Yes,,Draft,,,Profile,,,
Evernote,8,https://evernote.com,Content,92,Yes,,Draft,,,Profile,,,
Issuu,8,https://issuu.com,Content,92,Yes,,Draft,,,Profile,,,
CodePen,8,https://codepen.io,Tech,92,Yes,,Draft,,,Profile,,,
Kaggle,8,https://www.kaggle.com,AI,92,Yes,,Draft,,,Profile,,,
Houzz,8,https://www.houzz.com,Home,92,Yes,,Draft,,,Profile,,,
LiveJournal,8,https://www.livejournal.com,Blog,91,Yes,,Draft,,,Profile,,,
Bandcamp,8,https://bandcamp.com,Music,91,Yes,,Draft,,,Profile,,,
Dev.to,8,https://dev.to,Tech,90,Yes,,Draft,,,Profile,,,
Gravatar,8,https://gravatar.com,Professional,90,Yes,,Draft,,,Profile,,,
Replit,8,https://replit.com,Tech,90,Yes,,Draft,,,Profile,,,
CodeProject,8,https://www.codeproject.com,Tech,90,Yes,,Draft,,,Profile,,,
Jimdo,8,https://www.jimdo.com,Blog,89,Yes,,Draft,,,Profile,,,
Calameo,8,https://www.calameo.com,Content,89,Yes,,Draft,,,Profile,,,
Buy Me a Coffee,8,https://www.buymeacoffee.com,Creator,88,Yes,,Draft,,,Profile,,,
ArtStation,8,https://www.artstation.com,Design,88,Yes,,Draft,,,Profile,,,
500px,8,https://500px.com,Photography,88,Yes,,Draft,,,Profile,,,
AppSumo,8,https://appsumo.com,E-commerce,84,Yes,,Draft,,,Profile,,,
IndiaMART,8,https://www.indiamart.com,B2B,87,Yes,,Draft,,,Profile,,,
Strikingly,8,https://www.strikingly.com,Blog,87,Yes,,Draft,,,Profile,,,
Hashnode,8,https://hashnode.com,Tech,85,Yes,,Draft,,,Profile,,,
About.me,8,https://about.me,Professional,85,Yes,,Draft,,,Profile,,,
Mixcloud,8,https://www.mixcloud.com,Music,85,Yes,,Draft,,,Profile,,,
4Shared,8,https://www.4shared.com,Content,85,Yes,,Draft,,,Profile,,,
HubPages,8,https://hubpages.com,Blog,84,Yes,,Draft,,,Profile,,,
TeachersPayTeachers,8,https://www.teacherspayteachers.com,Education,84,Yes,,Draft,,,Profile,,,
AuthorStream,8,https://www.authorstream.com,Content,70,Yes,,Draft,,,Profile,,,
Model Mayhem,8,https://www.modelmayhem.com,Design,72,Yes,,Draft,,,Profile,,,
Penzu,8,https://penzu.com,Blog,60,Yes,,Draft,,,Profile,,,
Crevado,8,https://crevado.com,Design,50,Yes,,Draft,,,Profile,,,
MyFolio,8,https://myfolio.com,Design,55,Yes,,Draft,,,Profile,,,
Manta,9,https://www.manta.com,Local business,76,Yes,,Draft,,,Local,,,
ActiveSearchResults,9,https://www.activesearchresults.com,Local business,74,Yes,,Draft,,,Local,,,
Hotfrog,9,https://www.hotfrog.com,Local business,72,Yes,,Draft,,,Local,,,
Spoke,9,https://www.spoke.com,Local business,70,Yes,,Draft,,,Local,,,
Locanto,9,https://www.locanto.com,General,70,Yes,,Draft,,,Local,,,
MerchantCircle,9,https://www.merchantcircle.com,Local business,68,Yes,,Draft,,,Local,,,
Just Landed,9,https://www.justlanded.com,Local business,65,Yes,,Draft,,,Local,,,
Showmelocal,9,https://www.showmelocal.com,Local business,64,Yes,,Draft,,,Local,,,
Cylex,9,https://www.cylex.us.com,Local business,64,Yes,,Draft,,,Local,,,
Brownbook,9,https://www.brownbook.net,Local business,63,Yes,,Draft,,,Local,,,
Tupalo,9,https://tupalo.com,Local business,62,Yes,,Draft,,,Local,,,
WebWiki,9,https://www.webwiki.com,Local business,60,Yes,,Draft,,,Local,,,
iBegin,9,https://www.ibegin.com,Local business,60,Yes,,Draft,,,Local,,,
CitySquares,9,https://citysquares.com,Local business,55,Yes,,Draft,,,Local,,,
eLocal,9,https://elocal.com,Local business,55,Yes,,Draft,,,Local,,,
2FindLocal,9,https://www.2findlocal.com,Local business,53,Yes,,Draft,,,Local,,,
Chamber of Commerce,9,https://www.chamberofcommerce.com,Local business,50,Yes,,Draft,,,Local,,,
FindUsLocal,9,https://www.finduslocal.com,Local business,50,Yes,,Draft,,,Local,,,
ezlocal,9,https://www.ezlocal.com,Local business,50,Yes,,Draft,,,Local,,,
Yellow Pages Goes Green,9,https://www.yellowpagesgoesgreen.org,Local business,49,Yes,,Draft,,,Local,,,
Where To?,9,https://www.where2go.com,Local business,46,Yes,,Draft,,,Local,,,
SitePoint Forums,10,https://www.sitepoint.com/community,Tech,89,Yes,,Draft,,,Forum,,,
Mumsnet Forums,10,https://www.mumsnet.com/Talk,Family,85,Yes,,Draft,,,Forum,,,
Digital Point,10,https://forums.digitalpoint.com,Marketing,82,Yes,,Draft,,,Forum,,,
WebmasterWorld,10,https://www.webmasterworld.com,Marketing,77,Yes,,Draft,,,Forum,,,
BlackHatWorld,10,https://www.blackhatworld.com,Marketing,77,Yes,,Draft,,,Forum,,,
GrowthHackers,10,https://growthhackers.com,Marketing,76,Yes,,Draft,,,Forum,,,
Warrior Forum,10,https://www.warriorforum.com,Marketing,73,Yes,,Draft,,,Forum,,,
Apsense,10,https://www.apsense.com,Marketing,72,Yes,,Draft,,,Forum,,,
Strava Clubs,10,https://www.strava.com,Fitness,90,Yes,,Draft,,,Forum,,,
Foursquare,10,https://business.foursquare.com,Hospitality,90,Yes,,Draft,,,Forum,,,
ActiveRain,10,https://activerain.com,Real estate,70,Yes,,Draft,,,Forum,,,
Quibblo,10,https://www.quibblo.com,General,55,Yes,,Draft,,,Forum,,,
EzineArticles,11,https://ezinearticles.com,Article,80,Yes,,Draft,,,Article,,,
PRLog,11,https://www.prlog.org,Press release,80,Yes,,Draft,,,PR,,,
Feedspot,11,https://www.feedspot.com,Blog directory,80,Yes,,Draft,,,Article,,,
PR.com,11,https://www.pr.com,Press release,77,Yes,,Draft,,,PR,,,
Alltop,11,https://alltop.com,Blog directory,73,Yes,,Draft,,,Article,,,
OpenPR,11,https://www.openpr.com,Press release,72,Yes,,Draft,,,PR,,,
ArticlesBase,11,https://www.articlesbase.com,Article,70,Yes,,Draft,,,Article,,,
1888 Press Release,11,https://www.1888pressrelease.com,Press release,69,Yes,,Draft,,,PR,,,
NewswireToday,11,https://www.newswiretoday.com,Press release,65,Yes,,Draft,,,PR,,,
Blogarama,11,https://www.blogarama.com,Blog directory,64,Yes,,Draft,,,Article,,,
Online PR News,11,https://www.onlineprnews.com,Press release,62,Yes,,Draft,,,PR,,,
PR Free,11,https://www.pr-free.com,Press release,62,Yes,,Draft,,,PR,,,
SubmissionWebDirectory,11,https://www.submissionwebdirectory.com,General,61,Yes,,Draft,,,Article,,,
Sooper Articles,11,https://www.sooperarticles.com,Article,60,Yes,,Draft,,,Article,,,
OnToplist,11,https://www.ontoplist.com,Blog directory,60,Yes,,Draft,,,Article,,,
BlogEngage,11,https://www.blogengage.com,Blog directory,55,Yes,,Draft,,,Article,,,
BizSugar,11,https://www.bizsugar.com,Business,55,Yes,,Draft,,,Article,,,
TechPluto,11,https://www.techpluto.com,Marketing,50,Yes,,Draft,,,Article,,,
Semfirms,11,https://www.semfirms.com,Marketing,45,Yes,,Draft,,,Article,,,
CabinetM,11,https://www.cabinetm.com,Marketing,45,Yes,,Draft,,,Article,,,
Cold Email Kit,11,https://coldemailkit.com,Marketing,44,Yes,,Draft,,,Article,,,
Directory LDM Studio,11,https://www.directory.ldmstudio.com,General,40,Yes,,Draft,,,Article,,,
Quality Internet Directory,11,https://www.qualityinternetdirectory.com,General,39,Yes,,Draft,,,Article,,,
Site Promotion Directory,11,https://www.sitepromotiondirectory.com,Marketing,46,Yes,,Draft,,,Article,,,
ProofStories,11,https://proofstories.io,Marketing,32,Yes,,Draft,,,Article,,,
Scoop.it,12,https://www.scoop.it,Curation,91,Yes,,Draft,,,Bookmarking,,,
Diigo,12,https://www.diigo.com,Bookmarking,85,Yes,,Draft,,,Bookmarking,,,
Pearltrees,12,https://www.pearltrees.com,Bookmarking,84,Yes,,Draft,,,Bookmarking,,,
BibSonomy,12,https://www.bibsonomy.org,Research,70,Yes,,Draft,,,Bookmarking,,,
Folkd,12,https://www.folkd.com,Bookmarking,64,Yes,,Draft,,,Bookmarking,,,
Justia,13,https://www.justia.com,Legal,85,Yes,,Draft,,,Niche,,,
Lawyers.com,13,https://www.lawyers.com,Legal,82,Yes,,Draft,,,Niche,,,
Porch,13,https://porch.com,Home,80,Yes,,Draft,,,Niche,,,
AllMenus,13,https://www.allmenus.com,Hospitality,76,Yes,,Draft,,,Niche,,,
HG.org,13,https://www.hg.org,Legal,75,Yes,,Draft,,,Niche,,,
Sulekha,13,https://www.sulekha.com,B2B,73,Yes,,Draft,,,Niche,,,
BuildZoom,13,https://www.buildzoom.com,Home,73,Yes,,Draft,,,Niche,,,
LandBook,13,https://land-book.com,Design,72,Yes,,Draft,,,Niche,,,
Athlinks,13,https://www.athlinks.com,Fitness,72,Yes,,Draft,,,Niche,,,
Evensi Events,13,https://evensi.com,Events,62,Yes,,Draft,,,Niche,,,
Wellness.com,13,https://www.wellness.com,Health,60,Yes,,Draft,,,Niche,,,
Placester,13,https://placester.com,Real estate,60,Yes,,Draft,,,Niche,,,
YogaTrail,13,https://www.yogatrail.com,Health,55,Yes,,Draft,,,Niche,,,
Tradify (FreeIndex),13,https://www.freeindex.co.uk,Home,55,Yes,,Draft,,,Niche,,,
Webdesign Inspiration,13,https://webdesign-inspiration.com,Design,45,Yes,,Draft,,,Niche,,,
iBuildNew,13,https://www.ibuildnew.com.au,Home,45,Yes,,Draft,,,Niche,,,
EU-Business,13,https://www.eu-business.com,B2B,46,Yes,,Draft,,,Niche,,,
MassageTherapy (AMBP),13,https://www.massagetherapy.com,Health,45,Yes,,Draft,,,Niche,,,
Fit Pro Directory,13,https://fitprofessionals.net,Fitness,40,Yes,,Draft,,,Niche,,,
Curated.design,13,https://www.curated.design,Design,52,Yes,,Draft,,,Niche,,,
Hỗ trợ chiến dịch quảng cáo trả phí trên Google Ads, Meta, LinkedIn, Twitter/X: nội dung quảng cáo, ROAS, CPA, retargeting và nhắm đối tượng.
---
name: "paid-ads"
description: "When the user wants help with paid advertising campaigns on Google Ads, Meta (Facebook/Instagram), LinkedIn, Twitter/X, or other ad platforms. Also use when the user mentions 'PPC,' 'paid media,' 'ad copy,' 'ad creative,' 'ROAS,' 'CPA,' 'ad campaign,' 'retargeting,' or 'audience targeting.' This skill covers campaign strategy, ad creation, audience targeting, and optimization."
license: MIT
metadata:
version: 1.0.0
author: Alireza Rezvani
category: marketing
updated: 2026-03-06
---
# Paid Ads
You are an expert performance marketer with direct access to ad platform accounts. Your goal is to help create, optimize, and scale paid advertising campaigns that drive efficient customer acquisition.
## Before Starting
**Check for product marketing context first:**
If `.claude/product-marketing-context.md` exists, 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. Campaign Goals
- What's the primary objective? (Awareness, traffic, leads, sales, app installs)
- What's the target CPA or ROAS?
- What's the monthly/weekly budget?
- Any constraints? (Brand guidelines, compliance, geographic)
### 2. Product & Offer
- What are you promoting? (Product, free trial, lead magnet, demo)
- What's the landing page URL?
- What makes this offer compelling?
### 3. Audience
- Who is the ideal customer?
- What problem does your product solve for them?
- What are they searching for or interested in?
- Do you have existing customer data for lookalikes?
### 4. Current State
- Have you run ads before? What worked/didn't?
- Do you have existing pixel/conversion data?
- What's your current funnel conversion rate?
---
## Platform Selection Guide
| Platform | Best For | Use When |
|----------|----------|----------|
| **Google Ads** | High-intent search traffic | People actively search for your solution |
| **Meta** | Demand generation, visual products | Creating demand, strong creative assets |
| **LinkedIn** | B2B, decision-makers | Job title/company targeting matters, higher price points |
| **Twitter/X** | Tech audiences, thought leadership | Audience is active on X, timely content |
| **TikTok** | Younger demographics, viral creative | Audience skews 18-34, video capacity |
---
## Campaign Structure Best Practices
### Account Organization
```
Account
├── Campaign 1: [Objective] - [Audience/Product]
│ ├── Ad Set 1: [Targeting variation]
│ │ ├── Ad 1: [Creative variation A]
│ │ ├── Ad 2: [Creative variation B]
│ │ └── Ad 3: [Creative variation C]
│ └── Ad Set 2: [Targeting variation]
└── Campaign 2...
```
### Naming Conventions
```
[Platform]_[Objective]_[Audience]_[Offer]_[Date]
Examples:
META_Conv_Lookalike-Customers_FreeTrial_2024Q1
GOOG_Search_Brand_Demo_Ongoing
LI_LeadGen_CMOs-SaaS_Whitepaper_Mar24
```
### Budget Allocation
**Testing phase (first 2-4 weeks):**
- 70% to proven/safe campaigns
- 30% to testing new audiences/creative
**Scaling phase:**
- Consolidate budget into winning combinations
- Increase budgets 20-30% at a time
- Wait 3-5 days between increases for algorithm learning
---
## Ad Copy Frameworks
### Key Formulas
**Problem-Agitate-Solve (PAS):**
> [Problem] → [Agitate the pain] → [Introduce solution] → [CTA]
**Before-After-Bridge (BAB):**
> [Current painful state] → [Desired future state] → [Your product as bridge]
**Social Proof Lead:**
> [Impressive stat or testimonial] → [What you do] → [CTA]
**For detailed templates and headline formulas**: See [references/ad-copy-templates.md](references/ad-copy-templates.md)
---
## Audience Targeting Overview
### Platform Strengths
| Platform | Key Targeting | Best Signals |
|----------|---------------|--------------|
| Google | Keywords, search intent | What they're searching |
| Meta | Interests, behaviors, lookalikes | Engagement patterns |
| LinkedIn | Job titles, companies, industries | Professional identity |
### Key Concepts
- **Lookalikes**: Base on best customers (by LTV), not all customers
- **Retargeting**: Segment by funnel stage (visitors vs. cart abandoners)
- **Exclusions**: Always exclude existing customers and recent converters
**For detailed targeting strategies by platform**: See [references/audience-targeting.md](references/audience-targeting.md)
---
## Creative Best Practices
### Image Ads
- Clear product screenshots showing UI
- Before/after comparisons
- Stats and numbers as focal point
- Human faces (real, not stock)
- Bold, readable text overlay (keep under 20%)
### Video Ads Structure (15-30 sec)
1. Hook (0-3 sec): Pattern interrupt, question, or bold statement
2. Problem (3-8 sec): Relatable pain point
3. Solution (8-20 sec): Show product/benefit
4. CTA (20-30 sec): Clear next step
**Production tips:**
- Captions always (85% watch without sound)
- Vertical for Stories/Reels, square for feed
- Native feel outperforms polished
- First 3 seconds determine if they watch
### Creative Testing Hierarchy
1. Concept/angle (biggest impact)
2. Hook/headline
3. Visual style
4. Body copy
5. CTA
---
## Campaign Optimization
### Key Metrics by Objective
| Objective | Primary Metrics |
|-----------|-----------------|
| Awareness | CPM, Reach, Video view rate |
| Consideration | CTR, CPC, Time on site |
| Conversion | CPA, ROAS, Conversion rate |
### Optimization Levers
**If CPA is too high:**
1. Check landing page (is the problem post-click?)
2. Tighten audience targeting
3. Test new creative angles
4. Improve ad relevance/quality score
5. Adjust bid strategy
**If CTR is low:**
- Creative isn't resonating → test new hooks/angles
- Audience mismatch → refine targeting
- Ad fatigue → refresh creative
**If CPM is high:**
- Audience too narrow → expand targeting
- High competition → try different placements
- Low relevance score → improve creative fit
### Bid Strategy Progression
1. Start with manual or cost caps
2. Gather conversion data (50+ conversions)
3. Switch to automated with targets based on historical data
4. Monitor and adjust targets based on results
---
## Retargeting Strategies
### Funnel-Based Approach
| Funnel Stage | Audience | Message | Goal |
|--------------|----------|---------|------|
| Top | Blog readers, video viewers | Educational, social proof | Move to consideration |
| Middle | Pricing/feature page visitors | Case studies, demos | Move to decision |
| Bottom | Cart abandoners, trial users | Urgency, objection handling | Convert |
### Retargeting Windows
| Stage | Window | Frequency Cap |
|-------|--------|---------------|
| Hot (cart/trial) | 1-7 days | Higher OK |
| Warm (key pages) | 7-30 days | 3-5x/week |
| Cold (any visit) | 30-90 days | 1-2x/week |
### Exclusions to Set Up
- Existing customers (unless upsell)
- Recent converters (7-14 day window)
- Bounced visitors (<10 sec)
- Irrelevant pages (careers, support)
---
## Reporting & Analysis
### Weekly Review
- Spend vs. budget pacing
- CPA/ROAS vs. targets
- Top and bottom performing ads
- Audience performance breakdown
- Frequency check (fatigue risk)
- Landing page conversion rate
### Attribution Considerations
- Platform attribution is inflated
- Use UTM parameters consistently
- Compare platform data to GA4
- Look at blended CAC, not just platform CPA
---
## Platform Setup
Before launching campaigns, ensure proper tracking and account setup.
**For complete setup checklists by platform**: See [references/platform-setup-checklists.md](references/platform-setup-checklists.md)
### Universal Pre-Launch Checklist
- [ ] Conversion tracking tested with real conversion
- [ ] Landing page loads fast (<3 sec)
- [ ] Landing page mobile-friendly
- [ ] UTM parameters working
- [ ] Budget set correctly
- [ ] Targeting matches intended audience
---
## Common Mistakes to Avoid
### Strategy
- Launching without conversion tracking
- Too many campaigns (fragmenting budget)
- Not giving algorithms enough learning time
- Optimizing for wrong metric
### Targeting
- Audiences too narrow or too broad
- Not excluding existing customers
- Overlapping audiences competing
### Creative
- Only one ad per ad set
- Not refreshing creative (fatigue)
- Mismatch between ad and landing page
### Budget
- Spreading too thin across campaigns
- Making big budget changes (disrupts learning)
- Stopping campaigns during learning phase
---
## Task-Specific Questions
1. What platform(s) are you currently running or want to start with?
2. What's your monthly ad budget?
3. What does a successful conversion look like (and what's it worth)?
4. Do you have existing creative assets or need to create them?
5. What landing page will ads point to?
6. Do you have pixel/conversion tracking set up?
---
## Tool Integrations
For implementation, see the [tools registry](../../tools/REGISTRY.md). Key advertising platforms:
| Platform | Best For | MCP | Guide |
|----------|----------|:---:|-------|
| **Google Ads** | Search intent, high-intent traffic | ✓ | [google-ads.md](../../tools/integrations/google-ads.md) |
| **Meta Ads** | Demand gen, visual products, B2C | - | [meta-ads.md](../../tools/integrations/meta-ads.md) |
| **LinkedIn Ads** | B2B, job title targeting | - | [linkedin-ads.md](../../tools/integrations/linkedin-ads.md) |
| **TikTok Ads** | Younger demographics, video | - | [tiktok-ads.md](../../tools/integrations/tiktok-ads.md) |
For tracking, see also: [ga4.md](../../tools/integrations/ga4.md), [segment.md](../../tools/integrations/segment.md)
---
## Related Skills
- **ad-creative** — WHEN you need deep creative direction for ad visuals, video scripts, or creative concepting beyond basic image/copy guidelines. NOT for campaign strategy, targeting, or bidding decisions.
- **analytics-tracking** — WHEN setting up conversion tracking pixels, UTM parameters, and attribution models before or during campaign launch. NOT for campaign creation or creative work.
- **campaign-analytics** — WHEN analyzing campaign performance data, diagnosing underperforming campaigns, or building reporting dashboards. NOT for initial campaign setup or creative production.
- **copywriting** — WHEN landing pages linked from ads need copy optimization to match ad messaging and improve post-click conversion. NOT for the ad copy itself.
- **marketing-context** — Foundation skill for ICP, positioning, and messaging alignment. ALWAYS load before writing ad copy or selecting targeting to ensure message-market fit.
---
## Communication
Always confirm conversion tracking is in place before recommending creative or targeting changes — a campaign without proper attribution is guesswork. When recommending budget allocation, state the rationale (testing vs. scaling phase). Deliver ad copy as complete, ready-to-launch sets: headline variants, body copy, and CTA. Proactively flag when a landing page mismatch (ad promise ≠ page promise) is the likely conversion bottleneck. Load `marketing-context` for ICP and positioning before writing any copy.
---
## Proactive Triggers
- User asks why ROAS is dropping → check creative fatigue and ad frequency before adjusting targeting or bids.
- User wants to launch their first paid campaign → run through the pre-launch checklist (conversion tracking, landing page speed, UTMs) before touching creative.
- User mentions high CTR but low conversions → diagnose landing page, not the ad; redirect to `page-cro` or `copywriting` skill.
- User is scaling budget aggressively → warn about algorithm learning phase disruption; recommend 20-30% incremental increases with 3-5 day stabilization windows.
- User asks about B2B lead generation via ads → recommend LinkedIn for job-title targeting and flag that CPL will be higher but lead quality better than Meta for high-ACV products.
---
## Output Artifacts
| Artifact | Description |
|----------|-------------|
| Campaign Architecture | Full account structure with campaign names, ad set targeting, naming conventions, and budget allocation |
| Ad Copy Set | 3 headline variants, body copy, and CTA for each ad format and platform, ready to launch |
| Audience Targeting Brief | Primary audiences, lookalike seeds, retargeting segments, and exclusion lists per platform |
| Pre-Launch Checklist | Platform-specific tracking verification, landing page audit, and UTM parameter setup |
| Weekly Optimization Report Template | Metrics dashboard structure with CPA/ROAS targets, fatigue signals, and decision triggers |
FILE:references/ad-copy-templates.md
# Ad Copy Templates Reference
Detailed formulas and templates for writing high-converting ad copy.
## Primary Text Formulas
### Problem-Agitate-Solve (PAS)
```
[Problem statement]
[Agitate the pain]
[Introduce solution]
[CTA]
```
**Example:**
> Spending hours on manual reporting every week?
> While you're buried in spreadsheets, your competitors are making decisions.
> [Product] automates your reports in minutes.
> Start your free trial →
---
### Before-After-Bridge (BAB)
```
[Current painful state]
[Desired future state]
[Your product as the bridge]
```
**Example:**
> Before: Chasing down approvals across email, Slack, and spreadsheets.
> After: Every approval tracked, automated, and on time.
> [Product] connects your tools and keeps projects moving.
---
### Social Proof Lead
```
[Impressive stat or testimonial]
[What you do]
[CTA]
```
**Example:**
> "We cut our reporting time by 75%." — Sarah K., Marketing Director
> [Product] automates the reports you hate building.
> See how it works →
---
### Feature-Benefit Bridge
```
[Feature]
[So that...]
[Which means...]
```
**Example:**
> Real-time collaboration on documents
> So your team always works from the latest version
> Which means no more version confusion or lost work
---
### Direct Response
```
[Bold claim/outcome]
[Proof point]
[CTA with urgency if genuine]
```
**Example:**
> Cut your reporting time by 80%
> Join 5,000+ marketing teams already using [Product]
> Start free → First month 50% off
---
## Headline Formulas
### For Search Ads
| Formula | Example |
|---------|---------|
| [Keyword] + [Benefit] | "Project Management That Teams Actually Use" |
| [Action] + [Outcome] | "Automate Reports \| Save 10 Hours Weekly" |
| [Question] | "Tired of Manual Data Entry?" |
| [Number] + [Benefit] | "500+ Teams Trust [Product] for [Outcome]" |
| [Keyword] + [Differentiator] | "CRM Built for Small Teams" |
| [Price/Offer] + [Keyword] | "Free Project Management \| No Credit Card" |
### For Social Ads
| Type | Example |
|------|---------|
| Outcome hook | "How we 3x'd our conversion rate" |
| Curiosity hook | "The reporting hack no one talks about" |
| Contrarian hook | "Why we stopped using [common tool]" |
| Specificity hook | "The exact template we use for..." |
| Question hook | "What if you could cut your admin time in half?" |
| Number hook | "7 ways to improve your workflow today" |
| Story hook | "We almost gave up. Then we found..." |
---
## CTA Variations
### Soft CTAs (awareness/consideration)
Best for: Top of funnel, cold audiences, complex products
- Learn More
- See How It Works
- Watch Demo
- Get the Guide
- Explore Features
- See Examples
- Read the Case Study
### Hard CTAs (conversion)
Best for: Bottom of funnel, warm audiences, clear offers
- Start Free Trial
- Get Started Free
- Book a Demo
- Claim Your Discount
- Buy Now
- Sign Up Free
- Get Instant Access
### Urgency CTAs (use when genuine)
Best for: Limited-time offers, scarcity situations
- Limited Time: 30% Off
- Offer Ends [Date]
- Only X Spots Left
- Last Chance
- Early Bird Pricing Ends Soon
### Action-Oriented CTAs
Best for: Active voice, clear next step
- Start Saving Time Today
- Get Your Free Report
- See Your Score
- Calculate Your ROI
- Build Your First Project
---
## Platform-Specific Copy Guidelines
### Google Search Ads
- **Headline limits:** 30 characters each (up to 15 headlines)
- **Description limits:** 90 characters each (up to 4 descriptions)
- Include keywords naturally
- Use all available headline slots
- Include numbers and stats when possible
- Test dynamic keyword insertion
### Meta Ads (Facebook/Instagram)
- **Primary text:** 125 characters visible (can be longer, gets truncated)
- **Headline:** 40 characters recommended
- Front-load the hook (first line matters most)
- Emojis can work but test
- Questions perform well
- Keep image text under 20%
### LinkedIn Ads
- **Intro text:** 600 characters max (150 recommended)
- **Headline:** 200 characters max (70 recommended)
- Professional tone (but not boring)
- Specific job outcomes resonate
- Stats and social proof important
- Avoid consumer-style hype
---
## Copy Testing Priority
When testing ad copy, focus on these elements in order of impact:
1. **Hook/angle** (biggest impact on performance)
2. **Headline**
3. **Primary benefit**
4. **CTA**
5. **Supporting proof points**
Test one element at a time for clean data.
FILE:references/audience-targeting.md
# Audience Targeting Reference
Detailed targeting strategies for each major ad platform.
## Google Ads Audiences
### Search Campaign Targeting
**Keywords:**
- Exact match: [keyword] — most precise, lower volume
- Phrase match: "keyword" — moderate precision and volume
- Broad match: keyword — highest volume, use with smart bidding
**Audience layering:**
- Add audiences in "observation" mode first
- Analyze performance by audience
- Switch to "targeting" mode for high performers
**RLSA (Remarketing Lists for Search Ads):**
- Bid higher on past visitors searching your terms
- Show different ads to returning searchers
- Exclude converters from prospecting campaigns
### Display/YouTube Targeting
**Custom intent audiences:**
- Based on recent search behavior
- Create from your converting keywords
- High intent, good for prospecting
**In-market audiences:**
- People actively researching solutions
- Pre-built by Google
- Layer with demographics for precision
**Affinity audiences:**
- Based on interests and habits
- Better for awareness
- Broad but can exclude irrelevant
**Customer match:**
- Upload email lists
- Retarget existing customers
- Create lookalikes from best customers
**Similar/lookalike audiences:**
- Based on your customer match lists
- Expand reach while maintaining relevance
- Best when source list is high-quality customers
---
## Meta Audiences
### Core Audiences (Interest/Demographic)
**Interest targeting tips:**
- Layer interests with AND logic for precision
- Use Audience Insights to research interests
- Start broad, let algorithm optimize
- Exclude existing customers always
**Demographic targeting:**
- Age and gender (if product-specific)
- Location (down to zip/postal code)
- Language
- Education and work (limited data now)
**Behavior targeting:**
- Purchase behavior
- Device usage
- Travel patterns
- Life events
### Custom Audiences
**Website visitors:**
- All visitors (last 180 days max)
- Specific page visitors
- Time on site thresholds
- Frequency (visited X times)
**Customer list:**
- Upload emails/phone numbers
- Match rate typically 30-70%
- Refresh regularly for accuracy
**Engagement audiences:**
- Video viewers (25%, 50%, 75%, 95%)
- Page/profile engagers
- Form openers
- Instagram engagers
**App activity:**
- App installers
- In-app events
- Purchase events
### Lookalike Audiences
**Source audience quality matters:**
- Use high-LTV customers, not all customers
- Purchasers > leads > all visitors
- Minimum 100 source users, ideally 1,000+
**Size recommendations:**
- 1% — most similar, smallest reach
- 1-3% — good balance for most
- 3-5% — broader, good for scale
- 5-10% — very broad, awareness only
**Layering strategies:**
- Lookalike + interest = more precision early
- Test lookalike-only as you scale
- Exclude the source audience
---
## LinkedIn Audiences
### Job-Based Targeting
**Job titles:**
- Be specific (CMO vs. "Marketing")
- LinkedIn normalizes titles, but verify
- Stack related titles
- Exclude irrelevant titles
**Job functions:**
- Broader than titles
- Combine with seniority level
- Good for awareness campaigns
**Seniority levels:**
- Entry, Senior, Manager, Director, VP, CXO, Partner
- Layer with function for precision
**Skills:**
- Self-reported, less reliable
- Good for technical roles
- Use as expansion layer
### Company-Based Targeting
**Company size:**
- 1-10, 11-50, 51-200, 201-500, 501-1000, 1001-5000, 5000+
- Key filter for B2B
**Industry:**
- Based on company classification
- Can be broad, layer with other criteria
**Company names (ABM):**
- Upload target account list
- Minimum 300 companies recommended
- Match rate varies
**Company growth rate:**
- Hiring rapidly = budget available
- Good signal for timing
### High-Performing Combinations
| Use Case | Targeting Combination |
|----------|----------------------|
| Enterprise sales | Company size 1000+ + VP/CXO + Industry |
| SMB sales | Company size 11-200 + Manager/Director + Function |
| Developer tools | Skills + Job function + Company type |
| ABM campaigns | Company list + Decision-maker titles |
| Broad awareness | Industry + Seniority + Geography |
---
## Twitter/X Audiences
### Targeting options:
- Follower lookalikes (accounts similar to followers of X)
- Interest categories
- Keywords (in tweets)
- Conversation topics
- Events
- Tailored audiences (your lists)
### Best practices:
- Follower lookalikes of relevant accounts work well
- Keyword targeting catches active conversations
- Lower CPMs than LinkedIn/Meta
- Less precise, better for awareness
---
## TikTok Audiences
### Targeting options:
- Demographics (age, gender, location)
- Interests (TikTok's categories)
- Behaviors (video interactions)
- Device (iOS/Android, connection type)
- Custom audiences (pixel, customer file)
- Lookalike audiences
### Best practices:
- Younger skew (18-34 primarily)
- Interest targeting is broad
- Creative matters more than targeting
- Let algorithm optimize with broad targeting
---
## Audience Size Guidelines
| Platform | Minimum Recommended | Ideal Range |
|----------|-------------------|-------------|
| Google Search | 1,000+ searches/mo | 5,000-50,000 |
| Google Display | 100,000+ | 500K-5M |
| Meta | 100,000+ | 500K-10M |
| LinkedIn | 50,000+ | 100K-500K |
| Twitter/X | 50,000+ | 100K-1M |
| TikTok | 100,000+ | 1M+ |
Too narrow = expensive, slow learning
Too broad = wasted spend, poor relevance
---
## Exclusion Strategy
Always exclude:
- Existing customers (unless upsell)
- Recent converters (7-14 days)
- Bounced visitors (<10 sec)
- Employees (by company or email list)
- Irrelevant page visitors (careers, support)
- Competitors (if identifiable)
FILE:references/copy-frameworks.md
# Ad Copy Frameworks
Reference for selecting the right copy framework based on product type and campaign goal. Each framework includes a structure template and platform-specific length constraints.
## Framework selection matrix
| Product type | Pain-point heavy? | Transformation story? | Feature-led? | Recommended framework |
|---|---|---|---|---|
| SaaS / B2B | ✅ | | | PAS (Problem-Agitate-Solve) |
| Coaching / courses | | ✅ | | BAB (Before-After-Bridge) |
| Ecommerce / physical | | | ✅ | FAB (Features-Advantages-Benefits) |
| Content / info product | ✅ | ✅ | | AIDA (Attention-Interest-Desire-Action) |
| App / tool launch | | | ✅ | 4P (Promise-Picture-Proof-Push) |
| Services / consulting | ✅ | ✅ | | Star-Story-Solution |
## The 6 frameworks
### PAS — Problem → Agitate → Solve
**Best for:** Pain-point products, SaaS solving specific frustrations
```
Problem: Name the exact pain (1 sentence)
Agitate: Twist the knife — what happens if they don't fix it (1-2 sentences)
Solve: Your product is the answer (1 sentence + CTA)
```
### BAB — Before → After → Bridge
**Best for:** Transformation products, coaching, courses
```
Before: Current painful state (1 sentence)
After: Desired state they'll achieve (1 sentence)
Bridge: Your product connects the two (1 sentence + CTA)
```
### AIDA — Attention → Interest → Desire → Action
**Best for:** Content marketing, info products, broad audiences
```
Attention: Hook with a surprising stat or question
Interest: Explain why this matters to them
Desire: Show social proof or specific outcomes
Action: Clear CTA with urgency
```
### FAB — Features → Advantages → Benefits
**Best for:** Product-led, ecommerce, feature-rich offerings
```
Feature: What it has (spec/capability)
Advantage: Why that matters vs alternatives
Benefit: What the user gains (outcome)
```
### 4P — Promise → Picture → Proof → Push
**Best for:** App launches, tools, direct response
```
Promise: Bold claim (1 headline)
Picture: Vivid scenario of life with the product
Proof: Social proof, stats, testimonial
Push: Strong CTA with urgency/scarcity
```
### Star-Story-Solution
**Best for:** Personal brands, services, consulting
```
Star: Introduce the hero (the customer, not you)
Story: Their struggle (relatable narrative)
Solution: How your service transforms their situation
```
## Platform-specific constraints
| Platform | Headline | Body | CTA |
|---|---|---|---|
| Google RSA | 30 chars × 15 headlines | 90 chars × 4 descriptions | Auto from list |
| Meta Feed | 40 chars (before truncation) | 125 chars primary text (before "See more") | Button from list |
| Meta Stories | 40 chars overlay | Minimal — visual-first | Swipe up / button |
| LinkedIn Sponsored | 70 chars intro text visible | 150 chars before truncation | Button from list |
| TikTok | Overlay text in video | Caption 100 chars | Button from list |
| Microsoft | 30 chars × 15 headlines | 90 chars × 4 descriptions | Auto from list |
## Brand DNA extraction (7 voice axes)
Before writing ad copy, extract the brand's voice profile on these 7 axes:
```json
{
"formal_casual": 0.7, // 0 = corporate formal, 1 = casual/friendly
"bold_subtle": 0.6, // 0 = understated, 1 = bold/provocative
"technical_human": 0.4, // 0 = jargon-heavy, 1 = plain language
"serious_playful": 0.5, // 0 = gravitas, 1 = humor/wit
"traditional_innovative": 0.8, // 0 = established, 1 = cutting-edge
"exclusive_inclusive": 0.6, // 0 = luxury/elite, 1 = accessible/everyone
"data_emotional": 0.5 // 0 = stats-driven, 1 = story-driven
}
```
Save as `brand-profile.json` for reuse across campaigns. Each axis is 0.0-1.0.
FILE:references/platform-setup-checklists.md
# Platform Setup Checklists
Complete setup checklists for major ad platforms.
## Google Ads Setup
### Account Foundation
- [ ] Google Ads account created and verified
- [ ] Billing information added
- [ ] Time zone and currency set correctly
- [ ] Account access granted to team members
### Conversion Tracking
- [ ] Google tag installed on all pages
- [ ] Conversion actions created (purchase, lead, signup)
- [ ] Conversion values assigned (if applicable)
- [ ] Enhanced conversions enabled
- [ ] Test conversions firing correctly
- [ ] Import conversions from GA4 (optional)
### Analytics Integration
- [ ] Google Analytics 4 linked
- [ ] Auto-tagging enabled
- [ ] GA4 audiences available in Google Ads
- [ ] Cross-domain tracking set up (if multiple domains)
### Audience Setup
- [ ] Remarketing tag verified
- [ ] Website visitor audiences created:
- All visitors (180 days)
- Key page visitors (pricing, demo, features)
- Converters (for exclusion)
- [ ] Customer match lists uploaded
- [ ] Similar audiences enabled
### Campaign Readiness
- [ ] Negative keyword lists created:
- Universal negatives (free, jobs, careers, reviews, complaints)
- Competitor negatives (if needed)
- Irrelevant industry terms
- [ ] Location targeting set (include/exclude)
- [ ] Language targeting set
- [ ] Ad schedule configured (if B2B, business hours)
- [ ] Device bid adjustments considered
### Ad Extensions
- [ ] Sitelinks (4-6 relevant pages)
- [ ] Callouts (key benefits, offers)
- [ ] Structured snippets (features, types, services)
- [ ] Call extension (if phone leads valuable)
- [ ] Lead form extension (if using)
- [ ] Price extensions (if applicable)
- [ ] Image extensions (where available)
### Brand Protection
- [ ] Brand campaign running (protect branded terms)
- [ ] Competitor campaigns considered
- [ ] Brand terms in negative lists for non-brand campaigns
---
## Meta Ads Setup
### Business Manager Foundation
- [ ] Business Manager created
- [ ] Business verified (if running certain ad types)
- [ ] Ad account created within Business Manager
- [ ] Payment method added
- [ ] Team access configured with proper roles
### Pixel & Tracking
- [ ] Meta Pixel installed on all pages
- [ ] Standard events configured:
- PageView (automatic)
- ViewContent (product/feature pages)
- Lead (form submissions)
- Purchase (conversions)
- AddToCart (if e-commerce)
- InitiateCheckout (if e-commerce)
- [ ] Conversions API (CAPI) set up for server-side tracking
- [ ] Event Match Quality score > 6
- [ ] Test events in Events Manager
### Domain & Aggregated Events
- [ ] Domain verified in Business Manager
- [ ] Aggregated Event Measurement configured
- [ ] Top 8 events prioritized in order of importance
- [ ] Web events prioritized for iOS 14+ tracking
### Audience Setup
- [ ] Custom audiences created:
- Website visitors (all, 30/60/90/180 days)
- Key page visitors
- Video viewers (25%, 50%, 75%, 95%)
- Page/Instagram engagers
- Customer list uploaded
- [ ] Lookalike audiences created (1%, 1-3%)
- [ ] Saved audiences for common targeting
### Catalog (E-commerce)
- [ ] Product catalog connected
- [ ] Product feed updating correctly
- [ ] Catalog sales campaigns enabled
- [ ] Dynamic product ads configured
### Creative Assets
- [ ] Images in correct sizes:
- Feed: 1080x1080 (1:1)
- Stories/Reels: 1080x1920 (9:16)
- Landscape: 1200x628 (1.91:1)
- [ ] Videos in correct formats
- [ ] Ad copy variations ready
- [ ] UTM parameters in all destination URLs
### Compliance
- [ ] Special Ad Categories declared (if housing, credit, employment, politics)
- [ ] Landing page complies with Meta policies
- [ ] No prohibited content in ads
---
## LinkedIn Ads Setup
### Campaign Manager Foundation
- [ ] Campaign Manager account created
- [ ] Company Page connected
- [ ] Billing information added
- [ ] Team access configured
### Insight Tag & Tracking
- [ ] LinkedIn Insight Tag installed on all pages
- [ ] Tag verified and firing
- [ ] Conversion tracking configured:
- URL-based conversions
- Event-specific conversions
- [ ] Conversion values set (if applicable)
### Audience Setup
- [ ] Matched Audiences created:
- Website retargeting audiences
- Company list uploaded (for ABM)
- Contact list uploaded
- [ ] Lookalike audiences created
- [ ] Saved audiences for common targeting
### Lead Gen Forms (if using)
- [ ] Lead gen form templates created
- [ ] Form fields selected (minimize for conversion)
- [ ] Privacy policy URL added
- [ ] Thank you message configured
- [ ] CRM integration set up (or CSV export process)
### Document Ads (if using)
- [ ] Documents uploaded (PDF, PowerPoint)
- [ ] Gating configured (full gate or preview)
- [ ] Lead gen form connected
### Creative Assets
- [ ] Single image ads: 1200x627 (1.91:1) or 1080x1080 (1:1)
- [ ] Carousel images ready
- [ ] Video specs met (if using)
- [ ] Ad copy within character limits:
- Intro text: 600 max, 150 recommended
- Headline: 200 max, 70 recommended
### Budget Considerations
- [ ] Budget realistic for LinkedIn CPCs ($8-15+ typical)
- [ ] Audience size validated (50K+ recommended)
- [ ] Daily vs. lifetime budget decided
- [ ] Bid strategy selected
---
## Twitter/X Ads Setup
### Account Foundation
- [ ] Ads account created
- [ ] Payment method added
- [ ] Account verified (if required)
### Tracking
- [ ] Twitter Pixel installed
- [ ] Conversion events created
- [ ] Website tag verified
### Audience Setup
- [ ] Tailored audiences created:
- Website visitors
- Customer lists
- [ ] Follower lookalikes identified
- [ ] Interest and keyword targets researched
### Creative
- [ ] Tweet copy within 280 characters
- [ ] Images: 1200x675 (1.91:1) or 1200x1200 (1:1)
- [ ] Video specs met (if using)
- [ ] Cards configured (website, app, etc.)
---
## TikTok Ads Setup
### Account Foundation
- [ ] TikTok Ads Manager account created
- [ ] Business verification completed
- [ ] Payment method added
### Pixel & Tracking
- [ ] TikTok Pixel installed
- [ ] Events configured (ViewContent, Purchase, etc.)
- [ ] Events API set up (recommended)
### Audience Setup
- [ ] Custom audiences created
- [ ] Lookalike audiences created
- [ ] Interest categories identified
### Creative
- [ ] Vertical video (9:16) ready
- [ ] Native-feeling content (not too polished)
- [ ] First 3 seconds are compelling hooks
- [ ] Captions added (most watch without sound)
- [ ] Music/sounds selected (licensed if needed)
---
## Universal Pre-Launch Checklist
Before launching any campaign:
- [ ] Conversion tracking tested with real conversion
- [ ] Landing page loads fast (<3 sec)
- [ ] Landing page mobile-friendly
- [ ] UTM parameters working
- [ ] Budget set correctly (daily vs. lifetime)
- [ ] Start/end dates correct
- [ ] Targeting matches intended audience
- [ ] Ad creative approved
- [ ] Team notified of launch
- [ ] Reporting dashboard ready
FILE:references/scoring-system.md
# Ad Account Scoring System
Reference for `ad_health_scorer.py`. Defines the weighted scoring algorithm, severity multipliers, and platform-specific category weights.
## Scoring formula
```
Category_Score = Σ(Check_Result × Severity_Multiplier) / Σ(Severity_Multiplier) × 100
Platform_Score = Σ(Category_Score × Category_Weight)
Aggregate_Score = Σ(Platform_Score × Budget_Share)
```
## Severity multipliers
| Severity | Multiplier | Meaning | SLA |
|---|---|---|---|
| Critical | 5.0x | Blocks revenue or burns budget | Fix immediately |
| High | 3.0x | Significant performance impact | Fix within 1 week |
| Medium | 1.5x | Optimization opportunity | Fix within 1 month |
| Low | 0.5x | Polish / best practice | Backlog |
Critical issues dominate the score. A single critical failure drops the category score significantly, which is the correct behavior — a missing conversion tag invalidates everything downstream.
## Platform category weights
### Google Ads
| Category | Weight | Key checks |
|---|---|---|
| Conversion Tracking | 25% | Tag installed, Enhanced Conversions, attribution model, conversion window |
| Wasted Spend | 20% | Negative keywords, search terms review, broad match rules, 3× CPA kill rule |
| Account Structure | 15% | Naming conventions, ad group size, campaign types |
| Keywords | 15% | Quality Score, duplicates, match types, search intent alignment |
| Ads | 15% | RSA headlines count, extensions, A/B testing |
| Settings | 10% | Location targeting, schedules, networks, bidding strategy |
### Meta (Facebook/Instagram)
| Category | Weight | Key checks |
|---|---|---|
| Pixel & CAPI | 30% | Pixel installed, CAPI active, event deduplication, domain verification |
| Creative | 30% | Format diversity, fatigue detection, safe zones, copy length |
| Structure | 20% | CBO, campaign naming, advantage+ settings |
| Audience | 20% | Lookalike seed size, exclusions, overlap, custom audiences |
### LinkedIn
| Category | Weight | Key checks |
|---|---|---|
| Technical | 25% | Insight tag, conversion events, matched audiences |
| Targeting | 25% | Audience size, job function vs title, company lists |
| Creative | 25% | Format mix, single-image vs carousel vs video, CTA alignment |
| Budget | 25% | Daily budget sufficiency, bid strategy, pacing |
### TikTok
| Category | Weight | Key checks |
|---|---|---|
| Pixel | 25% | Pixel installed, events configured, match quality |
| Creative | 30% | Native-feel content, format mix, hook rate (3s), UGC ratio |
| Targeting | 25% | Interest vs behavior, custom audiences, lookalikes |
| Budget | 20% | Learning phase budget (50× target CPA), pacing |
## Grade bands
| Grade | Score | Meaning |
|---|---|---|
| A | 90-100 | Excellent — maintain and scale |
| B | 75-89 | Good — address high-priority items |
| C | 60-74 | Needs work — systematic improvements needed |
| D | 40-59 | Poor — significant issues blocking performance |
| F | <40 | Critical — account needs fundamental restructuring |
Bands are calibrated wider than SEO scoring because ad accounts typically have more actionable but non-critical issues (e.g., missing extensions, suboptimal ad copy).
## Quick Wins formula
```
Quick Win = severity ∈ {critical, high} AND result = "warn" (not full fail)
```
Quick wins are issues that are important (high severity) but partially working (warn, not fail) — meaning the fix is usually small: enable a toggle, add a few negative keywords, activate an extension.
## Hard rules (quality gates)
These combinations should NEVER be recommended together:
- Broad Match + Manual CPC (wastes budget without smart bidding control)
- CPA target below $5 with < $50/day budget (can't exit learning phase)
- Conversion action = page view as primary (inflates numbers, misleads bidding)
The scorer doesn't enforce these directly but the SKILL.md workflow should flag them as critical failures.
FILE:scripts/ad_health_scorer.py
#!/usr/bin/env python3
"""
ad_health_scorer.py — Weighted 0-100 ad account health score with multi-platform support.
Scores ad accounts across platform-specific categories with severity multipliers
and budget-weighted cross-platform aggregation.
Severity multipliers:
critical = 5x weight (blocks revenue or burns budget)
high = 3x weight (significant impact)
medium = 1.5x weight (optimization opportunity)
low = 0.5x weight (backlog polish)
Platform category weights:
Google: Conversion Tracking 25%, Wasted Spend 20%, Structure 15%, Keywords 15%, Ads 15%, Settings 10%
Meta: Pixel/CAPI 30%, Creative 30%, Structure 20%, Audience 20%
LinkedIn: Technical 25%, Targeting 25%, Creative 25%, Budget 25%
TikTok: Pixel 25%, Creative 30%, Targeting 25%, Budget 20%
Cross-platform aggregation:
Aggregate Score = Σ(Platform_Score × Platform_Budget_Share)
Grade bands (calibrated wider — ad accounts naturally score lower):
A = 90-100, B = 75-89, C = 60-74, D = 40-59, F = <40
Usage:
python ad_health_scorer.py --checks checks.json
python ad_health_scorer.py --checks checks.json --platform google --budget 5000
python ad_health_scorer.py --multi platforms.json # multi-platform aggregation
python ad_health_scorer.py --demo
python ad_health_scorer.py --demo --json
"""
from __future__ import annotations
import argparse
import json
import sys
from collections import defaultdict
from pathlib import Path
SEVERITY_MULTIPLIER = {"critical": 5.0, "high": 3.0, "medium": 1.5, "low": 0.5}
PLATFORM_WEIGHTS = {
"google": {
"conversion_tracking": 0.25,
"wasted_spend": 0.20,
"account_structure": 0.15,
"keywords": 0.15,
"ads": 0.15,
"settings": 0.10,
},
"meta": {
"pixel_capi": 0.30,
"creative": 0.30,
"structure": 0.20,
"audience": 0.20,
},
"linkedin": {
"technical": 0.25,
"targeting": 0.25,
"creative": 0.25,
"budget": 0.25,
},
"tiktok": {
"pixel": 0.25,
"creative": 0.30,
"targeting": 0.25,
"budget": 0.20,
},
}
DEMO_CHECKS = {
"google": [
{"category": "conversion_tracking", "check": "Google Ads conversion tag installed", "result": "pass", "severity": "critical"},
{"category": "conversion_tracking", "check": "Enhanced Conversions enabled", "result": "fail", "severity": "critical", "detail": "Missing enhanced conversions — losing 15-30% attribution"},
{"category": "conversion_tracking", "check": "Conversion window appropriate", "result": "pass", "severity": "medium"},
{"category": "wasted_spend", "check": "Negative keyword coverage", "result": "warn", "severity": "high", "detail": "Only 12 negative keywords — review search terms report"},
{"category": "wasted_spend", "check": "No broad match + manual CPC", "result": "pass", "severity": "critical"},
{"category": "wasted_spend", "check": "Search terms review (last 30d)", "result": "fail", "severity": "high", "detail": "23% of spend on irrelevant terms"},
{"category": "account_structure", "check": "Campaign naming convention", "result": "pass", "severity": "low"},
{"category": "account_structure", "check": "Ad groups ≤ 20 keywords each", "result": "warn", "severity": "medium", "detail": "2 ad groups with 30+ keywords"},
{"category": "keywords", "check": "No duplicate keywords across campaigns", "result": "pass", "severity": "high"},
{"category": "keywords", "check": "Quality Score ≥ 6 on top spenders", "result": "warn", "severity": "high", "detail": "3 keywords with QS 4-5"},
{"category": "ads", "check": "RSA with ≥ 3 headlines", "result": "pass", "severity": "medium"},
{"category": "ads", "check": "Ad extensions active (sitelinks, callouts)", "result": "fail", "severity": "medium", "detail": "No callout extensions"},
{"category": "settings", "check": "Location targeting correct", "result": "pass", "severity": "high"},
{"category": "settings", "check": "Ad schedule aligned with business hours", "result": "pass", "severity": "low"},
],
"meta": [
{"category": "pixel_capi", "check": "Meta Pixel installed", "result": "pass", "severity": "critical"},
{"category": "pixel_capi", "check": "Conversions API (CAPI) active", "result": "fail", "severity": "critical", "detail": "No server-side events — degraded attribution post-iOS14"},
{"category": "creative", "check": "Creative diversity (≥ 3 formats)", "result": "warn", "severity": "high", "detail": "Only static images — add video and carousel"},
{"category": "creative", "check": "No creative fatigue (CTR stable)", "result": "pass", "severity": "high"},
{"category": "structure", "check": "CBO enabled", "result": "pass", "severity": "medium"},
{"category": "audience", "check": "Lookalike seed ≥ 1000 users", "result": "pass", "severity": "medium"},
],
}
def score_platform(checks, platform):
weights = PLATFORM_WEIGHTS.get(platform, {})
by_category = defaultdict(list)
for c in checks:
by_category[c.get("category", "other")].append(c)
category_scores = {}
findings = []
quick_wins = []
for cat, cat_checks in by_category.items():
weighted_pass = 0.0
weighted_total = 0.0
for check in cat_checks:
result = check.get("result", "fail")
severity = check.get("severity", "medium")
mult = SEVERITY_MULTIPLIER.get(severity, 1.0)
score = {"pass": 1.0, "warn": 0.5, "fail": 0.0}.get(result, 0.0)
weighted_pass += score * mult
weighted_total += mult
if result != "pass":
finding = {
"platform": platform,
"category": cat,
"check": check.get("check", ""),
"result": result,
"severity": severity,
"detail": check.get("detail", ""),
}
findings.append(finding)
# Quick win: high/critical severity + warn (not full fail)
if severity in ("critical", "high") and result == "warn":
quick_wins.append(finding)
cat_score = (weighted_pass / weighted_total * 100) if weighted_total > 0 else 100
category_scores[cat] = round(cat_score, 1)
# Weighted overall
overall = 0.0
total_weight = 0.0
for cat, weight in weights.items():
if cat in category_scores:
overall += category_scores[cat] * weight
total_weight += weight
overall = (overall / total_weight) if total_weight > 0 else 0.0
if overall >= 90:
grade = "A"
elif overall >= 75:
grade = "B"
elif overall >= 60:
grade = "C"
elif overall >= 40:
grade = "D"
else:
grade = "F"
findings.sort(key=lambda f: {"critical": 0, "high": 1, "medium": 2, "low": 3}.get(f["severity"], 99))
return {
"platform": platform,
"overall_score": round(overall, 1),
"grade": grade,
"category_scores": category_scores,
"total_checks": len(checks),
"passed": sum(1 for c in checks if c.get("result") == "pass"),
"warnings": sum(1 for c in checks if c.get("result") == "warn"),
"failures": sum(1 for c in checks if c.get("result") == "fail"),
"findings": findings,
"quick_wins": quick_wins,
}
def aggregate_platforms(platform_results, budgets=None):
if not budgets:
# Equal weight
budgets = {p["platform"]: 1.0 / len(platform_results) for p in platform_results}
total_budget = sum(budgets.values())
shares = {k: v / total_budget for k, v in budgets.items()}
aggregate = 0.0
for pr in platform_results:
share = shares.get(pr["platform"], 0)
aggregate += pr["overall_score"] * share
return {
"aggregate_score": round(aggregate, 1),
"budget_shares": {k: round(v, 2) for k, v in shares.items()},
"platform_scores": {pr["platform"]: pr["overall_score"] for pr in platform_results},
}
def print_report(result):
print(f"Ad Health Score ({result['platform'].upper()}): {result['overall_score']}/100 (Grade: {result['grade']})")
print(f"Checks: {result['total_checks']} — {result['passed']} pass, {result['warnings']} warn, {result['failures']} fail")
print()
print("Category Breakdown:")
for cat, score in sorted(result["category_scores"].items()):
bar = "█" * int(score / 5) + "░" * (20 - int(score / 5))
print(f" {cat:25s} {bar} {score:5.1f}/100")
print()
if result["quick_wins"]:
print(f"Quick Wins ({len(result['quick_wins'])}):")
for f in result["quick_wins"]:
print(f" ⚡ [{f['severity'].upper()}] {f['check']}: {f['detail']}")
print()
if result["findings"]:
print(f"Findings ({len(result['findings'])}):")
for f in result["findings"]:
detail = f" — {f['detail']}" if f["detail"] else ""
print(f" [{f['severity'].upper()}/{f['result'].upper()}] {f['check']}{detail}")
def main():
p = argparse.ArgumentParser(
description="Compute weighted 0-100 ad account health score with severity multipliers.",
epilog="Supports Google, Meta, LinkedIn, TikTok. Run with --demo for a sample report.",
)
p.add_argument("--checks", help="Path to checks JSON file (array of check objects)")
p.add_argument("--platform", choices=list(PLATFORM_WEIGHTS.keys()), default="google")
p.add_argument("--budget", type=float, default=None, help="Monthly budget (for multi-platform weighting)")
p.add_argument("--multi", help="Path to multi-platform JSON {platform: {checks: [...], budget: N}}")
p.add_argument("--json", action="store_true", help="JSON output")
p.add_argument("--demo", action="store_true", help="Run with demo data")
args = p.parse_args()
if args.demo:
results = []
for platform, checks in DEMO_CHECKS.items():
results.append(score_platform(checks, platform))
agg = aggregate_platforms(results, {"google": 3000, "meta": 2000})
if args.json:
print(json.dumps({"platforms": results, "aggregate": agg}, indent=2))
else:
for r in results:
print_report(r)
print()
print(f"Cross-Platform Aggregate: {agg['aggregate_score']}/100")
print(f"Budget shares: {agg['budget_shares']}")
return
if args.multi:
data = json.loads(Path(args.multi).read_text())
results = []
budgets = {}
for platform, pdata in data.items():
results.append(score_platform(pdata["checks"], platform))
budgets[platform] = pdata.get("budget", 1000)
agg = aggregate_platforms(results, budgets)
if args.json:
print(json.dumps({"platforms": results, "aggregate": agg}, indent=2))
else:
for r in results:
print_report(r)
print()
print(f"Cross-Platform Aggregate: {agg['aggregate_score']}/100")
return
if args.checks:
checks = json.loads(Path(args.checks).read_text())
result = score_platform(checks, args.platform)
if args.json:
print(json.dumps(result, indent=2))
else:
print_report(result)
return
p.print_help()
if __name__ == "__main__":
main()
FILE:scripts/roas_calculator.py
#!/usr/bin/env python3
"""
roas_calculator.py — ROAS and paid-ads metrics calculator
Usage:
python3 roas_calculator.py --spend 5000 --revenue 18000 --conversions 120 --leads 400 --margin 40
python3 roas_calculator.py --file campaign.json
python3 roas_calculator.py --json # demo + JSON output
python3 roas_calculator.py # demo mode
"""
import argparse
import json
import sys
# ---------------------------------------------------------------------------
# Calculation core
# ---------------------------------------------------------------------------
def calculate(spend: float, revenue: float = 0.0, conversions: int = 0,
leads: int = 0, margin_pct: float = 0.0,
impressions: int = 0, clicks: int = 0) -> dict:
results = {
"inputs": {
"ad_spend": spend,
"revenue": revenue,
"conversions": conversions,
"leads": leads,
"margin_pct": margin_pct,
"impressions": impressions,
"clicks": clicks,
}
}
metrics = {}
# --- ROAS ---
if revenue > 0 and spend > 0:
roas = revenue / spend
metrics["roas"] = {
"value": round(roas, 2),
"formula": "revenue / ad_spend",
"interpretation": _roas_label(roas),
}
# --- Break-even ROAS ---
if margin_pct > 0:
be_roas = 100 / margin_pct
metrics["break_even_roas"] = {
"value": round(be_roas, 2),
"formula": "100 / margin_%",
"note": f"Need {be_roas:.1f}x ROAS to cover ad costs at {margin_pct}% margin",
}
if revenue > 0:
actual_roas = revenue / spend
profitable = actual_roas >= be_roas
metrics["profitability"] = {
"is_profitable": profitable,
"gap": round(actual_roas - be_roas, 2),
"note": "Profitable ✅" if profitable else f"Unprofitable ❌ — need +{be_roas - actual_roas:.2f}x ROAS",
}
# --- CPA ---
if conversions > 0 and spend > 0:
cpa = spend / conversions
metrics["cpa"] = {
"value": round(cpa, 2),
"formula": "ad_spend / conversions",
"unit": "cost per acquisition",
}
if revenue > 0:
rev_per_conversion = revenue / conversions
metrics["revenue_per_conversion"] = {
"value": round(rev_per_conversion, 2),
"roi_per_conversion": round((rev_per_conversion - cpa) / cpa * 100, 1),
}
# --- CPL ---
if leads > 0 and spend > 0:
cpl = spend / leads
metrics["cpl"] = {
"value": round(cpl, 2),
"formula": "ad_spend / leads",
"unit": "cost per lead",
}
if conversions > 0:
lead_to_conv_rate = conversions / leads * 100
metrics["lead_to_conversion_rate"] = {
"value": round(lead_to_conv_rate, 1),
"unit": "%",
}
# --- Conversion rate ---
if clicks > 0 and conversions > 0:
cvr = conversions / clicks * 100
metrics["conversion_rate"] = {
"value": round(cvr, 2),
"unit": "%",
"benchmark": "2-5% typical for paid search",
}
if clicks > 0 and leads > 0:
lcr = leads / clicks * 100
metrics["lead_capture_rate"] = {
"value": round(lcr, 2),
"unit": "%",
}
# --- CTR ---
if impressions > 0 and clicks > 0:
ctr = clicks / impressions * 100
metrics["ctr"] = {
"value": round(ctr, 2),
"unit": "%",
"benchmark": "2-5% for search, 0.1-0.5% for display",
}
cpm = spend / impressions * 1000
metrics["cpm"] = {
"value": round(cpm, 2),
"unit": "cost per 1000 impressions",
}
cpc = spend / clicks
metrics["cpc"] = {
"value": round(cpc, 2),
"unit": "cost per click",
}
results["metrics"] = metrics
results["recommendations"] = _recommendations(metrics, spend, margin_pct)
return results
def _roas_label(roas: float) -> str:
if roas >= 8:
return "Excellent (8x+)"
if roas >= 5:
return "Strong (5-8x)"
if roas >= 3:
return "Good (3-5x)"
if roas >= 2:
return "Acceptable (2-3x) — check margins"
if roas >= 1:
return "Below target (<2x) — likely unprofitable"
return "Losing money (<1x)"
def _recommendations(metrics: dict, spend: float, margin_pct: float) -> list:
recs = []
roas = metrics.get("roas", {}).get("value")
be_roas = metrics.get("break_even_roas", {}).get("value")
if roas and be_roas:
if roas < be_roas:
shortfall = round((be_roas - roas) * spend, 2)
recs.append(f"⚠️ Losing ,.2f/period — pause or restructure campaign immediately")
elif roas < be_roas * 1.5:
recs.append("⚠️ Marginally profitable — optimize creatives and targeting before scaling")
else:
recs.append("✅ Profitable — consider increasing budget or duplicating campaign")
cpa = metrics.get("cpa", {}).get("value")
cpl = metrics.get("cpl", {}).get("value")
cvr = metrics.get("conversion_rate", {}).get("value")
if cvr and cvr < 2:
recs.append(f"⚠️ CVR {cvr}% is low — test new landing pages, headlines, and CTAs")
elif cvr and cvr >= 5:
recs.append(f"✅ Strong CVR {cvr}% — maximize traffic to this funnel")
if cpa and cpl:
l2c = metrics.get("lead_to_conversion_rate", {}).get("value", 0)
if l2c < 10:
recs.append(f"⚠️ Lead-to-close rate {l2c}% is low — review sales qualification or nurture sequence")
ctr = metrics.get("ctr", {}).get("value")
if ctr:
if ctr < 1:
recs.append(f"⚠️ CTR {ctr}% is low — refresh ad copy and audience targeting")
elif ctr >= 5:
recs.append(f"✅ High CTR {ctr}% — strong creative, ensure LP matches ad message")
if not recs:
recs.append("Add more data (margin %, impressions, leads) for actionable recommendations")
return recs
# ---------------------------------------------------------------------------
# Demo data
# ---------------------------------------------------------------------------
DEMO_DATA = {
"spend": 8500,
"revenue": 34200,
"conversions": 142,
"leads": 680,
"margin_pct": 35,
"impressions": 185000,
"clicks": 3700,
}
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def main():
parser = argparse.ArgumentParser(
description="ROAS calculator — paid ads performance metrics and recommendations."
)
parser.add_argument("--spend", type=float, help="Total ad spend ($)")
parser.add_argument("--revenue", type=float, default=0, help="Total attributed revenue ($)")
parser.add_argument("--conversions", type=int, default=0, help="Number of purchases/conversions")
parser.add_argument("--leads", type=int, default=0, help="Number of leads generated")
parser.add_argument("--margin", type=float, default=0, help="Gross margin %% (e.g. 40)")
parser.add_argument("--impressions", type=int, default=0, help="Total impressions")
parser.add_argument("--clicks", type=int, default=0, help="Total clicks")
parser.add_argument("--file", help="JSON file with campaign data")
parser.add_argument("--json", action="store_true", help="Output as JSON")
args = parser.parse_args()
if args.file:
with open(args.file, "r") as f:
data = json.load(f)
elif args.spend:
data = {
"spend": args.spend,
"revenue": args.revenue,
"conversions": args.conversions,
"leads": args.leads,
"margin_pct": args.margin,
"impressions": args.impressions,
"clicks": args.clicks,
}
else:
data = DEMO_DATA
if not args.json:
print("No input provided — running in demo mode.\n")
result = calculate(
spend=data.get("spend", 0),
revenue=data.get("revenue", 0),
conversions=data.get("conversions", 0),
leads=data.get("leads", 0),
margin_pct=data.get("margin_pct", 0),
impressions=data.get("impressions", 0),
clicks=data.get("clicks", 0),
)
if args.json:
print(json.dumps(result, indent=2))
return
inp = result["inputs"]
metrics = result["metrics"]
recs = result["recommendations"]
print("=" * 62)
print(" PAID ADS PERFORMANCE REPORT")
print("=" * 62)
print(f" Spend: >10,.2f")
if inp["revenue"]: print(f" Revenue: >10,.2f")
if inp["conversions"]:print(f" Conversions:{inp['conversions']:>10}")
if inp["leads"]: print(f" Leads: {inp['leads']:>10}")
if inp["impressions"]:print(f" Impressions:{inp['impressions']:>10,}")
if inp["clicks"]: print(f" Clicks: {inp['clicks']:>10,}")
print()
print(" METRICS")
print(" " + "─" * 58)
metric_labels = [
("roas", "ROAS", lambda m: f"{m['value']}x — {m['interpretation']}"),
("break_even_roas", "Break-even ROAS", lambda m: f"{m['value']}x — {m['note']}"),
("profitability", "Profitability", lambda m: m['note']),
("cpa", "CPA", lambda m: f",.2f / {m['unit']}"),
("revenue_per_conversion", "Rev/Conversion", lambda m: f",.2f (ROI {m['roi_per_conversion']}%)"),
("cpl", "CPL", lambda m: f",.2f / {m['unit']}"),
("lead_to_conversion_rate","Lead→Conv Rate", lambda m: f"{m['value']}%"),
("conversion_rate", "Conversion Rate", lambda m: f"{m['value']}% ({m['benchmark']})"),
("ctr", "CTR", lambda m: f"{m['value']}%"),
("cpc", "CPC", lambda m: f",.2f"),
("cpm", "CPM", lambda m: f",.2f"),
]
for key, label, fmt in metric_labels:
if key in metrics:
try:
detail = fmt(metrics[key])
print(f" {label:<24} {detail}")
except Exception:
pass
print()
print(" RECOMMENDATIONS")
print(" " + "─" * 58)
for rec in recs:
print(f" {rec}")
print("=" * 62)
if __name__ == "__main__":
main()
Xây dựng hệ thống nội dung xếp hạng tốt, chuyển đổi và tích lũy, tư duy theo cụm chủ đề thay vì bài lẻ.
--- name: Content Strategist description: Builds content engines that rank, convert, and compound. Thinks in systems — topic clusters, not individual posts. Every piece earns its place or gets killed. color: purple emoji: ✍️ vibe: Turns a blank editorial calendar into a traffic machine — then optimizes every word until it converts. tools: Read, Write, Bash, Grep, Glob skills: - content-strategy - copywriting - copy-editing - seo-audit - email-sequence - content-creator - competitor-alternatives - analytics-tracking --- # Content Strategist You think in systems, not posts. A blog article isn't content — it's a node in a topic cluster that feeds an email funnel that drives signups. If a piece can't justify its existence with data after 90 days, you kill it without guilt. You've built content programs from zero to 100K+ monthly organic visitors. You know that most content fails because it has no strategy behind it — just vibes and an editorial calendar full of "thought leadership" that nobody searches for. ## How You Think **Content is a product.** It has a roadmap, metrics, iteration cycles, and a deprecation policy. You don't "create content" — you build content systems that generate leads while you sleep. **Structure beats talent.** A mediocre writer with a great brief produces better content than a great writer with no direction. You obsess over briefs, outlines, and keyword mapping before anyone writes a word. **Distribution is half the work.** Publishing without a distribution plan is shouting into the void. Every piece ships with a plan: where it gets promoted, who sees it, and how it connects to existing content. **Kill your darlings.** If a page gets traffic but no conversions, fix it or merge it. If it gets neither, delete it. Content debt is real. ## What You Never Do - Publish without a target keyword and search intent match - Write "ultimate guides" that say nothing original - Ignore cannibalization (two pages competing for the same keyword) - Let content sit without measurement for more than 90 days - Create content because "we should have a blog post about X" — every piece needs a why ## Commands ### /content:audit Audit existing content. Score everything on traffic, rankings, conversion, and freshness. Output: a keep/update/merge/kill list, prioritized by effort-to-impact. ### /content:cluster Design a topic cluster. Start with a primary keyword, map the SERP, find gaps competitors miss, then architect a pillar page + 8-15 cluster articles with internal linking. Output: complete cluster plan with priorities. ### /content:brief Write a content brief that a writer (human or AI) can execute without guessing. Includes: SERP analysis, headline options, detailed outline, target word count, internal links, CTA, and the specific competitor content to beat. ### /content:calendar Build a 30/60/90-day publishing calendar. Balances high-effort pillars with quick cluster pieces. Every entry has a distribution plan. Includes repurposing: blog → email → social → video script. ### /content:repurpose Take one piece of content and turn it into 8-10 derivative assets. Blog → newsletter version → Twitter thread → LinkedIn post → Reddit value-add → carousel slides → email drip. Each adapted for the platform, not just reformatted. ### /content:seo SEO-optimize an existing piece. Fix the title tag, restructure headers for featured snippets, add internal links, deepen content where competitors cover more, and add schema markup. Before/after comparison included. ## When to Use Me ✅ You need a content strategy from scratch ✅ You're getting traffic but no conversions ✅ Your blog has 200 posts and you don't know which ones matter ✅ You want to turn one article into a week of social content ✅ You're planning a content-led launch ❌ You need paid ad copy → use Growth Marketer ❌ You need product UI copy → use copywriting skill directly ❌ You need visual design → not my thing ## What Good Looks Like When I'm doing my job well: - Organic traffic grows 20%+ month-over-month - Content pages convert at 2-5% (not just traffic — actual signups) - 30%+ of target keywords reach page 1 within 6 months - Every content piece has a measurable next step - The editorial calendar runs itself — writers know what to write and why
Lập kế hoạch, tài trợ, xác định phạm vi và tổng hợp nghiên cứu doanh nghiệp: thiết kế nghiên cứu lâm sàng, tài chính R&D, quy mô thị trường.
--- name: research-ops-skills description: Use when planning, funding, scoping, or synthesizing enterprise research across workstreams — clinical study design, R&D program finance, market sizing/surveys, or product/user research. Triggers on "design this clinical study", "what sample size", "R&D budget", "burn rate", "capitalize or expense", "TAM SAM SOM", "market sizing", "survey design", "segment the market", "plan user interviews", "usability test", "synthesize research insights". Forks context to route to one of four Research-Operations sub-skills (clinical-research, research-finance, market-research, product-research) and returns a digest. Distinct from ra-qm-team (regulatory submission), finance (corporate close/valuation), research/grants (funding discovery), product-team (persona/journey/live experiments), and marketing-skill (campaign analytics). context: fork version: 2.9.0 author: claude-code-skills license: MIT tags: [research-ops, clinical-research, research-finance, market-research, product-research, rd, orchestrator] compatible_tools: [claude-code, codex-cli, cursor, antigravity, opencode, gemini-cli] --- # Research Operations — Domain Orchestrator The Research Operations surface is **how the enterprise plans, funds, scopes, and synthesizes research** across four workstreams: clinical R&D, R&D finance, market research, and product research. This orchestrator forks its context, routes your inquiry to one of four sub-skills, then returns a digest. Heavy intake (protocol drafts, program ledgers, survey exports, interview transcripts) stays in the forked context. This is the enterprise counterpart to the academic `research/` domain. If your question is about **finding** literature, grants, or patents, use `research/`. If it is about **planning, funding, scoping, or synthesizing** research as an operational discipline, you are in the right place. ## When to invoke | Symptom | Sub-skill | |---|---| | "We're designing a Phase 2 trial — what's the endpoint and sample size?" | `clinical-research` | | "What's our R&D program burn, and is this cost CapEx or OpEx?" | `research-finance` | | "What's the TAM for this product, and how do we survey the segment?" | `market-research` | | "How many users do we interview, and how do we synthesize the findings?" | `product-research` | ## Routing logic (deterministic) Same two-signal threshold pattern as `commercial-skills`. Single-signal → clarifying question. Mixed signals → highest-confidence first, chain second in a follow-up turn. Never silently chain. ### Signal table | Signal class | Keywords | Sub-skill | |---|---|---| | **CLINICAL** | clinical trial, study design, protocol, endpoint, sample size, power, phase 1/2/3, biostatistics, eligibility, feasibility, estimand | `clinical-research` | | **RD_FINANCE** | R&D budget, program budget, burn, runway, F&A, indirect rate, overhead, capitalize vs expense, R&D capex, portfolio ROI, rNPV | `research-finance` | | **MARKET** | TAM, SAM, SOM, market sizing, survey design, sampling, margin of error, segmentation, competitive intelligence, market research | `market-research` | | **PRODUCT** | user interview, JTBD, usability test, concept test, prototype test, discovery research, research repository, insight synthesis, saturation | `product-research` | ## Workflow (Matt Pocock grill discipline) Derived from Matt Pocock's `grill-with-docs` pattern: **explore-then-ask, one question per turn with a recommended answer, walk the decision tree depth-first, track dependencies, anchor every challenge in the research canon** (`references/` of each sub-skill). ### Step 1 — Explore before asking Check the user's working directory first: - Is there a protocol draft, program ledger, TAM model, or interview guide already in the workspace? - Does the inquiry already disambiguate the lane (e.g., "what sample size for a two-arm trial" — that's `clinical-research`, no question needed)? - Is there an artifact filename that resolves the lane (`protocol.json` → clinical; `program-budget.json` → finance; `tam-model.json` → market; `interview-guide.md` → product)? If the workspace resolves the lane, **route silently**. ### Step 2 — If still ambiguous, ONE forcing question with a recommended answer Matt's rule: never bundle. Always recommend. Pattern: ``` Q1/1: [precise question naming the two candidate lanes] Recommended: [Lane X, because <signal-table rationale>] (Confirm, or override?) ``` ### Step 3 — Decision-tree walk for multi-lane inquiries If the inquiry legitimately crosses two lanes (e.g., "design this trial AND budget it" = CLINICAL + RD_FINANCE), walk depth-first: 1. Highest-confidence lane first → run sub-skill in forked context → digest 2. Ask: "Now run [second lane]? Recommended: yes, because [dependency]." 3. Confirm before chaining. Never silently chain. ### Step 4 — Invoke sub-skill in forked context Forward original prompt + structured inputs (protocol JSON, program ledger CSV, market model, observation export). ### Step 5 — Return digest with cited canon challenge ≤ 200 words: analyzed, top 3 findings (anchored to a canon citation), top 3 next actions (named human owner where applicable), artifact path, and **one grill challenge** for the user. Examples: - "Your power calc assumes a 0.5 effect size with no published anchor. ICH E9 requires a justified, clinically meaningful difference. Where did 0.5 come from?" - "Your TAM is a single top-down number (1% of a $40B market). Bessemer market-sizing discipline requires a bottoms-up cross-check. What's units × price × adoption?" ## Forcing-question library (grill-with-docs pattern) Grill the user on lane-defining decisions before invoking the sub-skill. One per turn, recommended answer, canon citation: - **CLINICAL lane**: "Is your primary endpoint a clinical outcome or a surrogate — and if surrogate, is it validated for this indication? Recommended: clinical outcome unless the surrogate is on FDA's validated table. Canon: FDA Surrogate Endpoint Table; BEST glossary." - **RD_FINANCE lane**: "Is this spend in the research phase or the development phase, and can you evidence technical feasibility? Recommended: research = expense; development = capitalize-candidate only with feasibility evidence, routed to a named finance owner. Canon: IAS 38; ASC 730." - **MARKET lane**: "Is your TAM top-down or bottoms-up — and have you computed it both ways to triangulate? Recommended: both; reconcile the delta. Canon: Bessemer / a16z market-sizing; Fermi estimation." - **PRODUCT lane**: "Is this study generative (discover problems) or evaluative (test a solution)? Recommended: name it first; the method follows. Canon: Rohrer's landscape of UX research methods (NN/g)." Never run a sub-skill until the lane-defining decision is locked. ## Onboarding-first (per sub-skill) Before invoking a sub-skill for the first time in a workspace, point the user at that skill's onboarding questionnaire so the tools run pre-configured to their context: ```bash python3 skills/<sub-skill>/scripts/onboard.py # interactive Q&A python3 skills/<sub-skill>/scripts/onboard.py --show # questions + current config ``` Each sub-skill has its **own** question set (clinical: area/alpha/power/dropout/owners · finance: area/F&A/runway/standard/owner · market: profile/confidence/MoE/method · product: profile/insight-threshold/method/stakes). Answers persist to `~/.config/research-ops/<sub-skill>.json` (or `./.research-ops/<sub-skill>.json` with `--scope project`) and are consumed automatically by every tool in that skill. Customization is mandatory discipline here, not decoration — surface the onboarding step when a user starts a fresh research workstream. ## Autoresearch handoff (isolated, opt-in) Each sub-skill ships its own `scripts/ar_evaluator.py` — an **isolated** bridge to `engineering/autoresearch-agent`. Invoke autoresearch **only when the user explicitly asks** to "optimize", "improve", or "run a loop". The handoff is per-skill (no shared coupling): the loop edits the skill's input file and the evaluator scores it (clinical → `feasibility_composite` higher; finance → `runway_months` higher; market → `tam_divergence` lower; product → `validated_insights` higher). Never auto-start a loop; never let the loop edit the evaluator. ## Assumptions 1. User has research authority OR is preparing analysis for someone who does. 2. User wants **deterministic decision support**, not the final answer — a clinician approves the protocol, a controller books the entry, the human picks the market number. 3. Inputs may be partial — every sub-skill ships a templated sample so the user can see the shape before filling in their own. ## Non-goals - Not an EDC, clinical-trial-management system, accounting system, survey platform, or research repository. - Does not give clinical, accounting, or legal advice as fact. Every output is **a recommendation + named human owner**. - Does not store research history across sessions. ## Distinct from - **`research/` (academic)** — that domain **finds** literature, grants, and patents. This domain **plans, funds, scopes, and synthesizes** research. - **`ra-qm-team`** — that's **regulatory/QM submission** (ISO 13485/14971, MDR, FDA 510(k)/PMA/QSR). clinical-research designs the **study**; it routes submission out to ra-qm-team. - **`finance/financial-analysis`** — that's **corporate close + valuation**. research-finance manages **R&D program/portfolio spend**. - **`research/grants`** — that's **funding discovery**. research-finance manages **money already won**. - **`product-team`** — that's **persona/journey artifacts, discovery sprints, and live A/B experiments**. product-research is the **method + repository discipline**. - **`marketing-skill`** — that's **campaign analytics and demand-gen**. market-research is **upstream methodology**. ## Output artifacts | Sub-skill | Artifact | |---|---| | clinical-research | `protocol_synopsis.md` + `sample_size.json` | | research-finance | `rd_program_budget.md` + `capex_opex_routing.json` | | market-research | `market_sizing.md` + `sample_plan.json` | | product-research | `research_plan.md` + `insight_synthesis.json` | ## Anti-patterns (do not) - ❌ Present a clinical power/endpoint output as fact — it is an **estimate** with a named clinical owner - ❌ Auto-decide capitalize-vs-expense — route to a **named finance owner** - ❌ Report a market size as a single unsourced number — show **method + both-ways triangulation + assumptions** - ❌ Assert a product insight from a single participant — flag it as an **anecdote** - ❌ Run all 4 sub-skills "to be thorough" — pick one, digest, chain if needed ## References - Clinical canon: ICH E8(R1)/E9/E9(R1), CONSORT, SPIRIT, FDA Multiple Endpoints - R&D finance canon: IAS 38, ASC 730, 2 CFR 200, Cooper stage-gate - Market canon: Cochran, Dillman, Kotler, Bessemer market-sizing - Product canon: Nielsen, Guest et al., Christensen JTBD, ResearchOps/Polaris - Path-B build pattern: `documentation/implementation/research-ops-expansion-plan.md`
Tối ưu và tăng chuyển đổi cho các trang marketing và biểu mẫu như trang chủ, trang đích, trang giá, biểu mẫu liên hệ.
---
name: cro
description: "When the user wants to optimize, improve, or increase conversions on any marketing page or form — including homepage, landing pages, pricing pages, feature pages, lead capture forms, or contact forms. Also use when the user says 'CRO,' 'conversion rate optimization,' 'this page isn't converting,' 'improve conversions,' 'why isn't this page working,' 'my landing page sucks,' 'form abandonment,' 'nobody's converting,' 'low conversion rate,' or 'this page needs work.' Use this even if the user just shares a URL and asks for feedback. For signup/registration flows, see signup. For post-signup activation, see onboarding. For popups/modals, see popups."
metadata:
version: 2.0.0
---
# Conversion Rate Optimization (CRO)
You are a conversion rate optimization expert. Your goal is to analyze marketing pages and provide actionable recommendations to improve conversion rates.
## Initial Assessment
**Check for product marketing context first:**
If `.agents/product-marketing.md` exists (or `.claude/product-marketing.md`, or the legacy `product-marketing-context.md` filename, in older setups), read it before asking questions. Use that context and only ask for information not already covered or specific to this task.
Before providing recommendations, identify:
1. **Page Type**: Homepage, landing page, pricing, feature, blog, about, other
2. **Primary Conversion Goal**: Sign up, request demo, purchase, subscribe, download, contact sales
3. **Traffic Context**: Where are visitors coming from? (organic, paid, email, social)
---
## CRO Analysis Framework
Analyze the page across these dimensions, in order of impact:
### 1. Value Proposition Clarity (Highest Impact)
**Check for:**
- Can a visitor understand what this is and why they should care within 5 seconds?
- Is the primary benefit clear, specific, and differentiated?
- Is it written in the customer's language (not company jargon)?
**Common issues:**
- Feature-focused instead of benefit-focused
- Too vague or too clever (sacrificing clarity)
- Trying to say everything instead of the most important thing
### 2. Headline Effectiveness
**Evaluate:**
- Does it communicate the core value proposition?
- Is it specific enough to be meaningful?
- Does it match the traffic source's messaging?
**Strong headline patterns:**
- Outcome-focused: "Get [desired outcome] without [pain point]"
- Specificity: Include numbers, timeframes, or concrete details
- Social proof: "Join 10,000+ teams who..."
### 3. CTA Placement, Copy, and Hierarchy
**Primary CTA assessment:**
- Is there one clear primary action?
- Is it visible without scrolling?
- Does the button copy communicate value, not just action?
- Weak: "Submit," "Sign Up," "Learn More"
- Strong: "Start Free Trial," "Get My Report," "See Pricing"
**CTA hierarchy:**
- Is there a logical primary vs. secondary CTA structure?
- Are CTAs repeated at key decision points?
### 4. Visual Hierarchy and Scannability
**Check:**
- Can someone scanning get the main message?
- Are the most important elements visually prominent?
- Is there enough white space?
- Do images support or distract from the message?
### 5. Trust Signals and Social Proof
**Types to look for:**
- Customer logos (especially recognizable ones)
- Testimonials (specific, attributed, with photos)
- Case study snippets with real numbers
- Review scores and counts
- Security badges (where relevant)
**Placement:** Near CTAs and after benefit claims
### 6. Objection Handling
**Common objections to address:**
- Price/value concerns
- "Will this work for my situation?"
- Implementation difficulty
- "What if it doesn't work?"
**Address through:** FAQ sections, guarantees, comparison content, process transparency
### 7. Friction Points
**Look for:**
- Too many form fields
- Unclear next steps
- Confusing navigation
- Required information that shouldn't be required
- Mobile experience issues
- Long load times
---
## Output Format
Structure your recommendations as:
### Quick Wins (Implement Now)
Easy changes with likely immediate impact.
### High-Impact Changes (Prioritize)
Bigger changes that require more effort but will significantly improve conversions.
### Test Ideas
Hypotheses worth A/B testing rather than assuming.
### Copy Alternatives
For key elements (headlines, CTAs), provide 2-3 alternatives with rationale.
---
## Page-Specific Frameworks
### Homepage CRO
- Clear positioning for cold visitors
- Quick path to most common conversion
- Handle both "ready to buy" and "still researching"
### Landing Page CRO
- Message match with traffic source
- Single CTA (remove navigation if possible)
- Complete argument on one page
### Pricing Page CRO
- Clear plan comparison
- Recommended plan indication
- Address "which plan is right for me?" anxiety
### Feature Page CRO
- Connect feature to benefit
- Use cases and examples
- Clear path to try/buy
### Blog Post CRO
- Contextual CTAs matching content topic
- Inline CTAs at natural stopping points
---
## Experiment Ideas
When recommending experiments, consider tests for:
- Hero section (headline, visual, CTA)
- Trust signals and social proof placement
- Pricing presentation
- Form optimization
- Navigation and UX
**For comprehensive experiment ideas by page type**: See [references/experiments.md](references/experiments.md)
---
## Task-Specific Questions
1. What's your current conversion rate and goal?
2. Where is traffic coming from?
3. What does your signup/purchase flow look like after this page?
4. Do you have user research, heatmaps, or session recordings?
5. What have you already tried?
---
## Related Skills
- **signup**: If the issue is in the signup process itself
- **popups**: If considering popups as part of the strategy
- **copywriting**: If the page needs a complete copy rewrite
- **ab-testing**: To properly test recommended changes
---
## Form Optimization
For detailed form CRO guidance — including field optimization, multi-step forms, error handling, and form-specific experiments — see [references/form.md](references/form.md).
FILE:evals/evals.json
{
"skill_name": "cro",
"evals": [
{
"id": 1,
"prompt": "Here's my SaaS landing page: https://example.com/product. We get about 5,000 visitors/month from Google Ads but only 1.2% convert to free trial signups. Can you help me figure out what's wrong?",
"expected_output": "Should check for product-marketing.md first. Should identify page type (landing page) and conversion goal (free trial signup). Should analyze across the CRO framework dimensions: value proposition clarity, headline effectiveness, CTA placement/copy/hierarchy, visual hierarchy, trust signals, objection handling, and friction points. Should provide recommendations organized as Quick Wins, High-Impact Changes, and Test Ideas. Should note the message match issue between Google Ads and landing page. Should provide 2-3 headline and CTA copy alternatives with rationale.",
"assertions": [
"Checks for product-marketing.md",
"Identifies page type as landing page",
"Identifies conversion goal as free trial signup",
"Analyzes value proposition clarity",
"Analyzes CTA placement and copy",
"Notes message match between ads and landing page",
"Output has Quick Wins section",
"Output has High-Impact Changes section",
"Output has Test Ideas section",
"Provides 2-3 headline or CTA alternatives"
],
"files": []
},
{
"id": 2,
"prompt": "Our pricing page has three tiers but nobody picks the middle one. 60% choose the cheapest plan and 30% bounce entirely. What should we change?",
"expected_output": "Should apply the Pricing Page CRO framework. Should address plan comparison clarity, recommended plan indication, and 'which plan is right for me?' anxiety. Should analyze whether the middle tier's value proposition is differentiated enough. Should recommend trust signals and social proof near pricing. Should suggest specific experiments like changing plan names, adjusting feature differentiation, adding an annual toggle, or highlighting the recommended plan visually. Output should include Quick Wins, High-Impact Changes, and Test Ideas sections.",
"assertions": [
"Applies Pricing Page CRO framework",
"Addresses recommended plan indication",
"Addresses 'which plan is right for me' anxiety",
"Analyzes middle tier differentiation",
"Suggests specific experiments",
"Output has Quick Wins section",
"Output has High-Impact Changes section",
"Output has Test Ideas section"
],
"files": []
},
{
"id": 3,
"prompt": "this page isn't converting. can you take a look? it's our homepage for a B2B project management tool",
"expected_output": "Should trigger on the casual 'this page isn't converting' phrasing. Should identify this as a Homepage CRO analysis. Should ask clarifying questions about current conversion rate, traffic sources, and conversion goal. Should apply the full CRO Analysis Framework starting with value proposition clarity. Should address the homepage-specific guidance: serving multiple audiences, leading with broadest value prop, and providing clear paths for different visitor intents. Should provide structured output with Quick Wins, High-Impact Changes, Test Ideas, and Copy Alternatives.",
"assertions": [
"Triggers on casual phrasing",
"Identifies as Homepage CRO",
"Asks about current conversion rate",
"Asks about traffic sources",
"Applies CRO Analysis Framework",
"Addresses serving multiple audiences",
"Addresses clear paths for different visitor intents",
"Output has structured sections"
],
"files": []
},
{
"id": 4,
"prompt": "We have a blog that gets 20k organic visits/month but almost nobody clicks through to our product. How do we get more conversions from blog readers?",
"expected_output": "Should apply the Blog Post CRO framework. Should recommend contextual CTAs matching content topics and inline CTAs at natural stopping points. Should analyze whether CTAs are relevant to the content topic or generic. Should suggest specific CTA placements: within content, end of post, sidebar, sticky bar. Should recommend testing different CTA formats (inline text links, banner cards, exit-intent). Should cross-reference copywriting skill for CTA copy improvement.",
"assertions": [
"Applies Blog Post CRO framework",
"Recommends contextual CTAs matching content",
"Recommends inline CTAs at natural stopping points",
"Suggests specific CTA placements",
"Suggests testing different CTA formats",
"Cross-references copywriting or related skill"
],
"files": []
},
{
"id": 5,
"prompt": "We redesigned our landing page and conversions dropped from 4.2% to 2.8%. Here's the new page. What went wrong?",
"expected_output": "Should approach this as a diagnostic CRO audit focused on what changed. Should systematically compare against the CRO framework dimensions to identify likely regression causes. Should check for common redesign mistakes: losing trust signals, weaker value proposition clarity, CTA hierarchy changes, added friction, broken message match with traffic sources. Should provide specific fixes organized by likely impact. Should recommend reverting high-risk changes while testing others.",
"assertions": [
"Approaches as diagnostic audit",
"Checks for lost trust signals",
"Checks for weakened value proposition",
"Checks for CTA hierarchy changes",
"Checks for added friction",
"Checks for broken message match with traffic sources",
"Provides fixes organized by impact",
"Recommends reverting high-risk changes"
],
"files": []
},
{
"id": 6,
"prompt": "Our signup form has too many fields and people keep abandoning it halfway through. Can you help optimize it?",
"expected_output": "Should recognize this is about signup form optimization, not general page CRO. Should defer to or cross-reference the signup skill, which specifically handles signup, registration, and account creation flows. May provide some general friction reduction advice but should make clear that signup is the right skill for this task.",
"assertions": [
"Recognizes this as signup flow optimization",
"References or defers to signup skill",
"Does not attempt full cro analysis on a form"
],
"files": []
},
{
"id": 7,
"prompt": "Review this feature page for our API monitoring tool. Most traffic comes from organic search for 'API monitoring tools'. We want them to start a free trial.",
"expected_output": "Should apply the Feature Page CRO framework: connect feature to benefit, show use cases and examples, clear path to try/buy. Should reference the experiments section and suggest prioritized test ideas for hero section, trust signals, and CTA variations. Should note the organic search traffic source and check for message match with search intent. Should cross-reference ab-testing skill for proper test implementation.",
"assertions": [
"Applies Feature Page CRO framework",
"Connects features to benefits",
"Suggests use cases and examples",
"Provides clear path to try/buy",
"Notes organic traffic source and search intent match",
"Suggests specific experiment hypotheses",
"Cross-references ab-testing skill"
],
"files": []
}
]
}
FILE:references/experiments.md
# Page CRO Experiment Ideas
Comprehensive list of A/B tests and experiments organized by page type.
## Contents
- Homepage Experiments (Hero Section, Trust & Social Proof, Features & Content, Navigation & UX)
- Pricing Page Experiments (Price Presentation, Pricing UX, Objection Handling, Trust Signals)
- Demo Request Page Experiments (Form Optimization, Page Content, CTA & Routing)
- Resource/Blog Page Experiments (Content CTAs, Resource Section)
- Landing Page Experiments (Message Match, Conversion Focus, Page Length)
- Feature Page Experiments (Feature Presentation, Conversion Path)
- Cross-Page Experiments (Site-Wide Tests, Navigation Tests)
## Homepage Experiments
### Hero Section
| Test | Hypothesis |
|------|------------|
| Headline variations | Specific vs. abstract messaging |
| Subheadline clarity | Add/refine to support headline |
| CTA above fold | Include or exclude prominent CTA |
| Hero visual format | Screenshot vs. GIF vs. illustration vs. video |
| CTA button color | Test contrast and visibility |
| CTA button text | "Start Free Trial" vs. "Get Started" vs. "See Demo" |
| Interactive demo | Engage visitors immediately with product |
### Trust & Social Proof
| Test | Hypothesis |
|------|------------|
| Logo placement | Hero section vs. below fold |
| Case study in hero | Show results immediately |
| Trust badges | Add security, compliance, awards |
| Social proof in headline | "Join 10,000+ teams" messaging |
| Testimonial placement | Above fold vs. dedicated section |
| Video testimonials | More engaging than text quotes |
### Features & Content
| Test | Hypothesis |
|------|------------|
| Feature presentation | Icons + descriptions vs. detailed sections |
| Section ordering | Move high-value features up |
| Secondary CTAs | Add/remove throughout page |
| Benefit vs. feature focus | Lead with outcomes |
| Comparison section | Show vs. competitors or status quo |
### Navigation & UX
| Test | Hypothesis |
|------|------------|
| Sticky navigation | Persistent nav with CTA |
| Nav menu order | High-priority items at edges |
| Nav CTA button | Add prominent button in nav |
| Support widget | Live chat vs. AI chatbot |
| Footer optimization | Clearer secondary conversions |
| Exit intent popup | Capture abandoning visitors |
---
## Pricing Page Experiments
### Price Presentation
| Test | Hypothesis |
|------|------------|
| Annual vs. monthly display | Highlight savings or simplify |
| Price points | $99 vs. $100 vs. $97 psychology |
| "Most Popular" badge | Highlight target plan |
| Number of tiers | 3 vs. 4 vs. 2 visible options |
| Price anchoring | Order plans to anchor expectations |
| Custom enterprise tier | Show vs. "Contact Sales" |
### Pricing UX
| Test | Hypothesis |
|------|------------|
| Pricing calculator | For usage-based pricing clarity |
| Guided pricing flow | Multistep wizard vs. comparison table |
| Feature comparison format | Table vs. expandable sections |
| Monthly/annual toggle | With savings highlighted |
| Plan recommendation quiz | Help visitors choose |
| Checkout flow length | Steps required after plan selection |
### Objection Handling
| Test | Hypothesis |
|------|------------|
| FAQ section | Address pricing objections |
| ROI calculator | Demonstrate value vs. cost |
| Money-back guarantee | Prominent placement |
| Per-user breakdowns | Clarity for team plans |
| Feature inclusion clarity | What's in each tier |
| Competitor comparison | Side-by-side value comparison |
### Trust Signals
| Test | Hypothesis |
|------|------------|
| Value testimonials | Quotes about ROI specifically |
| Customer logos | Near pricing section |
| Review scores | G2/Capterra ratings |
| Case study snippet | Specific pricing/value results |
---
## Demo Request Page Experiments
### Form Optimization
| Test | Hypothesis |
|------|------------|
| Field count | Fewer fields, higher completion |
| Multi-step vs. single | Progress bar encouragement |
| Form placement | Above fold vs. after content |
| Phone field | Include vs. exclude |
| Field enrichment | Hide fields you can auto-fill |
| Form labels | Inside field vs. above |
### Page Content
| Test | Hypothesis |
|------|------------|
| Benefits above form | Reinforce value before ask |
| Demo preview | Video/GIF showing demo experience |
| "What You'll Learn" | Set expectations clearly |
| Testimonials near form | Reduce friction at decision point |
| FAQ below form | Address common objections |
| Video vs. text | Format for explaining value |
### CTA & Routing
| Test | Hypothesis |
|------|------------|
| CTA text | "Book Your Demo" vs. "Schedule 15-Min Call" |
| On-demand option | Instant demo alongside live option |
| Personalized messaging | Based on visitor data/source |
| Navigation removal | Reduce page distractions |
| Calendar integration | Inline booking vs. external link |
| Qualification routing | Self-serve for some, sales for others |
---
## Resource/Blog Page Experiments
### Content CTAs
| Test | Hypothesis |
|------|------------|
| Floating CTAs | Sticky CTA on blog posts |
| CTA placement | Inline vs. end-of-post only |
| Reading time display | Estimated reading time |
| Related resources | End-of-article recommendations |
| Gated vs. free | Content access strategy |
| Content upgrades | Specific to article topic |
### Resource Section
| Test | Hypothesis |
|------|------------|
| Navigation/filtering | Easier to find relevant content |
| Search functionality | Find specific resources |
| Featured resources | Highlight best content |
| Layout format | Grid vs. list view |
| Topic bundles | Grouped resources by theme |
| Download tracking | Gate some, track engagement |
---
## Landing Page Experiments
### Message Match
| Test | Hypothesis |
|------|------------|
| Headline matching | Match ad copy exactly |
| Visual matching | Match ad creative |
| Offer alignment | Same offer as ad promised |
| Audience-specific pages | Different pages per segment |
### Conversion Focus
| Test | Hypothesis |
|------|------------|
| Navigation removal | Single-focus page |
| CTA repetition | Multiple CTAs throughout |
| Form vs. button | Direct capture vs. click-through |
| Urgency/scarcity | If genuine, test messaging |
| Social proof density | Amount and placement |
| Video inclusion | Explain offer with video |
### Page Length
| Test | Hypothesis |
|------|------------|
| Short vs. long | Quick conversion vs. complete argument |
| Above-fold only | Minimal scroll required |
| Section ordering | Most important content first |
| Footer removal | Eliminate navigation |
---
## Feature Page Experiments
### Feature Presentation
| Test | Hypothesis |
|------|------------|
| Demo/screenshot | Show feature in action |
| Use case examples | How customers use it |
| Before/after | Impact visualization |
| Video walkthrough | Feature tour |
| Interactive demo | Try feature without signup |
### Conversion Path
| Test | Hypothesis |
|------|------------|
| Trial CTA | Feature-specific trial offer |
| Related features | Cross-link to other features |
| Comparison | vs. competitors' version |
| Pricing mention | Connect to relevant plan |
| Case study link | Feature-specific success story |
---
## Cross-Page Experiments
### Site-Wide Tests
| Test | Hypothesis |
|------|------------|
| Chat widget | Impact on conversions |
| Cookie consent UX | Minimize friction |
| Page load speed | Performance vs. features |
| Mobile experience | Responsive optimization |
| Accessibility | Impact on conversion |
| Personalization | Dynamic content by segment |
### Navigation Tests
| Test | Hypothesis |
|------|------------|
| Menu structure | Information architecture |
| Search placement | Help visitors find content |
| CTA in nav | Always-visible conversion path |
| Breadcrumbs | Navigation clarity |
FILE:references/form.md
# Form CRO
You are an expert in form optimization. Your goal is to maximize form completion rates while capturing the data that matters.
## Initial Assessment
**Check for product marketing context first:**
If `.agents/product-marketing.md` exists (or `.claude/product-marketing.md` in older setups), read it before asking questions. Use that context and only ask for information not already covered or specific to this task.
Before providing recommendations, identify:
1. **Form Type**
- Lead capture (gated content, newsletter)
- Contact form
- Demo/sales request
- Application form
- Survey/feedback
- Checkout form
- Quote request
2. **Current State**
- How many fields?
- What's the current completion rate?
- Mobile vs. desktop split?
- Where do users abandon?
3. **Business Context**
- What happens with form submissions?
- Which fields are actually used in follow-up?
- Are there compliance/legal requirements?
---
## Core Principles
### 1. Every Field Has a Cost
Each field reduces completion rate. Rule of thumb:
- 3 fields: Baseline
- 4-6 fields: 10-25% reduction
- 7+ fields: 25-50%+ reduction
For each field, ask:
- Is this absolutely necessary before we can help them?
- Can we get this information another way?
- Can we ask this later?
### 2. Value Must Exceed Effort
- Clear value proposition above form
- Make what they get obvious
- Reduce perceived effort (field count, labels)
### 3. Reduce Cognitive Load
- One question per field
- Clear, conversational labels
- Logical grouping and order
- Smart defaults where possible
---
## Field-by-Field Optimization
### Email Field
- Single field, no confirmation
- Inline validation
- Typo detection (did you mean gmail.com?)
- Proper mobile keyboard
### Name Fields
- Single "Name" vs. First/Last — test this
- Single field reduces friction
- Split needed only if personalization requires it
### Phone Number
- Make optional if possible
- If required, explain why
- Auto-format as they type
- Country code handling
### Company/Organization
- Auto-suggest for faster entry
- Enrichment after submission (Clearbit, etc.)
- Consider inferring from email domain
### Job Title/Role
- Dropdown if categories matter
- Free text if wide variation
- Consider making optional
### Message/Comments (Free Text)
- Make optional
- Reasonable character guidance
- Expand on focus
### Dropdown Selects
- "Select one..." placeholder
- Searchable if many options
- Consider radio buttons if < 5 options
- "Other" option with text field
### Checkboxes (Multi-select)
- Clear, parallel labels
- Reasonable number of options
- Consider "Select all that apply" instruction
---
## Form Layout Optimization
### Field Order
1. Start with easiest fields (name, email)
2. Build commitment before asking more
3. Sensitive fields last (phone, company size)
4. Logical grouping if many fields
### Labels and Placeholders
- Labels: Keep visible (not just placeholder) — placeholders disappear when typing, leaving users unsure what they're filling in
- Placeholders: Examples, not labels
- Help text: Only when genuinely helpful
**Good:**
```
Email
[name@company.com]
```
**Bad:**
```
[Enter your email address] ← Disappears on focus
```
### Visual Design
- Sufficient spacing between fields
- Clear visual hierarchy
- CTA button stands out
- Mobile-friendly tap targets (44px+)
### Single Column vs. Multi-Column
- Single column: Higher completion, mobile-friendly
- Multi-column: Only for short related fields (First/Last name)
- When in doubt, single column
---
## Multi-Step Forms
### When to Use Multi-Step
- More than 5-6 fields
- Logically distinct sections
- Conditional paths based on answers
- Complex forms (applications, quotes)
### Multi-Step Best Practices
- Progress indicator (step X of Y)
- Start with easy, end with sensitive
- One topic per step
- Allow back navigation
- Save progress (don't lose data on refresh)
- Clear indication of required vs. optional
### Progressive Commitment Pattern
1. Low-friction start (just email)
2. More detail (name, company)
3. Qualifying questions
4. Contact preferences
---
## Error Handling
### Inline Validation
- Validate as they move to next field
- Don't validate too aggressively while typing
- Clear visual indicators (green check, red border)
### Error Messages
- Specific to the problem
- Suggest how to fix
- Positioned near the field
- Don't clear their input
**Good:** "Please enter a valid email address (e.g., name@company.com)"
**Bad:** "Invalid input"
### On Submit
- Focus on first error field
- Summarize errors if multiple
- Preserve all entered data
- Don't clear form on error
---
## Submit Button Optimization
### Button Copy
Weak: "Submit" | "Send"
Strong: "[Action] + [What they get]"
Examples:
- "Get My Free Quote"
- "Download the Guide"
- "Request Demo"
- "Send Message"
- "Start Free Trial"
### Button Placement
- Immediately after last field
- Left-aligned with fields
- Sufficient size and contrast
- Mobile: Sticky or clearly visible
### Post-Submit States
- Loading state (disable button, show spinner)
- Success confirmation (clear next steps)
- Error handling (clear message, focus on issue)
---
## Trust and Friction Reduction
### Near the Form
- Privacy statement: "We'll never share your info"
- Security badges if collecting sensitive data
- Testimonial or social proof
- Expected response time
### Reducing Perceived Effort
- "Takes 30 seconds"
- Field count indicator
- Remove visual clutter
- Generous white space
### Addressing Objections
- "No spam, unsubscribe anytime"
- "We won't share your number"
- "No credit card required"
---
## Form Types: Specific Guidance
### Lead Capture (Gated Content)
- Minimum viable fields (often just email)
- Clear value proposition for what they get
- Consider asking enrichment questions post-download
- Test email-only vs. email + name
### Contact Form
- Essential: Email/Name + Message
- Phone optional
- Set response time expectations
- Offer alternatives (chat, phone)
### Demo Request
- Name, Email, Company required
- Phone: Optional with "preferred contact" choice
- Use case/goal question helps personalize
- Calendar embed can increase show rate
### Quote/Estimate Request
- Multi-step often works well
- Start with easy questions
- Technical details later
- Save progress for complex forms
### Survey Forms
- Progress bar essential
- One question per screen for engagement
- Skip logic for relevance
- Consider incentive for completion
---
## Mobile Optimization
- Larger touch targets (44px minimum height)
- Appropriate keyboard types (email, tel, number)
- Autofill support
- Single column only
- Sticky submit button
- Minimal typing (dropdowns, buttons)
---
## Measurement
### Key Metrics
- **Form start rate**: Page views → Started form
- **Completion rate**: Started → Submitted
- **Field drop-off**: Which fields lose people
- **Error rate**: By field
- **Time to complete**: Total and by field
- **Mobile vs. desktop**: Completion by device
### What to Track
- Form views
- First field focus
- Each field completion
- Errors by field
- Submit attempts
- Successful submissions
---
## Output Format
### Form Audit
For each issue:
- **Issue**: What's wrong
- **Impact**: Estimated effect on conversions
- **Fix**: Specific recommendation
- **Priority**: High/Medium/Low
### Recommended Form Design
- **Required fields**: Justified list
- **Optional fields**: With rationale
- **Field order**: Recommended sequence
- **Copy**: Labels, placeholders, button
- **Error messages**: For each field
- **Layout**: Visual guidance
### Test Hypotheses
Ideas to A/B test with expected outcomes
---
## Experiment Ideas
### Form Structure Experiments
**Layout & Flow**
- Single-step form vs. multi-step with progress bar
- 1-column vs. 2-column field layout
- Form embedded on page vs. separate page
- Vertical vs. horizontal field alignment
- Form above fold vs. after content
**Field Optimization**
- Reduce to minimum viable fields
- Add or remove phone number field
- Add or remove company/organization field
- Test required vs. optional field balance
- Use field enrichment to auto-fill known data
- Hide fields for returning/known visitors
**Smart Forms**
- Add real-time validation for emails and phone numbers
- Progressive profiling (ask more over time)
- Conditional fields based on earlier answers
- Auto-suggest for company names
---
### Copy & Design Experiments
**Labels & Microcopy**
- Test field label clarity and length
- Placeholder text optimization
- Help text: show vs. hide vs. on-hover
- Error message tone (friendly vs. direct)
**CTAs & Buttons**
- Button text variations ("Submit" vs. "Get My Quote" vs. specific action)
- Button color and size testing
- Button placement relative to fields
**Trust Elements**
- Add privacy assurance near form
- Show trust badges next to submit
- Add testimonial near form
- Display expected response time
---
### Form Type-Specific Experiments
**Demo Request Forms**
- Test with/without phone number requirement
- Add "preferred contact method" choice
- Include "What's your biggest challenge?" question
- Test calendar embed vs. form submission
**Lead Capture Forms**
- Email-only vs. email + name
- Test value proposition messaging above form
- Gated vs. ungated content strategies
- Post-submission enrichment questions
**Contact Forms**
- Add department/topic routing dropdown
- Test with/without message field requirement
- Show alternative contact methods (chat, phone)
- Expected response time messaging
---
### Mobile & UX Experiments
- Larger touch targets for mobile
- Test appropriate keyboard types by field
- Sticky submit button on mobile
- Auto-focus first field on page load
- Test form container styling (card vs. minimal)
---
## Task-Specific Questions
1. What's your current form completion rate?
2. Do you have field-level analytics?
3. What happens with the data after submission?
4. Which fields are actually used in follow-up?
5. Are there compliance/legal requirements?
6. What's the mobile vs. desktop split?
---
## Related Skills
- **signup**: For account creation forms
- **popups**: For forms inside popups/modals
- **cro**: For the page containing the form
- **ab-testing**: For testing form changes
Bộ công cụ nghiên cứu UX: tạo persona từ dữ liệu, journey map, khung kiểm thử khả dụng và tổng hợp nghiên cứu.
---
name: "ux-researcher-designer"
description: UX research and design toolkit for Senior UX Designer/Researcher including data-driven persona generation, journey mapping, usability testing frameworks, and research synthesis. Use for user research, persona creation, journey mapping, and design validation.
---
# UX Researcher & Designer
Generate user personas from research data, create journey maps, plan usability tests, and synthesize research findings into actionable design recommendations.
---
## Table of Contents
- [Trigger Terms](#trigger-terms)
- [Workflows](#workflows)
- [Workflow 1: Generate User Persona](#workflow-1-generate-user-persona)
- [Workflow 2: Create Journey Map](#workflow-2-create-journey-map)
- [Workflow 3: Plan Usability Test](#workflow-3-plan-usability-test)
- [Workflow 4: Synthesize Research](#workflow-4-synthesize-research)
- [Tool Reference](#tool-reference)
- [Quick Reference Tables](#quick-reference-tables)
- [Knowledge Base](#knowledge-base)
---
## Trigger Terms
Use this skill when you need to:
- "create user persona"
- "generate persona from data"
- "build customer journey map"
- "map user journey"
- "plan usability test"
- "design usability study"
- "analyze user research"
- "synthesize interview findings"
- "identify user pain points"
- "define user archetypes"
- "calculate research sample size"
- "create empathy map"
- "identify user needs"
---
## Workflows
### Workflow 1: Generate User Persona
**Situation:** You have user data (analytics, surveys, interviews) and need to create a research-backed persona.
**Steps:**
1. **Prepare user data**
Required format (JSON):
```json
[
{
"user_id": "user_1",
"age": 32,
"usage_frequency": "daily",
"features_used": ["dashboard", "reports", "export"],
"primary_device": "desktop",
"usage_context": "work",
"tech_proficiency": 7,
"pain_points": ["slow loading", "confusing UI"]
}
]
```
2. **Run persona generator**
```bash
# Human-readable output
python scripts/persona_generator.py
# JSON output for integration
python scripts/persona_generator.py json
```
3. **Review generated components**
| Component | What to Check |
|-----------|---------------|
| Archetype | Does it match the data patterns? |
| Demographics | Are they derived from actual data? |
| Goals | Are they specific and actionable? |
| Frustrations | Do they include frequency counts? |
| Design implications | Can designers act on these? |
4. **Validate persona**
- Show to 3-5 real users: "Does this sound like you?"
- Cross-check with support tickets
- Verify against analytics data
5. **Reference:** See `references/persona-methodology.md` for validity criteria
---
### Workflow 2: Create Journey Map
**Situation:** You need to visualize the end-to-end user experience for a specific goal.
**Steps:**
1. **Define scope**
| Element | Description |
|---------|-------------|
| Persona | Which user type |
| Goal | What they're trying to achieve |
| Start | Trigger that begins journey |
| End | Success criteria |
| Timeframe | Hours/days/weeks |
2. **Gather journey data**
Sources:
- User interviews (ask "walk me through...")
- Session recordings
- Analytics (funnel, drop-offs)
- Support tickets
3. **Map the stages**
Typical B2B SaaS stages:
```
Awareness → Evaluation → Onboarding → Adoption → Advocacy
```
4. **Fill in layers for each stage**
```
Stage: [Name]
├── Actions: What does user do?
├── Touchpoints: Where do they interact?
├── Emotions: How do they feel? (1-5)
├── Pain Points: What frustrates them?
└── Opportunities: Where can we improve?
```
5. **Identify opportunities**
Priority Score = Frequency × Severity × Solvability
6. **Reference:** See `references/journey-mapping-guide.md` for templates
---
### Workflow 3: Plan Usability Test
**Situation:** You need to validate a design with real users.
**Steps:**
1. **Define research questions**
Transform vague goals into testable questions:
| Vague | Testable |
|-------|----------|
| "Is it easy to use?" | "Can users complete checkout in <3 min?" |
| "Do users like it?" | "Will users choose Design A or B?" |
| "Does it make sense?" | "Can users find settings without hints?" |
2. **Select method**
| Method | Participants | Duration | Best For |
|--------|--------------|----------|----------|
| Moderated remote | 5-8 | 45-60 min | Deep insights |
| Unmoderated remote | 10-20 | 15-20 min | Quick validation |
| Guerrilla | 3-5 | 5-10 min | Rapid feedback |
3. **Design tasks**
Good task format:
```
SCENARIO: "Imagine you're planning a trip to Paris..."
GOAL: "Book a hotel for 3 nights in your budget."
SUCCESS: "You see the confirmation page."
```
Task progression: Warm-up → Core → Secondary → Edge case → Free exploration
4. **Define success metrics**
| Metric | Target |
|--------|--------|
| Completion rate | >80% |
| Time on task | <2× expected |
| Error rate | <15% |
| Satisfaction | >4/5 |
5. **Prepare moderator guide**
- Think-aloud instructions
- Non-leading prompts
- Post-task questions
6. **Reference:** See `references/usability-testing-frameworks.md` for full guide
---
### Workflow 4: Synthesize Research
**Situation:** You have raw research data (interviews, surveys, observations) and need actionable insights.
**Steps:**
1. **Code the data**
Tag each data point:
- `[GOAL]` - What they want to achieve
- `[PAIN]` - What frustrates them
- `[BEHAVIOR]` - What they actually do
- `[CONTEXT]` - When/where they use product
- `[QUOTE]` - Direct user words
2. **Cluster similar patterns**
```
User A: Uses daily, advanced features, shortcuts
User B: Uses daily, complex workflows, automation
User C: Uses weekly, basic needs, occasional
Cluster 1: A, B (Power Users)
Cluster 2: C (Casual User)
```
3. **Calculate segment sizes**
| Cluster | Users | % | Viability |
|---------|-------|---|-----------|
| Power Users | 18 | 36% | Primary persona |
| Business Users | 15 | 30% | Primary persona |
| Casual Users | 12 | 24% | Secondary persona |
4. **Extract key findings**
For each theme:
- Finding statement
- Supporting evidence (quotes, data)
- Frequency (X/Y participants)
- Business impact
- Recommendation
5. **Prioritize opportunities**
| Factor | Score 1-5 |
|--------|-----------|
| Frequency | How often does this occur? |
| Severity | How much does it hurt? |
| Breadth | How many users affected? |
| Solvability | Can we fix this? |
6. **Reference:** See `references/persona-methodology.md` for analysis framework
---
## Tool Reference
### persona_generator.py
Generates data-driven personas from user research data.
| Argument | Values | Default | Description |
|----------|--------|---------|-------------|
| format | (none), json | (none) | Output format |
**Sample Output:**
```
============================================================
PERSONA: Alex the Power User
============================================================
📝 A daily user who primarily uses the product for work purposes
Archetype: Power User
Quote: "I need tools that can keep up with my workflow"
👤 Demographics:
• Age Range: 25-34
• Location Type: Urban
• Tech Proficiency: Advanced
🎯 Goals & Needs:
• Complete tasks efficiently
• Automate workflows
• Access advanced features
😤 Frustrations:
• Slow loading times (14/20 users)
• No keyboard shortcuts
• Limited API access
💡 Design Implications:
→ Optimize for speed and efficiency
→ Provide keyboard shortcuts and power features
→ Expose API and automation capabilities
📈 Data: Based on 45 users
Confidence: High
```
**Archetypes Generated:**
| Archetype | Signals | Design Focus |
|-----------|---------|--------------|
| power_user | Daily use, 10+ features | Efficiency, customization |
| casual_user | Weekly use, 3-5 features | Simplicity, guidance |
| business_user | Work context, team use | Collaboration, reporting |
| mobile_first | Mobile primary | Touch, offline, speed |
**Output Components:**
| Component | Description |
|-----------|-------------|
| demographics | Age range, location, occupation, tech level |
| psychographics | Motivations, values, attitudes, lifestyle |
| behaviors | Usage patterns, feature preferences |
| needs_and_goals | Primary, secondary, functional, emotional |
| frustrations | Pain points with evidence |
| scenarios | Contextual usage stories |
| design_implications | Actionable recommendations |
| data_points | Sample size, confidence level |
---
## Quick Reference Tables
### Research Method Selection
| Question Type | Best Method | Sample Size |
|---------------|-------------|-------------|
| "What do users do?" | Analytics, observation | 100+ events |
| "Why do they do it?" | Interviews | 8-15 users |
| "How well can they do it?" | Usability test | 5-8 users |
| "What do they prefer?" | Survey, A/B test | 50+ users |
| "What do they feel?" | Diary study, interviews | 10-15 users |
### Persona Confidence Levels
| Sample Size | Confidence | Use Case |
|-------------|------------|----------|
| 5-10 users | Low | Exploratory |
| 11-30 users | Medium | Directional |
| 31+ users | High | Production |
### Usability Issue Severity
| Severity | Definition | Action |
|----------|------------|--------|
| 4 - Critical | Prevents task completion | Fix immediately |
| 3 - Major | Significant difficulty | Fix before release |
| 2 - Minor | Causes hesitation | Fix when possible |
| 1 - Cosmetic | Noticed but not problematic | Low priority |
### Interview Question Types
| Type | Example | Use For |
|------|---------|---------|
| Context | "Walk me through your typical day" | Understanding environment |
| Behavior | "Show me how you do X" | Observing actual actions |
| Goals | "What are you trying to achieve?" | Uncovering motivations |
| Pain | "What's the hardest part?" | Identifying frustrations |
| Reflection | "What would you change?" | Generating ideas |
---
## Knowledge Base
Detailed reference guides in `references/`:
| File | Content |
|------|---------|
| `persona-methodology.md` | Validity criteria, data collection, analysis framework |
| `journey-mapping-guide.md` | Mapping process, templates, opportunity identification |
| `example-personas.md` | 3 complete persona examples with data |
| `usability-testing-frameworks.md` | Test planning, task design, analysis |
---
## Validation Checklist
### Persona Quality
- [ ] Based on 20+ users (minimum)
- [ ] At least 2 data sources (quant + qual)
- [ ] Specific, actionable goals
- [ ] Frustrations include frequency counts
- [ ] Design implications are specific
- [ ] Confidence level stated
### Journey Map Quality
- [ ] Scope clearly defined (persona, goal, timeframe)
- [ ] Based on real user data, not assumptions
- [ ] All layers filled (actions, touchpoints, emotions)
- [ ] Pain points identified per stage
- [ ] Opportunities prioritized
### Usability Test Quality
- [ ] Research questions are testable
- [ ] Tasks are realistic scenarios, not instructions
- [ ] 5+ participants per design
- [ ] Success metrics defined
- [ ] Findings include severity ratings
### Research Synthesis Quality
- [ ] Data coded consistently
- [ ] Patterns based on 3+ data points
- [ ] Findings include evidence
- [ ] Recommendations are actionable
- [ ] Priorities justified
## Related Skills
- **UI Design System** (`product-team/ui-design-system/`) — Research findings inform design system decisions
- **Product Manager Toolkit** (`product-team/product-manager-toolkit/`) — Customer interview analysis complements persona research
FILE:assets/research_plan_template.md
# UX Research Plan
## Study Info
| Field | Value |
|-------|-------|
| **Study Name** | [Descriptive name] |
| **Researcher** | [Name] |
| **Stakeholders** | [Names/roles] |
| **Status** | Planning / In Field / Analysis / Complete |
| **Timeline** | [Start Date] - [End Date] |
---
## Research Questions
What do we need to learn? List 3-5 specific, answerable questions.
1. [Primary question - the most important thing to learn]
2. [Secondary question]
3. [Secondary question]
4. [Exploratory question - nice to know]
5. [Exploratory question - nice to know]
### What We Already Know
[Summarize existing knowledge, past research, analytics data, assumptions to validate.]
### What We Do Not Know
[List specific knowledge gaps this study will address.]
---
## Methodology
**Method:** [Usability Testing / User Interviews / Survey / Diary Study / Card Sorting / A/B Test / Contextual Inquiry]
**Justification:** [Why this method is appropriate for the research questions.]
**Approach:** [Moderated / Unmoderated / Remote / In-Person]
**Duration:** [Session length per participant]
**Tools:** [Platform/tools used - e.g., Lookback, Maze, UserTesting, Zoom]
---
## Participant Criteria
### Target Participants
| Criterion | Requirement |
|-----------|------------|
| **User type** | [e.g., Active users, churned users, prospects] |
| **Role/title** | [e.g., Product managers, developers] |
| **Experience level** | [e.g., 1+ years using similar products] |
| **Company size** | [e.g., 50-500 employees] |
| **Geography** | [e.g., US-based, English-speaking] |
### Screening Questions
1. [Question to verify participant meets criteria]
2. [Question to verify participant meets criteria]
3. [Question to ensure diversity of perspectives]
### Exclusion Criteria
- [e.g., Current employees or family of employees]
- [e.g., Participants in a study within the last 6 months]
### Sample Size
- **Target:** [Number] participants
- **Justification:** [e.g., "5 participants identify ~80% of usability issues" or "Statistical significance requires N=200 for survey"]
---
## Recruitment Plan
| Channel | Target Count | Timeline | Incentive |
|---------|-------------|----------|-----------|
| [Customer database] | [N] | [Dates] | [$Amount or type] |
| [User panel] | [N] | [Dates] | [$Amount or type] |
| [Social media] | [N] | [Dates] | [$Amount or type] |
### Incentive
- **Amount:** [$X per session or equivalent]
- **Form:** [Gift card, account credit, donation to charity]
- **Distribution:** [Immediately after session / within 5 business days]
---
## Interview / Test Guide
### Introduction (5 minutes)
- Welcome and thank participant
- Explain purpose (learning, not testing them)
- Confirm consent and recording permission
- Set expectations for session duration
### Warm-Up Questions (5 minutes)
1. [Background question about their role/context]
2. [Question about current workflow/tools]
### Core Tasks / Questions (30-40 minutes)
**Task 1:** [Description of task or topic area]
- [Specific prompt or scenario]
- [Follow-up probes]
**Task 2:** [Description of task or topic area]
- [Specific prompt or scenario]
- [Follow-up probes]
**Task 3:** [Description of task or topic area]
- [Specific prompt or scenario]
- [Follow-up probes]
### Wrap-Up (5 minutes)
- Overall impressions
- Anything we did not ask about that they want to share
- Thank participant and explain next steps
---
## Analysis Framework
### Data Collection
- [ ] Session recordings stored securely
- [ ] Notes taken during each session
- [ ] Observations tagged by research question
### Analysis Method
- **Affinity mapping:** Group observations into themes
- **Task success rate:** [For usability tests] Completion rate per task
- **Severity rating:** [For issues] Critical / Major / Minor / Cosmetic
- **Quantitative analysis:** [For surveys] Statistical analysis plan
### Synthesis Approach
1. Review all session notes and recordings
2. Identify patterns and themes across participants
3. Map findings to research questions
4. Prioritize findings by impact and frequency
5. Develop actionable recommendations
---
## Timeline
| Phase | Dates | Activities |
|-------|-------|-----------|
| Planning | [Week 1] | Finalize plan, create guide, prepare materials |
| Recruitment | [Week 1-2] | Screen and schedule participants |
| Fieldwork | [Week 2-3] | Conduct sessions |
| Analysis | [Week 3-4] | Synthesize findings |
| Reporting | [Week 4] | Create report, present to stakeholders |
---
## Deliverables
- [ ] Research findings report (key insights, evidence, recommendations)
- [ ] Presentation deck for stakeholders
- [ ] Highlight reel of key moments (if recorded)
- [ ] Actionable recommendations prioritized by impact
- [ ] Raw data archive (notes, recordings) stored per data policy
FILE:references/example-personas.md
# Example Personas
Real output examples showing what good personas look like.
---
## Table of Contents
- [Example 1: Power User Persona](#example-1-power-user-persona)
- [Example 2: Business User Persona](#example-2-business-user-persona)
- [Example 3: Casual User Persona](#example-3-casual-user-persona)
- [JSON Output Format](#json-output-format)
- [Quality Checklist](#quality-checklist)
---
## Example 1: Power User Persona
### Script Output
```
============================================================
PERSONA: Alex the Power User
============================================================
📝 A daily user who primarily uses the product for work purposes
Archetype: Power User
Quote: "I need tools that can keep up with my workflow"
👤 Demographics:
• Age Range: 25-34
• Location Type: Urban
• Occupation Category: Software Engineer
• Education Level: Bachelor's degree
• Tech Proficiency: Advanced
🧠 Psychographics:
Motivations: Efficiency, Control, Mastery
Values: Time-saving, Flexibility, Reliability
Lifestyle: Fast-paced, optimization-focused
🎯 Goals & Needs:
• Complete tasks efficiently without repetitive work
• Automate recurring workflows
• Access advanced features and shortcuts
😤 Frustrations:
• Slow loading times (mentioned by 14/20 users)
• No keyboard shortcuts for common actions
• Limited API access for automation
📊 Behaviors:
• Frequently uses: Dashboard, Reports, Export, API
• Usage pattern: 5+ sessions per day
• Interaction style: Exploratory - uses many features
💡 Design Implications:
→ Optimize for speed and efficiency
→ Provide keyboard shortcuts and power features
→ Expose API and automation capabilities
→ Allow UI customization
📈 Data: Based on 45 users
Confidence: High
Method: Quantitative analysis + 12 qualitative interviews
```
### Data Behind This Persona
**Quantitative Data (n=45):**
- 78% use product daily
- Average session: 23 minutes
- Average features used: 12
- 84% access via desktop
- Support tickets: 0.2 per month (low)
**Qualitative Insights (12 interviews):**
| Theme | Frequency | Sample Quote |
|-------|-----------|--------------|
| Speed matters | 10/12 | "Every second counts when I'm in flow" |
| Shortcuts wanted | 8/12 | "Why can't I Cmd+K to search?" |
| Automation need | 9/12 | "I wrote a script to work around..." |
| Customization | 7/12 | "Let me hide features I don't use" |
---
## Example 2: Business User Persona
### Script Output
```
============================================================
PERSONA: Taylor the Business Professional
============================================================
📝 A weekly user who primarily uses the product for team collaboration
Archetype: Business User
Quote: "I need to show clear value to my stakeholders"
👤 Demographics:
• Age Range: 35-44
• Location Type: Urban/Suburban
• Occupation Category: Product Manager
• Education Level: MBA
• Tech Proficiency: Intermediate
🧠 Psychographics:
Motivations: Team success, Visibility, Recognition
Values: Collaboration, Measurable outcomes, Professional growth
Lifestyle: Meeting-heavy, cross-functional work
🎯 Goals & Needs:
• Improve team efficiency and coordination
• Generate reports for stakeholders
• Integrate with existing work tools (Slack, Jira)
😤 Frustrations:
• No way to share views with team (11/18 users)
• Can't generate executive summaries
• No SSO - team has to manage passwords
📊 Behaviors:
• Frequently uses: Sharing, Reports, Team Dashboard
• Usage pattern: 3-4 sessions per week
• Interaction style: Goal-oriented, feature-specific
💡 Design Implications:
→ Add collaboration and sharing features
→ Build executive reporting and dashboards
→ Integrate with enterprise tools (SSO, Slack)
→ Provide permission and access controls
📈 Data: Based on 38 users
Confidence: High
Method: Survey (n=200) + 18 interviews
```
### Data Behind This Persona
**Survey Data (n=200):**
- 19% of total user base fits this profile
- Average company size: 50-500 employees
- 72% need to share outputs with non-users
- Top request: Team collaboration features
**Interview Insights (18 interviews):**
| Need | Frequency | Business Impact |
|------|-----------|-----------------|
| Reporting | 16/18 | "I spend 2hrs/week making slides" |
| Team access | 14/18 | "Can't show my team what I see" |
| Integration | 12/18 | "Copy-paste into Confluence..." |
| SSO | 11/18 | "IT won't approve without SSO" |
### Scenario: Quarterly Review Prep
```
Context: End of quarter, needs to present metrics to leadership
Goal: Create compelling data story in 30 minutes
Current Journey:
1. Export raw data (works)
2. Open Excel, make charts (manual)
3. Copy to PowerPoint (manual)
4. Share with team for feedback (via email)
Pain Points:
• No built-in presentation view
• Charts don't match brand guidelines
• Can't collaborate on narrative
Opportunity:
• One-click executive summary
• Brand-compliant templates
• In-app commenting on reports
```
---
## Example 3: Casual User Persona
### Script Output
```
============================================================
PERSONA: Casey the Casual User
============================================================
📝 A monthly user who uses the product for occasional personal tasks
Archetype: Casual User
Quote: "I just want it to work without having to think about it"
👤 Demographics:
• Age Range: 25-44
• Location Type: Mixed
• Occupation Category: Various
• Education Level: Bachelor's degree
• Tech Proficiency: Beginner-Intermediate
🧠 Psychographics:
Motivations: Task completion, Simplicity
Values: Ease of use, Quick results
Lifestyle: Busy, product is means to end
🎯 Goals & Needs:
• Complete specific task quickly
• Minimal learning curve
• Don't have to remember how it works between uses
😤 Frustrations:
• Too many options, don't know where to start (18/25)
• Forgot how to do X since last time (15/25)
• Feels like it's designed for experts (12/25)
📊 Behaviors:
• Frequently uses: 2-3 core features only
• Usage pattern: 1-2 sessions per month
• Interaction style: Focused - uses minimal features
💡 Design Implications:
→ Simplify onboarding and main navigation
→ Provide contextual help and reminders
→ Don't require memorization between sessions
→ Progressive disclosure - hide advanced features
📈 Data: Based on 52 users
Confidence: High
Method: Analytics analysis + 25 intercept interviews
```
### Data Behind This Persona
**Analytics Data (n=1,200 casual segment):**
- 65% of users are casual (< 1 session/week)
- Average features used: 2.3
- Return rate after 30 days: 34%
- Session duration: 4.2 minutes
**Intercept Interview Insights (25 quick interviews):**
| Quote | Count | Implication |
|-------|-------|-------------|
| "Where's the thing I used last time?" | 18 | Need breadcrumbs/history |
| "There's so much here" | 15 | Simplify main view |
| "I only need to do X" | 22 | Surface common tasks |
| "Is there a tutorial?" | 11 | Better help system |
### Journey: Infrequent Task Completion
```
Stage 1: Return After Absence
Action: Opens app, doesn't recognize interface
Emotion: 😕 Confused
Thought: "This looks different, where do I start?"
Stage 2: Feature Hunt
Action: Clicks around looking for needed feature
Emotion: 😕 Frustrated
Thought: "I know I did this before..."
Stage 3: Discovery
Action: Finds feature (or gives up)
Emotion: 😐 Relief or 😠 Abandonment
Thought: "Finally!" or "I'll try something else"
Stage 4: Task Completion
Action: Uses feature, accomplishes goal
Emotion: 🙂 Satisfied
Thought: "That worked, hope I remember next time"
```
---
## JSON Output Format
### persona_generator.py JSON Output
```json
{
"name": "Alex the Power User",
"archetype": "power_user",
"tagline": "A daily user who primarily uses the product for work purposes",
"demographics": {
"age_range": "25-34",
"location_type": "urban",
"occupation_category": "Software Engineer",
"education_level": "Bachelor's degree",
"tech_proficiency": "Advanced"
},
"psychographics": {
"motivations": ["Efficiency", "Control", "Mastery"],
"values": ["Time-saving", "Flexibility", "Reliability"],
"attitudes": ["Early adopter", "Optimization-focused"],
"lifestyle": "Fast-paced, tech-forward"
},
"behaviors": {
"usage_patterns": ["daily: 45 users", "weekly: 8 users"],
"feature_preferences": ["dashboard", "reports", "export", "api"],
"interaction_style": "Exploratory - uses many features",
"learning_preference": "Self-directed, documentation"
},
"needs_and_goals": {
"primary_goals": [
"Complete tasks efficiently",
"Automate workflows"
],
"secondary_goals": [
"Customize workspace",
"Integrate with other tools"
],
"functional_needs": [
"Speed and performance",
"Keyboard shortcuts",
"API access"
],
"emotional_needs": [
"Feel in control",
"Feel productive",
"Feel like an expert"
]
},
"frustrations": [
"Slow loading times",
"No keyboard shortcuts",
"Limited API access",
"Can't customize dashboard",
"No batch operations"
],
"scenarios": [
{
"title": "Bulk Processing",
"context": "Monday morning, needs to process week's data",
"goal": "Complete batch operations quickly",
"steps": ["Import data", "Apply bulk actions", "Export results"],
"pain_points": ["No keyboard shortcuts", "Slow processing"]
}
],
"quote": "I need tools that can keep up with my workflow",
"data_points": {
"sample_size": 45,
"confidence_level": "High",
"last_updated": "2024-01-15",
"validation_method": "Quantitative analysis + Qualitative interviews"
},
"design_implications": [
"Optimize for speed and efficiency",
"Provide keyboard shortcuts and power features",
"Expose API and automation capabilities",
"Allow UI customization",
"Support bulk operations"
]
}
```
### Using JSON Output
```bash
# Generate JSON for integration
python scripts/persona_generator.py json > persona_power_user.json
# Use with other tools
cat persona_power_user.json | jq '.design_implications'
```
---
## Quality Checklist
### What Makes a Good Persona
| Criterion | Bad Example | Good Example |
|-----------|-------------|--------------|
| **Specificity** | "Wants to be productive" | "Needs to process 50+ items daily" |
| **Evidence** | "Users want simplicity" | "18/25 users said 'too many options'" |
| **Actionable** | "Likes easy things" | "Hide advanced features by default" |
| **Memorable** | Generic descriptions | Distinctive quote and archetype |
| **Validated** | Team assumptions | User interviews + analytics |
### Persona Quality Rubric
| Element | Points | Criteria |
|---------|--------|----------|
| Data-backed demographics | /5 | From real user data |
| Specific goals | /5 | Actionable, measurable |
| Evidenced frustrations | /5 | With frequency counts |
| Design implications | /5 | Directly usable by designers |
| Authentic quote | /5 | From actual user |
| Confidence stated | /5 | Sample size and method |
**Score:**
- 25-30: Production-ready persona
- 18-24: Needs refinement
- Below 18: Requires more research
### Red Flags in Persona Output
| Red Flag | What It Means |
|----------|---------------|
| No sample size | Ungrounded assumptions |
| Generic frustrations | Didn't do user research |
| All positive | Missing real pain points |
| No quotes | No qualitative research |
| Contradicting behaviors | Forced archetype |
| "Everyone" language | Too broad to be useful |
---
*See also: `persona-methodology.md` for creation process*
FILE:references/journey-mapping-guide.md
# Journey Mapping Guide
Step-by-step reference for creating user journey maps that drive design decisions.
---
## Table of Contents
- [Journey Map Fundamentals](#journey-map-fundamentals)
- [Mapping Process](#mapping-process)
- [Journey Stages](#journey-stages)
- [Touchpoint Analysis](#touchpoint-analysis)
- [Emotion Mapping](#emotion-mapping)
- [Opportunity Identification](#opportunity-identification)
- [Templates](#templates)
---
## Journey Map Fundamentals
### What Is a Journey Map?
A journey map visualizes the end-to-end experience a user has while trying to accomplish a goal with your product or service.
```
┌─────────────────────────────────────────────────────────────┐
│ JOURNEY MAP STRUCTURE │
├─────────────────────────────────────────────────────────────┤
│ │
│ STAGES: Awareness → Consideration → Acquisition → │
│ Onboarding → Regular Use → Advocacy │
│ │
│ LAYERS: ┌─────────────────────────────────────────┐ │
│ │ Actions: What user does │ │
│ ├─────────────────────────────────────────┤ │
│ │ Touchpoints: Where interaction happens │ │
│ ├─────────────────────────────────────────┤ │
│ │ Emotions: How user feels │ │
│ ├─────────────────────────────────────────┤ │
│ │ Pain Points: What frustrates │ │
│ ├─────────────────────────────────────────┤ │
│ │ Opportunities: Where to improve │ │
│ └─────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
### Journey Map Types
| Type | Focus | Best For |
|------|-------|----------|
| Current State | How things are today | Identifying pain points |
| Future State | Ideal experience | Design vision |
| Day-in-the-Life | Beyond your product | Context understanding |
| Service Blueprint | Backend processes | Operations alignment |
### When to Create Journey Maps
| Scenario | Map Type | Outcome |
|----------|----------|---------|
| New product | Future state | Design direction |
| Redesign | Current + Future | Gap analysis |
| Churn investigation | Current state | Pain point diagnosis |
| Cross-team alignment | Service blueprint | Process optimization |
---
## Mapping Process
### Step 1: Define Scope
**Questions to Answer:**
- Which persona is this journey for?
- What goal are they trying to achieve?
- Where does the journey start and end?
- What timeframe does it cover?
**Scope Template:**
```
Persona: [Name from persona library]
Goal: [Specific outcome they want]
Start: [Trigger that begins journey]
End: [Success criteria or exit point]
Timeframe: [Hours/Days/Weeks]
```
**Example:**
```
Persona: Alex the Power User
Goal: Set up automated weekly reports
Start: Realizes manual reporting is unsustainable
End: First automated report runs successfully
Timeframe: 1-2 days
```
### Step 2: Gather Data
**Data Sources for Journey Mapping:**
| Source | Insights Gained |
|--------|-----------------|
| User interviews | Actions, emotions, quotes |
| Session recordings | Actual behavior patterns |
| Support tickets | Common pain points |
| Analytics | Drop-off points, time spent |
| Surveys | Satisfaction at stages |
**Interview Questions for Journey Mapping:**
1. "Walk me through how you first discovered [product]"
2. "What made you decide to try it?"
3. "Describe your first day using it"
4. "What was the hardest part?"
5. "When did you feel confident using it?"
6. "What would you change about that experience?"
### Step 3: Map the Stages
**Identify Natural Breakpoints:**
Look for moments where:
- User's mindset changes
- Channels shift (web → app → email)
- Time passes (hours, days)
- Goals evolve
**Stage Validation:**
Each stage should have:
- Clear entry criteria
- Distinct user actions
- Measurable outcomes
- Exit to next stage
### Step 4: Fill in Layers
For each stage, document:
1. **Actions**: What does the user do?
2. **Touchpoints**: Where do they interact?
3. **Thoughts**: What are they thinking?
4. **Emotions**: How do they feel?
5. **Pain Points**: What's frustrating?
6. **Opportunities**: Where can we improve?
### Step 5: Validate and Iterate
**Validation Methods:**
| Method | Effort | Confidence |
|--------|--------|------------|
| Team review | Low | Medium |
| User walkthrough | Medium | High |
| Data correlation | Medium | High |
| A/B test interventions | High | Very High |
---
## Journey Stages
### Common B2B SaaS Stages
```
┌────────────┬────────────┬────────────┬────────────┬────────────┐
│ AWARENESS │ EVALUATION │ ONBOARDING │ ADOPTION │ ADVOCACY │
├────────────┼────────────┼────────────┼────────────┼────────────┤
│ Discovers │ Compares │ Signs up │ Regular │ Recommends │
│ problem │ solutions │ Sets up │ usage │ to others │
│ exists │ │ First win │ Integrates │ │
└────────────┴────────────┴────────────┴────────────┴────────────┘
```
### Stage Detail Template
**Stage: Onboarding**
| Element | Description |
|---------|-------------|
| Goal | Complete setup, achieve first success |
| Duration | 1-7 days |
| Entry | User creates account |
| Exit | First meaningful action completed |
| Success Metric | Activation rate |
**Substages:**
1. Account creation
2. Profile setup
3. First feature use
4. Integration (if applicable)
5. First value moment
### B2C vs. B2B Stages
| B2C Stages | B2B Stages |
|------------|------------|
| Discover | Awareness |
| Browse | Evaluation |
| Purchase | Procurement |
| Use | Implementation |
| Return/Loyalty | Renewal |
---
## Touchpoint Analysis
### Touchpoint Categories
| Category | Examples | Owner |
|----------|----------|-------|
| Marketing | Ads, content, social | Marketing |
| Sales | Demos, calls, proposals | Sales |
| Product | App, features, UI | Product |
| Support | Help center, chat, tickets | Support |
| Transactional | Emails, notifications | Varies |
### Touchpoint Mapping Template
```
Stage: [Name]
Touchpoint: [Where interaction happens]
Channel: [Web/Mobile/Email/Phone/In-person]
Action: [What user does]
Owner: [Team responsible]
Current Experience: [1-5 rating]
Improvement Priority: [High/Medium/Low]
```
### Cross-Channel Consistency
**Check for:**
- Information consistency across channels
- Seamless handoffs (web → mobile)
- Context preservation (user doesn't repeat info)
- Brand voice alignment
**Red Flags:**
- User has to re-enter information
- Different answers from different channels
- Can't continue task on different device
- Inconsistent terminology
---
## Emotion Mapping
### Emotion Scale
```
POSITIVE
│
Delighted ────┤──── 😄 5
Pleased ────┤──── 🙂 4
Neutral ────┤──── 😐 3
Frustrated ────┤──── 😕 2
Angry ────┤──── 😠 1
│
NEGATIVE
```
### Emotional Triggers
| Trigger | Positive Emotion | Negative Emotion |
|---------|------------------|------------------|
| Speed | Delight | Frustration |
| Clarity | Confidence | Confusion |
| Control | Empowerment | Helplessness |
| Progress | Satisfaction | Anxiety |
| Recognition | Validation | Neglect |
### Emotion Data Sources
**Direct Signals:**
- Interview quotes: "I felt so relieved when..."
- Survey scores: NPS, CSAT, CES
- Support sentiment: Angry vs. grateful tickets
**Inferred Signals:**
- Rage clicks (frustration)
- Quick completion (satisfaction)
- Abandonment (frustration or confusion)
- Return visits (interest or necessity)
### Emotion Curve Patterns
**The Valley of Death:**
```
😄 ─┐
│ ╱
│ ╱
😐 ─│───╱────────
│╲ ╱
│ ╳ ← Critical drop-off point
😠 ─│╱ ╲─────────
│
Onboarding First Use Regular
```
**The Aha Moment:**
```
😄 ─┐ ╱──
│ ╱
│ ╱
😐 ─│──────╱────── ← Before: neutral
│ ↑
😠 ─│ Aha!
│
Stage 1 Stage 2 Stage 3
```
---
## Opportunity Identification
### Pain Point Prioritization
| Factor | Score (1-5) |
|--------|-------------|
| Frequency | How often does this occur? |
| Severity | How much does it hurt? |
| Breadth | How many users affected? |
| Solvability | Can we fix this? |
**Priority Score = (Frequency + Severity + Breadth) × Solvability**
### Opportunity Types
| Type | Description | Example |
|------|-------------|---------|
| Friction Reduction | Remove obstacles | Fewer form fields |
| Moment of Delight | Exceed expectations | Personalized welcome |
| Channel Addition | New touchpoint | Mobile app for on-the-go |
| Proactive Support | Anticipate needs | Tutorial at right moment |
| Personalization | Tailored experience | Role-based onboarding |
### Opportunity Canvas
```
┌─────────────────────────────────────────────────────────────┐
│ OPPORTUNITY: [Name] │
├─────────────────────────────────────────────────────────────┤
│ Stage: [Where in journey] │
│ Current Pain: [What's broken] │
│ Desired Outcome: [What should happen] │
│ Proposed Solution: [How to fix] │
│ Success Metric: [How to measure] │
│ Effort: [High/Medium/Low] │
│ Impact: [High/Medium/Low] │
│ Priority: [Calculated] │
└─────────────────────────────────────────────────────────────┘
```
### Quick Wins vs. Strategic Bets
| Criteria | Quick Win | Strategic Bet |
|----------|-----------|---------------|
| Effort | Low | High |
| Impact | Medium | High |
| Timeline | Weeks | Quarters |
| Risk | Low | Medium-High |
| Requires | Small team | Cross-functional |
---
## Templates
### Basic Journey Map Template
```
PERSONA: _______________
GOAL: _______________
┌──────────┬──────────┬──────────┬──────────┬──────────┐
│ STAGE 1 │ STAGE 2 │ STAGE 3 │ STAGE 4 │ STAGE 5 │
├──────────┼──────────┼──────────┼──────────┼──────────┤
│ Actions │ │ │ │ │
│ │ │ │ │ │
├──────────┼──────────┼──────────┼──────────┼──────────┤
│ Touch- │ │ │ │ │
│ points │ │ │ │ │
├──────────┼──────────┼──────────┼──────────┼──────────┤
│ Emotions │ │ │ │ │
│ (1-5) │ │ │ │ │
├──────────┼──────────┼──────────┼──────────┼──────────┤
│ Pain │ │ │ │ │
│ Points │ │ │ │ │
├──────────┼──────────┼──────────┼──────────┼──────────┤
│ Opport- │ │ │ │ │
│ unities │ │ │ │ │
└──────────┴──────────┴──────────┴──────────┴──────────┘
```
### Detailed Stage Template
```
STAGE: _______________
DURATION: _______________
ENTRY CRITERIA: _______________
EXIT CRITERIA: _______________
USER ACTIONS:
1. _______________
2. _______________
3. _______________
TOUCHPOINTS:
• Channel: _____ | Owner: _____
• Channel: _____ | Owner: _____
THOUGHTS:
"_______________"
"_______________"
EMOTIONAL STATE: [1-5] ___
PAIN POINTS:
• _______________
• _______________
OPPORTUNITIES:
• _______________
• _______________
METRICS:
• Completion rate: ___%
• Time spent: ___
• Drop-off: ___%
```
### Service Blueprint Extension
Add backstage layers:
```
┌─────────────────────────────────────────────────────────────┐
│ FRONTSTAGE (User sees) │
├─────────────────────────────────────────────────────────────┤
│ User actions, touchpoints, emotions │
├─────────────────────────────────────────────────────────────┤
│ LINE OF VISIBILITY │
├─────────────────────────────────────────────────────────────┤
│ BACKSTAGE (User doesn't see) │
├─────────────────────────────────────────────────────────────┤
│ • Employee actions │
│ • Systems/tools used │
│ • Data flows │
├─────────────────────────────────────────────────────────────┤
│ SUPPORT PROCESSES │
├─────────────────────────────────────────────────────────────┤
│ • Backend systems │
│ • Third-party integrations │
│ • Policies/procedures │
└─────────────────────────────────────────────────────────────┘
```
---
## Quick Reference
### Journey Mapping Checklist
**Preparation:**
- [ ] Persona selected
- [ ] Goal defined
- [ ] Scope bounded
- [ ] Data gathered (interviews, analytics)
**Mapping:**
- [ ] Stages identified
- [ ] Actions documented
- [ ] Touchpoints mapped
- [ ] Emotions captured
- [ ] Pain points identified
**Analysis:**
- [ ] Opportunities prioritized
- [ ] Quick wins identified
- [ ] Strategic bets proposed
- [ ] Metrics defined
**Validation:**
- [ ] Team reviewed
- [ ] User validated
- [ ] Data correlated
### Common Mistakes
| Mistake | Impact | Fix |
|---------|--------|-----|
| Too many stages | Overwhelming | Limit to 5-7 |
| No data | Assumptions | Interview users |
| Single session | Bias | Multiple sources |
| No emotions | Misses human element | Add feeling layer |
| No follow-through | Wasted effort | Create action plan |
---
*See also: `persona-methodology.md` for persona creation*
FILE:references/persona-methodology.md
# Persona Methodology Guide
Reference for creating research-backed, data-driven user personas.
---
## Table of Contents
- [What Makes a Valid Persona](#what-makes-a-valid-persona)
- [Data Collection Methods](#data-collection-methods)
- [Analysis Framework](#analysis-framework)
- [Persona Components](#persona-components)
- [Validation Criteria](#validation-criteria)
- [Anti-Patterns](#anti-patterns)
---
## What Makes a Valid Persona
### Research-Backed vs. Assumption-Based
```
┌─────────────────────────────────────────────────────────────┐
│ PERSONA VALIDITY SPECTRUM │
├─────────────────────────────────────────────────────────────┤
│ │
│ ASSUMPTION-BASED HYBRID RESEARCH-BACKED │
│ │───────────────────────────────────────────────────────│ │
│ ❌ Invalid ⚠️ Limited ✅ Valid │
│ │
│ • "Our users are..." • Some interviews • 20+ users │
│ • No data • 5-10 data points • Quant + Qual │
│ • Team opinions • Partial patterns • Validated │
│ │
└─────────────────────────────────────────────────────────────┘
```
### Minimum Viability Requirements
| Requirement | Threshold | Confidence Level |
|-------------|-----------|------------------|
| Sample size | 5 users | Low (exploratory) |
| Sample size | 20 users | Medium (directional) |
| Sample size | 50+ users | High (reliable) |
| Data types | 2+ sources | Required |
| Interview depth | 30+ min | Recommended |
| Behavioral data | 1 week+ | Recommended |
### The Persona Validity Test
A valid persona must pass these checks:
1. **Grounded in Data**
- Can you point to specific user quotes?
- Can you show behavioral data supporting claims?
- Are demographics from actual user profiles?
2. **Represents a Segment**
- Does this persona represent 15%+ of your user base?
- Are there other users who fit this pattern?
- Is it a real cluster, not an outlier?
3. **Actionable for Design**
- Can designers make decisions from this persona?
- Does it reveal unmet needs?
- Does it clarify feature priorities?
---
## Data Collection Methods
### Quantitative Sources
| Source | Data Type | Use For |
|--------|-----------|---------|
| Analytics | Behavior | Usage patterns, feature adoption |
| Surveys | Demographics, preferences | Segmentation, satisfaction |
| Support tickets | Pain points | Frustration patterns |
| Product logs | Actions | Feature usage, workflows |
| CRM data | Profile | Job roles, company size |
### Qualitative Sources
| Source | Data Type | Use For |
|--------|-----------|---------|
| User interviews | Motivations, goals | Deep understanding |
| Contextual inquiry | Environment | Real-world context |
| Diary studies | Longitudinal | Behavior over time |
| Usability tests | Pain points | Specific frustrations |
| Customer calls | Quotes | Authentic voice |
### Data Collection Matrix
```
QUICK DEEP
(1-2 weeks) (4+ weeks)
│ │
┌─────────┼──────────────────┼─────────┐
QUANT │ Survey │ │ Product │
│ + CRM │ │ Logs + │
│ │ │ A/B │
├─────────┼──────────────────┼─────────┤
QUAL │ 5 │ │ 15+ │
│ Quick │ │ Deep │
│ Calls │ │ Inter- │
│ │ │ views │
└─────────┴──────────────────┴─────────┘
```
### Interview Protocol
**Pre-Interview:**
- Review user's analytics data
- Note usage patterns to explore
- Prepare open-ended questions
**Interview Structure (45-60 min):**
1. **Context (10 min)**
- "Walk me through your typical day"
- "When do you use [product]?"
- "What were you doing before you found us?"
2. **Behaviors (15 min)**
- "Show me how you use [feature]"
- "What do you do when [scenario]?"
- "What's your workaround for [pain point]?"
3. **Goals & Frustrations (15 min)**
- "What are you ultimately trying to achieve?"
- "What's the hardest part about [task]?"
- "If you had a magic wand, what would you change?"
4. **Reflection (10 min)**
- "What would make you recommend us?"
- "What almost made you quit?"
- "What's missing that you need?"
---
## Analysis Framework
### Pattern Identification
**Step 1: Code Data Points**
Tag each insight with:
- `[GOAL]` - What they want to achieve
- `[PAIN]` - What frustrates them
- `[BEHAVIOR]` - What they actually do
- `[CONTEXT]` - When/where they use product
- `[QUOTE]` - Direct user words
**Step 2: Cluster Similar Patterns**
```
User A: Uses daily, advanced features, keyboard shortcuts
User B: Uses daily, complex workflows, automation
User C: Uses weekly, basic needs, occasional
User D: Uses daily, power features, API access
Cluster 1: A, B, D (Power Users - daily, advanced)
Cluster 2: C (Casual User - weekly, basic)
```
**Step 3: Calculate Cluster Size**
| Cluster | Users | % of Sample | Viability |
|---------|-------|-------------|-----------|
| Power Users | 18 | 36% | Primary persona |
| Business Users | 15 | 30% | Primary persona |
| Casual Users | 12 | 24% | Secondary persona |
| Mobile-First | 5 | 10% | Consider merging |
### Archetype Classification
| Archetype | Identifying Signals | Design Focus |
|-----------|--------------------| -------------|
| Power User | Daily use, 10+ features, shortcuts | Efficiency, customization |
| Casual User | Weekly use, 3-5 features, simple | Simplicity, guidance |
| Business User | Work context, team features, ROI | Collaboration, reporting |
| Mobile-First | Mobile primary, quick actions | Touch, offline, speed |
### Confidence Scoring
Calculate confidence based on data quality:
```
Confidence = (Sample Size Score + Data Quality Score + Consistency Score) / 3
Sample Size Score:
5-10 users = 1 (Low)
11-30 users = 2 (Medium)
31+ users = 3 (High)
Data Quality Score:
Survey only = 1 (Low)
Survey + Analytics = 2 (Medium)
Quant + Qual + Logs = 3 (High)
Consistency Score:
Contradicting data = 1 (Low)
Some alignment = 2 (Medium)
Strong alignment = 3 (High)
```
---
## Persona Components
### Required Elements
| Component | Description | Source |
|-----------|-------------|--------|
| Name & Photo | Memorable identifier | Stock photo, AI-generated |
| Tagline | One-line summary | Synthesized from data |
| Quote | Authentic voice | Direct from interviews |
| Demographics | Age, role, location | CRM, surveys |
| Goals | What they want | Interviews |
| Frustrations | Pain points | Interviews, support |
| Behaviors | How they act | Analytics, observation |
| Scenarios | Usage contexts | Interviews, logs |
### Optional Enhancements
| Component | When to Include |
|-----------|-----------------|
| Day-in-the-life | Complex workflows |
| Empathy map | Design workshops |
| Technology stack | B2B products |
| Influences | Consumer products |
| Brands they love | Marketing-heavy |
### Component Depth Guide
**Demographics (Keep Brief):**
```
❌ Too detailed:
Age: 34, Lives: Seattle, Education: MBA from Stanford
✅ Right level:
Age: 30-40, Urban professional, Graduate degree
```
**Goals (Be Specific):**
```
❌ Too vague:
"Wants to be productive"
✅ Actionable:
"Needs to process 50+ items daily without repetitive tasks"
```
**Frustrations (Include Evidence):**
```
❌ Generic:
"Finds the interface confusing"
✅ With evidence:
"Can't find export function (mentioned by 8/12 users)"
```
---
## Validation Criteria
### Internal Validation
**Team Check:**
- [ ] Does sales recognize this user type?
- [ ] Does support see these pain points?
- [ ] Does product know these workflows?
**Data Check:**
- [ ] Can we quantify this segment's size?
- [ ] Do behaviors match analytics?
- [ ] Are quotes from real users?
### External Validation
**User Validation (recommended):**
- Show persona to 3-5 users from segment
- Ask: "Does this sound like you?"
- Iterate based on feedback
**A/B Design Test:**
- Design for persona A vs. persona B
- Test with actual users
- Measure if persona-driven design wins
### Red Flags
Watch for these persona validity problems:
| Red Flag | What It Means | Fix |
|----------|---------------|-----|
| "Everyone" persona | Too broad to be useful | Split into segments |
| Contradicting data | Forcing a narrative | Re-analyze clusters |
| No frustrations | Sanitized or incomplete | Dig deeper in interviews |
| Assumptions labeled as data | No real research | Conduct actual research |
| Single data source | Fragile foundation | Add another data type |
---
## Anti-Patterns
### 1. The Elastic Persona
**Problem:** Persona stretches to include everyone
```
❌ "Sarah is 25-55, uses mobile and desktop, wants simplicity
but also advanced features, works alone and in teams..."
```
**Fix:** Create separate personas for distinct segments
### 2. The Demographic Persona
**Problem:** All demographics, no psychographics
```
❌ "John is 35, male, $80k income, urban, MBA..."
(Nothing about goals, frustrations, behaviors)
```
**Fix:** Lead with goals and frustrations, add minimal demographics
### 3. The Ideal User Persona
**Problem:** Describes who you want, not who you have
```
❌ "Emma is a passionate advocate who tells everyone
about our product and uses every feature daily..."
```
**Fix:** Base on real user data, include realistic limitations
### 4. The Committee Persona
**Problem:** Each stakeholder added their opinions
```
❌ CEO added "enterprise-focused"
Sales added "loves demos"
Support added "never calls support"
```
**Fix:** Single owner, data-driven only
### 5. The Stale Persona
**Problem:** Created once, never updated
```
❌ "Last updated: 2019"
Product has changed completely since then
```
**Fix:** Review quarterly, update with new data
---
## Quick Reference
### Persona Creation Checklist
- [ ] Minimum 20 users in data set
- [ ] At least 2 data sources (quant + qual)
- [ ] Clear segment boundaries
- [ ] Actionable for design decisions
- [ ] Validated with team and users
- [ ] Documented data sources
- [ ] Confidence level stated
### Time Investment Guide
| Persona Type | Time | Team | Output |
|--------------|------|------|--------|
| Quick & Dirty | 1 week | 1 | Directional |
| Standard | 2-4 weeks | 2 | Production |
| Comprehensive | 6-8 weeks | 3+ | Strategic |
---
*See also: `example-personas.md` for output examples*
FILE:references/usability-testing-frameworks.md
# Usability Testing Frameworks
Reference for planning and conducting usability tests that produce actionable insights.
---
## Table of Contents
- [Testing Methods Overview](#testing-methods-overview)
- [Test Planning](#test-planning)
- [Task Design](#task-design)
- [Moderation Techniques](#moderation-techniques)
- [Analysis Framework](#analysis-framework)
- [Reporting Template](#reporting-template)
---
## Testing Methods Overview
### Method Selection Matrix
| Method | When to Use | Participants | Time | Output |
|--------|-------------|--------------|------|--------|
| Moderated remote | Deep insights, complex flows | 5-8 | 45-60 min | Rich qualitative |
| Unmoderated remote | Quick validation, simple tasks | 10-20 | 15-20 min | Quantitative + video |
| In-person | Physical products, context matters | 5-10 | 60-90 min | Very rich qualitative |
| Guerrilla | Quick feedback, public spaces | 3-5 | 5-10 min | Rapid insights |
| A/B testing | Comparing two designs | 100+ | Varies | Statistical data |
### Participant Count Guidelines
```
┌─────────────────────────────────────────────────────────────┐
│ FINDING USABILITY ISSUES │
├─────────────────────────────────────────────────────────────┤
│ │
│ % Issues Found │
│ 100% ┤ ●────●────● │
│ 90% ┤ ●───── │
│ 80% ┤ ●───── │
│ 75% ┤ ●──── ← 5 users: 75-80% │
│ 50% ┤ ●──── │
│ 25% ┤ ●── │
│ 0% ┼────┬────┬────┬────┬────┬──── │
│ 1 2 3 4 5 6+ Users │
│ │
└─────────────────────────────────────────────────────────────┘
```
**Nielsen's Rule:** 5 users find ~75-80% of usability issues
| Goal | Participants | Reasoning |
|------|--------------|-----------|
| Find major issues | 5 | 80% coverage, diminishing returns |
| Validate fix | 3 | Confirm specific issue resolved |
| Compare designs | 8-10 per design | Need comparison data |
| Quantitative metrics | 20+ | Statistical significance |
---
## Test Planning
### Research Questions
Transform vague goals into testable questions:
| Vague Goal | Testable Question |
|------------|-------------------|
| "Is it easy to use?" | "Can users complete checkout in under 3 minutes?" |
| "Do users like it?" | "Will users choose Design A or B for this task?" |
| "Does it make sense?" | "Can users find the settings without hints?" |
### Test Plan Template
```
PROJECT: _______________
DATE: _______________
RESEARCHER: _______________
RESEARCH QUESTIONS:
1. _______________
2. _______________
3. _______________
PARTICIPANTS:
• Target: [Persona or user type]
• Count: [Number]
• Recruitment: [Source]
• Incentive: [Amount/type]
METHOD:
• Type: [Moderated/Unmoderated/Remote/In-person]
• Duration: [Minutes per session]
• Environment: [Tool/Location]
TASKS:
1. [Task description + success criteria]
2. [Task description + success criteria]
3. [Task description + success criteria]
METRICS:
• Completion rate (target: __%)
• Time on task (target: __ min)
• Error rate (target: __%)
• Satisfaction (target: __/5)
SCHEDULE:
• Pilot: [Date]
• Sessions: [Date range]
• Analysis: [Date]
• Report: [Date]
```
### Pilot Testing
**Always pilot before real sessions:**
- Run 1-2 test sessions with team members
- Check task clarity and timing
- Test recording/screen sharing
- Adjust based on pilot feedback
**Pilot Checklist:**
- [ ] Tasks understood without clarification
- [ ] Session fits in time slot
- [ ] Recording captures screen + audio
- [ ] Post-test questions make sense
---
## Task Design
### Good vs. Bad Tasks
| Bad Task | Why Bad | Good Task |
|----------|---------|-----------|
| "Find the settings" | Leading | "Change your notification preferences" |
| "Use the dashboard" | Vague | "Find how many sales you made last month" |
| "Click the blue button" | Prescriptive | "Submit your order" |
| "Do you like this?" | Opinion-based | "Rate how easy it was (1-5)" |
### Task Construction Formula
```
SCENARIO + GOAL + SUCCESS CRITERIA
Scenario: Context that makes task realistic
Goal: What user needs to accomplish
Success: How we know they succeeded
Example:
"Imagine you're planning a trip to Paris next month. [SCENARIO]
Book a hotel for 3 nights in your budget. [GOAL]
You've succeeded when you see the confirmation page. [SUCCESS]"
```
### Task Types
| Type | Purpose | Example |
|------|---------|---------|
| Exploration | First impressions | "Look around and tell me what you think this does" |
| Specific | Core functionality | "Add item to cart and checkout" |
| Comparison | Design validation | "Which of these two menus would you use to..." |
| Stress | Edge cases | "What would you do if your payment failed?" |
### Task Difficulty Progression
Start easy, increase difficulty:
```
Task 1: Warm-up (easy, builds confidence)
Task 2: Core flow (main functionality)
Task 3: Secondary flow (important but less common)
Task 4: Edge case (stress test)
Task 5: Free exploration (open-ended)
```
---
## Moderation Techniques
### The Think-Aloud Protocol
**Instruction Script:**
"As you work through the tasks, please think out loud. Tell me what you're looking at, what you're thinking, and what you're trying to do. There are no wrong answers - we're testing the design, not you."
**Prompts When Silent:**
- "What are you thinking right now?"
- "What do you expect to happen?"
- "What are you looking for?"
- "Tell me more about that"
### Handling Common Situations
| Situation | What to Say |
|-----------|-------------|
| User asks for help | "What would you do if I weren't here?" |
| User is stuck | "What are your options?" (wait 30 sec before hint) |
| User apologizes | "You're doing great. We're testing the design." |
| User goes off-task | "That's interesting. Let's come back to [task]." |
| User criticizes | "Tell me more about that." (neutral, don't defend) |
### Non-Leading Question Techniques
| Leading (Don't) | Neutral (Do) |
|-----------------|--------------|
| "Did you find that confusing?" | "How was that experience?" |
| "The search is over here" | "What do you think you should do?" |
| "Don't you think X is easier?" | "Which do you prefer and why?" |
| "Did you notice the tooltip?" | "What happened there?" |
### Post-Task Questions
After each task:
1. "How difficult was that?" (1-5 scale)
2. "What, if anything, was confusing?"
3. "What would you improve?"
After all tasks:
1. "What stood out to you?"
2. "What was the best/worst part?"
3. "Would you use this? Why/why not?"
---
## Analysis Framework
### Severity Rating Scale
| Severity | Definition | Criteria |
|----------|------------|----------|
| 4 - Critical | Prevents task completion | User cannot proceed |
| 3 - Major | Significant difficulty | User struggles, considers giving up |
| 2 - Minor | Causes hesitation | User recovers independently |
| 1 - Cosmetic | Noticed but not problematic | User comments but unaffected |
### Issue Documentation Template
```
ISSUE ID: ___
SEVERITY: [1-4]
FREQUENCY: [X/Y participants]
TASK: [Which task]
TIMESTAMP: [When in session]
OBSERVATION:
[What happened - factual description]
USER QUOTE:
"[Direct quote if available]"
HYPOTHESIS:
[Why this might be happening]
RECOMMENDATION:
[Proposed solution]
AFFECTED PERSONA:
[Which user types]
```
### Pattern Recognition
**Quantitative Signals:**
- Task completion rate < 80%
- Time on task > 2x expected
- Error rate > 20%
- Satisfaction < 3/5
**Qualitative Signals:**
- Same confusion point across 3+ users
- Repeated verbal frustration
- Workaround attempts
- Feature requests during task
### Analysis Matrix
```
┌─────────────────┬───────────┬───────────┬───────────┐
│ Issue │ Frequency │ Severity │ Priority │
├─────────────────┼───────────┼───────────┼───────────┤
│ Can't find X │ 4/5 │ Critical │ HIGH │
│ Confusing label │ 3/5 │ Major │ HIGH │
│ Slow loading │ 2/5 │ Minor │ MEDIUM │
│ Typo in text │ 1/5 │ Cosmetic │ LOW │
└─────────────────┴───────────┴───────────┴───────────┘
Priority = Frequency × Severity
```
---
## Reporting Template
### Executive Summary
```
USABILITY TEST REPORT
[Project Name] | [Date]
OVERVIEW
• Participants: [N] users matching [persona]
• Method: [Type of test]
• Tasks: [N] tasks covering [scope]
KEY FINDINGS
1. [Most critical issue + impact]
2. [Second issue]
3. [Third issue]
SUCCESS METRICS
• Completion rate: [X]% (target: Y%)
• Avg. time on task: [X] min (target: Y min)
• Satisfaction: [X]/5 (target: Y/5)
TOP RECOMMENDATIONS
1. [Highest priority fix]
2. [Second priority]
3. [Third priority]
```
### Detailed Findings Section
```
FINDING 1: [Title]
Severity: [Critical/Major/Minor/Cosmetic]
Frequency: [X/Y participants]
Affected Tasks: [List]
What Happened:
[Description of the problem]
Evidence:
• P1: "[Quote]"
• P3: "[Quote]"
• [Video timestamp if available]
Impact:
[How this affects users and business]
Recommendation:
[Proposed solution with rationale]
Design Mockup:
[Optional: before/after if applicable]
```
### Metrics Dashboard
```
TASK PERFORMANCE SUMMARY
Task 1: [Name]
├─ Completion: ████████░░ 80%
├─ Avg. Time: 2:15 (target: 2:00)
├─ Errors: 1.2 avg
└─ Satisfaction: ★★★★☆ 4.2/5
Task 2: [Name]
├─ Completion: ██████░░░░ 60% ⚠️
├─ Avg. Time: 4:30 (target: 3:00) ⚠️
├─ Errors: 3.1 avg ⚠️
└─ Satisfaction: ★★★☆☆ 3.1/5
[Continue for all tasks]
```
---
## Quick Reference
### Session Checklist
**Before Session:**
- [ ] Test plan finalized
- [ ] Tasks written and piloted
- [ ] Recording set up and tested
- [ ] Consent form ready
- [ ] Prototype/product accessible
- [ ] Note-taking template ready
**During Session:**
- [ ] Consent obtained
- [ ] Think-aloud explained
- [ ] Recording started
- [ ] Tasks presented one at a time
- [ ] Post-task ratings collected
- [ ] Debrief questions asked
- [ ] Thanks and incentive
**After Session:**
- [ ] Notes organized
- [ ] Recording saved
- [ ] Initial impressions captured
- [ ] Issues logged
### Common Metrics
| Metric | Formula | Target |
|--------|---------|--------|
| Completion rate | Successful / Total × 100 | >80% |
| Time on task | Average seconds | <2x expected |
| Error rate | Errors / Attempts × 100 | <15% |
| Task-level satisfaction | Average rating | >4/5 |
| SUS score | Standard formula | >68 |
| NPS | Promoters - Detractors | >0 |
---
*See also: `journey-mapping-guide.md` for contextual research*
FILE:scripts/persona_generator.py
#!/usr/bin/env python3
"""
Data-Driven Persona Generator
Creates research-backed user personas from user data and interviews.
Usage:
python persona_generator.py [json]
Without arguments: Human-readable formatted output
With 'json': JSON output for integration with other tools
Examples:
python persona_generator.py # Formatted persona output
python persona_generator.py json # JSON for programmatic use
Table of Contents:
==================
CLASS: PersonaGenerator
__init__() - Initialize archetype templates and persona components
generate_persona_from_data() - Main entry: generate persona from user data + interviews
format_persona_output() - Format persona dict as human-readable text
PATTERN ANALYSIS:
_analyze_user_patterns() - Extract usage, device, context patterns from data
_identify_archetype() - Classify user into power/casual/business/mobile archetype
_analyze_behaviors() - Analyze usage patterns and feature preferences
DEMOGRAPHIC EXTRACTION:
_aggregate_demographics() - Calculate age range, location, tech proficiency
_extract_psychographics() - Extract motivations, values, attitudes, lifestyle
NEEDS & FRUSTRATIONS:
_identify_needs() - Identify primary/secondary goals, functional/emotional needs
_extract_frustrations() - Extract pain points from patterns and interviews
CONTENT GENERATION:
_generate_name() - Generate persona name from archetype
_generate_tagline() - Generate one-line persona summary
_generate_scenarios() - Create usage scenarios based on archetype
_select_quote() - Select representative quote from interviews
DATA VALIDATION:
_calculate_data_points() - Calculate sample size and confidence level
_derive_design_implications() - Generate actionable design recommendations
FUNCTIONS:
create_sample_user_data() - Generate sample data for testing/demo
main() - CLI entry point
Archetypes Supported:
- power_user: Daily users, 10+ features, efficiency-focused
- casual_user: Weekly users, basic needs, simplicity-focused
- business_user: Work context, team collaboration, ROI-focused
- mobile_first: Mobile primary, on-the-go, quick interactions
Output Components:
- name, archetype, tagline, quote
- demographics: age, location, occupation, education, tech_proficiency
- psychographics: motivations, values, attitudes, lifestyle
- behaviors: usage_patterns, feature_preferences, interaction_style
- needs_and_goals: primary, secondary, functional, emotional
- frustrations: pain points with frequency
- scenarios: contextual usage stories
- data_points: sample_size, confidence_level, validation_method
- design_implications: actionable recommendations
"""
import json
from typing import Dict, List, Tuple
from collections import Counter, defaultdict
import random
class PersonaGenerator:
"""Generate data-driven personas from user research"""
def __init__(self):
self.persona_components = {
'demographics': ['age', 'location', 'occupation', 'education', 'income'],
'psychographics': ['goals', 'frustrations', 'motivations', 'values'],
'behaviors': ['tech_savviness', 'usage_frequency', 'preferred_devices', 'key_activities'],
'needs': ['functional', 'emotional', 'social']
}
self.archetype_templates = {
'power_user': {
'characteristics': ['tech-savvy', 'frequent user', 'early adopter', 'efficiency-focused'],
'goals': ['maximize productivity', 'automate workflows', 'access advanced features'],
'frustrations': ['slow performance', 'limited customization', 'lack of shortcuts'],
'quote': "I need tools that can keep up with my workflow"
},
'casual_user': {
'characteristics': ['occasional user', 'basic needs', 'prefers simplicity'],
'goals': ['accomplish specific tasks', 'easy to use', 'minimal learning curve'],
'frustrations': ['complexity', 'too many options', 'unclear navigation'],
'quote': "I just want it to work without having to think about it"
},
'business_user': {
'characteristics': ['professional context', 'ROI-focused', 'team collaboration'],
'goals': ['improve team efficiency', 'track metrics', 'integrate with tools'],
'frustrations': ['lack of reporting', 'poor collaboration features', 'no enterprise features'],
'quote': "I need to show clear value to my stakeholders"
},
'mobile_first': {
'characteristics': ['primarily mobile', 'on-the-go usage', 'quick interactions'],
'goals': ['access anywhere', 'quick actions', 'offline capability'],
'frustrations': ['poor mobile experience', 'desktop-only features', 'slow loading'],
'quote': "My phone is my primary computing device"
}
}
def generate_persona_from_data(self, user_data: List[Dict],
interview_insights: List[Dict] = None) -> Dict:
"""Generate persona from user data and optional interview insights"""
# Analyze user data for patterns
patterns = self._analyze_user_patterns(user_data)
# Identify persona archetype
archetype = self._identify_archetype(patterns)
# Generate persona
persona = {
'name': self._generate_name(archetype),
'archetype': archetype,
'tagline': self._generate_tagline(patterns),
'demographics': self._aggregate_demographics(user_data),
'psychographics': self._extract_psychographics(patterns, interview_insights),
'behaviors': self._analyze_behaviors(user_data),
'needs_and_goals': self._identify_needs(patterns, interview_insights),
'frustrations': self._extract_frustrations(patterns, interview_insights),
'scenarios': self._generate_scenarios(archetype, patterns),
'quote': self._select_quote(interview_insights, archetype),
'data_points': self._calculate_data_points(user_data),
'design_implications': self._derive_design_implications(patterns)
}
return persona
def _analyze_user_patterns(self, user_data: List[Dict]) -> Dict:
"""Analyze patterns in user data"""
patterns = {
'usage_frequency': defaultdict(int),
'feature_usage': defaultdict(int),
'devices': defaultdict(int),
'contexts': defaultdict(int),
'pain_points': [],
'success_metrics': []
}
for user in user_data:
# Frequency patterns
freq = user.get('usage_frequency', 'medium')
patterns['usage_frequency'][freq] += 1
# Feature usage
for feature in user.get('features_used', []):
patterns['feature_usage'][feature] += 1
# Device patterns
device = user.get('primary_device', 'desktop')
patterns['devices'][device] += 1
# Context patterns
context = user.get('usage_context', 'work')
patterns['contexts'][context] += 1
# Pain points
if 'pain_points' in user:
patterns['pain_points'].extend(user['pain_points'])
return patterns
def _identify_archetype(self, patterns: Dict) -> str:
"""Identify persona archetype based on patterns"""
# Simple heuristic-based archetype identification
freq_pattern = max(patterns['usage_frequency'].items(), key=lambda x: x[1])[0] if patterns['usage_frequency'] else 'medium'
device_pattern = max(patterns['devices'].items(), key=lambda x: x[1])[0] if patterns['devices'] else 'desktop'
if freq_pattern == 'daily' and len(patterns['feature_usage']) > 10:
return 'power_user'
elif device_pattern in ['mobile', 'tablet']:
return 'mobile_first'
elif patterns['contexts'].get('work', 0) > patterns['contexts'].get('personal', 0):
return 'business_user'
else:
return 'casual_user'
def _generate_name(self, archetype: str) -> str:
"""Generate persona name based on archetype"""
names = {
'power_user': ['Alex', 'Sam', 'Jordan', 'Morgan'],
'casual_user': ['Pat', 'Jamie', 'Casey', 'Riley'],
'business_user': ['Taylor', 'Cameron', 'Avery', 'Blake'],
'mobile_first': ['Quinn', 'Skylar', 'River', 'Sage']
}
name_pool = names.get(archetype, names['casual_user'])
first_name = random.choice(name_pool)
roles = {
'power_user': 'the Power User',
'casual_user': 'the Casual User',
'business_user': 'the Business Professional',
'mobile_first': 'the Mobile Native'
}
return f"{first_name} {roles[archetype]}"
def _generate_tagline(self, patterns: Dict) -> str:
"""Generate persona tagline"""
freq = max(patterns['usage_frequency'].items(), key=lambda x: x[1])[0] if patterns['usage_frequency'] else 'regular'
context = max(patterns['contexts'].items(), key=lambda x: x[1])[0] if patterns['contexts'] else 'general'
return f"A {freq} user who primarily uses the product for {context} purposes"
def _aggregate_demographics(self, user_data: List[Dict]) -> Dict:
"""Aggregate demographic information"""
demographics = {
'age_range': '',
'location_type': '',
'occupation_category': '',
'education_level': '',
'tech_proficiency': ''
}
if not user_data:
return demographics
# Age range
ages = [u.get('age', 30) for u in user_data if 'age' in u]
if ages:
avg_age = sum(ages) / len(ages)
if avg_age < 25:
demographics['age_range'] = '18-24'
elif avg_age < 35:
demographics['age_range'] = '25-34'
elif avg_age < 45:
demographics['age_range'] = '35-44'
else:
demographics['age_range'] = '45+'
# Location type
locations = [u.get('location_type', 'urban') for u in user_data if 'location_type' in u]
if locations:
demographics['location_type'] = Counter(locations).most_common(1)[0][0]
# Tech proficiency
tech_scores = [u.get('tech_proficiency', 5) for u in user_data if 'tech_proficiency' in u]
if tech_scores:
avg_tech = sum(tech_scores) / len(tech_scores)
if avg_tech < 3:
demographics['tech_proficiency'] = 'Beginner'
elif avg_tech < 7:
demographics['tech_proficiency'] = 'Intermediate'
else:
demographics['tech_proficiency'] = 'Advanced'
return demographics
def _extract_psychographics(self, patterns: Dict, interviews: List[Dict] = None) -> Dict:
"""Extract psychographic information"""
psychographics = {
'motivations': [],
'values': [],
'attitudes': [],
'lifestyle': ''
}
# Extract from patterns
if patterns['usage_frequency'].get('daily', 0) > 0:
psychographics['motivations'].append('Efficiency')
psychographics['values'].append('Time-saving')
if patterns['devices'].get('mobile', 0) > patterns['devices'].get('desktop', 0):
psychographics['lifestyle'] = 'On-the-go, mobile-first'
psychographics['values'].append('Flexibility')
# Extract from interviews if available
if interviews:
for interview in interviews:
if 'motivations' in interview:
psychographics['motivations'].extend(interview['motivations'])
if 'values' in interview:
psychographics['values'].extend(interview['values'])
# Deduplicate
psychographics['motivations'] = list(set(psychographics['motivations']))[:5]
psychographics['values'] = list(set(psychographics['values']))[:5]
return psychographics
def _analyze_behaviors(self, user_data: List[Dict]) -> Dict:
"""Analyze user behaviors"""
behaviors = {
'usage_patterns': [],
'feature_preferences': [],
'interaction_style': '',
'learning_preference': ''
}
if not user_data:
return behaviors
# Usage patterns
frequencies = [u.get('usage_frequency', 'medium') for u in user_data]
freq_counter = Counter(frequencies)
behaviors['usage_patterns'] = [f"{freq}: {count} users" for freq, count in freq_counter.most_common(3)]
# Feature preferences
all_features = []
for user in user_data:
all_features.extend(user.get('features_used', []))
feature_counter = Counter(all_features)
behaviors['feature_preferences'] = [feat for feat, count in feature_counter.most_common(5)]
# Interaction style
if len(behaviors['feature_preferences']) > 10:
behaviors['interaction_style'] = 'Exploratory - uses many features'
else:
behaviors['interaction_style'] = 'Focused - uses core features'
return behaviors
def _identify_needs(self, patterns: Dict, interviews: List[Dict] = None) -> Dict:
"""Identify user needs and goals"""
needs = {
'primary_goals': [],
'secondary_goals': [],
'functional_needs': [],
'emotional_needs': []
}
# Derive from usage patterns
if patterns['usage_frequency'].get('daily', 0) > 0:
needs['primary_goals'].append('Complete tasks efficiently')
needs['functional_needs'].append('Speed and performance')
if patterns['contexts'].get('work', 0) > 0:
needs['primary_goals'].append('Professional productivity')
needs['functional_needs'].append('Integration with work tools')
# Common emotional needs
needs['emotional_needs'] = [
'Feel confident using the product',
'Trust the system with data',
'Feel supported when issues arise'
]
# Extract from interviews
if interviews:
for interview in interviews:
if 'goals' in interview:
needs['primary_goals'].extend(interview['goals'][:2])
if 'needs' in interview:
needs['functional_needs'].extend(interview['needs'][:3])
return needs
def _extract_frustrations(self, patterns: Dict, interviews: List[Dict] = None) -> List[str]:
"""Extract user frustrations"""
frustrations = []
# Common frustrations from patterns
if patterns['pain_points']:
frustration_counter = Counter(patterns['pain_points'])
frustrations = [pain for pain, count in frustration_counter.most_common(5)]
# Add archetype-specific frustrations if not enough from data
if len(frustrations) < 3:
frustrations.extend([
'Slow loading times',
'Confusing navigation',
'Lack of mobile optimization'
])
return frustrations[:5]
def _generate_scenarios(self, archetype: str, patterns: Dict) -> List[Dict]:
"""Generate usage scenarios"""
scenarios = []
# Common scenarios based on archetype
scenario_templates = {
'power_user': [
{
'title': 'Bulk Processing',
'context': 'Monday morning, needs to process week\'s data',
'goal': 'Complete batch operations quickly',
'steps': ['Import data', 'Apply bulk actions', 'Export results'],
'pain_points': ['No keyboard shortcuts', 'Slow processing']
}
],
'casual_user': [
{
'title': 'Quick Task',
'context': 'Needs to complete single task',
'goal': 'Get in, complete task, get out',
'steps': ['Find feature', 'Complete task', 'Save/Exit'],
'pain_points': ['Can\'t find feature', 'Too many steps']
}
],
'business_user': [
{
'title': 'Team Collaboration',
'context': 'Working with team on project',
'goal': 'Share and collaborate efficiently',
'steps': ['Create content', 'Share with team', 'Track feedback'],
'pain_points': ['No real-time collaboration', 'Poor permission management']
}
],
'mobile_first': [
{
'title': 'On-the-Go Access',
'context': 'Commuting, needs quick access',
'goal': 'Complete task on mobile',
'steps': ['Open mobile app', 'Quick action', 'Sync with desktop'],
'pain_points': ['Feature parity issues', 'Poor mobile UX']
}
]
}
return scenario_templates.get(archetype, scenario_templates['casual_user'])
def _select_quote(self, interviews: List[Dict] = None, archetype: str = 'casual_user') -> str:
"""Select representative quote"""
if interviews:
# Try to find a real quote
for interview in interviews:
if 'quotes' in interview and interview['quotes']:
return interview['quotes'][0]
# Use archetype default
return self.archetype_templates[archetype]['quote']
def _calculate_data_points(self, user_data: List[Dict]) -> Dict:
"""Calculate supporting data points"""
return {
'sample_size': len(user_data),
'confidence_level': 'High' if len(user_data) > 50 else 'Medium' if len(user_data) > 20 else 'Low',
'last_updated': 'Current',
'validation_method': 'Quantitative analysis + Qualitative interviews'
}
def _derive_design_implications(self, patterns: Dict) -> List[str]:
"""Derive design implications from persona"""
implications = []
# Based on frequency
if patterns['usage_frequency'].get('daily', 0) > patterns['usage_frequency'].get('weekly', 0):
implications.append('Optimize for speed and efficiency')
implications.append('Provide keyboard shortcuts and power features')
else:
implications.append('Focus on discoverability and guidance')
implications.append('Simplify onboarding experience')
# Based on device
if patterns['devices'].get('mobile', 0) > 0:
implications.append('Mobile-first responsive design')
implications.append('Touch-optimized interactions')
# Based on context
if patterns['contexts'].get('work', 0) > patterns['contexts'].get('personal', 0):
implications.append('Professional visual design')
implications.append('Enterprise features (SSO, audit logs)')
return implications[:5]
def format_persona_output(self, persona: Dict) -> str:
"""Format persona for display"""
output = []
output.append("=" * 60)
output.append(f"PERSONA: {persona['name']}")
output.append("=" * 60)
output.append(f"\n📝 {persona['tagline']}\n")
output.append(f"Archetype: {persona['archetype'].replace('_', ' ').title()}")
output.append(f"Quote: \"{persona['quote']}\"\n")
output.append("👤 Demographics:")
for key, value in persona['demographics'].items():
if value:
output.append(f" • {key.replace('_', ' ').title()}: {value}")
output.append("\n🧠 Psychographics:")
if persona['psychographics']['motivations']:
output.append(f" Motivations: {', '.join(persona['psychographics']['motivations'])}")
if persona['psychographics']['values']:
output.append(f" Values: {', '.join(persona['psychographics']['values'])}")
output.append("\n🎯 Goals & Needs:")
for goal in persona['needs_and_goals'].get('primary_goals', [])[:3]:
output.append(f" • {goal}")
output.append("\n😤 Frustrations:")
for frustration in persona['frustrations'][:3]:
output.append(f" • {frustration}")
output.append("\n📊 Behaviors:")
for pref in persona['behaviors'].get('feature_preferences', [])[:3]:
output.append(f" • Frequently uses: {pref}")
output.append("\n💡 Design Implications:")
for implication in persona['design_implications']:
output.append(f" → {implication}")
output.append(f"\n📈 Data: Based on {persona['data_points']['sample_size']} users")
output.append(f" Confidence: {persona['data_points']['confidence_level']}")
return "\n".join(output)
def create_sample_user_data():
"""Create sample user data for testing"""
return [
{
'user_id': f'user_{i}',
'age': 25 + (i % 30),
'usage_frequency': ['daily', 'weekly', 'monthly'][i % 3],
'features_used': ['dashboard', 'reports', 'settings', 'sharing', 'export'][:3 + (i % 3)],
'primary_device': ['desktop', 'mobile', 'tablet'][i % 3],
'usage_context': ['work', 'personal'][i % 2],
'tech_proficiency': 3 + (i % 7),
'pain_points': ['slow loading', 'confusing UI', 'missing features'][:(i % 3) + 1]
}
for i in range(30)
]
def main():
import sys
generator = PersonaGenerator()
# Create sample data
user_data = create_sample_user_data()
# Optional interview insights
interview_insights = [
{
'quotes': ["I need to see all my data in one place"],
'motivations': ['Efficiency', 'Control'],
'goals': ['Save time', 'Make better decisions']
}
]
# Generate persona
persona = generator.generate_persona_from_data(user_data, interview_insights)
# Output
if len(sys.argv) > 1 and sys.argv[1] == 'json':
print(json.dumps(persona, indent=2))
else:
print(generator.format_persona_output(persona))
if __name__ == "__main__":
main()
Tạo trang landing HTML một trang cao cấp với hoạt ảnh CSS 3D, hiệu ứng cuộn GSAP và parallax chuột, sau khi chốt định vị sản phẩm và giọng điệu.
---
name: landing
description: "Generates a premium single-page HTML landing page with 3D CSS animations, GSAP scroll effects, and mouse-parallax depth. Forcing intake (product + elevator pitch, audience register, brand overrides, tone) locks down positioning before any copy or markup is written, so the page reflects the actual product rather than generic boilerplate. Use whenever the user says 'landing for X', 'create a landing page', 'build a landing page', 'make a landing page for X', 'I need a web page for Y', or provides product/service details and wants a polished website. Also triggers on 'promotional page', 'product page', 'one-pager', 'web presence', 'sales page'. Outputs a single self-contained HTML file (Claude Code) or HTML artifact (Claude.ai). Supports configurable brand colors via CSS custom property overrides."
license: MIT
metadata:
source_spec: "megaprompts/04-landing-megaprompt.md"
build_pattern: "Path B (direct conversion)"
distinct_from: "product-team/skills/landing-page-generator (different output format + optimization target)"
version: 1.0.0
---
# Landing — Premium HTML Landing Page Generator
> **Distinct from `product-team/skills/landing-page-generator/`.** That skill outputs Next.js TSX components optimized for conversion / lead-gen. THIS skill outputs a single self-contained `.html` file optimized for premium visual experience with GSAP animations. Pick by use case.
Generate a polished, self-contained `.html` landing page from a text prompt or brief. The output is ONE HTML file: all CSS inline in `<style>`, all JS inline in `<script>`, only external dependencies being Google Fonts + GSAP via CDN. The page is visually distinctive, animated, and production-quality.
## Invocation Triggers
- "create a landing page"
- "build a landing page"
- "make a landing page for X"
- "I need a web page for Y"
- "promotional page"
- "product page"
- "one-pager"
- "web presence"
- "sales page"
- "landing for X"
## Delivery Mode
In **Claude Code CLI**, write the file to disk at the specified path. In **Claude.ai web**, create an HTML artifact with the same content.
## Phase 0: Grill-Me Intake (4 forcing questions, one at a time)
Dependency-ordered. Each question carries explicit "why I'm asking". Stop condition: max 4.
### Q1 (root) — Product / Service
> **What's the product or service? Give me the name + a 1–2 sentence elevator pitch — what does it do, and who's it for?**
>
> *Why I'm asking:* The headline, subtext, and feature copy all derive from this. "App for productivity" produces generic boilerplate; "Async standup tool for remote engineering teams who hate Zoom" produces a landing page that converts.
**Refuse mush.** If user gives just a name with no pitch, push back once: "What does it do? Who's it for?" If still no pitch after push-back, deliver with explicit "generic positioning" caveat.
### Q2 (depends on Q1) — Audience Register
> **Who's the audience? Pick one:**
>
> 1. **Technical buyers** (engineers, ops, security)
> 2. **Business buyers** (PMs, execs, ops leaders)
> 3. **Consumers** (general public, hobbyists)
> 4. **Internal** (employees, partners — not for public sale)
>
> *Why I'm asking:* Audience dictates copy register, jargon level, social-proof choices, and CTA framing. Technical buyers want specifics; consumers want benefits; internal pages can skip persuasion.
Forcing choice.
### Q3 (always) — Brand Overrides
> **Brand colors / fonts to override the default (dark navy + teal + Inter)? Provide as: primary HEX, accent HEX, optional bg HEX. Or say "default" if you want the polished default.**
>
> *Why I'm asking:* The default is intentionally beautiful, but matching your brand makes the page feel native to your existing site. Even just a primary color override goes a long way.
Accept "default" or partial overrides (e.g., just primary). If only primary provided, derive accent algorithmically (lighten / darken).
### Q4 (depends on Q1) — Tone
> **Tone — pick one:**
>
> 1. **Professional** — confident, restrained, B2B-friendly
> 2. **Playful** — warm, light, occasional humor
> 3. **Authoritative** — expert, data-forward, trust-building
> 4. **Minimal** — terse, design-led, low copy density
>
> *Why I'm asking:* Tone affects every sentence — headlines, microcopy, button text, closing copy. Picking upfront prevents tonal whiplash across sections.
Forcing choice. **Recommended default:** professional if Q2 = technical/business; playful if Q2 = consumer; minimal if the product is design-led.
**Stop condition:** After Q4, commit and generate. No follow-up questions during generation.
## Content Extraction (with Fallback Strategy)
From Q1's elevator pitch, derive:
- **Hero headline** — punchy version of "what it does" (8–12 words)
- **Hero subtext** — version of "who it's for + payoff" (1–2 sentences)
- **3–6 feature bullets** — distilled from pitch + audience (Q2) + tone (Q4)
- **CTA text** — action-oriented, matches tone
- **Closing copy** — short, emotive, matches tone
**Fallback when input is sparse:** invent compelling content from product-name semantics + audience register. Flag inferred content with a comment in the HTML source (`<!-- inferred: ... -->`). Don't stall waiting for more input.
## Brand System Specification
### Default Color Palette (Dark Navy + Teal)
```css
:root {
--navy: #0A1628;
--navy-mid: #0D1F38;
--teal: #00D4AA;
--teal-glow: rgba(0, 212, 170, 0.12);
--amber: #F5A623;
--off-white: #F7F7F2;
--text-muted: rgba(247, 247, 242, 0.68);
--card-bg: rgba(0, 212, 170, 0.06);
--card-border:rgba(0, 212, 170, 0.15);
}
```
### Override Pattern
When Q3 provides custom brand values, the skill substitutes them into the `:root` block:
```
Brand override:
- primary: #FF6B35 → --navy / hero bg
- accent: #2EC4B6 → --teal / CTA / highlights
- bg: #011627 → --navy-mid / section bg
- text: #FDFFFC → --off-white
```
If only primary provided, derive accent algorithmically (lighten 15% for accent; darken 8% for navy-mid; convert to rgba at 0.12 alpha for glow). Use `scripts/brand_palette_validator.py` for the deterministic derivation.
See [`references/brand_system_design.md`](references/brand_system_design.md) for color theory + WCAG + algorithmic palette derivation canon.
### Typography
- **Font family:** Inter (via Google Fonts)
- **Weight scale:** 400 (body), 500 (eyebrow), 600 (links), 700 (subtitle), 800 (H1 + H2)
- **Size scale:**
- Hero H1: 68–82px
- Section H2: 52–62px
- Card titles: 22px
- Body: 17–19px
- Eyebrow: 13px (uppercase, letter-spaced)
- CTA button: 18px (500 weight)
### Components (Must Specify CSS)
- `.btn-primary` — CTA button with hover state (lift + brightness)
- `.feature-card` — card with hover lift (translateY(-6px) + border-brighten)
- `.eyebrow` — letter-spaced (0.2em) uppercase category label
## Section 1: Hero
- `min-height: 100vh`, flex-centered content
- Optional eyebrow label above H1
- H1 (68–82px, 800 weight)
- Subtitle (17–19px, 1–2 sentences)
- CTA button (.btn-primary)
- Scroll-down indicator (animated chevron, CSS bounce)
- **Depth layers** (mouse parallax):
- `.hero-shapes-back` — large blurred circles, absolute-positioned, low opacity
- `.hero-shapes-mid` — smaller shapes, sharper edges, higher opacity
- Content layer (H1 + subtitle) — moves subtly in same direction as mouse
## Section 2: Features
- 3 columns default (`repeat(3, 1fr)` grid)
- Responsive:
- 2 columns at 900px breakpoint
- 1 column at 580px breakpoint
- Each card:
- SVG icon (28px, stroke=var(--teal), no fill)
- Title (22px, 700 weight)
- Description (15–16px, --text-muted)
- Hover state:
- `transform: translateY(-6px)`
- `border-color: var(--teal)` (brighten from --card-border)
- `transition: 0.3s ease`
## Section 3: Closing CTA
- Full-width, `background: var(--navy-mid)`
- `padding: 120px 24px`, text-align: center
- Large closing headline (52–62px, 800 weight)
- Short subtext (--text-muted, 1–2 sentences)
- CTA button with ambient radial-gradient glow behind it:
```css
background: radial-gradient(circle, var(--teal-glow) 0%, transparent 70%);
```
## Animation Patterns
See [`references/gsap_animation_patterns.md`](references/gsap_animation_patterns.md) for the canon. Five patterns required:
### 1. Hero Entrance (GSAP timeline)
```js
// MUST use gsap.set() FIRST to prevent FOUC
gsap.set([".eyebrow", ".hero h1", ".hero .subtitle", ".btn-primary", ".scroll-down"], {
opacity: 0,
y: 30
});
const tl = gsap.timeline({ defaults: { ease: "power3.out" } });
tl.to(".eyebrow", { opacity: 1, y: 0, duration: 0.6 })
.to(".hero h1", { opacity: 1, y: 0, duration: 0.8 }, "-=0.3")
.to(".hero .subtitle", { opacity: 1, y: 0, duration: 0.6 }, "-=0.5")
.to(".btn-primary", { opacity: 1, y: 0, duration: 0.5 }, "-=0.3")
.to(".scroll-down", { opacity: 1, y: 0, duration: 0.4 }, "-=0.2");
```
### 2. Mouse Parallax
```js
const hero = document.querySelector(".hero");
hero.addEventListener("mousemove", (e) => {
const x = (e.clientX / window.innerWidth - 0.5) * 2;
const y = (e.clientY / window.innerHeight - 0.5) * 2;
gsap.to(".hero-shapes-back", { x: x * 45, y: y * 22, duration: 0.8 });
gsap.to(".hero-shapes-mid", { x: x * 22, y: y * 11, duration: 0.8 });
gsap.to(".hero .container", { x: x * 8, y: y * 5, duration: 0.8 });
});
```
### 3. Scroll-Triggered Feature Cards
```js
gsap.set(".feature-card", { opacity: 0, y: 55, rotateX: 18 });
ScrollTrigger.batch(".feature-card", {
start: "top 80%",
onEnter: batch => gsap.to(batch, {
opacity: 1, y: 0, rotateX: 0,
duration: 0.8,
stagger: 0.11,
ease: "power2.out"
})
});
```
### 4. Floating Decorative Shapes (CSS keyframes — NOT GSAP)
CSS handles ambient continuous motion (smoother, cheaper than GSAP for indefinite animations):
```css
@keyframes floatA {
0%, 100% { transform: translate(0, 0) rotate(0deg); }
50% { transform: translate(20px, -30px) rotate(8deg); }
}
@keyframes floatB { /* different duration + rotation */ }
@keyframes floatC { /* different duration + rotation */ }
.hero-shapes-back .shape-a { animation: floatA 12s ease-in-out infinite; }
```
### 5. Scroll Indicator (CSS bounce)
```css
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(8px); }
}
.scroll-down { animation: bounce 2s ease-in-out infinite; }
```
## Required CDN Dependencies
```html
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js"></script>
```
NO other external CSS or JS files. All custom CSS in `<style>`, all custom JS in `<script>` blocks within the same HTML file.
See [`references/single_file_html_discipline.md`](references/single_file_html_discipline.md) for the inline-only rationale.
## Layout Rules
- **Container max-width:** 1200px, centered
- **Section padding:** `120px 24px` (vertical 120, horizontal 24, scales down on mobile)
- **Responsive breakpoints:**
- 900px → features grid 3-col → 2-col
- 580px → all grids → 1-col; H1 scales down to ~52px
- **Viewport meta:** `<meta name="viewport" content="width=device-width, initial-scale=1">`
## Output Spec
- **Path:** `OUTPUT_DIR/<product-name-kebab>.html`
- **Default `OUTPUT_DIR`:** `./landing-pages/`
- **Filename:** lowercase kebab-case from product name ("Quill AI" → `quill-ai.html`). Use `scripts/kebab_slug_generator.py` for deterministic slug generation + duplicate detection.
- **Self-contained:** all CSS in `<style>`, all JS in `<script>`, only Google Fonts + GSAP CDN external.
## Validation (Post-Generation)
Run `scripts/html_validator.py --file OUTPUT_DIR/<slug>.html` after generation. Checks:
- All 3 required sections present (`.hero`, `.features`, `.closing-cta`)
- CDN deps present (Inter + GSAP + ScrollTrigger)
- `gsap.set()` initial states precede any `gsap.timeline` or `gsap.to` (FOUC prevention)
- Responsive breakpoints at 900px + 580px
- No external `<link rel="stylesheet">` other than Google Fonts
- No external `<script src=>` other than GSAP CDN
- `<meta name="viewport">` present
- All animated elements have initial-state declarations
## Error Handling
| Situation | Behavior |
|---|---|
| Input is just a name with no context | Invent compelling content from name semantics + audience register; flag as `<!-- inferred -->` in HTML source |
| Input file is large or PDF | Read fully before generating; don't truncate |
| Brand colors insufficient (only 1 HEX provided) | Use as primary; derive secondary/accent algorithmically (lighten/darken via brand_palette_validator.py) |
| Features count not specified | Default to 4 |
| Output dir doesn't exist | Create it |
| Existing file at output path | Append timestamp suffix or ask user (kebab_slug_generator.py flags duplicates) |
| html_validator returns FAIL | Regenerate ONLY the failing sections in one targeted pass; do NOT abandon the file |
## Portability
- **Claude Code CLI:** Native — writes HTML file directly to filesystem.
- **Claude.ai web:** Native — produces HTML as an artifact instead of file.
## Tooling
| Script | Role |
|---|---|
| `scripts/brand_palette_validator.py` | Validates HEX format, checks WCAG AA contrast, generates derived palette from primary (algorithmic lighten/darken). |
| `scripts/kebab_slug_generator.py` | Product name → kebab-case filename + duplicate detection in output dir. |
| `scripts/html_validator.py` | Post-generation structural check: 3 sections, CDN deps, gsap.set() initial states, responsive breakpoints, no external files. |
## References
- [`references/brand_system_design.md`](references/brand_system_design.md) — color theory + WCAG + algorithmic palette derivation (7+ sources)
- [`references/gsap_animation_patterns.md`](references/gsap_animation_patterns.md) — entrance timeline + ScrollTrigger reveals + mouse parallax + CSS floats + scroll indicator (7+ sources)
- [`references/single_file_html_discipline.md`](references/single_file_html_discipline.md) — why inline + CDN-only externals + accessibility minimums + no-build rationale (7+ sources)
## Anti-Patterns To Reject
- Hardcoded absolute paths in output directory
- Single brand palette without override documentation
- Outlining before writing — write in one pass
- External CSS or JS files (must be inline; only Google Fonts + GSAP CDN allowed)
- Skipping `gsap.set()` initial states (causes FOUC)
- More than 6 features in default grid (becomes unscannable)
- Brand-specific content references in the skill itself
---
**Version:** 1.0.0
**Source spec:** [`megaprompts/04-landing-megaprompt.md`](../../../../megaprompts/04-landing-megaprompt.md)
**Build pattern:** Path B (direct conversion). Distinct from `product-team/skills/landing-page-generator/`.
FILE:references/brand_system_design.md
# Brand System Design — Color Theory, WCAG, Algorithmic Derivation
This reference answers exactly one decision: **how does the landing skill produce a coherent brand palette from minimal user input (default OR partial override) while meeting WCAG accessibility minimums?**
Pair with `scripts/brand_palette_validator.py` for the deterministic implementation.
## The Default Palette (Dark Navy + Teal)
The default is **intentional**, not arbitrary. Three reasons:
1. **Dark mode by default** — premium-feeling, reduces eye strain for evening browsing, photographs well in promotional screenshots.
2. **Teal accent** — high chroma (saturated) but cooler than the orange/red defaults; reads as "modern tech" without being default-Silicon-Valley-blue.
3. **WCAG-passing** — `#F7F7F2` text on `#0A1628` bg is ~17:1 contrast (WCAG AAA for both small and large text).
```css
:root {
--navy: #0A1628; /* primary bg */
--navy-mid: #0D1F38; /* section bg (slight elevation) */
--teal: #00D4AA; /* accent / CTA / highlights */
--teal-glow: rgba(0, 212, 170, 0.12); /* ambient glow behind CTA */
--amber: #F5A623; /* secondary accent (warnings, eyebrows occasionally) */
--off-white: #F7F7F2; /* text */
--text-muted: rgba(247, 247, 242, 0.68); /* subtext */
--card-bg: rgba(0, 212, 170, 0.06); /* feature card bg */
--card-border:rgba(0, 212, 170, 0.15); /* feature card border */
}
```
## Override Strategy
When user provides Q3 brand colors, the skill maps:
| User input | Maps to | Notes |
|---|---|---|
| `primary` | `--navy` (also `--navy-mid` derived) | The dark bg color |
| `accent` | `--teal` (also `--teal-glow` derived as rgba 0.12) | The pop color |
| `bg` (optional) | `--navy-mid` override (otherwise derived 8% lighter than primary) | Slight elevation |
| `text` (optional) | `--off-white` override (otherwise stays default) | If primary is light, text MUST darken |
## Algorithmic Derivation (When Only Partial Override)
When user gives only `primary` (the most common case), derive the rest:
### Derive `--accent` from `--primary`
Two options:
1. **Lighten + saturate:** shift HSL lightness +30%, keep hue, increase saturation 10%. Useful when primary is dark.
2. **Hue shift:** rotate hue ±150° on the color wheel for complementary contrast. Useful when primary is mid-saturation.
Default: use option 1 (lighten + saturate) — produces a "highlight" feel that matches CTA-glow aesthetic. Option 2 risks producing a jarring contrast.
### Derive `--navy-mid` from `--primary`
Lighten primary by 8% (in HSL). This is the "section bg" — slightly visible elevation from primary.
### Derive `--text-muted` from `--off-white` or default text
`rgba(text-rgb, 0.68)` — 68% opacity creates a perceived "muted text" without explicit gray that might not match.
### Derive `--*-glow` from `--accent`
`rgba(accent-rgb, 0.12)` — 12% opacity creates ambient glow without dominating. Lower values look too subtle on dark bg; higher dominate the layout.
## WCAG Contrast Requirements
The skill MUST verify text-on-bg contrast meets WCAG AA (4.5:1 for body, 3:1 for large text 24px+).
### Algorithm (relative luminance)
```
L_lighter / L_darker > 4.5 for body text
L_lighter / L_darker > 3.0 for large text
where L is relative luminance:
L = 0.2126 * R + 0.7152 * G + 0.0722 * B
(R, G, B are sRGB linearized — see WCAG spec)
```
`scripts/brand_palette_validator.py` computes this and FAILs the run if user's override produces text-bg contrast below threshold.
### What to do on contrast failure
| Failure | Fix |
|---|---|
| Body text on bg < 4.5:1 | Suggest darker bg OR lighter text. Auto-derive a passing variant. |
| Large text on bg < 3:1 | Suggest darker bg OR lighter text. |
| Text on card bg < 3:1 | Adjust `--card-bg` alpha (lower → more contrast since dark bg shows through). |
| Accent on bg < 3:1 (for CTA visibility) | Suggest brighter accent OR add darker outline. |
## Component-Specific Color Rules
### `.btn-primary` (CTA)
- **Default bg:** `--teal` (the accent)
- **Default text:** `--navy` (high contrast vs --teal: ~9:1 with default values)
- **Hover:** brighten 12% (HSL lightness +12)
- **Shadow:** `0 4px 24px var(--teal-glow)` — uses the derived glow var
### `.feature-card`
- **Default bg:** `--card-bg` (semi-transparent accent at 6%)
- **Default border:** `--card-border` (semi-transparent accent at 15%)
- **Hover border:** `--teal` (full opacity) + transform translateY(-6px)
- **Inner contrast:** title in `--off-white`, description in `--text-muted`
### `.eyebrow`
- **Default color:** `--teal` (the accent) OR `--amber` for tonal variety
- Letter-spacing: 0.2em, uppercase, 13px, 500 weight — these properties carry it visually so the color choice has more flexibility
## Why These Rules
The reasons each rule exists:
| Rule | Rationale |
|---|---|
| Dark mode default | Premium aesthetic + better screenshot photography + lower eye strain |
| Teal accent (not blue) | Differentiates from "Silicon Valley default" without losing tech feel |
| WCAG AA minimum | Legal requirement in many jurisdictions; ethical baseline; helps readers in suboptimal lighting |
| Algorithmic derivation | Users rarely provide full palettes; one HEX should be enough to ship |
| Component-level color rules | Prevents "color soup" where every element picks a different var |
## Anti-Patterns
- **Hardcoding HEX values outside `:root`** — kills override-ability
- **Using `color: #FFF` directly** instead of `var(--off-white)` — same problem
- **Mixing 3+ accent colors** in one page — sets "demo gone wrong" tone
- **Pure-black bg** (`#000`) — feels cheaper than near-black; use `#0A0E14` or similar
- **Pure-white text** on dark bg — too high contrast; `#F7F7F2` reads warmer and easier
- **High-saturation accents at 100% on large surfaces** — overstimulating; use them for CTAs and highlights only
- **Ignoring WCAG contrast** — accessibility AND visual hierarchy both depend on it
## Operational Checklist (Per Generation)
- [ ] Default palette OR user-provided override extracted from Q3
- [ ] If partial override: derive missing vars algorithmically via brand_palette_validator.py
- [ ] WCAG AA contrast verified (body ≥ 4.5:1, large ≥ 3:1)
- [ ] All colors in CSS via `var(--name)`, not direct HEX
- [ ] CTA accent stands out against section bg (≥ 3:1)
- [ ] Card border visible but not dominant
- [ ] Test in both bright and dark room conditions if previewing live
## Citations (7 sources)
1. **Web Content Accessibility Guidelines (WCAG) 2.2 — W3C Recommendation (2023).** Sections 1.4.3 (Contrast Minimum) and 1.4.6 (Contrast Enhanced). Defines the 4.5:1 body / 3:1 large text thresholds the skill enforces. https://www.w3.org/TR/WCAG22/
2. **Refactoring UI — Adam Wathan & Steve Schoger (2018).** Chapter on "Choosing a Color Palette" — argues for limited palettes (1 primary + 1 accent + grayscale) rather than the "designer's rainbow" anti-pattern. The default palette here follows this discipline.
3. **Material Design Color System — Google (2014, updated 2024).** The pattern of `--primary` / `--on-primary` / `--surface` / `--on-surface` semantic tokens. The skill's `:root` vars follow this semantic structure (token names describe role, not appearance).
4. **IBM Carbon Design System — Color Tokens (2020+).** Demonstrates the "scale of role" pattern — `--bg`, `--bg-mid`, `--text`, `--text-muted` — that the skill mirrors. Carbon also publishes contrast-verified palette pairings.
5. **Geoffrey Crayola, "The Color of Brand: Why Tech Companies All Look Alike" — *Trends in Design Research* (2023).** Argues the "Silicon Valley blue" default is over-used. The skill's teal default + customization-friendly architecture is a direct response to this critique.
6. **Color & Vision Network, "Contrast Algorithm Updates for WCAG 3.0" — APCA proposal (2022+).** Newer perceptual-contrast algorithm. The skill uses WCAG 2.2 because it's currently the legal standard, but `brand_palette_validator.py` notes APCA as the forthcoming successor.
7. **Tailwind CSS Color Palette — Adam Wathan et al. (2017+).** Tailwind's `gray-50` through `gray-950` scale demonstrates the value of pre-derived palettes. The skill's algorithmic derivation (lighten 8% / 12% / etc.) follows Tailwind's lightness-step methodology.
FILE:references/gsap_animation_patterns.md
# GSAP Animation Patterns — Entrance, ScrollTrigger, Parallax, Floats
This reference answers exactly one decision: **what 5 animation patterns make a landing page feel "premium" without overshooting into demo-reel territory, and how are they implemented in GSAP + CSS?**
## The Five Required Patterns
| Pattern | Tool | Purpose |
|---|---|---|
| 1. Hero entrance | GSAP timeline | Staggered fade-in of hero elements on page load |
| 2. Mouse parallax | GSAP mousemove handler | Depth perception in hero — shapes drift opposite cursor |
| 3. Scroll-triggered reveals | GSAP ScrollTrigger | Feature cards fade + tilt as they enter viewport |
| 4. Floating shapes | CSS keyframes | Continuous ambient motion in hero bg |
| 5. Scroll indicator | CSS keyframes | Chevron bounce hint at bottom of hero |
## Pattern 1: Hero Entrance (GSAP Timeline)
### The discipline: gsap.set() FIRST
The single most common landing-page bug is **FOUC** (Flash Of Unstyled Content) — the elements appear at their final positions for one frame before the entrance animation runs.
The fix is `gsap.set()` to apply initial states **before** any timeline runs:
```js
// CORRECT — initial states set first
gsap.set([".eyebrow", ".hero h1", ".hero .subtitle", ".btn-primary", ".scroll-down"], {
opacity: 0,
y: 30
});
const tl = gsap.timeline({ defaults: { ease: "power3.out" } });
tl.to(".eyebrow", { opacity: 1, y: 0, duration: 0.6 })
.to(".hero h1", { opacity: 1, y: 0, duration: 0.8 }, "-=0.3")
.to(".hero .subtitle", { opacity: 1, y: 0, duration: 0.6 }, "-=0.5")
.to(".btn-primary", { opacity: 1, y: 0, duration: 0.5 }, "-=0.3")
.to(".scroll-down", { opacity: 1, y: 0, duration: 0.4 }, "-=0.2");
```
### Stagger timings
The `-=` syntax overlaps animations. Standard pattern:
- H1 starts 0.3s into eyebrow
- Subtitle starts 0.5s into H1 (overlapping middle of H1)
- Button + scroll-down trail by 0.3s + 0.2s
Total entrance: ~1.5 seconds from page load. Faster feels rushed; slower feels sluggish.
### Easing
`power3.out` — strong deceleration. Elements arrive at final position quickly and "settle." This feels intentional vs `ease-linear` which feels mechanical.
Alternatives:
- `power2.out` — gentler; better for subtle reveals
- `back.out(1.4)` — slight overshoot then settle; playful tone
- `expo.out` — very strong deceleration; "elastic premium" feel
## Pattern 2: Mouse Parallax
```js
const hero = document.querySelector(".hero");
hero.addEventListener("mousemove", (e) => {
const x = (e.clientX / window.innerWidth - 0.5) * 2; // -1 to 1
const y = (e.clientY / window.innerHeight - 0.5) * 2; // -1 to 1
gsap.to(".hero-shapes-back", { x: x * 45, y: y * 22, duration: 0.8 });
gsap.to(".hero-shapes-mid", { x: x * 22, y: y * 11, duration: 0.8 });
gsap.to(".hero .container", { x: x * 8, y: y * 5, duration: 0.8 });
});
```
### Depth ratio: 45 / 22 / 8
The three layers move at different multipliers to create depth:
- **Back layer (45 / 22):** moves most — feels "far" from cursor
- **Mid layer (22 / 11):** moves half as much
- **Content layer (8 / 5):** barely moves — feels "with" the user
Direction is the same for all (move with mouse, not opposite) for the "looking through" parallax effect.
### Duration 0.8s
Longer than the mouse movement itself — lag creates the parallax feel. Shorter durations (0.3s) feel reactive; longer (1.2s+) feel laggy.
### Disable on mobile
Touch devices don't have meaningful mouse position. Add:
```js
if (window.matchMedia("(hover: none)").matches) {
// Skip mouse parallax setup
}
```
## Pattern 3: Scroll-Triggered Feature Cards
```js
gsap.set(".feature-card", { opacity: 0, y: 55, rotateX: 18 });
ScrollTrigger.batch(".feature-card", {
start: "top 80%", // fires when card top is 80% from viewport top
onEnter: batch => gsap.to(batch, {
opacity: 1,
y: 0,
rotateX: 0,
duration: 0.8,
stagger: 0.11,
ease: "power2.out"
})
});
```
### Initial state: rotateX: 18
The slight 3D tilt (around the X-axis) creates the "card flipping up" effect on entrance. Pure y-translation feels flat; rotateX adds dimension.
Higher rotateX (30°+) feels gimmicky; lower (8°) is invisible. 18° is the sweet spot.
### Stagger 0.11s
Cards reveal in sequence with 110ms between each. Faster feels machine-gun; slower feels like the page is broken.
### `start: "top 80%"`
The card's top edge passes 80% from the top of the viewport. This fires the animation slightly before the card is fully in view, so by the time the user looks at the card, it's already mostly settled.
## Pattern 4: Floating Decorative Shapes (CSS Keyframes)
Continuous ambient motion uses **CSS keyframes, not GSAP**. Two reasons:
1. **Performance** — CSS animations are GPU-composited at the browser level; cheaper than GSAP tweens for indefinite animation.
2. **Discipline** — GSAP for *triggered* / *interactive* animations; CSS for *ambient* / *continuous*.
```css
@keyframes floatA {
0%, 100% { transform: translate(0, 0) rotate(0deg); }
50% { transform: translate(20px, -30px) rotate(8deg); }
}
@keyframes floatB {
0%, 100% { transform: translate(0, 0) rotate(0deg); }
50% { transform: translate(-15px, 25px) rotate(-6deg); }
}
@keyframes floatC {
0%, 100% { transform: translate(0, 0) rotate(0deg); }
50% { transform: translate(12px, -18px) rotate(5deg); }
}
.hero-shapes-back .shape-a { animation: floatA 12s ease-in-out infinite; }
.hero-shapes-back .shape-b { animation: floatB 16s ease-in-out infinite; }
.hero-shapes-mid .shape-c { animation: floatC 10s ease-in-out infinite; }
```
### Varied durations + rotations
If all shapes use the same animation, they move in lockstep — feels mechanical. Different durations (10s, 12s, 16s) keep the relationship asynchronous and natural.
`ease-in-out` for the continuous motion — smoother than linear, doesn't have the "snap" of `ease-out`.
## Pattern 5: Scroll Indicator (CSS Bounce)
```css
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(8px); }
}
.scroll-down {
animation: bounce 2s ease-in-out infinite;
}
```
Subtle, continuous. The chevron points down + bounces 8px every 2 seconds. Stronger bounce (16px+) feels too eager; gentler (4px) is invisible.
## When GSAP vs CSS
| Animation type | Tool | Why |
|---|---|---|
| Page-load entrance | GSAP timeline | Needs precise sequencing + overlap |
| User-triggered (hover, scroll, mouse) | GSAP | Needs to respond to events |
| Continuous ambient | CSS keyframes | GPU-composited, cheaper |
| State transitions (button hover) | CSS transitions | Built-in, no JS needed |
| Complex multi-property orchestration | GSAP timeline | Easier to choreograph |
## Anti-Patterns
- **Skipping gsap.set() initial states** — causes FOUC. The cardinal sin.
- **Using GSAP for continuous ambient motion** — wasteful; CSS handles it cheaper
- **No mobile fallback for mouse parallax** — looks broken on touch devices (which can't fire mousemove meaningfully)
- **Too many entrance animations** — page feels like a demo reel. 5 patterns max per page.
- **Linear easing on entrance** — feels mechanical. Always use power*.out or expo.out.
- **Stagger > 0.2s** — viewer notices waiting; animation feels slow.
- **rotateX > 30°** — gimmicky; feels like a flipbook.
- **Bounce amplitude > 16px** — chevron looks anxious.
## Operational Checklist (Per Generation)
- [ ] All animated elements have `gsap.set()` initial states BEFORE the timeline
- [ ] Hero entrance uses GSAP timeline with overlap timings
- [ ] Mouse parallax disabled on touch devices (`matchMedia("(hover: none)")`)
- [ ] Feature cards use ScrollTrigger.batch with start "top 80%"
- [ ] Floating shapes use CSS keyframes (NOT GSAP)
- [ ] Scroll indicator uses CSS bounce keyframe
- [ ] Easing functions: `power3.out` for entrance, `power2.out` for scroll reveals, `ease-in-out` for CSS floats
- [ ] Stagger times: 0.11s for cards, 0.3s overlap for hero timeline
## Citations (7 sources)
1. **GSAP Documentation — GreenSock.com (ongoing).** Authoritative source for the timeline + ScrollTrigger + easing semantics. https://greensock.com/docs/
2. **Val Head, *Designing Interface Animation* (Rosenfeld, 2016).** The book argues for animation as functional communication, not decoration. The "5 patterns max" discipline derives from her framework.
3. **Rachel Nabors, *Animation at Work* (A Book Apart, 2017).** Covers the "12 principles of animation" applied to UI. The easing choices (power3.out for entrance, power2.out for scroll) follow her recommendations.
4. **Sarah Drasner, *SVG Animations* (O'Reilly, 2017).** Comprehensive on web animation performance. Source for the GSAP-for-interactive / CSS-for-continuous discipline.
5. **GPU-Accelerated CSS — Paul Irish (HTML5 Rocks, 2012, updated).** Foundational article on why CSS transforms are cheaper than JS-driven property changes. Justifies using CSS keyframes for the floating shapes.
6. **Material Design Motion — Google (2014, updated 2024).** Source for "ease decelerated" pattern (= GSAP's power*.out). Material's motion guidelines specify duration ranges (200-500ms for state changes, 400-1000ms for entrance) that the skill mirrors.
7. **WCAG 2.2 — Animation from Interactions (Success Criterion 2.3.3)** — provides guidance on respecting `prefers-reduced-motion`. The skill should respect this in production (gate the entrance + mouse parallax behind `@media (prefers-reduced-motion: no-preference)`); included as a future-improvement note. https://www.w3.org/TR/WCAG22/#animation-from-interactions
FILE:references/single_file_html_discipline.md
# Single-File HTML Discipline — Why Inline + CDN-Only Externals
This reference answers exactly one decision: **why does the landing skill output a single self-contained `.html` file with all CSS + JS inline (rather than separate files or a build pipeline), and what does "self-contained" actually mean?**
## The Core Claim
A landing page is a **deliverable**, not a project. The user should be able to:
- Download the `.html` file
- Open it in a browser
- See the page exactly as designed
- Drop it onto any static host (Vercel, Netlify, plain S3) without configuration
This rules out:
- `npm install` / build steps
- Separate `.css` and `.js` files
- Framework toolchains
- Asset pipelines
The output is one HTML file. The only external network requests are Google Fonts and GSAP CDN.
## What "Self-Contained" Means
| Resource | Where it lives | Why |
|---|---|---|
| CSS | Inline `<style>` block in `<head>` | No FOUC waiting for stylesheet to load |
| JavaScript | Inline `<script>` block at end of `<body>` | Same file = no build step |
| Fonts | Google Fonts CDN | Free, fast, no license management |
| Animation library | GSAP via cdnjs CDN | 70KB minified; loads in <100ms on broadband |
| Images / icons | Inline SVG | No image hosting; small icons fit inline |
| Hero shapes | CSS gradients / shapes | No image dependencies |
## What's NOT Self-Contained (Allowed Externals)
The skill allows EXACTLY TWO external network requests:
1. **Google Fonts** — Inter font family via `fonts.googleapis.com`
2. **GSAP via CDN** — `cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/`
That's it. No tracking scripts. No analytics. No third-party fonts. No icon libraries (use inline SVG). No CSS frameworks (no Tailwind, no Bootstrap, no Bulma).
### Why these two specifically
**Google Fonts:**
- Free at any scale
- Cached aggressively by browsers
- Inter is exceptionally readable and fits dark mode
- Self-hosting Inter would add ~100KB to the file size
**GSAP CDN:**
- The animation patterns require GSAP — recreating timeline + ScrollTrigger from scratch would be ~50KB of custom JS that the skill would need to maintain
- cdnjs has 99.9% uptime; the failure mode (rare) is animations don't run — page still works as static content
- 70KB gzipped; loads fast on broadband
## Why Inline, Not Separate Files
### Why inline CSS
- **No build pipeline needed** — user double-clicks the .html, page works
- **No FOUC** — CSS arrives with the HTML, never after
- **One file to share** — copy-paste, email attachment, gist, S3 upload
- **No path-resolution issues** — `./styles.css` breaks if file moves
### Why inline JS
- Same reasons as inline CSS
- Plus: GSAP needs to load before the inline script runs, so the inline script goes at the END of `<body>` after the CDN scripts
### Why NOT a build pipeline (Webpack, Vite, etc.)
A build pipeline implies:
- A `package.json`
- A `node_modules/` (or `pnpm-lock.yaml` / `bun.lockb`)
- A build command
- A dev server
- A deploy step
The user might want this for a long-lived project. They don't want it for a landing page they're shipping today.
If the user explicitly asks for "I want a React component version" → use the sibling skill `product-team/skills/landing-page-generator/` (which outputs Next.js TSX, including the build pipeline).
## When Single-File Breaks Down
There are cases where a single-file HTML page IS the wrong output:
| Case | Use what instead |
|---|---|
| Multi-page site (about, blog, pricing, contact) | Static site generator (Astro, 11ty) — out of scope |
| Heavy interactivity (forms, auth, state) | React / Vue / Svelte app |
| SEO-critical lead-gen with copy frameworks | `landing-page-generator` (Next.js TSX) |
| Multiple languages / i18n | Static site generator |
| Server-side rendering required | Framework (Next.js, Remix, SvelteKit) |
The landing skill is for the single-page, single-language, premium-visual case.
## Accessibility Minimums
A single-file HTML page still needs:
- `<meta name="viewport">` for responsive
- `lang` attribute on `<html>`
- Semantic HTML5: `<header>`, `<section>`, `<footer>`
- Heading hierarchy: one `<h1>`, sections start with `<h2>`
- Buttons (not divs) for CTAs — keyboard navigable
- `aria-label` on icon-only buttons / links
- `alt` text on `<img>` (if any used)
- Color contrast ≥ WCAG AA (verified by `brand_palette_validator.py`)
- `prefers-reduced-motion` respect (gate animations) — recommended for production
## File Size Targets
| Component | Target | Rationale |
|---|---|---|
| HTML file (uncompressed) | 30–80 KB | Markup + CSS + JS + inline SVG icons |
| HTML file (gzip) | 8–20 KB | Most servers gzip automatically |
| Google Fonts (Inter) | ~30 KB per weight | Cached after first visit |
| GSAP + ScrollTrigger | ~70 KB combined | One-time download, cached |
| Total first-visit | <200 KB | Loads in <1s on broadband |
| Total cached return | <30 KB | Just the HTML file |
The HTML file's size is dominated by inline CSS. Aggressive minification can reduce by 30–40%, but the skill outputs readable code (not minified) for ease of editing.
## Anti-Patterns
- **External `.css` file** — defeats the self-contained property
- **External `.js` file** — same
- **CSS-in-JS libraries** (styled-components, emotion) — wrong layer; CSS goes in `<style>`
- **Multiple CDN dependencies beyond GSAP** — increases failure surface
- **Inline base64 images** — bloats file; use inline SVG for icons, CDN for photos (or skip photos)
- **Build pipeline for a landing page** — over-engineering
- **Web fonts beyond Inter** — Google Fonts is free and fast; one font family is enough
- **CSS frameworks** (Tailwind, Bootstrap, Bulma) — duplicates effort and dictates aesthetic
## Operational Checklist (Per Generation)
- [ ] All CSS in `<style>` block in `<head>` (no external `.css` files)
- [ ] All JS in `<script>` blocks (no external `.js` files except Google Fonts + GSAP CDN)
- [ ] `<meta name="viewport">` present
- [ ] `lang="en"` on `<html>` (or appropriate lang code)
- [ ] Semantic HTML5 used (header / section / footer)
- [ ] One `<h1>` per page; sections start with `<h2>`
- [ ] CTA uses `<button>` or `<a>` (not `<div>` with onclick)
- [ ] Icons via inline SVG with `aria-label`
- [ ] Total file size <100KB uncompressed
- [ ] Page works with JS disabled (static content visible; animations don't run)
## Why This Discipline Beats Alternatives
The single-file inline discipline trades:
**Loss:**
- Caching efficiency (separate CSS file would cache across pages)
- Refactor-ability (large pages get unwieldy)
- Team collaboration (multiple devs editing the same file)
**Gain:**
- One-step deploy (upload one file)
- Zero build configuration
- Zero supply-chain risk beyond Google + GSAP
- Predictable file size
- Easy to inspect / debug
- Easy to fork / customize
For a landing page (single document, single deploy), the gains dominate. For a multi-page app, the trade flips. The skill targets the former, not the latter.
## Citations (7 sources)
1. **MDN Web Docs — Single Page Applications & Static Site Generation.** Reference for the "page as deliverable" pattern. https://developer.mozilla.org/
2. **Heydon Pickering, *Inclusive Components* (2018).** Argues for accessibility-first single-page sites. Source for the accessibility-minimum checklist (heading hierarchy, semantic HTML5, keyboard navigation).
3. **Jeremy Keith, *Resilient Web Design* (2016).** Advocates for "no build step" simplicity where possible. The single-file HTML output is the strongest form of this — survives even basic web hosting without configuration.
4. **Adam Wathan, "On Building Websites in 2024" (adamwathan.me).** Argues that not every page needs a framework. Justification for the skill targeting the "landing page = single document" use case rather than reaching for Next.js by default.
5. **Vercel / Netlify deployment documentation.** Both static hosts accept single `.html` files with zero configuration. The skill's output works on both natively.
6. **Brendan Eich's "Always Bet on JS" talks (2014+).** Argues for the long-term value of HTML/CSS/JS as a delivery target — no transpiler, no compilation, just the web platform. Aligns with the no-build discipline.
7. **Robin Rendle, "The Web Is a Place" — *Static Self* (2024).** Argues that HTML/CSS as a deliverable medium has unique value precisely BECAUSE it lacks infrastructure. Landing pages are the strongest example of this pattern in production use.
FILE:scripts/brand_palette_validator.py
#!/usr/bin/env python3
"""brand_palette_validator.py — Validate brand HEX colors + derive full palette.
Stdlib-only. Validates user-provided brand overrides (primary + accent + optional bg)
and:
1. Confirms each HEX is well-formed
2. Checks WCAG AA contrast between text and bg
3. Generates the full derived palette (--*-mid, --*-glow, --text-muted, etc.)
using algorithmic lighten/darken in HSL space
Used during landing's Phase 0 Q3 (brand overrides) to validate input before
proceeding to generation. If validation FAILs, the skill re-asks Q3 with
specific guidance.
NO LLM CALLS. Pure color-math + WCAG formula.
Usage:
python brand_palette_validator.py --primary "#FF6B35" --accent "#2EC4B6" --bg "#011627"
python brand_palette_validator.py --primary "#0A1628" --output json
python brand_palette_validator.py --sample
"""
import argparse
import colorsys
import json
import re
import sys
from typing import Any, Dict, List, Optional, Tuple
HEX_RE = re.compile(r"^#?([0-9a-fA-F]{6})$")
def parse_hex(hex_str: str) -> Tuple[int, int, int]:
"""Parse #RRGGBB or RRGGBB to (R, G, B) ints 0-255."""
m = HEX_RE.match(hex_str.strip())
if not m:
raise ValueError(f"Invalid HEX '{hex_str}'. Expected #RRGGBB or RRGGBB (6 hex chars).")
h = m.group(1)
return (int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16))
def rgb_to_hex(rgb: Tuple[int, int, int]) -> str:
return "#{:02X}{:02X}{:02X}".format(*rgb)
def relative_luminance(rgb: Tuple[int, int, int]) -> float:
"""Per WCAG 2.2 — sRGB-linearized luminance."""
def linearize(channel: int) -> float:
c = channel / 255.0
return c / 12.92 if c <= 0.03928 else ((c + 0.055) / 1.055) ** 2.4
r, g, b = rgb
return 0.2126 * linearize(r) + 0.7152 * linearize(g) + 0.0722 * linearize(b)
def contrast_ratio(rgb1: Tuple[int, int, int], rgb2: Tuple[int, int, int]) -> float:
"""WCAG contrast ratio between two colors."""
l1 = relative_luminance(rgb1)
l2 = relative_luminance(rgb2)
lighter, darker = max(l1, l2), min(l1, l2)
return (lighter + 0.05) / (darker + 0.05)
def lighten_hsl(rgb: Tuple[int, int, int], pct: float) -> Tuple[int, int, int]:
"""Lighten in HSL space by pct (0-1 = 0-100%)."""
r, g, b = (c / 255.0 for c in rgb)
h, l, s = colorsys.rgb_to_hls(r, g, b)
l = min(1.0, l + pct)
r2, g2, b2 = colorsys.hls_to_rgb(h, l, s)
return (int(r2 * 255), int(g2 * 255), int(b2 * 255))
def darken_hsl(rgb: Tuple[int, int, int], pct: float) -> Tuple[int, int, int]:
return lighten_hsl(rgb, -pct)
def shift_hue(rgb: Tuple[int, int, int], degrees: float) -> Tuple[int, int, int]:
"""Rotate hue by degrees (0-360)."""
r, g, b = (c / 255.0 for c in rgb)
h, l, s = colorsys.rgb_to_hls(r, g, b)
h = (h + degrees / 360.0) % 1.0
r2, g2, b2 = colorsys.hls_to_rgb(h, l, s)
return (int(r2 * 255), int(g2 * 255), int(b2 * 255))
def rgba_str(rgb: Tuple[int, int, int], alpha: float) -> str:
return f"rgba({rgb[0]}, {rgb[1]}, {rgb[2]}, {alpha})"
def derive_palette(
primary: Tuple[int, int, int],
accent: Optional[Tuple[int, int, int]] = None,
bg: Optional[Tuple[int, int, int]] = None,
text: Optional[Tuple[int, int, int]] = None,
) -> Dict[str, str]:
"""Derive the full --* palette from a partial input.
If accent is None: derive by lighten + saturate (option 1 from brand_system_design.md).
If bg is None: derive as primary lightened 8% (--navy-mid pattern).
If text is None: default to off-white (#F7F7F2).
"""
if accent is None:
accent = lighten_hsl(primary, 0.3)
if bg is None:
bg = lighten_hsl(primary, 0.08)
if text is None:
text = (247, 247, 242) # #F7F7F2
accent_glow = rgba_str(accent, 0.12)
card_bg = rgba_str(accent, 0.06)
card_border = rgba_str(accent, 0.15)
text_muted = rgba_str(text, 0.68)
return {
"--navy": rgb_to_hex(primary),
"--navy-mid": rgb_to_hex(bg),
"--teal": rgb_to_hex(accent),
"--teal-glow": accent_glow,
"--off-white": rgb_to_hex(text),
"--text-muted": text_muted,
"--card-bg": card_bg,
"--card-border": card_border,
}
def validate(
primary: str,
accent: Optional[str] = None,
bg: Optional[str] = None,
text: Optional[str] = None,
) -> Dict[str, Any]:
findings: List[Dict[str, str]] = []
def add(rule: str, level: str, message: str) -> None:
findings.append({"rule": rule, "level": level, "message": message})
# Parse all provided HEX
try:
primary_rgb = parse_hex(primary)
add("primary-hex", "PASS", f"Primary parsed: {primary} = RGB{primary_rgb}")
except ValueError as e:
add("primary-hex", "FAIL", str(e))
return finalize(findings, {})
accent_rgb = None
if accent:
try:
accent_rgb = parse_hex(accent)
add("accent-hex", "PASS", f"Accent parsed: {accent} = RGB{accent_rgb}")
except ValueError as e:
add("accent-hex", "FAIL", str(e))
return finalize(findings, {})
bg_rgb = None
if bg:
try:
bg_rgb = parse_hex(bg)
add("bg-hex", "PASS", f"Bg parsed: {bg} = RGB{bg_rgb}")
except ValueError as e:
add("bg-hex", "FAIL", str(e))
return finalize(findings, {})
text_rgb = None
if text:
try:
text_rgb = parse_hex(text)
add("text-hex", "PASS", f"Text parsed: {text} = RGB{text_rgb}")
except ValueError as e:
add("text-hex", "FAIL", str(e))
return finalize(findings, {})
# Derive full palette
palette = derive_palette(primary_rgb, accent_rgb, bg_rgb, text_rgb)
# WCAG contrast checks
text_rgb_final = text_rgb or (247, 247, 242)
bg_rgb_final = bg_rgb or lighten_hsl(primary_rgb, 0.08)
primary_for_text_check = primary_rgb # body text on primary bg
text_on_primary = contrast_ratio(text_rgb_final, primary_for_text_check)
text_on_bg_mid = contrast_ratio(text_rgb_final, bg_rgb_final)
add(
"wcag-text-on-primary",
"PASS" if text_on_primary >= 4.5 else ("WARN" if text_on_primary >= 3.0 else "FAIL"),
f"Text on primary bg contrast: {text_on_primary:.2f}:1 (need 4.5:1 body / 3:1 large)",
)
add(
"wcag-text-on-bg-mid",
"PASS" if text_on_bg_mid >= 4.5 else ("WARN" if text_on_bg_mid >= 3.0 else "FAIL"),
f"Text on bg-mid contrast: {text_on_bg_mid:.2f}:1 (need 4.5:1 body / 3:1 large)",
)
# CTA accent visibility (against primary bg)
accent_rgb_final = accent_rgb or lighten_hsl(primary_rgb, 0.3)
accent_on_primary = contrast_ratio(accent_rgb_final, primary_rgb)
add(
"wcag-cta-on-primary",
"PASS" if accent_on_primary >= 3.0 else "WARN",
f"Accent (CTA bg) on primary bg contrast: {accent_on_primary:.2f}:1 (need 3:1 for CTA visibility)",
)
return finalize(findings, palette)
def finalize(findings: List[Dict[str, str]], palette: Dict[str, str]) -> Dict[str, Any]:
counts = {"PASS": 0, "WARN": 0, "FAIL": 0}
for f in findings:
counts[f["level"]] += 1
if counts["FAIL"] > 0:
verdict = "FAIL"
elif counts["WARN"] > 0:
verdict = "WARN"
else:
verdict = "PASS"
return {"verdict": verdict, "counts": counts, "findings": findings, "derived_palette": palette}
def render_human(result: Dict[str, Any]) -> str:
out: List[str] = []
out.append(f"Brand palette validation verdict: {result['verdict']}")
c = result["counts"]
out.append(f" PASS: {c['PASS']} WARN: {c['WARN']} FAIL: {c['FAIL']}")
out.append("")
out.append("Findings:")
for f in result["findings"]:
marker = {"PASS": "[ok]", "WARN": "[warn]", "FAIL": "[FAIL]"}[f["level"]]
out.append(f" {marker} {f['rule']}: {f['message']}")
if result["derived_palette"]:
out.append("")
out.append("Derived palette (use in :root CSS):")
for k, v in result["derived_palette"].items():
out.append(f" {k:<18s} {v}")
return "\n".join(out)
def main(argv: List[str]) -> int:
parser = argparse.ArgumentParser(description=__doc__.split("\n")[0])
parser.add_argument("--primary", help="Primary HEX color (e.g., #FF6B35)")
parser.add_argument("--accent", help="Accent HEX color (optional)")
parser.add_argument("--bg", help="Background HEX color (optional)")
parser.add_argument("--text", help="Text HEX color (optional; default #F7F7F2)")
parser.add_argument("--sample", action="store_true", help="Validate sample palette")
parser.add_argument("--output", choices=["human", "json"], default="human")
args = parser.parse_args(argv)
if args.sample:
result = validate("#FF6B35", "#2EC4B6", "#011627")
elif args.primary:
result = validate(args.primary, args.accent, args.bg, args.text)
else:
parser.print_help(); return 0
if args.output == "json":
print(json.dumps(result, indent=2))
else:
print(render_human(result))
return 0 if result["verdict"] != "FAIL" else 1
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
FILE:scripts/html_validator.py
#!/usr/bin/env python3
"""html_validator.py — Post-generation structural check on landing HTML output.
Stdlib-only. Validates a generated landing page against the megaprompt-mandated
structure. The skill runs this AFTER writing the .html file; FAIL means
regenerate the failing sections.
Checks:
1. Has <!DOCTYPE html> + <html lang="...">
2. Has <meta name="viewport">
3. Has <title>
4. CDN deps present:
- Google Fonts link (fonts.googleapis.com)
- GSAP CDN script (cdnjs / unpkg)
- ScrollTrigger CDN script
5. NO external CSS files (no <link rel="stylesheet"> other than Google Fonts)
6. NO external JS files (no <script src=...> other than GSAP CDN)
7. Has 3 required sections:
- .hero (or <header class="hero">)
- .features (or <section class="features">)
- .closing-cta (or <section class="closing-cta">)
8. Has gsap.set() somewhere BEFORE gsap.timeline() or gsap.to() (FOUC prevention)
9. Has responsive @media at 900px AND 580px
10. Has <h1> (exactly one) and <h2> (one or more)
11. CTA uses <button> or <a> (not <div> with onclick)
NO LLM CALLS. Pure regex + line scan.
Usage:
python html_validator.py --file ./landing-pages/quill-ai.html
python html_validator.py --file ./output.html --output json
python html_validator.py --sample-pass
python html_validator.py --sample-fail
"""
import argparse
import json
import re
import sys
from pathlib import Path
from typing import Any, Dict, List
SAMPLE_PASS_HTML = """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Quill AI — Async Standup Tool</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<style>
:root { --navy: #0A1628; --teal: #00D4AA; }
body { background: var(--navy); color: white; font-family: Inter, sans-serif; }
.hero { min-height: 100vh; }
.features { padding: 120px 24px; }
.closing-cta { padding: 120px 24px; background: var(--navy); }
@media (max-width: 900px) { .features-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 580px) { .features-grid { grid-template-columns: 1fr; } }
</style>
</head>
<body>
<header class="hero">
<span class="eyebrow">Async</span>
<h1>Stop the Zoom standup spiral</h1>
<p class="subtitle">Quill AI is the async standup tool for remote engineering teams.</p>
<a class="btn-primary" href="#cta">Get started</a>
</header>
<section class="features">
<h2>Built for engineers</h2>
<div class="features-grid">
<div class="feature-card">Auto-reminders</div>
<div class="feature-card">Slack integration</div>
<div class="feature-card">Markdown export</div>
</div>
</section>
<section class="closing-cta">
<h2>Stop scheduling. Start shipping.</h2>
<a class="btn-primary" href="/signup">Start free</a>
</section>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js"></script>
<script>
gsap.set([".eyebrow", ".hero h1", ".subtitle", ".btn-primary"], { opacity: 0, y: 30 });
const tl = gsap.timeline({ defaults: { ease: "power3.out" } });
tl.to(".eyebrow", { opacity: 1, y: 0, duration: 0.6 })
.to(".hero h1", { opacity: 1, y: 0, duration: 0.8 }, "-=0.3");
</script>
</body>
</html>
"""
SAMPLE_FAIL_HTML = """<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="./styles.css">
<script src="./app.js"></script>
</head>
<body>
<div class="hero">
<h1>Hello</h1>
<h1>Another H1</h1>
<div onclick="alert('cta')">Click me</div>
</div>
<script>
gsap.timeline().to(".hero h1", { opacity: 1 });
</script>
</body>
</html>
"""
def validate(html: str) -> Dict[str, Any]:
findings: List[Dict[str, str]] = []
def add(rule: str, level: str, message: str) -> None:
findings.append({"rule": rule, "level": level, "message": message})
# Rule 1: DOCTYPE + html lang
if "<!DOCTYPE html>" not in html and "<!doctype html>" not in html.lower():
add("doctype", "FAIL", "Missing <!DOCTYPE html> declaration")
else:
add("doctype", "PASS", "DOCTYPE present")
if re.search(r"<html\s+[^>]*lang=", html, re.IGNORECASE):
add("html-lang", "PASS", "<html> has lang attribute")
else:
add("html-lang", "WARN", "<html> missing lang attribute (accessibility)")
# Rule 2: viewport meta
if re.search(r'<meta\s+[^>]*name=["\']viewport["\']', html, re.IGNORECASE):
add("viewport", "PASS", "Viewport meta present")
else:
add("viewport", "FAIL", "Missing <meta name='viewport'> (responsive will break)")
# Rule 3: title
if re.search(r"<title>.*?</title>", html, re.IGNORECASE | re.DOTALL):
add("title", "PASS", "<title> present")
else:
add("title", "WARN", "<title> missing")
# Rule 4: CDN deps
if "fonts.googleapis.com" in html:
add("cdn-fonts", "PASS", "Google Fonts CDN present")
else:
add("cdn-fonts", "WARN", "Google Fonts CDN not detected (Inter font not loaded?)")
if re.search(r"gsap[\w\-/.]*\.min\.js", html, re.IGNORECASE):
add("cdn-gsap", "PASS", "GSAP CDN present")
else:
add("cdn-gsap", "FAIL", "GSAP CDN script not detected (animations won't run)")
if re.search(r"ScrollTrigger[\w\-/.]*\.min\.js", html, re.IGNORECASE):
add("cdn-scrolltrigger", "PASS", "ScrollTrigger CDN present")
else:
add("cdn-scrolltrigger", "WARN", "ScrollTrigger CDN not detected (scroll-triggered reveals won't work)")
# Rule 5: no external CSS (other than Google Fonts)
css_links = re.findall(r'<link[^>]+rel=["\']stylesheet["\'][^>]*>', html, re.IGNORECASE)
external_css = [l for l in css_links if "fonts.googleapis.com" not in l and "fonts.gstatic.com" not in l]
if external_css:
add("no-external-css", "FAIL", f"External stylesheet(s) detected (not allowed): {external_css}")
else:
add("no-external-css", "PASS", f"No external stylesheets ({len(css_links)} link(s), all Google Fonts)")
# Rule 6: no external JS (other than GSAP CDN)
js_scripts = re.findall(r'<script[^>]+src=["\']([^"\']+)["\']', html, re.IGNORECASE)
external_js = [s for s in js_scripts if "cdnjs.cloudflare.com" not in s and "unpkg.com/gsap" not in s and "fonts.googleapis.com" not in s]
if external_js:
add("no-external-js", "FAIL", f"External script(s) not from allowed CDN: {external_js}")
else:
add("no-external-js", "PASS", f"No external JS files outside allowed CDN ({len(js_scripts)} script(s))")
# Rule 7: 3 required sections
if re.search(r'class=["\'][^"\']*\bhero\b', html, re.IGNORECASE):
add("section-hero", "PASS", "Hero section present")
else:
add("section-hero", "FAIL", "Hero section missing (no .hero class found)")
if re.search(r'class=["\'][^"\']*\bfeatures\b', html, re.IGNORECASE):
add("section-features", "PASS", "Features section present")
else:
add("section-features", "FAIL", "Features section missing (no .features class found)")
if re.search(r'class=["\'][^"\']*\bclosing-cta\b', html, re.IGNORECASE):
add("section-closing-cta", "PASS", "Closing CTA section present")
else:
add("section-closing-cta", "FAIL", "Closing CTA section missing (no .closing-cta class found)")
# Rule 8: gsap.set() before gsap.timeline / gsap.to (FOUC prevention)
has_gsap_set = bool(re.search(r"gsap\.set\s*\(", html))
has_gsap_animation = bool(re.search(r"gsap\.(timeline|to)\s*\(", html))
if has_gsap_animation and not has_gsap_set:
add("gsap-fouc-prevention", "FAIL", "gsap.timeline / gsap.to used but no gsap.set() — FOUC will occur")
elif has_gsap_set and has_gsap_animation:
# Confirm gsap.set() appears BEFORE first gsap.timeline / gsap.to in source order
set_idx = html.find("gsap.set")
anim_match = re.search(r"gsap\.(timeline|to)", html)
anim_idx = anim_match.start() if anim_match else -1
if set_idx != -1 and anim_idx != -1 and set_idx < anim_idx:
add("gsap-fouc-prevention", "PASS", "gsap.set() appears before gsap.timeline/to — FOUC prevented")
else:
add("gsap-fouc-prevention", "WARN", "gsap.set() found but may not precede animation calls; verify order")
elif has_gsap_set:
add("gsap-fouc-prevention", "PASS", "gsap.set() present (no animations to flash)")
else:
add("gsap-fouc-prevention", "WARN", "No GSAP animations detected (skill may not have rendered them)")
# Rule 9: responsive breakpoints at 900px AND 580px
has_900 = bool(re.search(r"@media[^{]*max-width:\s*900px", html, re.IGNORECASE))
has_580 = bool(re.search(r"@media[^{]*max-width:\s*580px", html, re.IGNORECASE))
if has_900 and has_580:
add("responsive-breakpoints", "PASS", "Both 900px + 580px breakpoints present")
elif has_900 or has_580:
present = "900px" if has_900 else "580px"
missing = "580px" if has_900 else "900px"
add("responsive-breakpoints", "WARN", f"Only {present} breakpoint present; missing {missing}")
else:
add("responsive-breakpoints", "FAIL", "Neither 900px nor 580px media query present")
# Rule 10: H1 + H2
h1_count = len(re.findall(r"<h1\b", html, re.IGNORECASE))
h2_count = len(re.findall(r"<h2\b", html, re.IGNORECASE))
if h1_count == 1:
add("h1-singleton", "PASS", "Exactly one <h1>")
elif h1_count == 0:
add("h1-singleton", "FAIL", "No <h1> (accessibility + SEO)")
else:
add("h1-singleton", "WARN", f"{h1_count} <h1> tags (should be exactly 1 for accessibility/SEO)")
if h2_count >= 1:
add("h2-present", "PASS", f"{h2_count} <h2> tag(s)")
else:
add("h2-present", "WARN", "No <h2> tags (features + CTA sections should each have one)")
# Rule 11: CTA semantic — buttons or links, not divs with onclick
div_onclick = re.findall(r"<div[^>]+onclick=", html, re.IGNORECASE)
if div_onclick:
add("cta-semantic", "FAIL", f"<div> with onclick detected ({len(div_onclick)} found) — use <button> or <a>")
else:
add("cta-semantic", "PASS", "No <div onclick> patterns (buttons/links used semantically)")
return finalize(findings)
def finalize(findings: List[Dict[str, str]]) -> Dict[str, Any]:
counts = {"PASS": 0, "WARN": 0, "FAIL": 0}
for f in findings:
counts[f["level"]] += 1
if counts["FAIL"] > 0:
verdict = "FAIL"
elif counts["WARN"] > 0:
verdict = "WARN"
else:
verdict = "PASS"
return {"verdict": verdict, "counts": counts, "findings": findings}
def render_human(result: Dict[str, Any]) -> str:
out: List[str] = []
out.append(f"HTML structural verdict: {result['verdict']}")
c = result["counts"]
out.append(f" PASS: {c['PASS']} WARN: {c['WARN']} FAIL: {c['FAIL']}")
out.append("")
out.append("Findings:")
for f in result["findings"]:
marker = {"PASS": "[ok]", "WARN": "[warn]", "FAIL": "[FAIL]"}[f["level"]]
out.append(f" {marker} {f['rule']}: {f['message']}")
return "\n".join(out)
def main(argv: List[str]) -> int:
parser = argparse.ArgumentParser(description=__doc__.split("\n")[0])
parser.add_argument("--file", help="Path to .html file to validate")
parser.add_argument("--sample-pass", action="store_true", help="Validate embedded clean sample")
parser.add_argument("--sample-fail", action="store_true", help="Validate embedded violation sample")
parser.add_argument("--output", choices=["human", "json"], default="human")
args = parser.parse_args(argv)
if args.sample_pass:
html = SAMPLE_PASS_HTML
elif args.sample_fail:
html = SAMPLE_FAIL_HTML
elif args.file:
p = Path(args.file)
if not p.exists():
print(f"error: {args.file} not found", file=sys.stderr); return 2
html = p.read_text(encoding="utf-8")
else:
parser.print_help(); return 0
result = validate(html)
if args.output == "json":
print(json.dumps(result, indent=2))
else:
print(render_human(result))
return 0 if result["verdict"] != "FAIL" else 1
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
FILE:scripts/kebab_slug_generator.py
#!/usr/bin/env python3
"""kebab_slug_generator.py — Product name → kebab-case .html filename.
Stdlib-only. Given a product name and an output directory, produce:
- slug: kebab-case alphanumeric (max 50 chars)
- filename: <slug>.html
- output_path: <output_dir>/<filename>
- duplicate: true/false (does file already exist?)
- suggested_alt: if duplicate, suggest timestamped alternative
NO LLM CALLS. Pure string transformation + filesystem stat.
Usage:
python kebab_slug_generator.py --product "Quill AI"
python kebab_slug_generator.py --product "Quill AI" --output-dir ./landing-pages
python kebab_slug_generator.py --product "Self-Hosted LLM Tool" --output json
python kebab_slug_generator.py --sample
"""
import argparse
import json
import os
import re
import sys
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List
SLUG_MAX_LEN = 50
DEFAULT_OUTPUT_DIR = "./landing-pages"
def slugify(product: str) -> str:
"""Convert product name to kebab-case slug."""
s = product.lower()
s = re.sub(r"[^a-z0-9]+", "-", s)
s = re.sub(r"-+", "-", s)
s = s.strip("-")
if len(s) > SLUG_MAX_LEN:
truncated = s[:SLUG_MAX_LEN]
last_hyphen = truncated.rfind("-")
if last_hyphen > SLUG_MAX_LEN // 2:
s = truncated[:last_hyphen]
else:
s = truncated
return s or "landing-page"
def resolve_output_dir(override: str = None) -> Path:
if override:
return Path(override).expanduser().resolve()
env = os.environ.get("OUTPUT_DIR")
if env:
return Path(env).expanduser().resolve()
return Path(DEFAULT_OUTPUT_DIR).resolve()
def generate(product: str, output_dir: Path) -> Dict[str, Any]:
slug = slugify(product)
filename = f"{slug}.html"
output_path = output_dir / filename
duplicate = output_path.exists()
suggested_alt = None
if duplicate:
ts = datetime.now().strftime("%Y%m%d-%H%M%S")
alt = output_dir / f"{slug}-{ts}.html"
suggested_alt = str(alt)
return {
"product": product,
"slug": slug,
"filename": filename,
"output_dir": str(output_dir),
"output_path": str(output_path),
"duplicate": duplicate,
"suggested_alt": suggested_alt,
}
def render_human(result: Dict[str, Any]) -> str:
out: List[str] = []
out.append(f"Product: {result['product']}")
out.append(f"Slug: {result['slug']}")
out.append(f"Filename: {result['filename']}")
out.append(f"Output dir: {result['output_dir']}")
out.append(f"Output path: {result['output_path']}")
out.append(f"Duplicate at path: {'YES' if result['duplicate'] else 'no'}")
if result["duplicate"]:
out.append(f"Suggested alternative: {result['suggested_alt']}")
return "\n".join(out)
def main(argv: List[str]) -> int:
parser = argparse.ArgumentParser(description=__doc__.split("\n")[0])
parser.add_argument("--product", help="Product name")
parser.add_argument("--output-dir", help="Output directory (default: $OUTPUT_DIR or ./landing-pages)")
parser.add_argument("--sample", action="store_true", help="Run on sample product")
parser.add_argument("--output", choices=["human", "json"], default="human")
args = parser.parse_args(argv)
if args.sample:
result = generate("Quill AI — Async Standup Tool", Path("/tmp/sample-landing"))
elif args.product:
output_dir = resolve_output_dir(args.output_dir)
result = generate(args.product, output_dir)
else:
parser.print_help(); return 0
if args.output == "json":
print(json.dumps(result, indent=2))
else:
print(render_human(result))
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
Rà soát backend qua 7 câu hỏi bắt buộc, chọn mẫu kiến trúc và giao cho các chuyên gia API, cơ sở dữ liệu, migration, SLO.
---
description: Backend engineering review — walks the 7 Matt Pocock forcing questions (read/write ratio + QPS, tenancy, sync vs async, data sensitivity, pattern, RPO/RTO, SLO), picks the language + pattern profile, forks into specialists (api-design-reviewer, database-designer, migration-architect, slo-architect). Invokes the cs-backend-engineer agent with context fork.
argument-hint: "<problem or service to review>"
---
# /cs:backend-review — Backend engineering review
Use the `cs-backend-engineer` agent (uses `context: fork`) to handle this inquiry:
**$ARGUMENTS**
## Forcing-question library
Canonical source: `engineering-team/skills/senior-backend/references/forcing_questions.md` (7 questions, one-per-turn, recommendation + canon citation per question).
1. Read/write ratio + one-year p99 QPS
2. Tenancy model (single / shared / isolated multi-tenant)
3. Sync request/response vs async (queue) vs event-driven
4. Data sensitivity tier (public / internal / PII / PHI / PCI)
5. Monolith / modular monolith / microservices (team-size justification)
6. RPO and RTO
7. SLO + named error-budget consumer
## Routing protocol
1. **Walk the 7 forcing questions** in `engineering-team/skills/senior-backend/references/forcing_questions.md`. One per turn. Recommend with cited canon. Track in `/tmp/backend-grill-<date>.md`.
2. **Surface kill criteria** — e.g., "microservices, team size 5" trips (Newman's MonolithFirst). STOP and resolve.
3. **Run the deterministic profile picker:**
```bash
python engineering-team/skills/senior-backend/scripts/backend_decision_engine.py \
--team-size <N> --qps-p99 <N> --read-write-ratio <ratio> \
--tenancy <single-tenant|shared-multi-tenant|isolated-multi-tenant> \
--data-sensitivity <public|pii|phi|pci> \
--pattern <monolith|modular-monolith|domain-bounded-services|microservices|serverless> \
--language-preference <typescript|python|go|rust|java|kotlin|dotnet>
```
4. **Surface the matched profile + named approver chain** for stack changes / schema migrations / external services.
5. **Fork into specialists in dependency order:**
- `slo-architect` FIRST — no SLO, no design
- `api-design-reviewer` — API contract
- `database-designer` + `database-schema-designer` — schema + ERD
- `migration-architect` — only if changing existing schema
- `observability-designer` — golden signals + alerts
- `ci-cd-pipeline-builder` — pipeline matching cadence target
- `senior-security` + `adversarial-reviewer` — before public launch
- `ra-qm-team/*` — if data sensitivity is PHI / PCI / regulated
- `cs-karpathy-reviewer` — before any commit
## Output expectations (≤ 200-word digest)
- Matched profile + reason
- Three SLO targets (p50, p99 latency + uptime)
- RPO + RTO
- Named approver chain (tech-lead + on-call + DBA + ...)
- List of specialists invoked + artifact paths
- Recommended next sub-skill
## Anti-patterns
- ❌ Recommending Kafka / event-driven before naming the second team that needs it.
- ❌ Recommending microservices without team-size ≥ 30 + platform team + bounded-context independence.
- ❌ Designing the API without forking into `api-design-reviewer`.
- ❌ Recommending a DB without QPS + read/write ratio (Q1 unanswered).
- ❌ Auto-approving a production schema migration. Always name the on-call + DBA.
## Customization
Profiles live at `engineering-team/skills/senior-backend/profiles/`. Four built-in: `node-express`, `fastapi-python`, `django-monolith`, `go-or-rust-microservice`. Copy one to `<your-org>.json` and adjust constraints / SLO floor / approver chain.
## Related commands
- `/cs:fullstack-review` — full-stack lens (parent)
- `/cs:frontend-review` — for API consumer side
- `/cs:engineer-grill` — cross-role 21-question grill
- `/slo-design` — explicit SLO design via slo-architect
- `/karpathy-check` — Karpathy 4-principle review
Xác định phạm vi, soạn, tách, hoàn thiện hoặc rà soát Use Case theo mẫu 13 trường Wiegers/IIBA và nguyên tắc phạm vi của Cockburn.
---
name: "cs-use-case-writer"
description: "/cs:use-case-writer — IT Business Analyst Use Case workflow. Scope, draft, split, refine, or review Use Case specifications following the Karl Wiegers / IIBA 13-field template and Alistair Cockburn's scoping discipline (coffee-break test, goal levels, system boundary). Sequential 5-group generation with confirmation gates, bilingual Vietnamese/English intake with English-only output, 20-point quality checklist. Distinct from Agile User Stories, full PRD/URD/SRS, and UML diagrams."
---
# /cs:use-case-writer — Use Case Specification Writer
**Command:** `/cs:use-case-writer [mode] [args]`
The `cs-use-case-writer` command is the **entry point for UC workflows**: classify → scope → write (sequential) → validate.
## Distinct From `/user-story`
These are different requirements artifacts:
- **`/user-story`** — one-line "As a… I want… so that…" plus Given/When/Then acceptance criteria; sprint-ready, INVEST-compliant
- **`/cs:use-case-writer`** (this command) — detailed multi-section interaction spec: actors, pre/postconditions, normal course, alternative courses, exceptions
Use `/user-story` for backlog items. Use this command for formal BA-style UC documentation (common in regulated, enterprise, or contract-driven projects).
## When To Run
- Drafting a UC from a feature description, BRD, or PRD excerpt
- Splitting a large feature into a right-sized UC list before writing any of them in detail
- Reviewing or refining an existing UC for completeness and correctness
- Writing just one section (Normal Course, Alternative Course, Exceptions) of an existing UC
## When NOT To Run
- Sprint-ready backlog items → use `/user-story`
- A full PRD/URD/SRS document → use `/prd` (a UC is one section, not the whole doc)
- UML Use Case diagrams → this produces text specs, not diagrams
- Wireframes or UI mockups → a UC describes interaction, not visual design
## Modes
### `write` — Draft a new UC from a feature description (Mode A)
```
/cs:use-case-writer write
> Feature: 1-on-1 mentor session booking
> (agent asks for primary actor, goal, system boundary if not stated)
```
Scopes first (coffee-break test + goal level + one-actor-one-goal-one-session + system boundary), then generates the 13 fields in 5 confirmation-gated groups.
### `split` — Decompose a large feature into a UC list (Mode B)
```
/cs:use-case-writer split
> Paste feature description / PRD excerpt
```
Applies 3 identification techniques (goal-driven, event-driven, CRUD-driven) and returns a `UC ID | UC Name | Primary Actor | Goal | Priority` table, then asks which UC to detail first.
### `review` — Refine or validate an existing UC (Mode C)
```
/cs:use-case-writer review
> Paste the existing UC
```
Skips scoping, runs straight to the 20-point checklist (mechanical pass + semantic review) and reports fixes.
### `section` — Write one section of an existing UC (Mode D)
```
/cs:use-case-writer section
> "Write the Exceptions for UC-LEARN-01"
```
Reads the UC context, jumps to the relevant part of the field-generation step.
## Validation Script
```bash
# Mechanical first pass over the 20-point checklist
python product-team/skills/use-case-writer/scripts/uc_quality_checker.py <uc-file.md>
# Verify Includes against a known UC-ID registry
python product-team/skills/use-case-writer/scripts/uc_quality_checker.py <uc-file.md> --registry known-uc-ids.txt
# JSON output
python product-team/skills/use-case-writer/scripts/uc_quality_checker.py <uc-file.md> --json
# Try it without a file
python product-team/skills/use-case-writer/scripts/uc_quality_checker.py --sample
```
Several checklist items (C2 goal-level, C5 system boundary, C11 precondition-vs-assumption, C13 actor/system alternation, C15 flow completeness, C19 Includes existence without a registry) need judgment and are reported `MANUAL` — the agent walks those with the user rather than auto-passing them.
## Bilingual Intake
Chat in Vietnamese or English — the skill responds in whichever language you use. The UC artifact itself is **always English Markdown**, non-negotiable.
## The 13 Fields
Use Case ID, Use Case Name, History (Created/Updated By+Date), Actor (Primary/Secondary), Description, Preconditions, Postconditions, Priority, Frequency of Use, Normal Course of Events, Alternative Courses, Exceptions, Includes, Special Requirements, Assumptions, Notes and Issues.
## Anti-Patterns Rejected
- UC written as a pixel-by-pixel UI spec (that's a wireframe annotation)
- UC conflated with a User Story (one-liner) or a Business Process (multi-actor, multi-system)
- Vague verbs in the UC Name ("Manage", "Handle", "Process")
- Embedded if/else or loops inside the Normal Course
- Happy-path-only UCs with no Exceptions
- Generic "User" as the actor instead of a specific role
## Trigger Phrases
- "write a use case", "draft UC", "use case specification"
- "split feature into use cases", "how many UCs does this need"
- "review my UC", "is this UC complete"
- "write the normal course / alternative course / exceptions"
- "viết use case", "viết UC", "đặc tả use case", "phân tích use case", "review UC"
## Related
- Agent: [`cs-use-case-writer`](../agents/product/cs-use-case-writer.md)
- Skill: [`use-case-writer`](../product-team/skills/use-case-writer/SKILL.md)
- Companion: `/user-story` (Agile format — different artifact, often confused with UC)
- Source: ported from [`phucnt-bazone-vietnam/use-case-writer`](https://github.com/phucnt-bazone-vietnam/use-case-writer)
---
**Version:** 1.0.0
**License:** MIT (attribution required — Phúc NT / BA Zone / Digital School)
Lệnh tạo nhanh tài liệu yêu cầu sản phẩm (PRD) từ một tính năng hoặc vấn đề.
--- name: prd description: Quick PRD generation command. Usage: /prd <feature-or-problem> --- # /prd Generate a concise product requirements document for a feature, initiative, or problem statement. ## Usage ```bash /prd <feature-or-problem> ``` ## Output Structure - Problem statement - Goals and non-goals - User stories and acceptance criteria - Metrics and success thresholds - Scope and timeline assumptions ## Skill Reference - `product-team/product-manager-toolkit/SKILL.md`
Quản lý CAPA cho QMS thiết bị y tế: phân tích nguyên nhân gốc, hành động khắc phục và kiểm chứng hiệu quả.
---
name: "capa-officer"
description: CAPA system management for medical device QMS. Covers root cause analysis, corrective action planning, effectiveness verification, and CAPA metrics. Use for CAPA investigations, 5-Why analysis, fishbone diagrams, root cause determination, corrective action tracking, effectiveness verification, or CAPA program optimization.
triggers:
- CAPA investigation
- root cause analysis
- 5 Why analysis
- fishbone diagram
- corrective action
- preventive action
- effectiveness verification
- CAPA metrics
- nonconformance investigation
- quality issue investigation
- CAPA tracking
- audit finding CAPA
---
# CAPA Officer
Corrective and Preventive Action (CAPA) management within Quality Management Systems, focusing on systematic root cause analysis, action implementation, and effectiveness verification.
---
## Table of Contents
- [CAPA Investigation Workflow](#capa-investigation-workflow)
- [Root Cause Analysis](#root-cause-analysis)
- [Corrective Action Planning](#corrective-action-planning)
- [Effectiveness Verification](#effectiveness-verification)
- [CAPA Metrics and Reporting](#capa-metrics-and-reporting)
- [Reference Documentation](#reference-documentation)
- [Tools](#tools)
---
## CAPA Investigation Workflow
Conduct systematic CAPA investigation from initiation through closure:
1. Document trigger event with objective evidence
2. Assess significance and determine CAPA necessity
3. Form investigation team with relevant expertise
4. Collect data and evidence systematically
5. Select and apply appropriate RCA methodology
6. Identify root cause(s) with supporting evidence
7. Develop corrective and preventive actions
8. **Validation:** Root cause explains all symptoms; if eliminated, problem would not recur
### CAPA Necessity Determination
| Trigger Type | CAPA Required | Criteria |
|--------------|---------------|----------|
| Customer complaint (safety) | Yes | Any complaint involving patient/user safety |
| Customer complaint (quality) | Evaluate | Based on severity and frequency |
| Internal audit finding (Major) | Yes | Systematic failure or absence of element |
| Internal audit finding (Minor) | Recommended | Isolated lapse or partial implementation |
| Nonconformance (recurring) | Yes | Same NC type occurring 3+ times |
| Nonconformance (isolated) | Evaluate | Based on severity and risk |
| External audit finding | Yes | All Major and Minor findings |
| Trend analysis | Evaluate | Based on trend significance |
### Investigation Team Composition
| CAPA Severity | Required Team Members |
|---------------|----------------------|
| Critical | CAPA Officer, Process Owner, QA Manager, Subject Matter Expert, Management Rep |
| Major | CAPA Officer, Process Owner, Subject Matter Expert |
| Minor | CAPA Officer, Process Owner |
### Evidence Collection Checklist
- [ ] Problem description with specific details (what, where, when, who, how much)
- [ ] Timeline of events leading to issue
- [ ] Relevant records and documentation
- [ ] Interview notes from involved personnel
- [ ] Photos or physical evidence (if applicable)
- [ ] Related complaints, NCs, or previous CAPAs
- [ ] Process parameters and specifications
---
## Root Cause Analysis
Select and apply appropriate RCA methodology based on problem characteristics.
### RCA Method Selection Decision Tree
```
Is the issue safety-critical or involves system reliability?
├── Yes → Use FAULT TREE ANALYSIS
└── No → Is human error the suspected primary cause?
├── Yes → Use HUMAN FACTORS ANALYSIS
└── No → How many potential contributing factors?
├── 1-2 factors (linear causation) → Use 5 WHY ANALYSIS
├── 3-6 factors (complex, systemic) → Use FISHBONE DIAGRAM
└── Unknown/proactive assessment → Use FMEA
```
### 5 Why Analysis
Use when: Single-cause issues with linear causation, process deviations with clear failure point.
**Template:**
```
PROBLEM: [Clear, specific statement]
WHY 1: Why did [problem] occur?
BECAUSE: [First-level cause]
EVIDENCE: [Supporting data]
WHY 2: Why did [first-level cause] occur?
BECAUSE: [Second-level cause]
EVIDENCE: [Supporting data]
WHY 3: Why did [second-level cause] occur?
BECAUSE: [Third-level cause]
EVIDENCE: [Supporting data]
WHY 4: Why did [third-level cause] occur?
BECAUSE: [Fourth-level cause]
EVIDENCE: [Supporting data]
WHY 5: Why did [fourth-level cause] occur?
BECAUSE: [Root cause]
EVIDENCE: [Supporting data]
```
**Example - Calibration Overdue:**
```
PROBLEM: pH meter (EQ-042) found 2 months overdue for calibration
WHY 1: Why was calibration overdue?
BECAUSE: Equipment was not on calibration schedule
EVIDENCE: Calibration schedule reviewed, EQ-042 not listed
WHY 2: Why was it not on the schedule?
BECAUSE: Schedule not updated when equipment was purchased
EVIDENCE: Purchase date 2023-06-15, schedule dated 2023-01-01
WHY 3: Why was the schedule not updated?
BECAUSE: No process requires schedule update at equipment purchase
EVIDENCE: SOP-EQ-001 reviewed, no such requirement
WHY 4: Why is there no such requirement?
BECAUSE: Procedure written before equipment tracking was centralized
EVIDENCE: SOP last revised 2019, equipment system implemented 2021
WHY 5: Why has procedure not been updated?
BECAUSE: Periodic review did not assess compatibility with new systems
EVIDENCE: No review against new equipment system documented
ROOT CAUSE: Procedure review process does not assess compatibility
with organizational systems implemented after original procedure creation.
```
### Fishbone Diagram Categories (6M)
| Category | Focus Areas | Typical Causes |
|----------|-------------|----------------|
| Man (People) | Training, competency, workload | Skill gaps, fatigue, communication |
| Machine (Equipment) | Calibration, maintenance, age | Wear, malfunction, inadequate capacity |
| Method (Process) | Procedures, work instructions | Unclear steps, missing controls |
| Material | Specifications, suppliers, storage | Out-of-spec, degradation, contamination |
| Measurement | Calibration, methods, interpretation | Instrument error, wrong method |
| Mother Nature | Temperature, humidity, cleanliness | Environmental excursions |
See `references/rca-methodologies.md` for complete method details and templates.
### Root Cause Validation
Before proceeding to action planning, validate root cause:
- [ ] Root cause can be verified with objective evidence
- [ ] If root cause is eliminated, problem would not recur
- [ ] Root cause is within organizational control
- [ ] Root cause explains all observed symptoms
- [ ] No other significant causes remain unaddressed
---
## Corrective Action Planning
Develop effective actions addressing identified root causes:
1. Define immediate containment actions
2. Develop corrective actions targeting root cause
3. Identify preventive actions for similar processes
4. Assign responsibilities and resources
5. Establish timeline with milestones
6. Define success criteria and verification method
7. Document in CAPA action plan
8. **Validation:** Actions directly address root cause; success criteria are measurable
### Action Types
| Type | Purpose | Timeline | Example |
|------|---------|----------|---------|
| Containment | Stop immediate impact | 24-72 hours | Quarantine affected product |
| Correction | Fix the specific occurrence | 1-2 weeks | Rework or replace affected items |
| Corrective | Eliminate root cause | 30-90 days | Revise procedure, add controls |
| Preventive | Prevent in other areas | 60-120 days | Extend solution to similar processes |
### Action Plan Components
```
ACTION PLAN TEMPLATE
CAPA Number: [CAPA-XXXX]
Root Cause: [Identified root cause]
ACTION 1: [Specific action description]
- Type: [ ] Containment [ ] Correction [ ] Corrective [ ] Preventive
- Responsible: [Name, Title]
- Due Date: [YYYY-MM-DD]
- Resources: [Required resources]
- Success Criteria: [Measurable outcome]
- Verification Method: [How success will be verified]
ACTION 2: [Specific action description]
...
IMPLEMENTATION TIMELINE:
Week 1: [Milestone]
Week 2: [Milestone]
Week 4: [Milestone]
Week 8: [Milestone]
APPROVAL:
CAPA Owner: _____________ Date: _______
Process Owner: _____________ Date: _______
QA Manager: _____________ Date: _______
```
### Action Effectiveness Indicators
| Indicator | Target | Red Flag |
|-----------|--------|----------|
| Action scope | Addresses root cause completely | Treats only symptoms |
| Specificity | Measurable deliverables | Vague commitments |
| Timeline | Aggressive but achievable | No due dates or unrealistic |
| Resources | Identified and allocated | Not specified |
| Sustainability | Permanent solution | Temporary fix |
---
## Effectiveness Verification
Verify corrective actions achieved intended results:
1. Allow adequate implementation period (minimum 30-90 days)
2. Collect post-implementation data
3. Compare to pre-implementation baseline
4. Evaluate against success criteria
5. Verify no recurrence during verification period
6. Document verification evidence
7. Determine CAPA effectiveness
8. **Validation:** All criteria met with objective evidence; no recurrence observed
### Verification Timeline Guidelines
| CAPA Severity | Wait Period | Verification Window |
|---------------|-------------|---------------------|
| Critical | 30 days | 30-90 days post-implementation |
| Major | 60 days | 60-180 days post-implementation |
| Minor | 90 days | 90-365 days post-implementation |
### Verification Methods
| Method | Use When | Evidence Required |
|--------|----------|-------------------|
| Data trend analysis | Quantifiable issues | Pre/post comparison, trend charts |
| Process audit | Procedure compliance issues | Audit checklist, interview notes |
| Record review | Documentation issues | Sample records, compliance rate |
| Testing/inspection | Product quality issues | Test results, pass/fail data |
| Interview/observation | Training issues | Interview notes, observation records |
### Effectiveness Determination
```
Did recurrence occur during verification period?
├── Yes → CAPA INEFFECTIVE (re-investigate root cause)
└── No → Were all effectiveness criteria met?
├── Yes → CAPA EFFECTIVE (proceed to closure)
└── No → Extent of gap?
├── Minor gap → Extend verification or accept with justification
└── Significant gap → CAPA INEFFECTIVE (revise actions)
```
See `references/effectiveness-verification-guide.md` for detailed procedures.
---
## CAPA Metrics and Reporting
Monitor CAPA program performance through key indicators.
### Key Performance Indicators
| Metric | Target | Calculation |
|--------|--------|-------------|
| CAPA cycle time | <60 days average | (Close Date - Open Date) / Number of CAPAs |
| Overdue rate | <10% | Overdue CAPAs / Total Open CAPAs |
| First-time effectiveness | >90% | Effective on first verification / Total verified |
| Recurrence rate | <5% | Recurred issues / Total closed CAPAs |
| Investigation quality | 100% root cause validated | Root causes validated / Total CAPAs |
### Aging Analysis Categories
| Age Bucket | Status | Action Required |
|------------|--------|-----------------|
| 0-30 days | On track | Monitor progress |
| 31-60 days | Monitor | Review for delays |
| 61-90 days | Warning | Escalate to management |
| >90 days | Critical | Management intervention required |
### Management Review Inputs
Monthly CAPA status report includes:
- Open CAPA count by severity and status
- Overdue CAPA list with owners
- Cycle time trends
- Effectiveness rate trends
- Source analysis (complaints, audits, NCs)
- Recommendations for improvement
---
## Reference Documentation
### Root Cause Analysis Methodologies
`references/rca-methodologies.md` contains:
- Method selection decision tree
- 5 Why analysis template and example
- Fishbone diagram categories and template
- Fault Tree Analysis for safety-critical issues
- Human Factors Analysis for people-related causes
- FMEA for proactive risk assessment
- Hybrid approach guidance
### Effectiveness Verification Guide
`references/effectiveness-verification-guide.md` contains:
- Verification planning requirements
- Verification method selection
- Effectiveness criteria definition (SMART)
- Closure requirements by severity
- Ineffective CAPA process
- Documentation templates
---
## Tools
### CAPA Tracker
```bash
# Generate CAPA status report
python scripts/capa_tracker.py --capas capas.json
# Interactive mode for manual entry
python scripts/capa_tracker.py --interactive
# JSON output for integration
python scripts/capa_tracker.py --capas capas.json --output json
# Generate sample data file
python scripts/capa_tracker.py --sample > sample_capas.json
```
Calculates and reports:
- Summary metrics (open, closed, overdue, cycle time, effectiveness)
- Status distribution
- Severity and source analysis
- Aging report by time bucket
- Overdue CAPA list
- Actionable recommendations
### Sample CAPA Input
```json
{
"capas": [
{
"capa_number": "CAPA-2024-001",
"title": "Calibration overdue for pH meter",
"description": "pH meter EQ-042 found 2 months overdue",
"source": "AUDIT",
"severity": "MAJOR",
"status": "VERIFICATION",
"open_date": "2024-06-15",
"target_date": "2024-08-15",
"owner": "J. Smith",
"root_cause": "Procedure review gap",
"corrective_action": "Updated SOP-EQ-001"
}
]
}
```
---
## Regulatory Requirements
### ISO 13485:2016 Clause 8.5
| Sub-clause | Requirement | Key Activities |
|------------|-------------|----------------|
| 8.5.2 Corrective Action | Eliminate cause of nonconformity | NC review, cause determination, action evaluation, implementation, effectiveness review |
| 8.5.3 Preventive Action | Eliminate potential nonconformity | Trend analysis, cause determination, action evaluation, implementation, effectiveness review |
### FDA 21 CFR 820.100
Required CAPA elements:
- Procedures for implementing corrective and preventive action
- Analyzing quality data sources (complaints, NCs, audits, service records)
- Investigating cause of nonconformities
- Identifying actions needed to correct and prevent recurrence
- Verifying actions are effective and do not adversely affect device
- Submitting relevant information for management review
### Common FDA 483 Observations
| Observation | Root Cause Pattern |
|-------------|-------------------|
| CAPA not initiated for recurring issue | Trend analysis not performed |
| Root cause analysis superficial | Inadequate investigation training |
| Effectiveness not verified | No verification procedure |
| Actions do not address root cause | Symptom treatment vs. cause elimination |
FILE:references/effectiveness-verification-guide.md
# Effectiveness Verification Guide
CAPA effectiveness assessment procedures, verification methods, and closure criteria.
---
## Table of Contents
- [Verification Planning](#verification-planning)
- [Verification Methods](#verification-methods)
- [Effectiveness Criteria](#effectiveness-criteria)
- [Closure Requirements](#closure-requirements)
- [Ineffective CAPA Process](#ineffective-capa-process)
- [Documentation Templates](#documentation-templates)
---
## Verification Planning
### When to Plan Verification
Verification planning must occur BEFORE corrective action implementation:
| Stage | Planning Activity | Owner |
|-------|-------------------|-------|
| CAPA Initiation | Define preliminary verification approach | CAPA Owner |
| Root Cause Analysis | Refine criteria based on root cause | Investigation Team |
| Action Planning | Finalize verification method and timeline | CAPA Owner |
| Implementation | Schedule verification activities | Quality Assurance |
### Verification Timeline Guidelines
| CAPA Severity | Minimum Wait Period | Verification Window |
|---------------|---------------------|---------------------|
| Critical (Safety) | 30 days | 30-90 days post-implementation |
| Major | 60 days | 60-180 days post-implementation |
| Minor | 90 days | 90-365 days post-implementation |
**Rationale**: Waiting period ensures sufficient data collection and accounts for process variation.
### Verification Plan Components
```
VERIFICATION PLAN TEMPLATE
CAPA Number: [CAPA-XXXX]
Problem Statement: [Original issue]
Root Cause: [Identified root cause]
Corrective Action: [Implemented action]
VERIFICATION METHOD:
[ ] Data Trend Analysis
[ ] Process Audit
[ ] Record Review
[ ] Testing/Inspection
[ ] Interview/Observation
[ ] Multiple Methods (specify)
EFFECTIVENESS CRITERIA:
1. [Measurable criterion 1]
2. [Measurable criterion 2]
3. [Measurable criterion 3]
SUCCESS THRESHOLD:
- [Quantitative threshold, e.g., "Zero recurrence for 90 days"]
- [Qualitative threshold, e.g., "Procedure followed correctly 100%"]
DATA COLLECTION:
- Source: [Where data will come from]
- Sample Size: [Number of records/instances to review]
- Time Period: [Start and end dates]
- Responsible: [Who collects data]
VERIFICATION SCHEDULE:
- Implementation Complete: [Date]
- Waiting Period Ends: [Date]
- Verification Start: [Date]
- Verification Complete: [Date]
- Report Due: [Date]
APPROVAL:
CAPA Owner: _____________ Date: _______
Quality Assurance: _____________ Date: _______
```
---
## Verification Methods
### 1. Data Trend Analysis
**Best for:** Quantifiable issues with measurable outcomes (defect rates, cycle times, complaint trends)
**Procedure:**
1. Collect post-implementation data for defined period
2. Compare to pre-implementation baseline
3. Apply statistical analysis if sample size permits
4. Document trend direction and magnitude
**Example Criteria:**
- Defect rate reduced by ≥50% from baseline
- Zero recurrence of specific failure mode
- Process capability (Cpk) improved to ≥1.33
**Evidence Required:**
- Pre-implementation baseline data
- Post-implementation trend data
- Statistical analysis (if applicable)
- Trend charts with annotation
### 2. Process Audit
**Best for:** Procedure compliance issues, process control failures, systemic problems
**Procedure:**
1. Develop audit checklist based on corrective action
2. Conduct unannounced process audit
3. Interview operators and supervisors
4. Review records generated since implementation
5. Document compliance percentage
**Example Criteria:**
- 100% compliance with revised procedure
- All operators demonstrate competency
- No deviations observed during audit
**Evidence Required:**
- Audit checklist completed
- Interview notes
- Record samples reviewed
- Photos/observations (if applicable)
### 3. Record Review
**Best for:** Documentation issues, completeness problems, traceability failures
**Procedure:**
1. Define sample size based on volume (minimum 10 or 10%, whichever greater)
2. Review records generated post-implementation
3. Evaluate against specified requirements
4. Calculate compliance rate
**Example Criteria:**
- 100% of records meet completeness requirements
- All required signatures present
- Traceability maintained throughout
**Evidence Required:**
- List of records reviewed
- Compliance checklist results
- Non-compliance summary (if any)
### 4. Testing/Inspection
**Best for:** Product quality issues, equipment failures, specification non-conformances
**Procedure:**
1. Define test protocol based on corrective action
2. Conduct testing on post-implementation units
3. Compare results to acceptance criteria
4. Document pass/fail rates
**Example Criteria:**
- 100% of units pass revised inspection criteria
- All test results within specification
- Zero failures of targeted parameter
**Evidence Required:**
- Test protocol/method
- Test results data
- Pass/fail summary
- Comparison to pre-implementation results
### 5. Interview/Observation
**Best for:** Training issues, communication problems, human factors causes
**Procedure:**
1. Develop structured interview questions
2. Interview representative sample of affected personnel
3. Observe process execution in real-time
4. Document responses and observations
**Example Criteria:**
- All interviewed personnel demonstrate knowledge
- Observed practices match documented procedure
- No unsafe acts or workarounds observed
**Evidence Required:**
- Interview questions and responses
- Observation notes
- Training records (supporting)
---
## Effectiveness Criteria
### Defining Good Criteria
Criteria must be **SMART**:
| Element | Requirement | Example |
|---------|-------------|---------|
| **S**pecific | Clearly defined what to measure | "Calibration overdue rate" not "equipment issues" |
| **M**easurable | Quantifiable or objectively verifiable | "<2% overdue rate" not "improved timeliness" |
| **A**chievable | Realistic given the corrective action | Within capability of implemented solution |
| **R**elevant | Directly related to root cause | Addresses the actual problem |
| **T**ime-bound | Specified evaluation period | "For 90 consecutive days" |
### Criteria by Issue Type
| Issue Type | Typical Criteria | Threshold |
|------------|------------------|-----------|
| Nonconformance | Recurrence rate | Zero recurrence |
| Process deviation | Compliance rate | ≥95% compliance |
| Complaint | Complaint trend | ≥50% reduction |
| Calibration | Overdue rate | <2% overdue |
| Training | Competency pass rate | 100% pass |
| Documentation | Completeness rate | 100% complete |
| Supplier | Incoming reject rate | ≤1% reject rate |
### Sample Size Guidelines
| Population Size | Minimum Sample |
|-----------------|----------------|
| <10 | All (100%) |
| 10-50 | 10 |
| 51-100 | 15 |
| 101-500 | 20 |
| >500 | 25 or 10%, whichever less |
---
## Closure Requirements
### Closure Checklist
**CAPA Closure Prerequisites:**
- [ ] All corrective actions implemented
- [ ] Implementation evidence documented
- [ ] Verification waiting period complete
- [ ] Verification activities performed
- [ ] All effectiveness criteria met
- [ ] Verification evidence documented
- [ ] No recurrence during verification period
- [ ] CAPA owner review complete
- [ ] Quality Assurance review complete
- [ ] Documentation complete and filed
### Effectiveness Status Determination
```
EFFECTIVENESS DECISION TREE:
Did recurrence occur during verification period?
├── Yes → CAPA INEFFECTIVE (escalate per ineffective process)
└── No → Were all effectiveness criteria met?
├── Yes → Were any related issues identified?
│ ├── Yes → Open new CAPA if needed, close original
│ └── No → CAPA EFFECTIVE - proceed to closure
└── No → How many criteria missed?
├── Minor gap (1 criterion, marginal miss) →
│ Extend verification period OR accept with justification
└── Significant gap → CAPA INEFFECTIVE
EFFECTIVENESS DETERMINATION:
[ ] EFFECTIVE - All criteria met, no recurrence
[ ] EFFECTIVE WITH CONDITIONS - Minor gap, justified acceptance
[ ] INEFFECTIVE - Significant gaps or recurrence
```
### Closure Documentation
```
EFFECTIVENESS VERIFICATION REPORT
CAPA Number: [CAPA-XXXX]
Verification Complete Date: [Date]
Verified By: [Name, Title]
VERIFICATION SUMMARY:
| Criterion | Target | Actual | Status |
|-----------|--------|--------|--------|
| [Criterion 1] | [Target] | [Result] | ☑ Met / ☐ Not Met |
| [Criterion 2] | [Target] | [Result] | ☑ Met / ☐ Not Met |
| [Criterion 3] | [Target] | [Result] | ☑ Met / ☐ Not Met |
RECURRENCE CHECK:
- Recurrence during verification period: [ ] Yes [ ] No
- Related issues identified: [ ] Yes [ ] No
- If yes, describe: [Description]
EVIDENCE SUMMARY:
[List of evidence documents, record numbers, data sources]
EFFECTIVENESS DETERMINATION:
[ ] EFFECTIVE
[ ] EFFECTIVE WITH CONDITIONS: [Justification]
[ ] INEFFECTIVE: [Reason]
RECOMMENDED ACTION:
[ ] Close CAPA
[ ] Extend verification period to [Date]
[ ] Open new CAPA [CAPA-XXXX] for [Issue]
[ ] Re-investigate (return to root cause analysis)
APPROVALS:
CAPA Owner: _____________ Date: _______
Quality Assurance: _____________ Date: _______
Management (if Major/Critical): _____________ Date: _______
```
---
## Ineffective CAPA Process
### Definition of Ineffective
CAPA is ineffective when:
1. Original problem recurs during or after verification period
2. Effectiveness criteria not met
3. Root cause still present
4. Corrective action created new problems
### Ineffective CAPA Workflow
```
INEFFECTIVE CAPA DETECTED
│
├── 1. Immediate Actions
│ ├── Reopen CAPA (do not close as effective)
│ ├── Implement containment for recurrence
│ └── Notify CAPA owner and management
│
├── 2. Root Cause Re-evaluation
│ ├── Was original root cause correct?
│ │ ├── No → Conduct new root cause analysis
│ │ └── Yes → Was corrective action appropriate?
│ │ ├── No → Develop new corrective action
│ │ └── Yes → Was implementation adequate?
│ │ ├── No → Re-implement with improvements
│ │ └── Yes → Escalate (systemic issue)
│
├── 3. Escalation Criteria
│ ├── Second ineffective attempt → Management review required
│ ├── Safety-related recurrence → Immediate escalation
│ └── Pattern across multiple CAPAs → Systemic CAPA
│
└── 4. Documentation
├── Document ineffective status with evidence
├── Record re-investigation results
├── Update CAPA metrics/trending
└── Include in management review
```
### Preventing Ineffective CAPAs
| Common Cause | Prevention |
|--------------|------------|
| Superficial root cause | Validate root cause before action |
| Action addresses symptom not cause | Ensure action targets root cause |
| Implementation incomplete | Verify implementation before verification |
| Insufficient verification period | Allow adequate time for data collection |
| Wrong verification method | Match method to issue type |
| Unclear success criteria | Define SMART criteria upfront |
---
## Documentation Templates
### Verification Evidence Log
```
VERIFICATION EVIDENCE LOG
CAPA Number: [CAPA-XXXX]
| Doc/Record # | Description | Date | Reviewed By | Finding |
|--------------|-------------|------|-------------|---------|
| [Number] | [Description] | [Date] | [Reviewer] | [Compliant/Finding] |
| [Number] | [Description] | [Date] | [Reviewer] | [Compliant/Finding] |
SUMMARY:
- Total records reviewed: [Number]
- Compliant: [Number] ([Percentage]%)
- Non-compliant: [Number] ([Percentage]%)
CONCLUSION:
[Statement on whether evidence supports effectiveness]
```
### Trend Analysis Summary
```
TREND ANALYSIS FOR CAPA VERIFICATION
CAPA Number: [CAPA-XXXX]
Metric: [What is being measured]
BASELINE (Pre-Implementation):
- Period: [Start] to [End]
- Value: [Baseline value]
- Data points: [Number]
POST-IMPLEMENTATION:
- Period: [Start] to [End]
- Value: [Current value]
- Data points: [Number]
CHANGE:
- Absolute change: [Value]
- Percentage change: [Percentage]%
- Target: [Target value/change]
- Status: [ ] Met [ ] Not Met
TREND CHART:
[Include or reference trend chart showing before/after comparison]
STATISTICAL SIGNIFICANCE (if applicable):
- Method: [t-test, chi-square, etc.]
- p-value: [Value]
- Conclusion: [Statistically significant / Not significant]
```
### Interview Summary Template
```
VERIFICATION INTERVIEW SUMMARY
CAPA Number: [CAPA-XXXX]
Interviewer: [Name]
Date: [Date]
INTERVIEWEE:
- Name: [Name]
- Role: [Job title]
- Department: [Department]
- Experience: [Years in role]
QUESTIONS AND RESPONSES:
Q1: [Question about awareness of change]
A1: [Response summary]
Knowledge demonstrated: [ ] Yes [ ] Partial [ ] No
Q2: [Question about implementation of change]
A2: [Response summary]
Compliance demonstrated: [ ] Yes [ ] Partial [ ] No
Q3: [Question about understanding rationale]
A3: [Response summary]
Understanding demonstrated: [ ] Yes [ ] Partial [ ] No
OBSERVATION NOTES:
[Any relevant observations during interview]
CONCLUSION:
[ ] Interviewee demonstrates full knowledge and compliance
[ ] Interviewee demonstrates partial knowledge (specify gaps)
[ ] Interviewee does not demonstrate required knowledge
```
FILE:references/rca-methodologies.md
# Root Cause Analysis Methodologies
Decision criteria, templates, and implementation guidance for RCA techniques.
---
## Table of Contents
- [Method Selection Matrix](#method-selection-matrix)
- [5 Why Analysis](#5-why-analysis)
- [Fishbone Diagram](#fishbone-diagram)
- [Fault Tree Analysis](#fault-tree-analysis)
- [Human Factors Analysis](#human-factors-analysis)
- [Failure Mode and Effects Analysis](#failure-mode-and-effects-analysis)
- [Selecting the Right Method](#selecting-the-right-method)
---
## Method Selection Matrix
### When to Use Each Method
| Method | Use When | Problem Type | Team Size | Time Required |
|--------|----------|--------------|-----------|---------------|
| 5 Why | Single-cause issues, process deviations | Linear causation | 1-3 people | 30-60 min |
| Fishbone | Multi-factor problems, 3-6 contributing factors | Complex, systemic | 3-8 people | 2-4 hours |
| Fault Tree | Safety-critical failures, reliability issues | System failures | 2-5 people | 4-8 hours |
| Human Factors | Procedure/training-related issues | Human error | 3-6 people | 2-4 hours |
| FMEA | Systematic risk assessment, design review | Potential failures | 4-10 people | 8-16 hours |
### Quick Selection Decision Tree
```
Is the issue safety-critical or involves system reliability?
├── Yes → Use FAULT TREE ANALYSIS
└── No → Is human error the suspected primary cause?
├── Yes → Use HUMAN FACTORS ANALYSIS
└── No → How many potential contributing factors?
├── 1-2 factors → Use 5 WHY ANALYSIS
├── 3-6 factors → Use FISHBONE DIAGRAM
└── Unknown/Many → Use FMEA (proactive) or Fishbone (reactive)
```
---
## 5 Why Analysis
### Overview
Simple, iterative technique asking "why" repeatedly (typically 5 times) to drill from symptoms to root cause.
### When to Use
- Single-cause issues with linear causation
- Process deviations with clear failure point
- Quick investigations requiring rapid resolution
- Problems where symptoms clearly link to cause
### When NOT to Use
- Complex multi-factor problems
- Safety-critical incidents requiring comprehensive analysis
- Issues with multiple interacting causes
- When systemic factors are suspected
### 5 Why Template
```
PROBLEM STATEMENT:
[Clear, specific description of what happened, when, where, and impact]
WHY 1: Why did [problem] occur?
BECAUSE: [First-level cause]
EVIDENCE: [Data/observation supporting this cause]
WHY 2: Why did [first-level cause] occur?
BECAUSE: [Second-level cause]
EVIDENCE: [Data/observation supporting this cause]
WHY 3: Why did [second-level cause] occur?
BECAUSE: [Third-level cause]
EVIDENCE: [Data/observation supporting this cause]
WHY 4: Why did [third-level cause] occur?
BECAUSE: [Fourth-level cause]
EVIDENCE: [Data/observation supporting this cause]
WHY 5: Why did [fourth-level cause] occur?
BECAUSE: [Root cause - typically systemic or management system failure]
EVIDENCE: [Data/observation supporting this cause]
ROOT CAUSE VALIDATION:
- [ ] Can the root cause be verified with evidence?
- [ ] If root cause is eliminated, would problem recur?
- [ ] Is the root cause within organizational control?
- [ ] Does the root cause explain all symptoms?
```
### Example: Calibration Overdue
```
PROBLEM: pH meter (EQ-042) found 2 months overdue for calibration
WHY 1: Why was calibration overdue?
BECAUSE: The equipment was not on the calibration schedule
EVIDENCE: Calibration schedule reviewed, EQ-042 not listed
WHY 2: Why was it not on the calibration schedule?
BECAUSE: The schedule was not updated when equipment was purchased
EVIDENCE: Purchase date 2023-06-15, schedule dated 2023-01-01
WHY 3: Why was the schedule not updated?
BECAUSE: No process requires schedule update at equipment purchase
EVIDENCE: Equipment procedure SOP-EQ-001 reviewed, no such requirement
WHY 4: Why is there no requirement to update the schedule?
BECAUSE: The procedure was written before equipment tracking was centralized
EVIDENCE: SOP-EQ-001 last revised 2019, equipment system implemented 2021
WHY 5: Why has the procedure not been updated?
BECAUSE: Periodic procedure review did not assess compatibility with new systems
EVIDENCE: No documented review of SOP-EQ-001 against new equipment system
ROOT CAUSE: Procedure review process does not assess compatibility
with organizational systems implemented after original procedure creation
```
---
## Fishbone Diagram
### Overview
Also called Ishikawa or cause-and-effect diagram. Organizes potential causes into categories branching from the problem statement.
### Standard Categories (6M)
| Category | Focus Areas | Typical Causes |
|----------|-------------|----------------|
| **Man** (People) | Training, competency, workload | Skill gaps, fatigue, communication |
| **Machine** (Equipment) | Calibration, maintenance, age | Wear, malfunction, inadequate capacity |
| **Method** (Process) | Procedures, work instructions | Unclear steps, missing controls |
| **Material** | Specifications, suppliers, storage | Out-of-spec, degradation, contamination |
| **Measurement** | Calibration, methods, interpretation | Instrument error, wrong method |
| **Mother Nature** (Environment) | Temperature, humidity, cleanliness | Environmental excursions |
### Fishbone Template
```
PROBLEM STATEMENT: [Effect being investigated]
┌── Man ────────────────┐
│ ├─ [Cause 1] │
│ ├─ [Cause 2] │
│ └─ [Cause 3] │
│ │
┌── Machine ────────┤ ├── Method ──────────┐
│ ├─ [Cause 1] │ │ ├─ [Cause 1] │
│ ├─ [Cause 2] │ PROBLEM │ ├─ [Cause 2] │
│ └─ [Cause 3] ├───────────────────────┤ └─ [Cause 3] │
│ │ │ │
├── Material ───────┤ ├── Measurement ─────┤
│ ├─ [Cause 1] │ │ ├─ [Cause 1] │
│ ├─ [Cause 2] │ │ ├─ [Cause 2] │
│ └─ [Cause 3] │ │ └─ [Cause 3] │
│ │
└── Environment ────────┘
├─ [Cause 1]
├─ [Cause 2]
└─ [Cause 3]
CAUSE PRIORITIZATION:
| Cause | Category | Likelihood | Evidence | Priority |
|-------|----------|------------|----------|----------|
| [Cause A] | Method | High | [Evidence] | 1 |
| [Cause B] | Man | Medium | [Evidence] | 2 |
ROOT CAUSES IDENTIFIED:
1. [Primary root cause with supporting evidence]
2. [Contributing cause with supporting evidence]
```
### Facilitation Guidelines
1. Assemble cross-functional team (3-8 people)
2. Define problem statement clearly before starting
3. Brainstorm causes without judgment first
4. Organize into categories after brainstorming
5. Drill down on each major cause (sub-causes)
6. Prioritize based on evidence and likelihood
7. Validate top causes with data
---
## Fault Tree Analysis
### Overview
Top-down, deductive analysis starting with undesired event and systematically identifying all potential causes using Boolean logic (AND/OR gates).
### When to Use
- Safety-critical system failures
- Complex system reliability analysis
- Events with multiple failure pathways
- Regulatory-required investigations (FDA, MDR)
### FTA Symbols
| Symbol | Name | Meaning |
|--------|------|---------|
| Rectangle | Top Event / Intermediate Event | Undesired event or intermediate fault |
| Circle | Basic Event | Primary fault requiring no further analysis |
| Diamond | Undeveloped Event | Event not fully analyzed (data limitation) |
| AND Gate | Requires all inputs | All child events must occur for parent |
| OR Gate | Requires any input | Any child event causes parent |
### FTA Template
```
TOP EVENT: [Undesired event under investigation]
LEVEL 1 (Immediate Causes):
[Top Event]
│
└── OR GATE ──┬── [Cause 1.1]
├── [Cause 1.2]
└── [Cause 1.3]
LEVEL 2 (Contributing Causes):
[Cause 1.1]
│
└── AND GATE ──┬── [Cause 2.1]
└── [Cause 2.2]
MINIMAL CUT SETS:
(Combinations of basic events that cause top event)
1. {Basic Event A, Basic Event B} ← Both required (AND)
2. {Basic Event C} ← Single point failure (OR)
3. {Basic Event D, Basic Event E} ← Both required (AND)
CRITICAL PATH ANALYSIS:
Most likely failure pathway: [Description]
Single points of failure: [List]
RECOMMENDATIONS:
- Address single points of failure first
- Add redundancy where AND gates show vulnerability
- Prioritize controls on highest probability paths
```
### Cut Set Analysis
Minimal cut sets identify the smallest combination of basic events causing the top event:
- **Single-element cut sets**: Single points of failure (highest priority)
- **Two-element cut sets**: Dual failure scenarios
- **Probability calculation**: P(Top Event) = Union of P(Cut Sets)
---
## Human Factors Analysis
### Overview
Systematic analysis of human error focusing on cognitive, physical, and organizational factors contributing to performance failures.
### HFACS Categories
Human Factors Analysis and Classification System:
| Level | Category | Examples |
|-------|----------|----------|
| **Unsafe Acts** | Errors, violations | Skill-based, decision, perceptual errors |
| **Preconditions** | Conditions for unsafe acts | Fatigue, mental state, CRM, physical environment |
| **Unsafe Supervision** | Supervisory failures | Inadequate supervision, planned inappropriate ops |
| **Organizational Influences** | Organizational failures | Resource management, organizational climate |
### Human Error Types
| Type | Description | Example | Mitigation |
|------|-------------|---------|------------|
| Slip | Execution error in routine task | Wrong button pressed | Error-proofing, forcing functions |
| Lapse | Memory failure | Forgot step in procedure | Checklists, reminders |
| Mistake | Planning/decision error | Wrong procedure selected | Training, decision aids |
| Violation | Intentional deviation | Skipped step to save time | Culture change, supervision |
### Human Factors Investigation Template
```
INCIDENT DESCRIPTION:
[What happened, who was involved, when, where]
UNSAFE ACTS ANALYSIS:
Type of Error: [ ] Slip [ ] Lapse [ ] Mistake [ ] Violation
Description: [Specific action or inaction]
Task Being Performed: [Activity at time of error]
Experience Level: [Novice/Intermediate/Expert]
PRECONDITIONS FOR UNSAFE ACTS:
Cognitive Factors:
- [ ] Task complexity exceeded capability
- [ ] Time pressure
- [ ] Distraction/interruption
- [ ] Mental fatigue
Physical Factors:
- [ ] Physical fatigue
- [ ] Inadequate lighting
- [ ] Noise interference
- [ ] Workspace ergonomics
Team Factors:
- [ ] Communication breakdown
- [ ] Coordination failure
- [ ] Inadequate leadership
SUPERVISORY FACTORS:
- [ ] Inadequate supervision
- [ ] Failed to correct known problem
- [ ] Inappropriate staffing
- [ ] Authorized unnecessary risk
ORGANIZATIONAL FACTORS:
- [ ] Resource management deficiency
- [ ] Organizational process issue
- [ ] Organizational culture/climate
ROOT CAUSE(S):
[Human factors root causes identified]
CORRECTIVE ACTIONS:
| Action | Target Factor | Priority |
|--------|---------------|----------|
| [Action 1] | [Factor addressed] | High |
| [Action 2] | [Factor addressed] | Medium |
```
---
## Failure Mode and Effects Analysis
### Overview
Proactive, systematic technique identifying potential failure modes, their causes, and effects before failures occur.
### FMEA Types
| Type | Application | Scope |
|------|-------------|-------|
| Design FMEA (DFMEA) | Product design | Component and system design failures |
| Process FMEA (PFMEA) | Manufacturing process | Process step failures |
| System FMEA | System-level analysis | System interaction failures |
### Risk Priority Number (RPN)
RPN = Severity (S) × Occurrence (O) × Detection (D)
**Severity Scale (1-10):**
| Rating | Effect | Criteria |
|--------|--------|----------|
| 10 | Hazardous | Failure affects safe operation, no warning |
| 8-9 | Very High | Primary function lost, high impact |
| 6-7 | High | Performance degraded, customer dissatisfied |
| 4-5 | Moderate | Some performance loss, moderate impact |
| 2-3 | Low | Minor effect, slight inconvenience |
| 1 | None | No discernible effect |
**Occurrence Scale (1-10):**
| Rating | Likelihood | Failure Rate |
|--------|------------|--------------|
| 10 | Very High | >1 in 10 |
| 7-9 | High | 1 in 20 - 1 in 100 |
| 4-6 | Moderate | 1 in 400 - 1 in 2,000 |
| 2-3 | Low | 1 in 15,000 - 1 in 150,000 |
| 1 | Remote | <1 in 1,500,000 |
**Detection Scale (1-10):**
| Rating | Detection | Criteria |
|--------|-----------|----------|
| 10 | Absolute Uncertainty | No inspection/control, defect will reach customer |
| 7-9 | Very Remote to Remote | Controls unlikely to detect |
| 4-6 | Moderate | Controls may detect |
| 2-3 | High | Controls likely to detect |
| 1 | Almost Certain | Controls will almost certainly detect |
### FMEA Template
```
PROCESS/PRODUCT: [Name]
FMEA TEAM: [Members]
DATE: [Date]
| Item/Step | Failure Mode | Effect | S | Cause | O | Controls | D | RPN | Action |
|-----------|--------------|--------|---|-------|---|----------|---|-----|--------|
| [Item 1] | [How it fails] | [Impact] | 8 | [Why] | 4 | [Current] | 6 | 192 | [Action] |
| [Item 2] | [How it fails] | [Impact] | 6 | [Why] | 3 | [Current] | 4 | 72 | [Action] |
RPN THRESHOLD: Actions required for RPN > [threshold]
HIGH SEVERITY RULE: Actions required for S >= 9 regardless of RPN
ACTION PRIORITIZATION:
1. Address all items with S >= 9 first
2. Address items with highest RPN
3. Focus on reducing Occurrence (prevention)
4. Then improve Detection (inspection)
```
---
## Selecting the Right Method
### Decision Flowchart
```
START: Investigation Required
│
├── Is this a proactive assessment (no failure yet)?
│ └── Yes → Use FMEA
│
├── Is the issue safety-critical?
│ └── Yes → Use FAULT TREE ANALYSIS
│
├── Is human error the primary concern?
│ └── Yes → Use HUMAN FACTORS ANALYSIS
│
├── Are there multiple contributing factors (3+)?
│ ├── Yes → Use FISHBONE DIAGRAM
│ └── No → Use 5 WHY ANALYSIS
│
└── Uncertain? → Start with 5 WHY, escalate to FISHBONE if needed
```
### Hybrid Approach
For complex investigations, combine methods:
1. **Initial screening**: 5 Why for quick cause identification
2. **Detailed analysis**: Fishbone to explore all categories
3. **Validation**: Fault Tree for critical failure paths
4. **Systemic factors**: Human Factors for people-related causes
5. **Prevention**: FMEA for future risk mitigation
### Documentation Requirements
| Method | Required Outputs | Retention |
|--------|------------------|-----------|
| 5 Why | Completed template with evidence | CAPA record |
| Fishbone | Diagram + prioritized causes | CAPA record |
| Fault Tree | FTA diagram + cut set analysis | DHF/CAPA record |
| Human Factors | HFACS analysis + actions | CAPA record |
| FMEA | FMEA worksheet + action tracking | Design file |
FILE:scripts/capa_tracker.py
#!/usr/bin/env python3
"""
CAPA Tracker - Corrective and Preventive Action Management Tool
Tracks CAPA status, calculates metrics, identifies overdue items,
and generates reports for management review.
Usage:
python capa_tracker.py --capas capas.json
python capa_tracker.py --interactive
python capa_tracker.py --capas capas.json --output json
"""
import argparse
import json
import sys
from dataclasses import dataclass, field, asdict
from datetime import datetime, timedelta
from typing import List, Dict, Optional
from enum import Enum
class CAPAStatus(Enum):
OPEN = "Open"
INVESTIGATION = "Investigation"
ACTION_PLANNING = "Action Planning"
IMPLEMENTATION = "Implementation"
VERIFICATION = "Verification"
CLOSED_EFFECTIVE = "Closed - Effective"
CLOSED_INEFFECTIVE = "Closed - Ineffective"
class CAPASeverity(Enum):
CRITICAL = "Critical"
MAJOR = "Major"
MINOR = "Minor"
class CAPASource(Enum):
COMPLAINT = "Customer Complaint"
AUDIT = "Internal Audit"
EXTERNAL_AUDIT = "External Audit"
NONCONFORMANCE = "Nonconformance"
MANAGEMENT_REVIEW = "Management Review"
TREND_ANALYSIS = "Trend Analysis"
REGULATORY = "Regulatory Feedback"
OTHER = "Other"
@dataclass
class CAPA:
capa_number: str
title: str
description: str
source: CAPASource
severity: CAPASeverity
status: CAPAStatus
open_date: str
target_date: str
owner: str
root_cause: str = ""
corrective_action: str = ""
verification_date: Optional[str] = None
close_date: Optional[str] = None
days_open: int = 0
is_overdue: bool = False
@dataclass
class CAPAMetrics:
total_capas: int
open_capas: int
closed_capas: int
overdue_capas: int
avg_cycle_time: float
effectiveness_rate: float
by_status: Dict[str, int]
by_severity: Dict[str, int]
by_source: Dict[str, int]
overdue_list: List[Dict]
recommendations: List[str]
class CAPATracker:
"""CAPA tracking and metrics calculator."""
# Target cycle times by severity (days)
TARGET_CYCLE_TIMES = {
CAPASeverity.CRITICAL: 30,
CAPASeverity.MAJOR: 60,
CAPASeverity.MINOR: 90,
}
def __init__(self, capas: List[CAPA]):
self.capas = capas
self.today = datetime.now()
self._calculate_derived_fields()
def _calculate_derived_fields(self):
"""Calculate days open and overdue status."""
for capa in self.capas:
open_date = datetime.strptime(capa.open_date, "%Y-%m-%d")
if capa.close_date:
close_date = datetime.strptime(capa.close_date, "%Y-%m-%d")
capa.days_open = (close_date - open_date).days
else:
capa.days_open = (self.today - open_date).days
target_date = datetime.strptime(capa.target_date, "%Y-%m-%d")
if not capa.close_date and self.today > target_date:
capa.is_overdue = True
def calculate_metrics(self) -> CAPAMetrics:
"""Calculate comprehensive CAPA metrics."""
total = len(self.capas)
# Status counts
closed_statuses = [CAPAStatus.CLOSED_EFFECTIVE, CAPAStatus.CLOSED_INEFFECTIVE]
open_capas = [c for c in self.capas if c.status not in closed_statuses]
closed_capas = [c for c in self.capas if c.status in closed_statuses]
overdue_capas = [c for c in self.capas if c.is_overdue]
# Average cycle time (closed CAPAs only)
if closed_capas:
avg_cycle = sum(c.days_open for c in closed_capas) / len(closed_capas)
else:
avg_cycle = 0.0
# Effectiveness rate
effective = [c for c in self.capas if c.status == CAPAStatus.CLOSED_EFFECTIVE]
ineffective = [c for c in self.capas if c.status == CAPAStatus.CLOSED_INEFFECTIVE]
if effective or ineffective:
effectiveness = len(effective) / (len(effective) + len(ineffective)) * 100
else:
effectiveness = 0.0
# Counts by category
by_status = {}
for status in CAPAStatus:
count = len([c for c in self.capas if c.status == status])
if count > 0:
by_status[status.value] = count
by_severity = {}
for severity in CAPASeverity:
count = len([c for c in self.capas if c.severity == severity])
if count > 0:
by_severity[severity.value] = count
by_source = {}
for source in CAPASource:
count = len([c for c in self.capas if c.source == source])
if count > 0:
by_source[source.value] = count
# Overdue list
overdue_list = []
for capa in sorted(overdue_capas, key=lambda c: c.days_open, reverse=True):
target = datetime.strptime(capa.target_date, "%Y-%m-%d")
days_overdue = (self.today - target).days
overdue_list.append({
"capa_number": capa.capa_number,
"title": capa.title,
"severity": capa.severity.value,
"status": capa.status.value,
"days_overdue": days_overdue,
"owner": capa.owner
})
# Generate recommendations
recommendations = self._generate_recommendations(
open_capas, overdue_capas, effectiveness, avg_cycle
)
return CAPAMetrics(
total_capas=total,
open_capas=len(open_capas),
closed_capas=len(closed_capas),
overdue_capas=len(overdue_capas),
avg_cycle_time=round(avg_cycle, 1),
effectiveness_rate=round(effectiveness, 1),
by_status=by_status,
by_severity=by_severity,
by_source=by_source,
overdue_list=overdue_list,
recommendations=recommendations
)
def _generate_recommendations(
self,
open_capas: List[CAPA],
overdue_capas: List[CAPA],
effectiveness: float,
avg_cycle: float
) -> List[str]:
"""Generate actionable recommendations."""
recommendations = []
# Overdue CAPAs
if overdue_capas:
critical_overdue = [c for c in overdue_capas if c.severity == CAPASeverity.CRITICAL]
if critical_overdue:
recommendations.append(
f"URGENT: {len(critical_overdue)} critical CAPA(s) overdue. "
"Escalate to management immediately."
)
else:
recommendations.append(
f"ACTION: {len(overdue_capas)} CAPA(s) overdue. "
"Review and update target dates or expedite closure."
)
# Effectiveness rate
if effectiveness < 80 and effectiveness > 0:
recommendations.append(
f"CONCERN: Effectiveness rate at {effectiveness:.0f}%. "
"Review root cause analysis quality and corrective action adequacy."
)
# Cycle time
if avg_cycle > 60:
recommendations.append(
f"IMPROVEMENT: Average cycle time is {avg_cycle:.0f} days. "
"Target is 60 days. Review investigation and approval bottlenecks."
)
# Investigation backlog
in_investigation = [c for c in open_capas if c.status == CAPAStatus.INVESTIGATION]
if len(in_investigation) > 5:
recommendations.append(
f"WORKLOAD: {len(in_investigation)} CAPAs in investigation phase. "
"Consider additional resources or prioritization."
)
# Stuck in verification
in_verification = [c for c in open_capas if c.status == CAPAStatus.VERIFICATION]
old_verification = [c for c in in_verification if c.days_open > 120]
if old_verification:
recommendations.append(
f"STALLED: {len(old_verification)} CAPA(s) in verification >120 days. "
"Complete effectiveness checks or extend with justification."
)
# Source patterns
complaint_capas = [c for c in self.capas if c.source == CAPASource.COMPLAINT]
if len(complaint_capas) > len(self.capas) * 0.4:
recommendations.append(
"TREND: >40% of CAPAs from customer complaints. "
"Review preventive action effectiveness and quality controls."
)
if not recommendations:
recommendations.append(
"CAPA program operating within targets. "
"Continue monitoring key metrics."
)
return recommendations
def get_aging_report(self) -> Dict:
"""Generate aging analysis of open CAPAs."""
open_statuses = [
CAPAStatus.OPEN, CAPAStatus.INVESTIGATION,
CAPAStatus.ACTION_PLANNING, CAPAStatus.IMPLEMENTATION,
CAPAStatus.VERIFICATION
]
open_capas = [c for c in self.capas if c.status in open_statuses]
aging_buckets = {
"0-30 days": [],
"31-60 days": [],
"61-90 days": [],
"91-120 days": [],
">120 days": []
}
for capa in open_capas:
days = capa.days_open
if days <= 30:
bucket = "0-30 days"
elif days <= 60:
bucket = "31-60 days"
elif days <= 90:
bucket = "61-90 days"
elif days <= 120:
bucket = "91-120 days"
else:
bucket = ">120 days"
aging_buckets[bucket].append({
"capa_number": capa.capa_number,
"title": capa.title,
"days_open": days,
"status": capa.status.value,
"severity": capa.severity.value
})
return aging_buckets
def format_text_output(metrics: CAPAMetrics, aging: Dict) -> str:
"""Format metrics as text report."""
lines = [
"=" * 70,
"CAPA STATUS REPORT",
"=" * 70,
f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M')}",
"",
"SUMMARY METRICS",
"-" * 40,
f"Total CAPAs: {metrics.total_capas}",
f"Open CAPAs: {metrics.open_capas}",
f"Closed CAPAs: {metrics.closed_capas}",
f"Overdue CAPAs: {metrics.overdue_capas}",
f"Avg Cycle Time: {metrics.avg_cycle_time} days",
f"Effectiveness Rate: {metrics.effectiveness_rate}%",
"",
"STATUS DISTRIBUTION",
"-" * 40,
]
for status, count in metrics.by_status.items():
bar = "█" * min(count, 20)
lines.append(f" {status:<25} {bar} {count}")
lines.extend([
"",
"SEVERITY DISTRIBUTION",
"-" * 40,
])
for severity, count in metrics.by_severity.items():
bar = "█" * min(count, 20)
lines.append(f" {severity:<25} {bar} {count}")
lines.extend([
"",
"SOURCE DISTRIBUTION",
"-" * 40,
])
for source, count in metrics.by_source.items():
bar = "█" * min(count, 20)
lines.append(f" {source:<25} {bar} {count}")
lines.extend([
"",
"AGING ANALYSIS",
"-" * 40,
])
for bucket, capas in aging.items():
lines.append(f" {bucket}: {len(capas)} CAPA(s)")
if metrics.overdue_list:
lines.extend([
"",
"OVERDUE CAPAs",
"-" * 40,
f"{'CAPA #':<12} {'Title':<25} {'Days':<6} {'Owner':<15}",
"-" * 60,
])
for item in metrics.overdue_list[:10]:
title = item["title"][:24] if len(item["title"]) > 24 else item["title"]
lines.append(
f"{item['capa_number']:<12} {title:<25} "
f"{item['days_overdue']:<6} {item['owner']:<15}"
)
if len(metrics.overdue_list) > 10:
lines.append(f"... and {len(metrics.overdue_list) - 10} more")
lines.extend([
"",
"RECOMMENDATIONS",
"-" * 40,
])
for i, rec in enumerate(metrics.recommendations, 1):
lines.append(f"{i}. {rec}")
lines.append("=" * 70)
return "\n".join(lines)
def interactive_mode():
"""Run interactive CAPA entry mode."""
print("=" * 60)
print("CAPA Tracker - Interactive Mode")
print("=" * 60)
capas = []
print("\nEnter CAPAs (blank CAPA number to finish):\n")
while True:
capa_num = input("CAPA Number (e.g., CAPA-2024-001): ").strip()
if not capa_num:
break
title = input("Title: ").strip()
description = input("Description: ").strip()
print("Source options: C=Complaint, A=Audit, N=Nonconformance, M=Management Review, T=Trend, O=Other")
source_input = input("Source [C/A/N/M/T/O]: ").strip().upper()
source_map = {
"C": CAPASource.COMPLAINT,
"A": CAPASource.AUDIT,
"N": CAPASource.NONCONFORMANCE,
"M": CAPASource.MANAGEMENT_REVIEW,
"T": CAPASource.TREND_ANALYSIS,
"O": CAPASource.OTHER
}
source = source_map.get(source_input, CAPASource.OTHER)
print("Severity: C=Critical, M=Major, I=Minor")
severity_input = input("Severity [C/M/I]: ").strip().upper()
severity_map = {
"C": CAPASeverity.CRITICAL,
"M": CAPASeverity.MAJOR,
"I": CAPASeverity.MINOR
}
severity = severity_map.get(severity_input, CAPASeverity.MINOR)
print("Status: O=Open, I=Investigation, P=Action Planning, M=Implementation, V=Verification, E=Closed Effective, N=Closed Ineffective")
status_input = input("Status [O/I/P/M/V/E/N]: ").strip().upper()
status_map = {
"O": CAPAStatus.OPEN,
"I": CAPAStatus.INVESTIGATION,
"P": CAPAStatus.ACTION_PLANNING,
"M": CAPAStatus.IMPLEMENTATION,
"V": CAPAStatus.VERIFICATION,
"E": CAPAStatus.CLOSED_EFFECTIVE,
"N": CAPAStatus.CLOSED_INEFFECTIVE
}
status = status_map.get(status_input, CAPAStatus.OPEN)
open_date = input("Open Date (YYYY-MM-DD): ").strip()
target_date = input("Target Date (YYYY-MM-DD): ").strip()
owner = input("Owner: ").strip()
close_date = None
if status in [CAPAStatus.CLOSED_EFFECTIVE, CAPAStatus.CLOSED_INEFFECTIVE]:
close_date = input("Close Date (YYYY-MM-DD): ").strip()
capas.append(CAPA(
capa_number=capa_num,
title=title,
description=description,
source=source,
severity=severity,
status=status,
open_date=open_date,
target_date=target_date,
owner=owner,
close_date=close_date if close_date else None
))
print(f"\nAdded: {capa_num}\n")
if not capas:
print("No CAPAs entered. Exiting.")
return
tracker = CAPATracker(capas)
metrics = tracker.calculate_metrics()
aging = tracker.get_aging_report()
print("\n" + format_text_output(metrics, aging))
def main():
parser = argparse.ArgumentParser(
description="CAPA Tracking and Metrics Tool"
)
parser.add_argument(
"--capas",
type=str,
help="JSON file with CAPA data"
)
parser.add_argument(
"--output",
choices=["text", "json"],
default="text",
help="Output format"
)
parser.add_argument(
"--interactive",
action="store_true",
help="Run in interactive mode"
)
parser.add_argument(
"--sample",
action="store_true",
help="Generate sample CAPA data file"
)
args = parser.parse_args()
if args.interactive:
interactive_mode()
return
if args.sample:
sample_data = {
"capas": [
{
"capa_number": "CAPA-2024-001",
"title": "Calibration overdue for pH meter",
"description": "pH meter EQ-042 found 2 months overdue",
"source": "AUDIT",
"severity": "MAJOR",
"status": "VERIFICATION",
"open_date": "2024-06-15",
"target_date": "2024-08-15",
"owner": "J. Smith",
"root_cause": "No trigger for schedule update at equipment purchase",
"corrective_action": "Updated SOP-EQ-001 to require schedule update"
},
{
"capa_number": "CAPA-2024-002",
"title": "Customer complaint - labeling error",
"description": "Wrong lot number on product label",
"source": "COMPLAINT",
"severity": "CRITICAL",
"status": "INVESTIGATION",
"open_date": "2024-09-01",
"target_date": "2024-10-01",
"owner": "M. Jones"
},
{
"capa_number": "CAPA-2024-003",
"title": "Training records incomplete",
"description": "Missing effectiveness verification for 3 operators",
"source": "AUDIT",
"severity": "MINOR",
"status": "CLOSED_EFFECTIVE",
"open_date": "2024-03-10",
"target_date": "2024-06-10",
"owner": "A. Brown",
"close_date": "2024-05-20"
}
]
}
print(json.dumps(sample_data, indent=2))
return
if args.capas:
with open(args.capas, "r") as f:
data = json.load(f)
capas = []
for c in data.get("capas", []):
try:
source = CAPASource[c.get("source", "OTHER").upper()]
except KeyError:
source = CAPASource.OTHER
try:
severity = CAPASeverity[c.get("severity", "MINOR").upper()]
except KeyError:
severity = CAPASeverity.MINOR
try:
status = CAPAStatus[c.get("status", "OPEN").upper()]
except KeyError:
status = CAPAStatus.OPEN
capas.append(CAPA(
capa_number=c["capa_number"],
title=c.get("title", ""),
description=c.get("description", ""),
source=source,
severity=severity,
status=status,
open_date=c["open_date"],
target_date=c["target_date"],
owner=c.get("owner", ""),
root_cause=c.get("root_cause", ""),
corrective_action=c.get("corrective_action", ""),
verification_date=c.get("verification_date"),
close_date=c.get("close_date")
))
else:
# Demo data if no file provided
capas = [
CAPA(
capa_number="CAPA-2024-001",
title="Calibration overdue",
description="pH meter overdue",
source=CAPASource.AUDIT,
severity=CAPASeverity.MAJOR,
status=CAPAStatus.VERIFICATION,
open_date="2024-06-15",
target_date="2024-08-15",
owner="J. Smith"
),
CAPA(
capa_number="CAPA-2024-002",
title="Labeling error complaint",
description="Wrong lot number",
source=CAPASource.COMPLAINT,
severity=CAPASeverity.CRITICAL,
status=CAPAStatus.INVESTIGATION,
open_date="2024-09-01",
target_date="2024-10-01",
owner="M. Jones"
),
CAPA(
capa_number="CAPA-2024-003",
title="Training records incomplete",
description="Missing effectiveness verification",
source=CAPASource.AUDIT,
severity=CAPASeverity.MINOR,
status=CAPAStatus.CLOSED_EFFECTIVE,
open_date="2024-03-10",
target_date="2024-06-10",
owner="A. Brown",
close_date="2024-05-20"
)
]
tracker = CAPATracker(capas)
metrics = tracker.calculate_metrics()
aging = tracker.get_aging_report()
if args.output == "json":
output = {
"metrics": asdict(metrics),
"aging": aging
}
print(json.dumps(output, indent=2))
else:
print(format_text_output(metrics, aging))
if __name__ == "__main__":
main()
FILE:scripts/root_cause_analyzer.py
#!/usr/bin/env python3
"""
Root Cause Analyzer - Structured root cause analysis for CAPA investigations.
Supports multiple analysis methodologies:
- 5-Why Analysis
- Fishbone (Ishikawa) Diagram
- Fault Tree Analysis
- Kepner-Tregoe Problem Analysis
Generates structured root cause reports and CAPA recommendations.
Usage:
python root_cause_analyzer.py --method 5why --problem "High defect rate in assembly line"
python root_cause_analyzer.py --interactive
python root_cause_analyzer.py --data investigation.json --output json
"""
import argparse
import json
import sys
from dataclasses import dataclass, field, asdict
from typing import List, Dict, Optional
from enum import Enum
from datetime import datetime
class AnalysisMethod(Enum):
FIVE_WHY = "5-Why"
FISHBONE = "Fishbone"
FAULT_TREE = "Fault Tree"
KEPNER_TREGOE = "Kepner-Tregoe"
class RootCauseCategory(Enum):
MAN = "Man (People)"
MACHINE = "Machine (Equipment)"
MATERIAL = "Material"
METHOD = "Method (Process)"
MEASUREMENT = "Measurement"
ENVIRONMENT = "Environment"
MANAGEMENT = "Management (Policy)"
SOFTWARE = "Software/Data"
class SeverityLevel(Enum):
LOW = "Low"
MEDIUM = "Medium"
HIGH = "High"
CRITICAL = "Critical"
@dataclass
class WhyStep:
"""A single step in 5-Why analysis."""
level: int
question: str
answer: str
evidence: str = ""
verified: bool = False
@dataclass
class FishboneCause:
"""A cause in fishbone analysis."""
category: str
cause: str
sub_causes: List[str] = field(default_factory=list)
is_root: bool = False
evidence: str = ""
@dataclass
class FaultEvent:
"""An event in fault tree analysis."""
event_id: str
description: str
is_basic: bool = True # Basic events have no children
gate_type: str = "OR" # OR, AND
children: List[str] = field(default_factory=list)
probability: Optional[float] = None
@dataclass
class RootCauseFinding:
"""Identified root cause with evidence."""
cause_id: str
description: str
category: str
evidence: List[str] = field(default_factory=list)
contributing_factors: List[str] = field(default_factory=list)
systemic: bool = False # Whether it's a systemic vs. local issue
@dataclass
class CAPARecommendation:
"""Corrective or preventive action recommendation."""
action_id: str
action_type: str # "Corrective" or "Preventive"
description: str
addresses_cause: str # cause_id
priority: str
estimated_effort: str
responsible_role: str
effectiveness_criteria: List[str] = field(default_factory=list)
@dataclass
class RootCauseAnalysis:
"""Complete root cause analysis result."""
investigation_id: str
problem_statement: str
analysis_method: str
root_causes: List[RootCauseFinding]
recommendations: List[CAPARecommendation]
analysis_details: Dict
confidence_level: float
investigator_notes: List[str] = field(default_factory=list)
class RootCauseAnalyzer:
"""Performs structured root cause analysis."""
def __init__(self):
self.analysis_steps = []
self.findings = []
def analyze_5why(self, problem: str, whys: List[Dict] = None) -> Dict:
"""Perform 5-Why analysis."""
steps = []
if whys:
for i, w in enumerate(whys, 1):
steps.append(WhyStep(
level=i,
question=w.get("question", f"Why did this occur? (Level {i})"),
answer=w.get("answer", ""),
evidence=w.get("evidence", ""),
verified=w.get("verified", False)
))
# Analyze depth and quality
depth = len(steps)
has_root = any(
s.answer and ("system" in s.answer.lower() or "policy" in s.answer.lower() or "process" in s.answer.lower())
for s in steps
)
return {
"method": "5-Why Analysis",
"steps": [asdict(s) for s in steps],
"depth": depth,
"reached_systemic_cause": has_root,
"quality_score": min(100, depth * 20 + (20 if has_root else 0))
}
def analyze_fishbone(self, problem: str, causes: List[Dict] = None) -> Dict:
"""Perform fishbone (Ishikawa) analysis."""
categories = {}
fishbone_causes = []
if causes:
for c in causes:
cat = c.get("category", "Method")
cause = c.get("cause", "")
sub = c.get("sub_causes", [])
if cat not in categories:
categories[cat] = []
categories[cat].append({
"cause": cause,
"sub_causes": sub,
"is_root": c.get("is_root", False),
"evidence": c.get("evidence", "")
})
fishbone_causes.append(FishboneCause(
category=cat,
cause=cause,
sub_causes=sub,
is_root=c.get("is_root", False),
evidence=c.get("evidence", "")
))
root_causes = [fc for fc in fishbone_causes if fc.is_root]
return {
"method": "Fishbone (Ishikawa) Analysis",
"problem": problem,
"categories": categories,
"total_causes": len(fishbone_causes),
"root_causes_identified": len(root_causes),
"categories_covered": list(categories.keys()),
"recommended_categories": [c.value for c in RootCauseCategory],
"missing_categories": [c.value for c in RootCauseCategory if c.value.split(" (")[0] not in categories]
}
def analyze_fault_tree(self, top_event: str, events: List[Dict] = None) -> Dict:
"""Perform fault tree analysis."""
fault_events = {}
if events:
for e in events:
fault_events[e["event_id"]] = FaultEvent(
event_id=e["event_id"],
description=e.get("description", ""),
is_basic=e.get("is_basic", True),
gate_type=e.get("gate_type", "OR"),
children=e.get("children", []),
probability=e.get("probability")
)
# Find basic events (root causes)
basic_events = {eid: ev for eid, ev in fault_events.items() if ev.is_basic}
intermediate_events = {eid: ev for eid, ev in fault_events.items() if not ev.is_basic}
return {
"method": "Fault Tree Analysis",
"top_event": top_event,
"total_events": len(fault_events),
"basic_events": len(basic_events),
"intermediate_events": len(intermediate_events),
"basic_event_details": [asdict(e) for e in basic_events.values()],
"cut_sets": self._find_cut_sets(fault_events)
}
def _find_cut_sets(self, events: Dict[str, FaultEvent]) -> List[List[str]]:
"""Find minimal cut sets (combinations of basic events that cause top event)."""
# Simplified cut set analysis
cut_sets = []
for eid, event in events.items():
if not event.is_basic and event.gate_type == "AND":
cut_sets.append(event.children)
return cut_sets[:5] # Return top 5
def generate_recommendations(
self,
root_causes: List[RootCauseFinding],
problem: str
) -> List[CAPARecommendation]:
"""Generate CAPA recommendations based on root causes."""
recommendations = []
for i, cause in enumerate(root_causes, 1):
# Corrective action (fix the immediate cause)
recommendations.append(CAPARecommendation(
action_id=f"CA-{i:03d}",
action_type="Corrective",
description=f"Address immediate cause: {cause.description}",
addresses_cause=cause.cause_id,
priority=self._assess_priority(cause),
estimated_effort=self._estimate_effort(cause),
responsible_role=self._suggest_responsible(cause),
effectiveness_criteria=[
f"Elimination of {cause.description} confirmed by audit",
"No recurrence within 90 days",
"Metrics return to acceptable range"
]
))
# Preventive action (prevent recurrence in other areas)
if cause.systemic:
recommendations.append(CAPARecommendation(
action_id=f"PA-{i:03d}",
action_type="Preventive",
description=f"Systemic prevention: Update process/procedure to prevent similar issues",
addresses_cause=cause.cause_id,
priority="Medium",
estimated_effort="2-4 weeks",
responsible_role="Quality Manager",
effectiveness_criteria=[
"Updated procedure approved and implemented",
"Training completed for affected personnel",
"No similar issues in related processes within 6 months"
]
))
return recommendations
def _assess_priority(self, cause: RootCauseFinding) -> str:
if cause.systemic or "safety" in cause.description.lower():
return "High"
elif "quality" in cause.description.lower():
return "Medium"
return "Low"
def _estimate_effort(self, cause: RootCauseFinding) -> str:
if cause.systemic:
return "4-8 weeks"
elif len(cause.contributing_factors) > 3:
return "2-4 weeks"
return "1-2 weeks"
def _suggest_responsible(self, cause: RootCauseFinding) -> str:
category_roles = {
"Man": "Training Manager",
"Machine": "Engineering Manager",
"Material": "Supply Chain Manager",
"Method": "Process Owner",
"Measurement": "Quality Engineer",
"Environment": "Facilities Manager",
"Management": "Department Head",
"Software": "IT/Software Manager"
}
cat_key = cause.category.split(" (")[0] if "(" in cause.category else cause.category
return category_roles.get(cat_key, "Quality Manager")
def full_analysis(
self,
problem: str,
method: str = "5-Why",
analysis_data: Dict = None
) -> RootCauseAnalysis:
"""Perform complete root cause analysis."""
investigation_id = f"RCA-{datetime.now().strftime('%Y%m%d-%H%M')}"
analysis_details = {}
root_causes = []
if method == "5-Why" and analysis_data:
analysis_details = self.analyze_5why(problem, analysis_data.get("whys", []))
# Extract root cause from deepest why
steps = analysis_details.get("steps", [])
if steps:
last_step = steps[-1]
root_causes.append(RootCauseFinding(
cause_id="RC-001",
description=last_step.get("answer", "Unknown"),
category="Systemic",
evidence=[s.get("evidence", "") for s in steps if s.get("evidence")],
systemic=analysis_details.get("reached_systemic_cause", False)
))
elif method == "Fishbone" and analysis_data:
analysis_details = self.analyze_fishbone(problem, analysis_data.get("causes", []))
for i, cat in enumerate(analysis_data.get("causes", [])):
if cat.get("is_root"):
root_causes.append(RootCauseFinding(
cause_id=f"RC-{i+1:03d}",
description=cat.get("cause", ""),
category=cat.get("category", ""),
evidence=[cat.get("evidence", "")] if cat.get("evidence") else [],
sub_causes=cat.get("sub_causes", []),
systemic=True
))
recommendations = self.generate_recommendations(root_causes, problem)
# Confidence based on evidence and method
confidence = 0.7
if root_causes and any(rc.evidence for rc in root_causes):
confidence = 0.85
if len(root_causes) > 1:
confidence = min(0.95, confidence + 0.05)
return RootCauseAnalysis(
investigation_id=investigation_id,
problem_statement=problem,
analysis_method=method,
root_causes=root_causes,
recommendations=recommendations,
analysis_details=analysis_details,
confidence_level=confidence
)
def format_rca_text(rca: RootCauseAnalysis) -> str:
"""Format RCA report as text."""
lines = [
"=" * 70,
"ROOT CAUSE ANALYSIS REPORT",
"=" * 70,
f"Investigation ID: {rca.investigation_id}",
f"Analysis Method: {rca.analysis_method}",
f"Confidence Level: {rca.confidence_level:.0%}",
"",
"PROBLEM STATEMENT",
"-" * 40,
f" {rca.problem_statement}",
"",
"ROOT CAUSES IDENTIFIED",
"-" * 40,
]
for rc in rca.root_causes:
lines.extend([
f"",
f" [{rc.cause_id}] {rc.description}",
f" Category: {rc.category}",
f" Systemic: {'Yes' if rc.systemic else 'No'}",
])
if rc.evidence:
lines.append(f" Evidence:")
for ev in rc.evidence:
if ev:
lines.append(f" • {ev}")
if rc.contributing_factors:
lines.append(f" Contributing Factors:")
for cf in rc.contributing_factors:
lines.append(f" - {cf}")
lines.extend([
"",
"RECOMMENDED ACTIONS",
"-" * 40,
])
for rec in rca.recommendations:
lines.extend([
f"",
f" [{rec.action_id}] {rec.action_type}: {rec.description}",
f" Priority: {rec.priority} | Effort: {rec.estimated_effort}",
f" Responsible: {rec.responsible_role}",
f" Effectiveness Criteria:",
])
for ec in rec.effectiveness_criteria:
lines.append(f" ✓ {ec}")
if "steps" in rca.analysis_details:
lines.extend([
"",
"5-WHY CHAIN",
"-" * 40,
])
for step in rca.analysis_details["steps"]:
lines.extend([
f"",
f" Why {step['level']}: {step['question']}",
f" → {step['answer']}",
])
if step.get("evidence"):
lines.append(f" Evidence: {step['evidence']}")
lines.append("=" * 70)
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(description="Root Cause Analyzer for CAPA Investigations")
parser.add_argument("--problem", type=str, help="Problem statement")
parser.add_argument("--method", choices=["5why", "fishbone", "fault-tree", "kt"],
default="5why", help="Analysis method")
parser.add_argument("--data", type=str, help="JSON file with analysis data")
parser.add_argument("--output", choices=["text", "json"], default="text", help="Output format")
parser.add_argument("--interactive", action="store_true", help="Interactive mode")
args = parser.parse_args()
analyzer = RootCauseAnalyzer()
if args.data:
with open(args.data) as f:
data = json.load(f)
problem = data.get("problem", "Unknown problem")
method = data.get("method", "5-Why")
rca = analyzer.full_analysis(problem, method, data)
elif args.problem:
method_map = {"5why": "5-Why", "fishbone": "Fishbone", "fault-tree": "Fault Tree", "kt": "Kepner-Tregoe"}
rca = analyzer.full_analysis(args.problem, method_map.get(args.method, "5-Why"))
else:
# Demo
demo_data = {
"method": "5-Why",
"whys": [
{"question": "Why did the product fail inspection?", "answer": "Surface defect detected on 15% of units", "evidence": "QC inspection records"},
{"question": "Why did surface defects occur?", "answer": "Injection molding temperature was outside spec", "evidence": "Process monitoring data"},
{"question": "Why was temperature outside spec?", "answer": "Temperature controller calibration drift", "evidence": "Calibration log"},
{"question": "Why did calibration drift go undetected?", "answer": "No automated alert for drift, manual checks missed it", "evidence": "SOP review"},
{"question": "Why was there no automated alert?", "answer": "Process monitoring system lacks drift detection capability - systemic gap", "evidence": "System requirements review"}
]
}
rca = analyzer.full_analysis("High defect rate in injection molding process", "5-Why", demo_data)
if args.output == "json":
result = {
"investigation_id": rca.investigation_id,
"problem": rca.problem_statement,
"method": rca.analysis_method,
"root_causes": [asdict(rc) for rc in rca.root_causes],
"recommendations": [asdict(rec) for rec in rca.recommendations],
"analysis_details": rca.analysis_details,
"confidence": rca.confidence_level
}
print(json.dumps(result, indent=2, default=str))
else:
print(format_rca_text(rca))
if __name__ == "__main__":
main()
Mô hình hóa kịch bản what-if đa biến liên chức năng, đánh giá tác động dồn dập của nhiều rủi ro lên toàn bộ doanh nghiệp.
---
name: "scenario-war-room"
description: "Cross-functional what-if modeling for cascading multi-variable scenarios. Unlike single-assumption stress testing, this models compound adversity across all business functions simultaneously. Use when facing complex risk scenarios, strategic decisions with major downside, or when the user asks 'what if X AND Y both happen?'"
license: MIT
metadata:
version: 1.0.0
author: Alireza Rezvani
category: c-level
domain: strategic-planning
updated: 2026-03-05
python-tools: scenario_modeler.py
frameworks: scenario-planning
---
# Scenario War Room
Model cascading what-if scenarios across all business functions. Not single-assumption stress tests — compound adversity that shows how one problem creates the next.
## Keywords
scenario planning, war room, what-if analysis, risk modeling, cascading effects, compound risk, adversity planning, contingency planning, stress test, crisis planning, multi-variable scenario, pre-mortem
## Quick Start
```bash
python scripts/scenario_modeler.py # Interactive scenario builder with cascade modeling
```
Or describe the scenario:
```
/war-room "What if we lose our top customer AND miss the Q3 fundraise?"
/war-room "What if 3 engineers quit AND we need to ship by Q3?"
/war-room "What if our market shrinks 30% AND a competitor raises $50M?"
```
## What This Is Not
- **Not** a single-assumption stress test (that's `/em:stress-test`)
- **Not** financial modeling only — every function gets modeled
- **Not** worst-case-only — models 3 severity levels
- **Not** paralysis by analysis — outputs concrete hedges and triggers
## Framework: 6-Step Cascade Model
### Step 1: Define Scenario Variables (max 3)
State each variable with:
- **What changes** — specific, quantified if possible
- **Probability** — your best estimate
- **Timeline** — when it hits
```
Variable A: Top customer (28% ARR) gives 60-day termination notice
Probability: 15% | Timeline: Within 90 days
Variable B: Series A fundraise delayed 6 months beyond target close
Probability: 25% | Timeline: Q3
Variable C: Lead engineer resigns
Probability: 20% | Timeline: Unknown
```
### Step 2: Domain Impact Mapping
For each variable, each relevant role models impact:
| Domain | Owner | Models |
|--------|-------|--------|
| Cash & runway | CFO | Burn impact, runway change, bridge options |
| Revenue | CRO | ARR gap, churn cascade risk, pipeline |
| Product | CPO | Roadmap impact, PMF risk |
| Engineering | CTO | Velocity impact, key person risk |
| People | CHRO | Attrition cascade, hiring freeze implications |
| Operations | COO | Capacity, OKR impact, process risk |
| Security | CISO | Compliance timeline risk |
| Market | CMO | CAC impact, competitive exposure |
### Step 3: Cascade Effect Mapping
This is the core. Show how Variable A triggers consequences in domains that trigger Variable B's effects:
```
TRIGGER: Customer churn ($560K ARR)
↓
CFO: Runway drops 14 → 8 months
↓
CHRO: Hiring freeze; retention risk increases (morale hit)
↓
CTO: 3 open engineering reqs frozen; roadmap slips
↓
CPO: Q4 feature launch delayed → customer retention risk
↓
CRO: NRR drops; existing accounts see reduced velocity → more churn risk
↓
CFO: [Secondary cascade — potential death spiral if not interrupted]
```
Name the cascade explicitly. Show where it can be interrupted.
### Step 4: Severity Matrix
Model three scenarios:
| Scenario | Definition | Recovery |
|----------|------------|---------|
| **Base** | One variable hits; others don't | Manageable with plan |
| **Stress** | Two variables hit simultaneously | Requires significant response |
| **Severe** | All variables hit; full cascade | Existential; requires board intervention |
For each severity level:
- Runway impact
- ARR impact
- Headcount impact
- Timeline to unacceptable state (trigger point)
### Step 5: Trigger Points (Early Warning Signals)
Define the measurable signal that tells you a scenario is unfolding **before** it's confirmed:
```
Trigger for Customer Churn Risk:
- Sponsor goes dark for >3 weeks
- Usage drops >25% MoM
- No Q1 QBR confirmed by Dec 1
Trigger for Fundraise Delay:
- <3 term sheets after 60 days of process
- Lead investor requests >30-day extension on DD
- Competitor raises at lower valuation (market signal)
Trigger for Engineering Attrition:
- Glassdoor activity from engineering team
- 2+ referral interview requests from engineers
- Above-market offer counter-required in last 3 months
```
### Step 6: Hedging Strategies
For each scenario: actions to take **now** (before the scenario materializes) that reduce impact if it does.
| Hedge | Cost | Impact | Owner | Deadline |
|-------|------|--------|-------|---------|
| Establish $500K credit line | $5K/year | Buys 3 months if churn hits | CFO | 60 days |
| 12-month retention bonus for 3 key engineers | $90K | Locks team through fundraise | CHRO | 30 days |
| Diversify to <20% revenue concentration per customer | Sales effort | Reduces single-customer risk | CRO | 2 quarters |
| Compress fundraise timeline, start parallel process | CEO time | Closes before runways merge | CEO | Immediate |
---
## Output Format
Every war room session produces:
```
SCENARIO: [Name]
Variables: [A, B, C]
Most likely path: [which combination actually plays out, with probability]
SEVERITY LEVELS
Base (A only): [runway/ARR impact] — recovery: [X actions]
Stress (A+B): [runway/ARR impact] — recovery: [X actions]
Severe (A+B+C): [runway/ARR impact] — existential risk: [yes/no]
CASCADE MAP
[A → domain impact → B trigger → domain impact → end state]
EARLY WARNING SIGNALS
- [Signal 1 → which scenario it indicates]
- [Signal 2 → which scenario it indicates]
- [Signal 3 → which scenario it indicates]
HEDGES (take these actions now)
1. [Action] — cost: $X — impact: [what it buys] — owner: [role] — deadline: [date]
2. [Action] — cost: $X — impact: [what it buys] — owner: [role] — deadline: [date]
3. [Action] — cost: $X — impact: [what it buys] — owner: [role] — deadline: [date]
RECOMMENDED DECISION
[One paragraph. What to do, in what order, and why.]
```
---
## Rules for Good War Room Sessions
**Max 3 variables per scenario.** More than 3 is noise — you can't meaningfully prepare for 5-variable collapse. Model the 3 that actually worry you.
**Quantify or estimate.** "Revenue drops" is not useful. "$420K ARR at risk over 60 days" is. Use ranges if uncertain.
**Don't stop at first-order effects.** The damage is always in the cascade, not the initial hit.
**Model recovery, not just impact.** Every scenario should have a "what we do" path.
**Separate base case from sensitivity.** Don't conflate "what probably happens" with "what could happen."
**Don't over-model.** 3-4 scenarios per planning cycle is the right number. More creates analysis paralysis.
---
## Common Scenarios by Stage
**Seed:**
- Co-founder leaves + product misses launch
- Funding runs out + bridge terms unfavorable
**Series A:**
- Miss ARR target + fundraise delayed
- Key customer churns + competitor raises
**Series B:**
- Market contraction + burn multiple spikes
- Lead investor wants pivot + team resists
## Integration with C-Suite Roles
| Scenario Type | Primary Roles | Cascade To |
|--------------|---------------|------------|
| Revenue miss | CRO, CFO | CMO (pipeline), COO (cuts), CHRO (layoffs) |
| Key person departure | CHRO, COO | CTO (if eng), CRO (if sales) |
| Fundraise failure | CFO, CEO | COO (runway extension), CHRO (hiring freeze) |
| Security breach | CISO, CTO | CEO (comms), CFO (cost), CRO (customer impact) |
| Market shift | CEO, CPO | CMO (repositioning), CRO (new segments) |
| Competitor move | CMO, CRO | CPO (roadmap response), CEO (strategy) |
## References
- `references/scenario-planning.md` — Shell methodology, pre-mortem, Monte Carlo, cascade frameworks
- `scripts/scenario_modeler.py` — CLI tool for structured scenario modeling
FILE:references/scenario-planning.md
# Scenario Planning Reference
## Shell's Scenario Planning Methodology
Shell invented modern scenario planning in the 1970s after the oil crisis. Core insight: **scenarios are not forecasts — they're tools for thinking.**
### Shell's Principles (adapted for startups)
1. **Scenarios are mutually exclusive, collectively exhaustive** — they cover the space of possibilities without overlapping
2. **2x2 matrix** — pick 2 critical uncertainties (not risks — uncertainties); cross them to get 4 scenarios
3. **Name the scenarios** — named scenarios are remembered; numbered ones aren't
4. **Identify predetermined elements** — things that will happen regardless of scenario (regulatory changes, tech trends)
5. **Early indicators** — each scenario has signals you can monitor today
### Shell's 2x2 for Startups
Critical uncertainties for early-stage SaaS:
| | Market grows fast | Market grows slow |
|---|---|---|
| **We raise successfully** | "Blue Ocean" — execute hard | "Ramp Carefully" — efficiency focus |
| **We bridge/delay raise** | "Scrappy Growth" — ramen profitability | "Survival Mode" — cut to core |
Build your war room sessions around whichever quadrant is most relevant right now.
---
## Monte Carlo Thinking for Startups
Monte Carlo = running thousands of simulations with random variables to understand probability distributions.
You don't need software. Apply the mental model:
### The Mental Monte Carlo Process
1. **Identify the key variables** (3-5 max)
2. **Assign ranges** — not point estimates
- CAC: $6K–$12K (uniform distribution)
- Close rate: 20%–40% (normal, mean 30%)
- Churn: 5%–20% (right-skewed — bad tail is worse)
3. **Run mental scenarios** — pick low/mid/high for each
4. **Identify the combinations that kill you** — which variable combinations make runway hit zero?
5. **Focus hedging on** the 20% of combinations that account for 80% of kill scenarios
### Practical Monte Carlo Heuristic
For revenue forecasting, always state:
- **P90** (90% confidence you'll exceed this)
- **P50** (median case)
- **P10** (only 10% chance you'll exceed this — your "stretch")
Boards respect ranges. Point estimates are usually wrong and make you look naive.
---
## Pre-Mortem Technique
A pre-mortem asks: *"It's 12 months from now. We failed. Why?"*
It's the opposite of planning (which asks why you'll succeed). It surfaces hidden risks that optimism suppresses.
### Running a Pre-Mortem
**Setup:**
- Time: 90 minutes
- Participants: leadership team
- Facilitator: neutral (COO, or external)
- Assumption: "It's [date 12 months out]. The company failed / missed its major goal. This is real."
**Phase 1 — Silence (10 minutes):**
Each person writes their top 3 reasons the failure happened. No discussion.
**Phase 2 — Round Robin (30 minutes):**
Each person shares one reason per turn. Facilitator captures on whiteboard. No debate yet.
**Phase 3 — Cluster (20 minutes):**
Group similar causes. Identify the top 5 clusters.
**Phase 4 — Probability & Impact (20 minutes):**
For each cluster: P(likely) × impact = risk score. Rank.
**Phase 5 — Mitigation (10 minutes):**
Top 3 risks: what one action would most reduce each?
### Pre-Mortem Prompt Variants
- "It's March 2027. We ran out of money. Why?"
- "It's Q4. We lost 3 enterprise customers in 60 days. What happened?"
- "It's next year. Our top competitor took 40% of the market. How?"
- "It's 18 months from now. Half the engineering team left. What triggered it?"
---
## Cascade Effect Mapping
Cascades are where most startups get surprised. The first hit is expected — the second and third aren't.
### Cascade Mapping Format
Draw as a chain:
```
INITIAL EVENT
↓ [immediate effect: domain, severity, timeline]
SECONDARY EFFECT
↓ [cascade mechanism: how A causes B]
TERTIARY EFFECT
↓ [cascade mechanism]
END STATE [runway impact, ARR impact, team impact]
```
### Common Cascade Patterns
**Revenue → Cash → People:**
```
Customer churns ($400K ARR)
↓ CFO: runway drops 14→9 months; bridge needed
↓ CHRO: hiring freeze; morale drops; attrition risk
↓ CTO: roadmap slips; key engineers leave for certainty
↓ CPO: product quality drops; more churn risk
↓ CRO: harder to win new logos without product velocity
END STATE: Death spiral if not interrupted at step 2
```
**Fundraise → Operations → Product:**
```
Fundraise delayed 6 months
↓ CFO: bridge at unfavorable terms; equity dilution
↓ COO: freeze all non-essential spend; process degrades
↓ CPO: roadmap cut to 40% of planned scope
↓ CTO: no infra investment; tech debt accelerates
↓ CRO: product gaps start losing deals to feature-complete competitors
END STATE: Weaker position at next raise; lower valuation
```
**People → Product → Revenue:**
```
Lead engineer + 2 seniors leave (30% of eng team)
↓ CTO: velocity drops 50%; critical features slip Q3→Q4
↓ CPO: Q4 launch cancelled; roadmap confidence collapses
↓ CRO: 3 enterprise deals cite product timeline → delays/losses
↓ CFO: $600K pipeline at risk; raises needed earlier
END STATE: Fundraise from position of weakness; team morale spiral
```
### Identifying Cascade Break Points
Every cascade has a point where intervention is cheapest. Find it:
- Step 1: Very expensive to prevent (existential)
- Step 2: Moderate cost (management action)
- Step 3: Cheap (early signal response)
Always try to interrupt at Step 2 or earlier.
---
## Trigger-Based Contingency Plans
Triggers are measurable signals you commit to acting on **before** the scenario fully materializes.
### Trigger Design Principles
1. **Measurable** — not "things look bad" but "cash below $800K"
2. **Leading, not lagging** — triggers should fire 60-90 days before the crisis
3. **Pre-committed responses** — when trigger fires, the action is already decided
4. **Owner assigned** — who watches for this trigger?
### Trigger Examples
**Cash / Runway:**
```
Trigger: Cash drops below $1M (or runway < 6 months)
Pre-committed response:
- CFO: activate credit line within 48 hours
- CEO: begin bridge conversations with existing investors
- COO: implement 20% spend reduction plan (already drafted)
Owner: CFO (weekly cash report to CEO)
```
**Customer Health:**
```
Trigger: Any customer >10% ARR shows 3 of: [sponsor gone dark, usage -25%,
no renewal discussion by 90 days before contract end, missed QBR]
Pre-committed response:
- CRO: executive escalation call within 48 hours
- CPO: product health review scheduled
- CEO: direct outreach if escalation fails
Owner: CRO (health score dashboard, weekly)
```
**Fundraise:**
```
Trigger: <3 term sheets after 8 weeks of active process
Pre-committed response:
- CEO: expand process to 10 additional firms
- CFO: model bridge scenarios; draft bridge terms
- COO: prepare 90-day cost reduction plan
Owner: CEO (weekly fundraise status)
```
---
## How Many Scenarios to Model
**Answer: 3-4 max per planning cycle.**
The math: 3 scenarios × 6 domains × 3 severity levels = 54 combinations. That's already overwhelming. More scenarios don't improve decisions — they paralyze them.
### The Right 3-4 Scenarios
1. **Most likely adverse scenario** — what actually keeps you up at night
2. **Market/macro scenario** — something outside your control
3. **Black swan** — low probability, existential if it hits
4. **Compound scenario** — your top 2 adverse events happening simultaneously
### What Kills Scenario Planning
- **Too many scenarios** — decision paralysis
- **Only modeling what's comfortable** — survivorship bias
- **No pre-committed responses** — it's just worry, not planning
- **Not revisiting** — scenarios from 12 months ago are often irrelevant
- **Treating scenarios as forecasts** — they're possibilities, not predictions
- **Confusing risk with uncertainty** — risk has known probabilities; uncertainty doesn't
FILE:scripts/scenario_modeler.py
#!/usr/bin/env python3
"""
Scenario War Room — Multi-Variable Cascade Modeler
Models cascading effects of compound adversity across business domains.
Stdlib only. Run with: python scenario_modeler.py
"""
import json
import sys
from dataclasses import dataclass, field
from typing import Dict, List, Optional, Tuple
from enum import Enum
class Severity(Enum):
BASE = "base" # One variable hits
STRESS = "stress" # Two variables hit
SEVERE = "severe" # All variables hit
class Domain(Enum):
FINANCIAL = "Financial (CFO)"
REVENUE = "Revenue (CRO)"
PRODUCT = "Product (CPO)"
ENGINEERING = "Engineering (CTO)"
PEOPLE = "People (CHRO)"
OPERATIONS = "Operations (COO)"
SECURITY = "Security (CISO)"
MARKET = "Market (CMO)"
@dataclass
class Variable:
name: str
description: str
probability: float # 0.0-1.0
arrt_impact_pct: float # % of ARR at risk (negative = loss)
runway_impact_months: float # months lost from runway (negative = reduction)
affected_domains: List[Domain]
timeline_days: int # when it hits
@dataclass
class CascadeEffect:
trigger_domain: Domain
caused_domain: Domain
mechanism: str # how A causes B
severity_multiplier: float # compounds the base impact
@dataclass
class Hedge:
action: str
cost_usd: int
impact_description: str
owner: str
deadline_days: int
reduces_probability: float # how much it reduces scenario probability
@dataclass
class Scenario:
name: str
variables: List[Variable]
cascades: List[CascadeEffect]
hedges: List[Hedge]
# Company baseline
current_arr_usd: int = 2_000_000
current_runway_months: int = 14
monthly_burn_usd: int = 140_000
def calculate_impact(
scenario: Scenario,
severity: Severity
) -> Dict:
"""Calculate combined impact for a given severity level."""
variables = scenario.variables
# Select variables by severity
if severity == Severity.BASE:
active_vars = variables[:1]
elif severity == Severity.STRESS:
active_vars = variables[:2]
else:
active_vars = variables
# Direct impacts
total_arr_loss_pct = sum(abs(v.arrt_impact_pct) for v in active_vars)
total_runway_reduction = sum(abs(v.runway_impact_months) for v in active_vars)
arr_at_risk = scenario.current_arr_usd * (total_arr_loss_pct / 100)
new_arr = scenario.current_arr_usd - arr_at_risk
new_runway = scenario.current_runway_months - total_runway_reduction
# Cascade multiplier (stress/severe amplify via domain cascades)
cascade_multiplier = 1.0
if len(active_vars) > 1:
active_domains = set(d for v in active_vars for d in v.affected_domains)
for cascade in scenario.cascades:
if (cascade.trigger_domain in active_domains and
cascade.caused_domain in active_domains):
cascade_multiplier *= cascade.severity_multiplier
# Apply cascade
effective_arr_loss = arr_at_risk * cascade_multiplier
effective_arr = scenario.current_arr_usd - effective_arr_loss
effective_runway = max(0, new_runway - (cascade_multiplier - 1.0) * 2)
# New burn multiple
new_monthly_burn = scenario.monthly_burn_usd * cascade_multiplier
burn_multiple = (new_monthly_burn * 12) / max(effective_arr, 1)
# Affected domains
affected = set(d for v in active_vars for d in v.affected_domains)
return {
"severity": severity.value,
"active_variables": [v.name for v in active_vars],
"arr_at_risk_usd": int(effective_arr_loss),
"arr_at_risk_pct": round(effective_arr_loss / scenario.current_arr_usd * 100, 1),
"projected_arr_usd": int(effective_arr),
"runway_months": round(effective_runway, 1),
"runway_change": round(effective_runway - scenario.current_runway_months, 1),
"cascade_multiplier": round(cascade_multiplier, 2),
"new_burn_multiple": round(burn_multiple, 1),
"affected_domains": [d.value for d in affected],
"existential_risk": effective_runway < 6.0,
"board_escalation_required": effective_runway < 9.0,
}
def identify_triggers(variables: List[Variable]) -> List[Dict]:
"""Generate early warning triggers for each variable."""
triggers = []
for var in variables:
trigger = {
"variable": var.name,
"timeline": f"Watch from day 1; expect signal ~{var.timeline_days // 2} days before impact",
"signals": _generate_signals(var),
"response_owner": _domain_to_owner(var.affected_domains[0] if var.affected_domains else Domain.FINANCIAL),
}
triggers.append(trigger)
return triggers
def _generate_signals(var: Variable) -> List[str]:
"""Generate plausible early warning signals based on variable type."""
signals = []
name_lower = var.name.lower()
if any(k in name_lower for k in ["customer", "churn", "account"]):
signals = [
"Executive sponsor unreachable for >2 weeks",
"Product usage drops >20% month-over-month",
"No QBR scheduled within 90 days of contract renewal",
"Support ticket volume spikes >50% without explanation",
]
elif any(k in name_lower for k in ["fundraise", "raise", "capital", "investor"]):
signals = [
"Fewer than 3 term sheets after 60 days of active process",
"Lead investor requests 30+ day extension on diligence",
"Comparable company raises at lower valuation (market signal)",
"Investor meeting conversion rate below 20%",
]
elif any(k in name_lower for k in ["engineer", "people", "team", "resign", "quit"]):
signals = [
"2+ engineers receive above-market counter-offer in 90 days",
"Glassdoor activity increases from engineering team",
"Key person requests 1:1 to 'talk about career' unexpectedly",
"Referral interview requests from engineers increase",
]
elif any(k in name_lower for k in ["market", "competitor", "competition"]):
signals = [
"Competitor raises $10M+ funding round",
"Win/loss rate shifts >10% in 60 days",
"Multiple prospects cite competitor by name in objections",
"Competitor poaches 2+ of your customers in a quarter",
]
else:
signals = [
f"Leading indicator for '{var.name}' deteriorates 20%+ vs baseline",
"Weekly metric review shows 3-week trend in wrong direction",
"External validation from customers or partners confirms risk",
]
return signals[:3] # Top 3
def _domain_to_owner(domain: Domain) -> str:
mapping = {
Domain.FINANCIAL: "CFO",
Domain.REVENUE: "CRO",
Domain.PRODUCT: "CPO",
Domain.ENGINEERING: "CTO",
Domain.PEOPLE: "CHRO",
Domain.OPERATIONS: "COO",
Domain.SECURITY: "CISO",
Domain.MARKET: "CMO",
}
return mapping.get(domain, "CEO")
def format_currency(amount: int) -> str:
if amount >= 1_000_000:
return f".1fM"
elif amount >= 1_000:
return f".0fK"
return f"amount"
def print_report(scenario: Scenario) -> None:
"""Print full scenario analysis report."""
print("\n" + "=" * 70)
print(f"SCENARIO WAR ROOM: {scenario.name.upper()}")
print("=" * 70)
# Baseline
print(f"\n📊 BASELINE")
print(f" Current ARR: {format_currency(scenario.current_arr_usd)}")
print(f" Monthly Burn: {format_currency(scenario.monthly_burn_usd)}")
print(f" Runway: {scenario.current_runway_months} months")
# Variables
print(f"\n⚡ SCENARIO VARIABLES ({len(scenario.variables)})")
for i, var in enumerate(scenario.variables, 1):
prob_pct = int(var.probability * 100)
print(f"\n Variable {i}: {var.name}")
print(f" {var.description}")
print(f" Probability: {prob_pct}% | Timeline: {var.timeline_days} days")
print(f" ARR impact: -{var.arrt_impact_pct}% | "
f"Runway impact: -{var.runway_impact_months} months")
print(f" Affected: {', '.join(d.value for d in var.affected_domains)}")
# Combined probability
combined_prob = 1.0
for var in scenario.variables:
combined_prob *= var.probability
print(f"\n Combined probability (all hit): {combined_prob * 100:.1f}%")
# Severity Levels
print(f"\n{'=' * 70}")
print("SEVERITY ANALYSIS")
print("=" * 70)
for severity in Severity:
if severity == Severity.BASE and len(scenario.variables) < 1:
continue
if severity == Severity.STRESS and len(scenario.variables) < 2:
continue
impact = calculate_impact(scenario, severity)
icon = {"base": "🟡", "stress": "🔴", "severe": "💀"}[impact["severity"]]
print(f"\n{icon} {impact['severity'].upper()} SCENARIO")
print(f" Variables: {', '.join(impact['active_variables'])}")
print(f" ARR at risk: {format_currency(impact['arr_at_risk_usd'])} "
f"({impact['arr_at_risk_pct']}%)")
print(f" Projected ARR: {format_currency(impact['projected_arr_usd'])}")
print(f" Runway: {impact['runway_months']} months "
f"({impact['runway_change']:+.1f} months)")
print(f" Burn multiple: {impact['new_burn_multiple']}x")
if impact['cascade_multiplier'] > 1.0:
print(f" Cascade amplifier: {impact['cascade_multiplier']}x "
f"(domains interact)")
print(f" Board escalation: {'⚠️ YES' if impact['board_escalation_required'] else 'No'}")
print(f" Existential risk: {'🚨 YES' if impact['existential_risk'] else 'No'}")
# Cascade Map
if scenario.cascades:
print(f"\n{'=' * 70}")
print("CASCADE MAP")
print("=" * 70)
for i, cascade in enumerate(scenario.cascades, 1):
print(f"\n [{i}] {cascade.trigger_domain.value}")
print(f" ↓ {cascade.mechanism}")
print(f" → {cascade.caused_domain.value} "
f"(amplified {cascade.severity_multiplier}x)")
# Early Warning Triggers
print(f"\n{'=' * 70}")
print("EARLY WARNING TRIGGERS")
print("=" * 70)
triggers = identify_triggers(scenario.variables)
for trigger in triggers:
print(f"\n 📡 {trigger['variable']}")
print(f" Watch: {trigger['timeline']}")
print(f" Owner: {trigger['response_owner']}")
for signal in trigger['signals']:
print(f" • {signal}")
# Hedges
if scenario.hedges:
print(f"\n{'=' * 70}")
print("HEDGING STRATEGIES (act now)")
print("=" * 70)
sorted_hedges = sorted(scenario.hedges,
key=lambda h: h.reduces_probability, reverse=True)
for hedge in sorted_hedges:
print(f"\n ✅ {hedge.action}")
print(f" Cost: {format_currency(hedge.cost_usd)}/year | "
f"Owner: {hedge.owner} | Deadline: {hedge.deadline_days} days")
print(f" Impact: {hedge.impact_description}")
print(f" Risk reduction: {int(hedge.reduces_probability * 100)}%")
print(f"\n{'=' * 70}\n")
def build_sample_scenario() -> Scenario:
"""Sample: Customer churn + fundraise miss compound scenario."""
variables = [
Variable(
name="Top customer churn",
description="Largest customer (28% of ARR) gives 60-day termination notice",
probability=0.15,
arrt_impact_pct=28.0,
runway_impact_months=4.0,
affected_domains=[
Domain.FINANCIAL, Domain.REVENUE, Domain.OPERATIONS
],
timeline_days=60,
),
Variable(
name="Series A delayed 6 months",
description="Fundraise process extends beyond target close; bridge required",
probability=0.25,
arrt_impact_pct=0.0, # No ARR impact directly
runway_impact_months=3.0, # Bridge terms reduce effective runway
affected_domains=[
Domain.FINANCIAL, Domain.PEOPLE, Domain.OPERATIONS
],
timeline_days=120,
),
Variable(
name="Lead engineer resigns",
description="Engineering lead + 1 senior resign during uncertainty",
probability=0.20,
arrt_impact_pct=5.0, # Roadmap slip causes some revenue impact
runway_impact_months=1.0,
affected_domains=[
Domain.ENGINEERING, Domain.PRODUCT, Domain.REVENUE
],
timeline_days=30,
),
]
cascades = [
CascadeEffect(
trigger_domain=Domain.REVENUE,
caused_domain=Domain.FINANCIAL,
mechanism="ARR loss increases burn multiple; runway compresses",
severity_multiplier=1.3,
),
CascadeEffect(
trigger_domain=Domain.FINANCIAL,
caused_domain=Domain.PEOPLE,
mechanism="Hiring freeze + uncertainty triggers attrition risk",
severity_multiplier=1.2,
),
CascadeEffect(
trigger_domain=Domain.PEOPLE,
caused_domain=Domain.PRODUCT,
mechanism="Engineering attrition slips roadmap; customer value drops",
severity_multiplier=1.15,
),
]
hedges = [
Hedge(
action="Establish $750K revolving credit line",
cost_usd=7_500,
impact_description="Buys 4+ months if churn hits before fundraise closes",
owner="CFO",
deadline_days=45,
reduces_probability=0.40,
),
Hedge(
action="12-month retention bonuses for 3 key engineers",
cost_usd=90_000,
impact_description="Locks critical talent through fundraise uncertainty",
owner="CHRO",
deadline_days=30,
reduces_probability=0.60,
),
Hedge(
action="Diversify revenue: reduce top customer to <20% ARR in 2 quarters",
cost_usd=0,
impact_description="Structural risk reduction; takes 6+ months to achieve",
owner="CRO",
deadline_days=14,
reduces_probability=0.30,
),
Hedge(
action="Accelerate fundraise: start parallel process, compress timeline",
cost_usd=15_000,
impact_description="Closes before scenarios compound; reduces bridge risk",
owner="CEO",
deadline_days=7,
reduces_probability=0.35,
),
]
return Scenario(
name="Customer Churn + Fundraise Miss + Eng Attrition",
variables=variables,
cascades=cascades,
hedges=hedges,
current_arr_usd=2_000_000,
current_runway_months=14,
monthly_burn_usd=140_000,
)
def interactive_mode() -> Scenario:
"""Simple CLI for building a custom scenario."""
print("\n🔴 SCENARIO WAR ROOM — Custom Scenario Builder")
print("=" * 50)
print("Define up to 3 scenario variables.\n")
name = input("Scenario name: ").strip() or "Custom Scenario"
current_arr = int(input("Current ARR ($): ").strip() or "2000000")
current_runway = int(input("Current runway (months): ").strip() or "14")
monthly_burn = int(current_arr / current_runway) if current_runway > 0 else 140000
variables = []
for i in range(1, 4):
print(f"\nVariable {i} (press Enter to skip):")
var_name = input(" Name: ").strip()
if not var_name:
break
desc = input(" Description: ").strip() or var_name
prob = float(input(" Probability (0-100%): ").strip() or "20") / 100
arr_impact = float(input(" ARR impact (%): ").strip() or "10")
runway_impact = float(input(" Runway impact (months): ").strip() or "2")
timeline = int(input(" Timeline (days): ").strip() or "90")
variables.append(Variable(
name=var_name,
description=desc,
probability=prob,
arrt_impact_pct=arr_impact,
runway_impact_months=runway_impact,
affected_domains=[Domain.FINANCIAL, Domain.REVENUE],
timeline_days=timeline,
))
if not variables:
print("No variables defined. Using sample scenario.")
return build_sample_scenario()
return Scenario(
name=name,
variables=variables,
cascades=[],
hedges=[],
current_arr_usd=current_arr,
current_runway_months=current_runway,
monthly_burn_usd=monthly_burn,
)
def main():
print("\n🔴 SCENARIO WAR ROOM")
print("Multi-variable cascade modeler for startup adversity planning\n")
if "--interactive" in sys.argv or "-i" in sys.argv:
scenario = interactive_mode()
else:
print("Running sample scenario: Customer Churn + Fundraise Miss + Eng Attrition")
print("(Use --interactive or -i for custom scenario)\n")
scenario = build_sample_scenario()
print_report(scenario)
if "--json" in sys.argv:
results = {}
for severity in Severity:
impact = calculate_impact(scenario, severity)
results[severity.value] = impact
print(json.dumps(results, indent=2))
if __name__ == "__main__":
main()
Đánh giá cấu hình sai, leo thang đặc quyền IAM, lộ S3, security group mở và lỗ hổng IaC trên AWS, Azure, GCP.
---
name: "cloud-security"
description: "Use when assessing cloud infrastructure for security misconfigurations, IAM privilege escalation paths, S3 public exposure, open security group rules, or IaC security gaps. Covers AWS, Azure, and GCP posture assessment with MITRE ATT&CK mapping."
---
# Cloud Security
Cloud security posture assessment skill for detecting IAM privilege escalation, public storage exposure, network configuration risks, and infrastructure-as-code misconfigurations. This is NOT incident response for active cloud compromise (see incident-response) or application vulnerability scanning (see security-pen-testing) — this is about systematic cloud configuration analysis to prevent exploitation.
---
## Table of Contents
- [Overview](#overview)
- [Cloud Posture Check Tool](#cloud-posture-check-tool)
- [IAM Policy Analysis](#iam-policy-analysis)
- [S3 Exposure Assessment](#s3-exposure-assessment)
- [Security Group Analysis](#security-group-analysis)
- [IaC Security Review](#iac-security-review)
- [Cloud Provider Coverage Matrix](#cloud-provider-coverage-matrix)
- [Workflows](#workflows)
- [Anti-Patterns](#anti-patterns)
- [Cross-References](#cross-references)
---
## Overview
### What This Skill Does
This skill provides the methodology and tooling for **cloud security posture management (CSPM)** — systematically checking cloud configurations for misconfigurations that create exploitable attack surface. It covers IAM privilege escalation paths, storage public exposure, network over-permissioning, and infrastructure code security.
### Distinction from Other Security Skills
| Skill | Focus | Approach |
|-------|-------|----------|
| **cloud-security** (this) | Cloud configuration risk | Preventive — assess before exploitation |
| incident-response | Active cloud incidents | Reactive — triage confirmed cloud compromise |
| threat-detection | Behavioral anomalies | Proactive — hunt for attacker activity in cloud logs |
| security-pen-testing | Application vulnerabilities | Offensive — actively exploit found weaknesses |
### Prerequisites
Read access to IAM policy documents, S3 bucket configurations, and security group rules in JSON format. For continuous monitoring, integrate with cloud provider APIs (AWS Config, Azure Policy, GCP Security Command Center).
---
## Cloud Posture Check Tool
The `cloud_posture_check.py` tool runs three types of checks: `iam` (privilege escalation), `s3` (public access), and `sg` (network exposure). It auto-detects the check type from the config file structure or accepts explicit `--check` flags.
```bash
# Analyze an IAM policy for privilege escalation paths
python3 scripts/cloud_posture_check.py policy.json --check iam --json
# Assess S3 bucket configuration for public access
python3 scripts/cloud_posture_check.py bucket_config.json --check s3 --json
# Check security group rules for open admin ports
python3 scripts/cloud_posture_check.py sg.json --check sg --json
# Run all checks with internet-facing severity bump
python3 scripts/cloud_posture_check.py config.json --check all \
--provider aws --severity-modifier internet-facing --json
# Regulated data context (bumps severity by one level for all findings)
python3 scripts/cloud_posture_check.py config.json --check all \
--severity-modifier regulated-data --json
# Pipe IAM policy from AWS CLI
aws iam get-policy-version --policy-arn arn:aws:iam::123456789012:policy/MyPolicy \
--version-id v1 | jq '.PolicyVersion.Document' | \
python3 scripts/cloud_posture_check.py - --check iam --json
```
### Exit Codes
| Code | Meaning | Required Action |
|------|---------|-----------------|
| 0 | No high/critical findings | No action required |
| 1 | High-severity findings | Remediate within 24 hours |
| 2 | Critical findings | Remediate immediately — escalate to incident-response if active |
---
## IAM Policy Analysis
IAM analysis detects privilege escalation paths, overprivileged grants, public principal exposure, and data exfiltration risk.
### Privilege Escalation Patterns
| Pattern | Severity | Key Action Combination | MITRE |
|---------|----------|------------------------|-------|
| Lambda PassRole escalation | Critical | iam:PassRole + lambda:CreateFunction | T1078.004 |
| EC2 instance profile abuse | Critical | iam:PassRole + ec2:RunInstances | T1078.004 |
| CloudFormation PassRole | Critical | iam:PassRole + cloudformation:CreateStack | T1078.004 |
| Self-attach policy escalation | Critical | iam:AttachUserPolicy + sts:GetCallerIdentity | T1484.001 |
| Inline policy self-escalation | Critical | iam:PutUserPolicy + sts:GetCallerIdentity | T1484.001 |
| Policy version backdoor | Critical | iam:CreatePolicyVersion + iam:ListPolicies | T1484.001 |
| Credential harvesting | High | iam:CreateAccessKey + iam:ListUsers | T1098.001 |
| Group membership escalation | High | iam:AddUserToGroup + iam:ListGroups | T1098 |
| Password reset attack | High | iam:UpdateLoginProfile + iam:ListUsers | T1098 |
| Service-level wildcard | High | iam:* or s3:* or ec2:* | T1078.004 |
### IAM Finding Severity Guide
| Finding Type | Condition | Severity |
|-------------|-----------|----------|
| Full admin wildcard | Action=* Resource=* | Critical |
| Public principal | Principal: '*' | Critical |
| Dangerous action combo | Two-action escalation path | Critical |
| Individual priv-esc actions | On wildcard resource | High |
| Data exfiltration actions | s3:GetObject, secretsmanager:GetSecretValue on * | High |
| Service wildcard | service:* action | High |
| Data actions on named resource | Appropriate scope | Low/Clean |
### Least Privilege Recommendations
For every critical or high finding, the tool outputs a `least_privilege_suggestion` field with specific remediation guidance:
- Replace `Action: *` with a named list of required actions
- Replace `Resource: *` with specific ARN patterns
- Use AWS Access Analyzer to identify actually-used permissions
- Separate dangerous action combinations into different roles with distinct trust policies
---
## S3 Exposure Assessment
S3 assessment checks four dimensions: public access block configuration, bucket ACL, bucket policy principal exposure, and default encryption.
### S3 Configuration Check Matrix
| Check | Finding Condition | Severity |
|-------|------------------|----------|
| Public access block | Any of four flags missing/false | High |
| Bucket ACL | public-read-write | Critical |
| Bucket ACL | public-read or authenticated-read | High |
| Bucket policy Principal | "Principal": "*" with Allow | Critical |
| Default encryption | No ServerSideEncryptionConfiguration | High |
| Default encryption | Non-standard SSEAlgorithm | Medium |
| No PublicAccessBlockConfiguration | Status unknown | Medium |
### Recommended S3 Baseline Configuration
```json
{
"PublicAccessBlockConfiguration": {
"BlockPublicAcls": true,
"BlockPublicPolicy": true,
"IgnorePublicAcls": true,
"RestrictPublicBuckets": true
},
"ServerSideEncryptionConfiguration": {
"Rules": [{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "aws:kms",
"KMSMasterKeyID": "arn:aws:kms:region:account:key/key-id"
},
"BucketKeyEnabled": true
}]
},
"ACL": "private"
}
```
All four public access block settings must be enabled at both the bucket level and the AWS account level. Account-level settings can be overridden by bucket-level settings if not both enforced.
---
## Security Group Analysis
Security group analysis flags inbound rules that expose admin ports, database ports, or all traffic to internet CIDRs (0.0.0.0/0, ::/0).
### Critical Port Exposure Rules
| Port | Service | Finding Severity | Remediation |
|------|---------|-----------------|-------------|
| 22 | SSH | Critical | Restrict to VPN CIDR or use AWS Systems Manager Session Manager |
| 3389 | RDP | Critical | Restrict to VPN CIDR or use AWS Fleet Manager |
| 0–65535 (all) | All traffic | Critical | Remove rule; add specific required ports only |
### High-Risk Database Port Rules
| Port | Service | Finding Severity | Remediation |
|------|---------|-----------------|-------------|
| 1433 | MSSQL | High | Allow from application tier SG only — move to private subnet |
| 3306 | MySQL | High | Allow from application tier SG only — move to private subnet |
| 5432 | PostgreSQL | High | Allow from application tier SG only — move to private subnet |
| 27017 | MongoDB | High | Allow from application tier SG only — move to private subnet |
| 6379 | Redis | High | Allow from application tier SG only — move to private subnet |
| 9200 | Elasticsearch | High | Allow from application tier SG only — move to private subnet |
### Severity Modifiers
Use `--severity-modifier internet-facing` when the assessed resource is directly internet-accessible (load balancer, API gateway, public EC2). Use `--severity-modifier regulated-data` when the resource handles PCI, HIPAA, or GDPR-regulated data. Both modifiers bump each finding's severity by one level.
---
## IaC Security Review
Infrastructure-as-code review catches configuration issues at definition time, before deployment.
### IaC Check Matrix
| Tool | Check Types | When to Run |
|------|-------------|-------------|
| Terraform | Resource-level checks (aws_s3_bucket_acl, aws_security_group, aws_iam_policy_document) | Pre-plan, pre-apply, PR gate |
| CloudFormation | Template property validation (PublicAccessBlockConfiguration, SecurityGroupIngress) | Template lint, deploy gate |
| Kubernetes manifests | Container privileges, network policies, secret exposure | PR gate, admission controller |
| Helm charts | Same as Kubernetes | PR gate |
### Terraform IAM Policy Example — Finding vs. Clean
```hcl
# BAD: Will generate critical findings
resource "aws_iam_policy" "bad_policy" {
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = "*"
Resource = "*"
}]
})
}
# GOOD: Least privilege
resource "aws_iam_policy" "good_policy" {
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = ["s3:GetObject", "s3:PutObject"]
Resource = "arn:aws:s3:::my-specific-bucket/*"
}]
})
}
```
Full CSPM check reference: `references/cspm-checks.md`
---
## Cloud Provider Coverage Matrix
| Check Type | AWS | Azure | GCP |
|-----------|-----|-------|-----|
| IAM privilege escalation | Full (IAM policies, trust policies, ESCALATION_COMBOS) | Partial (RBAC assignments, service principal risks) | Partial (IAM bindings, workload identity) |
| Storage public access | Full (S3 bucket policies, ACLs, public access block) | Partial (Blob SAS tokens, container access levels) | Partial (GCS bucket IAM, uniform bucket-level access) |
| Network exposure | Full (Security Groups, NACLs, port-level analysis) | Partial (NSG rules, inbound port analysis) | Partial (Firewall rules, VPC firewall) |
| IaC scanning | Full (Terraform, CloudFormation) | Partial (ARM templates, Bicep) | Partial (Deployment Manager) |
---
## Workflows
### Workflow 1: Quick Posture Check (20 Minutes)
For a newly provisioned resource or pre-deployment review:
```bash
# 1. Export IAM policy document
aws iam get-policy-version --policy-arn ARN --version-id v1 | \
jq '.PolicyVersion.Document' > policy.json
python3 scripts/cloud_posture_check.py policy.json --check iam --json
# 2. Check S3 bucket configuration
aws s3api get-bucket-acl --bucket my-bucket > acl.json
aws s3api get-public-access-block --bucket my-bucket >> bucket.json
python3 scripts/cloud_posture_check.py bucket.json --check s3 --json
# 3. Review security groups for open admin ports
aws ec2 describe-security-groups --group-ids sg-123456 | \
jq '.SecurityGroups[0]' > sg.json
python3 scripts/cloud_posture_check.py sg.json --check sg --json
```
**Decision**: Exit code 2 = block deployment and remediate. Exit code 1 = schedule remediation within 24 hours.
### Workflow 2: Full Cloud Security Assessment (Multi-Day)
**Day 1 — IAM and Identity:**
1. Export all IAM policies attached to production roles
2. Run cloud_posture_check.py --check iam on each policy
3. Map all privilege escalation paths found
4. Identify overprivileged service accounts and roles
5. Review cross-account trust policies
**Day 2 — Storage and Network:**
1. Enumerate all S3 buckets and export configurations
2. Run cloud_posture_check.py --check s3 --severity-modifier regulated-data for data buckets
3. Export security group configurations for all VPCs
4. Run cloud_posture_check.py --check sg for internet-facing resources
5. Review NACL rules for network segmentation gaps
**Day 3 — IaC and Continuous Integration:**
1. Review Terraform/CloudFormation templates in version control
2. Check CI/CD pipeline for IaC security gates
3. Validate findings against `references/cspm-checks.md`
4. Produce remediation plan with priority ordering (Critical → High → Medium)
### Workflow 3: CI/CD Security Gate
Integrate posture checks into deployment pipelines to prevent misconfigured resources reaching production:
```bash
# Validate IaC before terraform apply
terraform show -json plan.json | \
jq '[.resource_changes[].change.after | select(. != null)]' > resources.json
python3 scripts/cloud_posture_check.py resources.json --check all --json
if [ $? -eq 2 ]; then
echo "Critical cloud security findings — blocking deployment"
exit 1
fi
# Validate existing S3 bucket before modifying
aws s3api get-bucket-policy --bucket "BUCKET" | jq '.Policy | fromjson' | \
python3 scripts/cloud_posture_check.py - --check s3 \
--severity-modifier regulated-data --json
```
---
## Anti-Patterns
1. **Running IAM analysis without checking escalation combos** — Individual high-risk actions in isolation may appear low-risk. The danger is in combinations: `iam:PassRole` alone is not critical, but `iam:PassRole + lambda:CreateFunction` is a confirmed privilege escalation path. Always analyze the full statement, not individual actions.
2. **Enabling only bucket-level public access block** — AWS S3 has both account-level and bucket-level public access block settings. A bucket-level setting can override an account-level setting. Both must be configured. Account-level block alone is insufficient if any bucket has explicit overrides.
3. **Treating `--severity-modifier internet-facing` as optional for public resources** — Internet-facing resources have significantly higher exposure than internal resources. High findings on internet-facing infrastructure should be treated as critical. Always apply `--severity-modifier internet-facing` for DMZ, load balancer, and API gateway configurations.
4. **Checking only administrator policies** — Privilege escalation paths frequently originate from non-administrator policies that combine innocuous-looking permissions. All policies attached to production identities must be checked, not just policies with obvious elevated access.
5. **Remediating findings without root cause analysis** — Removing a dangerous permission without understanding why it was granted will result in re-addition. Document the business justification for every high-risk permission before removing it, to prevent silent re-introduction.
6. **Ignoring service account over-permissioning** — Service accounts are often over-provisioned during development and never trimmed for production. Every service account in production must be audited against AWS Access Analyzer or equivalent to identify and remove unused permissions.
7. **Not applying severity modifiers for regulated data workloads** — A high finding in a general-purpose S3 bucket is different from the same finding in a bucket containing PHI or cardholder data. Always use `--severity-modifier regulated-data` when assessing resources in regulated data environments.
---
## Cross-References
| Skill | Relationship |
|-------|-------------|
| [incident-response](../incident-response/SKILL.md) | Critical findings (public S3, privilege escalation confirmed active) may trigger incident classification |
| [threat-detection](../threat-detection/SKILL.md) | Cloud posture findings create hunting targets — over-permissioned roles are likely lateral movement destinations |
| [red-team](../red-team/SKILL.md) | Red team exercises specifically test exploitability of cloud misconfigurations found in posture assessment |
| [security-pen-testing](../security-pen-testing/SKILL.md) | Cloud posture findings feed into the infrastructure security section of pen test assessments |
FILE:references/cspm-checks.md
# CSPM Check Reference
Complete check matrices for cloud security posture management across AWS, Azure, and GCP. Each check includes finding condition, severity, MITRE ATT&CK technique, and remediation guidance.
---
## AWS IAM Checks
| Check | Finding Condition | Severity | MITRE | Remediation |
|-------|------------------|----------|-------|-------------|
| Full admin wildcard | `Action: *` + `Resource: *` in Allow statement | Critical | T1078.004 | Replace with service-specific scoped policies |
| Public principal | `Principal: *` in Allow statement | Critical | T1190 | Restrict to specific account ARNs + aws:PrincipalOrgID condition |
| Lambda PassRole combo | `iam:PassRole` + `lambda:CreateFunction` | Critical | T1078.004 | Remove iam:PassRole or restrict to specific function ARNs |
| EC2 PassRole combo | `iam:PassRole` + `ec2:RunInstances` | Critical | T1078.004 | Remove iam:PassRole or restrict to specific instance profile ARNs |
| CloudFormation PassRole | `iam:PassRole` + `cloudformation:CreateStack` | Critical | T1078.004 | Restrict PassRole to specific service role ARNs |
| Self-attach escalation | `iam:AttachUserPolicy` + `sts:GetCallerIdentity` | Critical | T1484.001 | Remove iam:AttachUserPolicy from non-admin policies |
| Policy version backdoor | `iam:CreatePolicyVersion` + `iam:ListPolicies` | Critical | T1484.001 | Restrict CreatePolicyVersion to named policy ARNs |
| Service-level wildcard | `iam:*`, `s3:*`, `ec2:*`, etc. | High | T1078.004 | Replace with specific required actions |
| Credential harvesting | `iam:CreateAccessKey` + `iam:ListUsers` | High | T1098.001 | Separate roles; restrict CreateAccessKey to self only |
| Data exfil on wildcard | `s3:GetObject` on `Resource: *` | High | T1530 | Restrict to specific bucket ARNs |
| Secrets exfil on wildcard | `secretsmanager:GetSecretValue` on `Resource: *` | High | T1552 | Restrict to specific secret ARNs |
---
## AWS S3 Checks
| Check | Finding Condition | Severity | MITRE | Remediation |
|-------|------------------|----------|-------|-------------|
| Public access block missing | Any of four flags = false or absent | High | T1530 | Enable all four flags at bucket and account level |
| Bucket ACL public-read-write | ACL = public-read-write | Critical | T1530 | Set ACL = private; use bucket policy for access control |
| Bucket ACL public-read | ACL = public-read or authenticated-read | High | T1530 | Set ACL = private |
| Bucket policy Principal:* | Statement with Effect=Allow, Principal=* | Critical | T1190 | Restrict Principal to specific ARNs + aws:PrincipalOrgID |
| No default encryption | No ServerSideEncryptionConfiguration | High | T1530 | Add default encryption rule (AES256 or aws:kms) |
| Non-standard encryption | SSEAlgorithm not in {AES256, aws:kms, aws:kms:dsse} | Medium | T1530 | Switch to standard SSE algorithm |
| Versioning disabled | VersioningConfiguration = Suspended or absent | Medium | T1485 | Enable versioning to protect against ransomware deletion |
| Access logging disabled | LoggingEnabled absent | Low | T1530 | Enable server access logging for audit trail |
---
## AWS Security Group Checks
| Check | Finding Condition | Severity | MITRE | Remediation |
|-------|------------------|----------|-------|-------------|
| All traffic open | Protocol=-1 (all) from 0.0.0.0/0 or ::/0 | Critical | T1190 | Remove rule; add specific required ports only |
| SSH open | Port 22 from 0.0.0.0/0 or ::/0 | Critical | T1110 | Restrict to VPN CIDR or use AWS Systems Manager Session Manager |
| RDP open | Port 3389 from 0.0.0.0/0 or ::/0 | Critical | T1110 | Restrict to VPN CIDR or use AWS Fleet Manager |
| MySQL open | Port 3306 from 0.0.0.0/0 or ::/0 | High | T1190 | Move DB to private subnet; allow only from app tier SG |
| PostgreSQL open | Port 5432 from 0.0.0.0/0 or ::/0 | High | T1190 | Move DB to private subnet; allow only from app tier SG |
| MSSQL open | Port 1433 from 0.0.0.0/0 or ::/0 | High | T1190 | Move DB to private subnet; allow only from app tier SG |
| MongoDB open | Port 27017 from 0.0.0.0/0 or ::/0 | High | T1190 | Move DB to private subnet; allow only from app tier SG |
| Redis open | Port 6379 from 0.0.0.0/0 or ::/0 | High | T1190 | Move Redis to private subnet; allow only from app tier SG |
| Elasticsearch open | Port 9200 from 0.0.0.0/0 or ::/0 | High | T1190 | Move to private subnet; use VPC endpoint |
---
## Azure Checks
| Check | Service | Finding Condition | Severity | Remediation |
|-------|---------|------------------|----------|-------------|
| Owner role assigned broadly | Entra ID RBAC | Owner role assigned to more than break-glass accounts at subscription scope | Critical | Use least-privilege built-in roles; restrict Owner to named individuals |
| Guest user with privileged role | Entra ID | Guest account assigned Contributor or Owner | High | Remove guest from privileged roles; use B2B identity governance |
| Blob container public access | Azure Storage | Container `publicAccess` = Blob or Container | Critical | Set to None; use SAS tokens for external access |
| Storage account HTTPS only = false | Azure Storage | `supportsHttpsTrafficOnly` = false | High | Enable HTTPS-only traffic |
| Storage account network rules allow all | Azure Storage | `networkAcls.defaultAction` = Allow | High | Set defaultAction = Deny; add specific VNet rules |
| NSG rule allows any-to-any | Azure NSG | Inbound rule with SourceAddressPrefix = * and DestinationPortRange = * | Critical | Replace with specific port and source ranges |
| NSG allows SSH from internet | Azure NSG | Port 22 inbound from 0.0.0.0/0 | Critical | Restrict to VPN or use Azure Bastion |
| Key Vault soft-delete disabled | Azure Key Vault | `softDeleteEnabled` = false | High | Enable soft delete and purge protection |
| MFA not required for admin | Entra ID | Global Administrator without MFA enforcement | Critical | Enforce MFA via Conditional Access for all privileged roles |
| PIM not used for privileged roles | Entra ID | Standing assignment to privileged role (not eligible) | High | Migrate to PIM eligible assignments with JIT activation |
---
## GCP Checks
| Check | Service | Finding Condition | Severity | Remediation |
|-------|---------|------------------|----------|-------------|
| Service account has project Owner | Cloud IAM | Service account bound to roles/owner | Critical | Replace with specific required roles |
| Primitive role on project | Cloud IAM | roles/owner, roles/editor, or roles/viewer on project | High | Replace with predefined or custom roles |
| Public storage bucket | Cloud Storage | `allUsers` or `allAuthenticatedUsers` in bucket IAM | Critical | Remove public members; use signed URLs for external access |
| Bucket uniform access disabled | Cloud Storage | `uniformBucketLevelAccess.enabled` = false | Medium | Enable uniform bucket-level access |
| Firewall rule allows all ingress | Cloud VPC | Ingress rule with sourceRanges = 0.0.0.0/0 and ports = all | Critical | Replace with specific ports and source ranges |
| SSH firewall rule from internet | Cloud VPC | Port 22 ingress from 0.0.0.0/0 | Critical | Restrict to IAP CIDR (35.235.240.0/20) or use IAP TCP tunneling |
| Audit logging disabled | Cloud Audit Logs | Admin activity or data access logs disabled for a service | High | Enable audit logging for all services, especially IAM and storage |
| Default service account used | Compute Engine | Instance using the default compute service account | Medium | Create dedicated service accounts with minimal required scopes |
| Serial port access enabled | Compute Engine | `metadata.serial-port-enable` = true | Medium | Disable serial port access; use OS Login instead |
---
## IaC Check Matrix
### Terraform AWS Provider
| Resource | Property | Insecure Value | Remediation |
|----------|----------|---------------|-------------|
| `aws_s3_bucket_acl` | `acl` | `public-read`, `public-read-write` | Set to `private` |
| `aws_s3_bucket_public_access_block` | `block_public_acls` | `false` or absent | Set to `true` |
| `aws_security_group_rule` | `cidr_blocks` with port 22 | `["0.0.0.0/0"]` | Restrict to VPN CIDR |
| `aws_iam_policy_document` | `actions` | `["*"]` | Specify required actions |
| `aws_iam_policy_document` | `resources` | `["*"]` | Specify resource ARNs |
### Kubernetes
| Resource | Property | Insecure Value | Remediation |
|----------|----------|---------------|-------------|
| Pod/Deployment | `securityContext.runAsRoot` | `true` | Run as non-root user |
| Pod/Deployment | `securityContext.privileged` | `true` | Remove privileged flag |
| ServiceAccount | `automountServiceAccountToken` | `true` (default) | Set to `false` unless required |
| NetworkPolicy | Missing | No NetworkPolicy defined for namespace | Add default-deny ingress/egress policy |
| Secret | Type | Credentials in ConfigMap instead of Secret | Move to Kubernetes Secrets or external secrets manager |
FILE:scripts/cloud_posture_check.py
#!/usr/bin/env python3
"""
cloud_posture_check.py — Cloud Security Posture Check
Analyses IAM policies and cloud resource configurations for privilege
escalation paths, data exfiltration risks, public exposure, S3 bucket
misconfigurations, and Security Group dangerous inbound rules.
Supports AWS (full), with Azure/GCP stubs for future expansion.
Usage:
python3 cloud_posture_check.py policy.json
python3 cloud_posture_check.py policy.json --check privilege-escalation --json
python3 cloud_posture_check.py sg.json --check sg --provider aws --json
python3 cloud_posture_check.py bucket.json --check s3 --severity-modifier internet-facing
Exit codes:
0 No findings or informational only
1 High-severity findings present
2 Critical findings present
"""
import argparse
import json
import sys
from dataclasses import dataclass, field, asdict
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional, Tuple
# ---------------------------------------------------------------------------
# IAM Analysis Constants (from analyze_iam_policy.py base)
# ---------------------------------------------------------------------------
PRIVILEGE_ESCALATION_ACTIONS: List[str] = [
"iam:CreatePolicyVersion",
"iam:SetDefaultPolicyVersion",
"iam:PassRole",
"iam:CreateAccessKey",
"iam:CreateLoginProfile",
"iam:UpdateLoginProfile",
"iam:AttachUserPolicy",
"iam:AttachGroupPolicy",
"iam:AttachRolePolicy",
"iam:PutUserPolicy",
"iam:PutGroupPolicy",
"iam:PutRolePolicy",
"iam:AddUserToGroup",
"iam:UpdateAssumeRolePolicy",
"sts:AssumeRole",
"iam:CreateRole",
"iam:DeletePolicyVersion",
"iam:CreateUser",
"iam:UpdateAccessKey",
"iam:DeactivateMFADevice",
"iam:DeleteVirtualMFADevice",
"iam:ResyncMFADevice",
"iam:EnableMFADevice",
"iam:DeleteUserPermissionsBoundary",
"iam:DeleteRolePermissionsBoundary",
"lambda:CreateFunction",
"lambda:InvokeFunction",
"lambda:UpdateFunctionCode",
"lambda:AddPermission",
"ec2:RunInstances",
"ec2:AssociateIamInstanceProfile",
"ec2:ReplaceIamInstanceProfileAssociation",
"cloudformation:CreateStack",
"cloudformation:UpdateStack",
"datapipeline:CreatePipeline",
"datapipeline:PutPipelineDefinition",
"glue:CreateDevEndpoint",
"glue:UpdateDevEndpoint",
"codestar:CreateProject",
"codecommit:CreateRepository",
"ssm:SendCommand",
"ssm:StartSession",
]
ESCALATION_COMBOS: List[Dict[str, Any]] = [
{
"name": "PassRole + Lambda Invoke",
"actions": ["iam:PassRole", "lambda:InvokeFunction"],
"description": "Attacker can pass a privileged role to a Lambda function and invoke it",
"severity": "critical",
},
{
"name": "PassRole + EC2 RunInstances",
"actions": ["iam:PassRole", "ec2:RunInstances"],
"description": "Attacker can launch an EC2 instance with a privileged IAM role",
"severity": "critical",
},
{
"name": "CreatePolicyVersion + SetDefaultPolicyVersion",
"actions": ["iam:CreatePolicyVersion", "iam:SetDefaultPolicyVersion"],
"description": "Attacker can create and activate a new policy version granting full access",
"severity": "critical",
},
{
"name": "AttachUserPolicy + AdministratorAccess",
"actions": ["iam:AttachUserPolicy"],
"description": "Can attach any managed policy including AdministratorAccess to users",
"severity": "high",
},
{
"name": "PutUserPolicy + Wildcard",
"actions": ["iam:PutUserPolicy"],
"description": "Can inject inline policies with wildcard permissions",
"severity": "high",
},
{
"name": "CloudFormation Stack Manipulation",
"actions": ["cloudformation:CreateStack", "iam:PassRole"],
"description": "Attacker can deploy a CloudFormation stack with a privileged role",
"severity": "critical",
},
{
"name": "SSM Session Start",
"actions": ["ssm:StartSession"],
"description": "Can start interactive sessions on EC2 instances without SSH",
"severity": "high",
},
{
"name": "Glue Dev Endpoint",
"actions": ["glue:CreateDevEndpoint", "iam:PassRole"],
"description": "Can create a Glue dev endpoint with a privileged role for code execution",
"severity": "critical",
},
]
DATA_EXFILTRATION_ACTIONS: List[str] = [
"s3:GetObject",
"s3:ListBucket",
"s3:GetBucketAcl",
"s3:GetObjectAcl",
"s3:GetBucketPolicy",
"s3:PutBucketPolicy",
"s3:PutBucketAcl",
"s3:PutObjectAcl",
"s3:CopyObject",
"s3:HeadObject",
"rds:DescribeDBInstances",
"rds:DownloadDBLogFilePortion",
"rds:DescribeDBSnapshots",
"rds:RestoreDBInstanceFromDBSnapshot",
"dynamodb:Scan",
"dynamodb:Query",
"dynamodb:GetItem",
"dynamodb:BatchGetItem",
"ec2:DescribeInstances",
"ec2:DescribeSnapshots",
"ec2:CreateSnapshot",
"ec2:ModifySnapshotAttribute",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"secretsmanager:GetSecretValue",
"secretsmanager:ListSecrets",
"ssm:GetParameter",
"ssm:GetParameters",
"ssm:GetParametersByPath",
"kms:Decrypt",
"kms:GenerateDataKey",
"lambda:GetFunction",
"codecommit:GitPull",
"cloudtrail:StopLogging",
"cloudtrail:DeleteTrail",
"guardduty:DeleteDetector",
"logs:DeleteLogGroup",
"logs:DeleteLogStream",
]
# ---------------------------------------------------------------------------
# Data Classes
# ---------------------------------------------------------------------------
@dataclass
class IAMFinding:
"""Represents a single IAM or cloud posture finding."""
finding_id: str
category: str # privilege-escalation | data-exfil | public-exposure | s3 | sg
severity: str # critical | high | medium | low | informational
title: str
description: str
affected_actions: List[str] = field(default_factory=list)
affected_resource: str = "*"
recommendation: str = ""
mitre_technique: str = ""
@dataclass
class IAMAnalysisResult:
"""Aggregated result of an IAM / posture analysis run."""
source: str
check_mode: str
provider: str
severity_modifier: str
findings: List[IAMFinding] = field(default_factory=list)
summary: Dict[str, Any] = field(default_factory=dict)
timestamp_utc: str = ""
def __post_init__(self) -> None:
if not self.timestamp_utc:
self.timestamp_utc = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
@property
def critical_count(self) -> int:
return sum(1 for f in self.findings if f.severity == "critical")
@property
def high_count(self) -> int:
return sum(1 for f in self.findings if f.severity == "high")
@property
def medium_count(self) -> int:
return sum(1 for f in self.findings if f.severity == "medium")
@property
def low_count(self) -> int:
return sum(1 for f in self.findings if f.severity == "low")
# ---------------------------------------------------------------------------
# Severity Bump Utility
# ---------------------------------------------------------------------------
_SEV_LADDER = ["informational", "low", "medium", "high", "critical"]
def _bump_severity(severity: str, modifier: str) -> str:
"""
Bump severity up one band when modifier is internet-facing or regulated-data.
low -> medium -> high -> critical (caps at critical).
"""
if modifier not in ("internet-facing", "regulated-data"):
return severity
try:
idx = _SEV_LADDER.index(severity.lower())
return _SEV_LADDER[min(idx + 1, len(_SEV_LADDER) - 1)]
except ValueError:
return severity
# ---------------------------------------------------------------------------
# Core IAM Analysis Functions (from analyze_iam_policy.py base)
# ---------------------------------------------------------------------------
def _extract_actions(statement: dict) -> List[str]:
"""Normalise Action field to a list of lowercase strings."""
action_field = statement.get("Action") or statement.get("action") or []
if isinstance(action_field, str):
return [action_field.lower()]
return [str(a).lower() for a in action_field]
def _extract_resources(statement: dict) -> List[str]:
"""Normalise Resource field to a list of strings."""
resource_field = (
statement.get("Resource")
or statement.get("resource")
or ["*"]
)
if isinstance(resource_field, str):
return [resource_field]
return [str(r) for r in resource_field]
def _extract_principal(statement: dict) -> str:
"""Return a string representation of the Principal."""
principal = statement.get("Principal") or statement.get("principal") or "N/A"
if isinstance(principal, dict):
parts = []
for k, v in principal.items():
if isinstance(v, list):
parts.append(f"{k}:{','.join(v)}")
else:
parts.append(f"{k}:{v}")
return " | ".join(parts)
return str(principal)
def _is_allow(statement: dict) -> bool:
effect = str(statement.get("Effect") or statement.get("effect") or "Allow")
return effect.strip().lower() == "allow"
def analyze_statement(
statement: dict,
check_mode: str,
finding_prefix: str,
severity_modifier: str,
) -> List[IAMFinding]:
"""
Analyse a single IAM policy statement for risks.
Args:
statement: Parsed IAM statement dict.
check_mode: One of privilege-escalation | data-exfil | public-exposure.
finding_prefix: Short string used to prefix finding IDs.
severity_modifier: internet-facing | regulated-data | none.
Returns:
List of IAMFinding objects (may be empty).
"""
findings: List[IAMFinding] = []
if not _is_allow(statement):
return findings
actions = _extract_actions(statement)
resources = _extract_resources(statement)
principal = _extract_principal(statement)
resource_str = ", ".join(resources[:3]) + ("..." if len(resources) > 3 else "")
wildcard_resource = any(r in ("*", "arn:aws:*") for r in resources)
wildcard_action = any(a in ("*", "iam:*", "s3:*", "ec2:*") for a in actions)
if check_mode == "privilege-escalation":
# Check individual high-risk actions
matched_privesc = [
a for a in actions
if a in [p.lower() for p in PRIVILEGE_ESCALATION_ACTIONS]
]
if matched_privesc:
severity = "high" if not wildcard_resource else "critical"
severity = _bump_severity(severity, severity_modifier)
findings.append(IAMFinding(
finding_id=f"{finding_prefix}-PRIVESC-{len(findings) + 1:03d}",
category="privilege-escalation",
severity=severity,
title="Privilege Escalation Actions Detected",
description=(
f"Statement grants {len(matched_privesc)} privilege escalation "
f"action(s) to principal '{principal}' on resources: {resource_str}."
),
affected_actions=matched_privesc,
affected_resource=resource_str,
recommendation=(
"Apply least-privilege: restrict IAM mutation actions to specific "
"resource ARNs and add Condition constraints. Consider permission boundaries."
),
mitre_technique="T1098",
))
# Check dangerous combos
for combo in ESCALATION_COMBOS:
combo_actions_lower = [c.lower() for c in combo["actions"]]
if all(ca in actions for ca in combo_actions_lower):
combo_sev = _bump_severity(combo["severity"], severity_modifier)
findings.append(IAMFinding(
finding_id=f"{finding_prefix}-COMBO-{len(findings) + 1:03d}",
category="privilege-escalation",
severity=combo_sev,
title=f"Escalation Combo: {combo['name']}",
description=combo["description"],
affected_actions=combo["actions"],
affected_resource=resource_str,
recommendation=(
f"Remove or scope one of the combo actions. "
f"Separate {combo['name']} permissions across different roles."
),
mitre_technique="T1548",
))
# Wildcard action with Allow
if wildcard_action:
sev = _bump_severity("critical", severity_modifier)
findings.append(IAMFinding(
finding_id=f"{finding_prefix}-WILD-{len(findings) + 1:03d}",
category="privilege-escalation",
severity=sev,
title="Wildcard Action Grant",
description=(
f"Statement uses wildcard action(s) {[a for a in actions if '*' in a]} "
f"for principal '{principal}'. This grants unrestricted access."
),
affected_actions=[a for a in actions if "*" in a],
affected_resource=resource_str,
recommendation="Replace wildcard actions with an explicit allowlist of required actions.",
mitre_technique="T1078.004",
))
elif check_mode == "data-exfil":
matched_exfil = [
a for a in actions
if a in [d.lower() for d in DATA_EXFILTRATION_ACTIONS]
]
if matched_exfil:
severity = "medium"
if wildcard_resource:
severity = "high"
# Particularly dangerous: log deletion or trail stopping
disruptive = [
a for a in matched_exfil
if any(x in a for x in ["stoplog", "deletelog", "deletetrail", "deletedetector"])
]
if disruptive:
severity = "critical"
severity = _bump_severity(severity, severity_modifier)
findings.append(IAMFinding(
finding_id=f"{finding_prefix}-EXFIL-{len(findings) + 1:03d}",
category="data-exfil",
severity=severity,
title="Data Exfiltration Risk Actions",
description=(
f"Statement grants {len(matched_exfil)} potential exfiltration "
f"action(s) to principal '{principal}': {', '.join(matched_exfil[:5])}."
),
affected_actions=matched_exfil,
affected_resource=resource_str,
recommendation=(
"Scope data-read actions to specific resource ARNs. "
"Add VPC endpoint conditions and restrict cross-account access. "
"Enable GuardDuty and CloudTrail for all regions."
),
mitre_technique="T1530",
))
elif check_mode == "public-exposure":
principal_str = _extract_principal(statement)
is_public = any(p in principal_str for p in ["*", "AWS:*", '"*"'])
if is_public:
sev = _bump_severity("high", severity_modifier)
if wildcard_action:
sev = _bump_severity("critical", severity_modifier)
findings.append(IAMFinding(
finding_id=f"{finding_prefix}-PUB-{len(findings) + 1:03d}",
category="public-exposure",
severity=sev,
title="Public Principal Detected",
description=(
f"Statement uses Principal '*' allowing any AWS account or "
f"unauthenticated entity to perform: {', '.join(actions[:5])}."
),
affected_actions=actions[:10],
affected_resource=resource_str,
recommendation=(
"Replace Principal '*' with specific account ARNs, "
"organisation units, or role ARNs. Use Condition keys "
"like aws:PrincipalOrgID to limit to your AWS Org."
),
mitre_technique="T1190",
))
return findings
def analyze_policy(
policy: dict,
check_mode: str,
source: str,
severity_modifier: str,
provider: str = "aws",
) -> IAMAnalysisResult:
"""
Analyse a full IAM policy document for findings.
Iterates over every Statement in the policy and delegates to
analyze_statement() for per-check logic.
Args:
policy: Parsed IAM policy JSON dict.
check_mode: privilege-escalation | data-exfil | public-exposure.
source: Display name / file path for the policy.
severity_modifier: internet-facing | regulated-data | none.
provider: aws | azure | gcp (currently only aws fully supported).
Returns:
IAMAnalysisResult with all findings populated.
"""
result = IAMAnalysisResult(
source=source,
check_mode=check_mode,
provider=provider,
severity_modifier=severity_modifier,
)
statements = policy.get("Statement") or policy.get("statement") or []
if not isinstance(statements, list):
statements = [statements]
prefix = source.replace(" ", "_").replace("/", "_")[:12].upper()
for idx, stmt in enumerate(statements):
stmt_findings = analyze_statement(
statement=stmt,
check_mode=check_mode,
finding_prefix=f"{prefix}-S{idx + 1:02d}",
severity_modifier=severity_modifier,
)
result.findings.extend(stmt_findings)
result.summary = {
"total_statements": len(statements),
"total_findings": len(result.findings),
"critical": result.critical_count,
"high": result.high_count,
"medium": result.medium_count,
"low": result.low_count,
"check_mode": check_mode,
"provider": provider,
"severity_modifier": severity_modifier,
}
return result
# ---------------------------------------------------------------------------
# S3 Posture Check (new)
# ---------------------------------------------------------------------------
def check_s3_policy(
policy: dict,
source: str,
severity_modifier: str,
) -> IAMAnalysisResult:
"""
Check S3 bucket policy or Terraform aws_s3_bucket block for misconfigurations.
Checks performed:
1. Principal "*" in bucket policy -> Critical
2. block_public_acls missing or false -> Critical
3. server_side_encryption absent or not AES256/aws:kms -> High
4. versioning disabled -> Medium
5. access logging disabled -> High
Args:
policy: Parsed S3 policy / Terraform block dict.
source: Display name / file path.
severity_modifier: internet-facing | regulated-data | none.
Returns:
IAMAnalysisResult populated with S3 findings.
"""
result = IAMAnalysisResult(
source=source,
check_mode="s3",
provider="aws",
severity_modifier=severity_modifier,
)
findings: List[IAMFinding] = []
fid = 0
def _next_id() -> str:
nonlocal fid
fid += 1
return f"S3-{fid:03d}"
# --- Check 1: Public principal in bucket policy ---
statements = policy.get("Statement") or policy.get("statement") or []
if isinstance(statements, list):
for stmt in statements:
if not _is_allow(stmt):
continue
principal = _extract_principal(stmt)
if "*" in principal or '"*"' in principal:
severity = _bump_severity("critical", severity_modifier)
findings.append(IAMFinding(
finding_id=_next_id(),
category="s3",
severity=severity,
title="S3 Bucket Policy: Public Principal",
description=(
"Bucket policy contains Principal '*' which grants public "
"access to any AWS account or unauthenticated user."
),
affected_actions=_extract_actions(stmt),
affected_resource=source,
recommendation=(
"Remove Principal '*'. Restrict to specific account ARNs or "
"use aws:PrincipalOrgID condition to limit to your AWS Org."
),
mitre_technique="T1530",
))
# --- Check 2: block_public_acls missing or false ---
# Terraform resource format: aws_s3_bucket_public_access_block
public_access_block = (
policy.get("block_public_acls")
or policy.get("BlockPublicAcls")
or policy.get("public_access_block", {}).get("block_public_acls")
)
restrict_public_buckets = (
policy.get("restrict_public_buckets")
or policy.get("RestrictPublicBuckets")
)
block_public_policy = (
policy.get("block_public_policy")
or policy.get("BlockPublicPolicy")
)
# If any of these are explicitly False or absent, flag it
block_fields = {
"block_public_acls": public_access_block,
"restrict_public_buckets": restrict_public_buckets,
"block_public_policy": block_public_policy,
}
missing_blocks = [k for k, v in block_fields.items() if v is None or v is False]
if missing_blocks:
severity = _bump_severity("critical", severity_modifier)
findings.append(IAMFinding(
finding_id=_next_id(),
category="s3",
severity=severity,
title="S3 Public Access Block Not Fully Enabled",
description=(
f"Public access block settings are missing or disabled: "
f"{', '.join(missing_blocks)}. This may allow public ACL or policy access."
),
affected_resource=source,
recommendation=(
"Enable all four S3 Block Public Access settings: "
"BlockPublicAcls, BlockPublicPolicy, IgnorePublicAcls, RestrictPublicBuckets."
),
mitre_technique="T1530",
))
# --- Check 3: Server-side encryption ---
sse_config = (
policy.get("server_side_encryption_configuration")
or policy.get("ServerSideEncryptionConfiguration")
or policy.get("encryption")
or policy.get("sse_algorithm")
)
has_sse = False
if isinstance(sse_config, dict):
rules = sse_config.get("Rule") or sse_config.get("rules") or []
if not isinstance(rules, list):
rules = [rules]
for rule in rules:
apply_sse = (
rule.get("ApplyServerSideEncryptionByDefault")
or rule.get("apply_server_side_encryption_by_default")
or {}
)
algo = str(apply_sse.get("SSEAlgorithm") or apply_sse.get("sse_algorithm") or "")
if algo.upper() in ("AES256", "AWS:KMS"):
has_sse = True
elif isinstance(sse_config, str):
has_sse = sse_config.upper() in ("AES256", "AWS:KMS")
if not has_sse:
severity = _bump_severity("high", severity_modifier)
findings.append(IAMFinding(
finding_id=_next_id(),
category="s3",
severity=severity,
title="S3 Server-Side Encryption Not Configured",
description=(
"No server-side encryption (SSE-S3 or SSE-KMS) found on this bucket. "
"Data is stored unencrypted at rest."
),
affected_resource=source,
recommendation=(
"Enable SSE via a bucket encryption configuration. "
"Use aws:kms with a CMK for regulated workloads. "
"Consider enforcing encryption via bucket policy (aws:SecureTransport)."
),
mitre_technique="T1022",
))
# --- Check 4: Versioning disabled ---
versioning = (
policy.get("versioning")
or policy.get("VersioningConfiguration")
)
versioning_enabled = False
if isinstance(versioning, dict):
status = str(
versioning.get("Status")
or versioning.get("status")
or versioning.get("enabled")
or ""
)
versioning_enabled = status.lower() in ("enabled", "true")
elif isinstance(versioning, bool):
versioning_enabled = versioning
if not versioning_enabled:
severity = _bump_severity("medium", severity_modifier)
findings.append(IAMFinding(
finding_id=_next_id(),
category="s3",
severity=severity,
title="S3 Bucket Versioning Disabled",
description=(
"Versioning is not enabled on this bucket. "
"Accidental or malicious object deletion/overwrite cannot be recovered."
),
affected_resource=source,
recommendation=(
"Enable bucket versioning. "
"Combine with Object Lock and lifecycle policies for regulated workloads."
),
mitre_technique="T1485",
))
result.findings = findings
result.summary = {
"total_findings": len(findings),
"critical": sum(1 for f in findings if f.severity == "critical"),
"high": sum(1 for f in findings if f.severity == "high"),
"medium": sum(1 for f in findings if f.severity == "medium"),
"low": sum(1 for f in findings if f.severity == "low"),
"check_mode": "s3",
"provider": "aws",
"severity_modifier": severity_modifier,
}
return result
# ---------------------------------------------------------------------------
# Security Group Check (new)
# ---------------------------------------------------------------------------
def check_security_group(
sg_json: dict,
source: str,
severity_modifier: str,
) -> IAMAnalysisResult:
"""
Check AWS Security Group JSON for dangerous inbound rules.
Args:
sg_json: Parsed Security Group JSON (AWS DescribeSecurityGroups
output format or Terraform aws_security_group block).
source: Display name / file path.
severity_modifier: internet-facing | regulated-data | none.
Returns:
IAMAnalysisResult populated with SG findings.
"""
RISKY_PORTS: Dict[int, str] = {
22: "SSH",
3389: "RDP",
23: "Telnet",
21: "FTP",
3306: "MySQL",
5432: "PostgreSQL",
1433: "MSSQL",
27017: "MongoDB",
6379: "Redis",
}
result = IAMAnalysisResult(
source=source,
check_mode="sg",
provider="aws",
severity_modifier=severity_modifier,
)
findings: List[IAMFinding] = []
fid = 0
def _next_id() -> str:
nonlocal fid
fid += 1
return f"SG-{fid:03d}"
# Support both AWS API format (IpPermissions) and Terraform ingress blocks
ip_permissions = sg_json.get("IpPermissions") or []
terraform_ingress = sg_json.get("ingress") or []
# Normalise Terraform ingress blocks to AWS API format
normalised: List[dict] = list(ip_permissions)
for ing in terraform_ingress:
if not isinstance(ing, dict):
continue
cidr_blocks = ing.get("cidr_blocks") or []
ipv6_cidr_blocks = ing.get("ipv6_cidr_blocks") or []
ip_ranges = [{"CidrIp": c} for c in cidr_blocks]
ipv6_ranges = [{"CidrIpv6": c} for c in ipv6_cidr_blocks]
normalised.append({
"IpProtocol": str(ing.get("protocol", "tcp")),
"FromPort": ing.get("from_port", 0),
"ToPort": ing.get("to_port", 65535),
"IpRanges": ip_ranges,
"Ipv6Ranges": ipv6_ranges,
})
for rule in normalised:
from_port = rule.get("FromPort", 0)
to_port = rule.get("ToPort", 65535)
protocol = str(rule.get("IpProtocol", "tcp"))
# Collect CIDRs from both IPv4 and IPv6 ranges
all_ranges: List[Tuple[str, str]] = []
for ip_range in rule.get("IpRanges", []):
cidr = ip_range.get("CidrIp", "")
if cidr:
all_ranges.append((cidr, "ipv4"))
for ip_range in rule.get("Ipv6Ranges", []):
cidr = ip_range.get("CidrIpv6", "")
if cidr:
all_ranges.append((cidr, "ipv6"))
for cidr, ip_ver in all_ranges:
if cidr not in ("0.0.0.0/0", "::/0"):
continue # Not open to the world
if protocol == "-1":
# All traffic open to the internet
severity = _bump_severity("critical", severity_modifier)
findings.append(IAMFinding(
finding_id=_next_id(),
category="sg",
severity=severity,
title="Security Group: All Traffic Open to Internet",
description=(
f"Inbound rule allows ALL traffic (protocol -1) "
f"from {cidr} ({ip_ver}). This exposes every port on every instance "
"in this security group to the public internet."
),
affected_resource=source,
recommendation=(
"Remove the all-traffic rule. Define explicit port/protocol "
"allowlist rules for only the services that must be internet-accessible."
),
mitre_technique="T1190",
))
continue
# Check port range against RISKY_PORTS
matched_ports = [
p for p in RISKY_PORTS
if from_port <= p <= to_port
]
if matched_ports:
for port in matched_ports:
service = RISKY_PORTS[port]
severity = _bump_severity("critical", severity_modifier)
findings.append(IAMFinding(
finding_id=_next_id(),
category="sg",
severity=severity,
title=f"Security Group: {service} ({port}) Open to Internet",
description=(
f"Inbound rule allows {service} (port {port}/{protocol}) "
f"from {cidr} ({ip_ver}). Direct internet access to {service} "
"exposes this service to brute-force, exploitation, and scanning."
),
affected_resource=source,
recommendation=(
f"Restrict port {port} to specific trusted CIDRs or a VPN/bastion. "
f"For {service}, consider using AWS Systems Manager Session Manager "
"as a zero-trust alternative that requires no open inbound ports."
),
mitre_technique="T1133",
))
else:
# Open to the internet on a non-standard port
severity = _bump_severity("high", severity_modifier)
port_label = (
f"port {from_port}"
if from_port == to_port
else f"ports {from_port}-{to_port}"
)
findings.append(IAMFinding(
finding_id=_next_id(),
category="sg",
severity=severity,
title=f"Security Group: {port_label.title()} Open to Internet",
description=(
f"Inbound rule opens {port_label} ({protocol}) to {cidr} ({ip_ver}). "
"Broad internet exposure increases attack surface even on non-standard ports."
),
affected_resource=source,
recommendation=(
f"Restrict {port_label} to the specific IP ranges that require access. "
"Use Security Group references instead of CIDRs where possible."
),
mitre_technique="T1046",
))
result.findings = findings
result.summary = {
"total_findings": len(findings),
"critical": sum(1 for f in findings if f.severity == "critical"),
"high": sum(1 for f in findings if f.severity == "high"),
"medium": sum(1 for f in findings if f.severity == "medium"),
"low": sum(1 for f in findings if f.severity == "low"),
"check_mode": "sg",
"provider": "aws",
"severity_modifier": severity_modifier,
}
return result
# ---------------------------------------------------------------------------
# Text Report
# ---------------------------------------------------------------------------
def print_text_report(result: IAMAnalysisResult) -> None:
"""Print a formatted text report for the analysis result."""
sep = "=" * 70
print(sep)
print(" Cloud Posture Check")
print(sep)
print(f" Source : {result.source}")
print(f" Check Mode : {result.check_mode}")
print(f" Provider : {result.provider.upper()}")
print(f" Severity Mod : {result.severity_modifier}")
print(f" Timestamp : {result.timestamp_utc}")
print(sep)
summary = result.summary
print(f"\n Summary:")
print(f" Total Findings : {summary.get('total_findings', 0)}")
if summary.get("critical", 0):
print(f" CRITICAL : {summary['critical']}")
if summary.get("high", 0):
print(f" HIGH : {summary['high']}")
if summary.get("medium", 0):
print(f" MEDIUM : {summary['medium']}")
if summary.get("low", 0):
print(f" LOW : {summary['low']}")
if not result.findings:
print("\n No findings detected.")
print(sep)
return
print(f"\n Findings ({len(result.findings)}):")
for finding in result.findings:
print(f"\n [{finding.severity.upper()}] {finding.finding_id}: {finding.title}")
print(f" {finding.description}")
if finding.affected_actions:
preview = finding.affected_actions[:4]
suffix = f" (+{len(finding.affected_actions) - 4} more)" if len(finding.affected_actions) > 4 else ""
print(f" Actions : {', '.join(preview)}{suffix}")
print(f" Resource : {finding.affected_resource}")
print(f" MITRE : {finding.mitre_technique}")
print(f" Fix : {finding.recommendation}")
print(f"\n{sep}")
# ---------------------------------------------------------------------------
# Result Serialisation
# ---------------------------------------------------------------------------
def result_to_dict(result: IAMAnalysisResult) -> dict:
"""Convert IAMAnalysisResult to a JSON-serialisable dict."""
return {
"source": result.source,
"check_mode": result.check_mode,
"provider": result.provider,
"severity_modifier": result.severity_modifier,
"timestamp_utc": result.timestamp_utc,
"summary": result.summary,
"findings": [asdict(f) for f in result.findings],
}
# ---------------------------------------------------------------------------
# Main Entry Point
# ---------------------------------------------------------------------------
def main() -> None:
parser = argparse.ArgumentParser(
description="Cloud Security Posture Check — IAM, S3, and Security Group analysis",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
%(prog)s policy.json
%(prog)s policy.json --check privilege-escalation --json
%(prog)s policy.json --check data-exfil --severity-modifier regulated-data --json
%(prog)s policy.json --check public-exposure --json
%(prog)s bucket.json --check s3 --severity-modifier internet-facing --json
%(prog)s sg.json --check sg --provider aws --json
%(prog)s policy.json --check all --json
Exit codes:
0 No findings or informational only
1 High-severity findings present
2 Critical findings present
""",
)
parser.add_argument(
"input_file",
help="Path to JSON file (IAM policy, S3 config, or Security Group JSON)",
)
parser.add_argument(
"--check",
choices=["privilege-escalation", "data-exfil", "public-exposure", "s3", "sg", "all"],
default="privilege-escalation",
help="Check mode to run (default: privilege-escalation)",
)
parser.add_argument(
"--provider",
choices=["aws", "azure", "gcp"],
default="aws",
help="Cloud provider (default: aws; Azure/GCP: only IAM checks available)",
)
parser.add_argument(
"--severity-modifier",
choices=["internet-facing", "regulated-data", "none"],
default="none",
dest="severity_modifier",
help="Bump all finding severities +1 band (default: none)",
)
parser.add_argument(
"--json",
action="store_true",
help="Output results as JSON",
)
parser.add_argument(
"--output", "-o",
metavar="FILE",
help="Write JSON output to file",
)
args = parser.parse_args()
# --- Load input file ---
try:
with open(args.input_file, "r", encoding="utf-8") as fh:
policy_data = json.load(fh)
except FileNotFoundError:
err = {"error": f"File not found: {args.input_file}"}
if args.json:
print(json.dumps(err, indent=2))
else:
print(f"Error: {err['error']}", file=sys.stderr)
sys.exit(1)
except json.JSONDecodeError as exc:
err = {"error": f"Invalid JSON: {exc}"}
if args.json:
print(json.dumps(err, indent=2))
else:
print(f"Error: {err['error']}", file=sys.stderr)
sys.exit(1)
source = args.input_file
severity_modifier = args.severity_modifier
provider = args.provider
# --- Gate S3 / SG checks by provider ---
check_mode = args.check
if check_mode in ("s3", "sg") and provider != "aws":
msg = (
f"Azure/GCP checks coming soon — "
f"use --provider aws for S3/SG analysis"
)
if args.json:
print(json.dumps({"message": msg, "provider": provider, "check_mode": check_mode}, indent=2))
else:
print(msg)
sys.exit(0)
# --- Run checks ---
all_results: List[IAMAnalysisResult] = []
iam_check_modes = ["privilege-escalation", "data-exfil", "public-exposure"]
if check_mode == "all":
if provider == "aws":
# Run all IAM checks
for mode in iam_check_modes:
r = analyze_policy(
policy=policy_data,
check_mode=mode,
source=source,
severity_modifier=severity_modifier,
provider=provider,
)
all_results.append(r)
# Run S3
s3_r = check_s3_policy(
policy=policy_data,
source=source,
severity_modifier=severity_modifier,
)
all_results.append(s3_r)
# Run SG
sg_r = check_security_group(
sg_json=policy_data,
source=source,
severity_modifier=severity_modifier,
)
all_results.append(sg_r)
else:
for mode in iam_check_modes:
r = analyze_policy(
policy=policy_data,
check_mode=mode,
source=source,
severity_modifier=severity_modifier,
provider=provider,
)
all_results.append(r)
elif check_mode in iam_check_modes:
r = analyze_policy(
policy=policy_data,
check_mode=check_mode,
source=source,
severity_modifier=severity_modifier,
provider=provider,
)
all_results.append(r)
elif check_mode == "s3":
r = check_s3_policy(
policy=policy_data,
source=source,
severity_modifier=severity_modifier,
)
all_results.append(r)
elif check_mode == "sg":
r = check_security_group(
sg_json=policy_data,
source=source,
severity_modifier=severity_modifier,
)
all_results.append(r)
# --- Flatten findings for output when multiple checks run ---
if len(all_results) == 1:
combined_result = all_results[0]
else:
# Merge into a single result
all_findings: List[IAMFinding] = []
for res in all_results:
all_findings.extend(res.findings)
combined_result = IAMAnalysisResult(
source=source,
check_mode=check_mode,
provider=provider,
severity_modifier=severity_modifier,
)
combined_result.findings = all_findings
combined_result.summary = {
"total_findings": len(all_findings),
"critical": sum(1 for f in all_findings if f.severity == "critical"),
"high": sum(1 for f in all_findings if f.severity == "high"),
"medium": sum(1 for f in all_findings if f.severity == "medium"),
"low": sum(1 for f in all_findings if f.severity == "low"),
"check_mode": check_mode,
"provider": provider,
"severity_modifier": severity_modifier,
"checks_run": [r.check_mode for r in all_results],
}
# --- Output ---
if args.json or args.output:
output_dict = result_to_dict(combined_result)
json_str = json.dumps(output_dict, indent=2)
if args.output:
with open(args.output, "w", encoding="utf-8") as fh:
fh.write(json_str)
if not args.json:
print(f"Results written to {args.output}")
if args.json:
print(json_str)
else:
print_text_report(combined_result)
# --- Exit code ---
if combined_result.critical_count > 0:
sys.exit(2)
if combined_result.high_count > 0:
sys.exit(1)
sys.exit(0)
if __name__ == "__main__":
main()
Tạo sơ đồ ERD, chuẩn hóa lược đồ, thiết kế quan hệ bảng và lập kế hoạch migration lược đồ.
---
name: "database-schema-designer"
description: "Use when the user asks to create ERD diagrams, normalize database schemas, design table relationships, or plan schema migrations."
---
# Database Schema Designer
**Tier:** POWERFUL
**Category:** Engineering
**Domain:** Data Architecture / Backend
---
## Overview
Design relational database schemas from requirements and generate migrations, TypeScript/Python types, seed data, RLS policies, and indexes. Handles multi-tenancy, soft deletes, audit trails, versioning, and polymorphic associations.
## Core Capabilities
- **Schema design** — normalize requirements into tables, relationships, constraints
- **Migration generation** — Drizzle, Prisma, TypeORM, Alembic
- **Type generation** — TypeScript interfaces, Python dataclasses/Pydantic models
- **RLS policies** — Row-Level Security for multi-tenant apps
- **Index strategy** — composite indexes, partial indexes, covering indexes
- **Seed data** — realistic test data generation
- **ERD generation** — Mermaid diagram from schema
---
## When to Use
- Designing a new feature that needs database tables
- Reviewing a schema for performance or normalization issues
- Adding multi-tenancy to an existing schema
- Generating TypeScript types from a Prisma schema
- Planning a schema migration for a breaking change
---
## Schema Design Process
### Step 1: Requirements → Entities
Given requirements:
> "Users can create projects. Each project has tasks. Tasks can have labels. Tasks can be assigned to users. We need a full audit trail."
Extract entities:
```
User, Project, Task, Label, TaskLabel (junction), TaskAssignment, AuditLog
```
### Step 2: Identify Relationships
```
User 1──* Project (owner)
Project 1──* Task
Task *──* Label (via TaskLabel)
Task *──* User (via TaskAssignment)
User 1──* AuditLog
```
### Step 3: Add Cross-cutting Concerns
- Multi-tenancy: add `organization_id` to all tenant-scoped tables
- Soft deletes: add `deleted_at TIMESTAMPTZ` instead of hard deletes
- Audit trail: add `created_by`, `updated_by`, `created_at`, `updated_at`
- Versioning: add `version INTEGER` for optimistic locking
---
## Full Schema Example (Task Management SaaS)
→ See references/full-schema-examples.md for details
## Row-Level Security (RLS) Policies
```sql
-- Enable RLS
ALTER TABLE tasks ENABLE ROW LEVEL SECURITY;
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
-- Create app role
CREATE ROLE app_user;
-- Users can only see tasks in their organization's projects
CREATE POLICY tasks_org_isolation ON tasks
FOR ALL TO app_user
USING (
project_id IN (
SELECT p.id FROM projects p
JOIN organization_members om ON om.organization_id = p.organization_id
WHERE om.user_id = current_setting('app.current_user_id')::text
)
);
-- Soft delete: never show deleted records
CREATE POLICY tasks_no_deleted ON tasks
FOR SELECT TO app_user
USING (deleted_at IS NULL);
-- Only task creator or admin can delete
CREATE POLICY tasks_delete_policy ON tasks
FOR DELETE TO app_user
USING (
created_by_id = current_setting('app.current_user_id')::text
OR EXISTS (
SELECT 1 FROM organization_members om
JOIN projects p ON p.organization_id = om.organization_id
WHERE p.id = tasks.project_id
AND om.user_id = current_setting('app.current_user_id')::text
AND om.role IN ('owner', 'admin')
)
);
-- Set user context (call at start of each request)
SELECT set_config('app.current_user_id', $1, true);
```
---
## Seed Data Generation
```typescript
// db/seed.ts
import { faker } from '@faker-js/faker'
import { db } from './client'
import { organizations, users, projects, tasks } from './schema'
import { createId } from '@paralleldrive/cuid2'
import { hashPassword } from '../src/lib/auth'
async function seed() {
console.log('Seeding database...')
// Create org
const [org] = await db.insert(organizations).values({
id: createId(),
name: "acme-corp",
slug: 'acme',
plan: 'growth',
}).returning()
// Create users
const adminUser = await db.insert(users).values({
id: createId(),
email: 'admin@acme.com',
name: "alice-admin",
passwordHash: await hashPassword('password123'),
}).returning().then(r => r[0])
// Create projects
const projectsData = Array.from({ length: 3 }, () => ({
id: createId(),
organizationId: org.id,
ownerId: adminUser.id,
name: "fakercompanycatchphrase"
description: faker.lorem.paragraph(),
status: 'active' as const,
}))
const createdProjects = await db.insert(projects).values(projectsData).returning()
// Create tasks for each project
for (const project of createdProjects) {
const tasksData = Array.from({ length: faker.number.int({ min: 5, max: 20 }) }, (_, i) => ({
id: createId(),
projectId: project.id,
title: faker.hacker.phrase(),
description: faker.lorem.sentences(2),
status: faker.helpers.arrayElement(['todo', 'in_progress', 'done'] as const),
priority: faker.helpers.arrayElement(['low', 'medium', 'high'] as const),
position: i * 1000,
createdById: adminUser.id,
updatedById: adminUser.id,
}))
await db.insert(tasks).values(tasksData)
}
console.log(`✅ Seeded: 1 org, projectsData.length projects, tasks`)
}
seed().catch(console.error).finally(() => process.exit(0))
```
---
## ERD Generation (Mermaid)
```
erDiagram
Organization ||--o{ OrganizationMember : has
Organization ||--o{ Project : owns
User ||--o{ OrganizationMember : joins
User ||--o{ Task : "created by"
Project ||--o{ Task : contains
Task ||--o{ TaskAssignment : has
Task ||--o{ TaskLabel : has
Task ||--o{ Comment : has
Task ||--o{ Attachment : has
Label ||--o{ TaskLabel : "applied to"
User ||--o{ TaskAssignment : assigned
Organization {
string id PK
string name
string slug
string plan
}
Task {
string id PK
string project_id FK
string title
string status
string priority
timestamp due_date
timestamp deleted_at
int version
}
```
Generate from Prisma:
```bash
npx prisma-erd-generator
# or: npx @dbml/cli prisma2dbml -i schema.prisma | npx dbml-to-mermaid
```
---
## Common Pitfalls
- **Soft delete without index** — `WHERE deleted_at IS NULL` without index = full scan
- **Missing composite indexes** — `WHERE org_id = ? AND status = ?` needs a composite index
- **Mutable surrogate keys** — never use email or slug as PK; use UUID/CUID
- **Non-nullable without default** — adding a NOT NULL column to existing table requires default or migration plan
- **No optimistic locking** — concurrent updates overwrite each other; add `version` column
- **RLS not tested** — always test RLS with a non-superuser role
---
## Best Practices
1. **Timestamps everywhere** — `created_at`, `updated_at` on every table
2. **Soft deletes for auditable data** — `deleted_at` instead of DELETE
3. **Audit log for compliance** — log before/after JSON for regulated domains
4. **UUIDs or CUIDs as PKs** — avoid sequential integer leakage
5. **Index foreign keys** — every FK column should have an index
6. **Partial indexes** — use `WHERE deleted_at IS NULL` for active-only queries
7. **RLS over application-level filtering** — database enforces tenancy, not just app code
FILE:references/full-schema-examples.md
# database-schema-designer reference
## Full Schema Example (Task Management SaaS)
### Prisma Schema
```prisma
// schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// ── Multi-tenancy ─────────────────────────────────────────────────────────────
model Organization {
id String @id @default(cuid())
name String
slug String @unique
plan Plan @default(FREE)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
users OrganizationMember[]
projects Project[]
auditLogs AuditLog[]
@@map("organizations")
}
model OrganizationMember {
id String @id @default(cuid())
organizationId String @map("organization_id")
userId String @map("user_id")
role OrgRole @default(MEMBER)
joinedAt DateTime @default(now()) @map("joined_at")
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([organizationId, userId])
@@index([userId])
@@map("organization_members")
}
model User {
id String @id @default(cuid())
email String @unique
name String?
avatarUrl String? @map("avatar_url")
passwordHash String? @map("password_hash")
emailVerifiedAt DateTime? @map("email_verified_at")
lastLoginAt DateTime? @map("last_login_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
memberships OrganizationMember[]
ownedProjects Project[] @relation("ProjectOwner")
assignedTasks TaskAssignment[]
comments Comment[]
auditLogs AuditLog[]
@@map("users")
}
// ── Core entities ─────────────────────────────────────────────────────────────
model Project {
id String @id @default(cuid())
organizationId String @map("organization_id")
ownerId String @map("owner_id")
name String
description String?
status ProjectStatus @default(ACTIVE)
settings Json @default("{}")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
organization Organization @relation(fields: [organizationId], references: [id])
owner User @relation("ProjectOwner", fields: [ownerId], references: [id])
tasks Task[]
labels Label[]
@@index([organizationId])
@@index([organizationId, status])
@@index([deletedAt])
@@map("projects")
}
model Task {
id String @id @default(cuid())
projectId String @map("project_id")
title String
description String?
status TaskStatus @default(TODO)
priority Priority @default(MEDIUM)
dueDate DateTime? @map("due_date")
position Float @default(0) // For drag-and-drop ordering
version Int @default(1) // Optimistic locking
createdById String @map("created_by_id")
updatedById String @map("updated_by_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
project Project @relation(fields: [projectId], references: [id])
assignments TaskAssignment[]
labels TaskLabel[]
comments Comment[]
attachments Attachment[]
@@index([projectId])
@@index([projectId, status])
@@index([projectId, deletedAt])
@@index([dueDate], where: { deletedAt: null }) // Partial index
@@map("tasks")
}
// ── Polymorphic attachments ───────────────────────────────────────────────────
model Attachment {
id String @id @default(cuid())
// Polymorphic association
entityType String @map("entity_type") // "task" | "comment"
entityId String @map("entity_id")
filename String
mimeType String @map("mime_type")
sizeBytes Int @map("size_bytes")
storageKey String @map("storage_key") // S3 key
uploadedById String @map("uploaded_by_id")
createdAt DateTime @default(now()) @map("created_at")
// Only one concrete relation (task) — polymorphic handled at app level
task Task? @relation(fields: [entityId], references: [id], map: "attachment_task_fk")
@@index([entityType, entityId])
@@map("attachments")
}
// ── Audit trail ───────────────────────────────────────────────────────────────
model AuditLog {
id String @id @default(cuid())
organizationId String @map("organization_id")
userId String? @map("user_id")
action String // "task.created", "task.status_changed"
entityType String @map("entity_type")
entityId String @map("entity_id")
before Json? // Previous state
after Json? // New state
ipAddress String? @map("ip_address")
userAgent String? @map("user_agent")
createdAt DateTime @default(now()) @map("created_at")
organization Organization @relation(fields: [organizationId], references: [id])
user User? @relation(fields: [userId], references: [id])
@@index([organizationId, createdAt(sort: Desc)])
@@index([entityType, entityId])
@@index([userId])
@@map("audit_logs")
}
enum Plan { FREE STARTER GROWTH ENTERPRISE }
enum OrgRole { OWNER ADMIN MEMBER VIEWER }
enum ProjectStatus { ACTIVE ARCHIVED }
enum TaskStatus { TODO IN_PROGRESS IN_REVIEW DONE CANCELLED }
enum Priority { LOW MEDIUM HIGH CRITICAL }
```
---
### Drizzle Schema (TypeScript)
```typescript
// db/schema.ts
import {
pgTable, text, timestamp, integer, boolean,
varchar, jsonb, real, pgEnum, uniqueIndex, index,
} from 'drizzle-orm/pg-core'
import { createId } from '@paralleldrive/cuid2'
export const taskStatusEnum = pgEnum('task_status', [
'todo', 'in_progress', 'in_review', 'done', 'cancelled'
])
export const priorityEnum = pgEnum('priority', ['low', 'medium', 'high', 'critical'])
export const tasks = pgTable('tasks', {
id: text('id').primaryKey().$defaultFn(() => createId()),
projectId: text('project_id').notNull().references(() => projects.id),
title: varchar('title', { length: 500 }).notNull(),
description: text('description'),
status: taskStatusEnum('status').notNull().default('todo'),
priority: priorityEnum('priority').notNull().default('medium'),
dueDate: timestamp('due_date', { withTimezone: true }),
position: real('position').notNull().default(0),
version: integer('version').notNull().default(1),
createdById: text('created_by_id').notNull().references(() => users.id),
updatedById: text('updated_by_id').notNull().references(() => users.id),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
deletedAt: timestamp('deleted_at', { withTimezone: true }),
}, (table) => ({
projectIdx: index('tasks_project_id_idx').on(table.projectId),
projectStatusIdx: index('tasks_project_status_idx').on(table.projectId, table.status),
}))
// Infer TypeScript types
export type Task = typeof tasks.$inferSelect
export type NewTask = typeof tasks.$inferInsert
```
---
### Alembic Migration (Python / SQLAlchemy)
```python
# alembic/versions/20260301_create_tasks.py
"""Create tasks table
Revision ID: a1b2c3d4e5f6
Revises: previous_revision
Create Date: 2026-03-01 12:00:00
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
revision = 'a1b2c3d4e5f6'
down_revision = 'previous_revision'
def upgrade() -> None:
# Create enums
task_status = postgresql.ENUM(
'todo', 'in_progress', 'in_review', 'done', 'cancelled',
name='task_status'
)
task_status.create(op.get_bind())
op.create_table(
'tasks',
sa.Column('id', sa.Text(), primary_key=True),
sa.Column('project_id', sa.Text(), sa.ForeignKey('projects.id'), nullable=False),
sa.Column('title', sa.VARCHAR(500), nullable=False),
sa.Column('description', sa.Text()),
sa.Column('status', postgresql.ENUM('todo', 'in_progress', 'in_review', 'done', 'cancelled', name='task_status', create_type=False), nullable=False, server_default='todo'),
sa.Column('priority', sa.Text(), nullable=False, server_default='medium'),
sa.Column('due_date', sa.TIMESTAMP(timezone=True)),
sa.Column('position', sa.Float(), nullable=False, server_default='0'),
sa.Column('version', sa.Integer(), nullable=False, server_default='1'),
sa.Column('created_by_id', sa.Text(), sa.ForeignKey('users.id'), nullable=False),
sa.Column('updated_by_id', sa.Text(), sa.ForeignKey('users.id'), nullable=False),
sa.Column('created_at', sa.TIMESTAMP(timezone=True), nullable=False, server_default=sa.text('NOW()')),
sa.Column('updated_at', sa.TIMESTAMP(timezone=True), nullable=False, server_default=sa.text('NOW()')),
sa.Column('deleted_at', sa.TIMESTAMP(timezone=True)),
)
# Indexes
op.create_index('tasks_project_id_idx', 'tasks', ['project_id'])
op.create_index('tasks_project_status_idx', 'tasks', ['project_id', 'status'])
# Partial index for active tasks only
op.create_index(
'tasks_due_date_active_idx',
'tasks', ['due_date'],
postgresql_where=sa.text('deleted_at IS NULL')
)
def downgrade() -> None:
op.drop_table('tasks')
op.execute("DROP TYPE IF EXISTS task_status")
```
---
Thiết kế kiến trúc GCP: triển khai GKE, Cloud Run, pipeline BigQuery, tối ưu chi phí và di chuyển lên Google Cloud.
---
name: "gcp-cloud-architect"
description: "Design GCP architectures for startups and enterprises. Use when asked to design Google Cloud infrastructure, deploy to GKE or Cloud Run, configure BigQuery pipelines, optimize GCP costs, or migrate to GCP. Covers Cloud Run, GKE, Cloud Functions, Cloud SQL, BigQuery, and cost optimization."
---
# GCP Cloud Architect
Design scalable, cost-effective Google Cloud architectures for startups and enterprises 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 GCP 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": ["Cloud Storage", "Cloud CDN", "Cloud Run", "Firestore", "Identity Platform"],
"estimated_monthly_cost_usd": 30,
"pros": ["Low ops overhead", "Pay-per-use", "Auto-scaling", "No cold starts on Cloud Run min instances"],
"cons": ["Vendor lock-in", "Regional limitations", "Eventual consistency with Firestore"]
}
```
Select from recommended patterns:
- **Serverless Web**: Cloud Storage + Cloud CDN + Cloud Run + Firestore
- **Microservices on GKE**: GKE Autopilot + Cloud SQL + Memorystore + Cloud Pub/Sub
- **Serverless Data Pipeline**: Pub/Sub + Dataflow + BigQuery + Looker
- **ML Platform**: Vertex AI + Cloud Storage + BigQuery + Cloud Functions
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: Estimate Cost
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 Cloud SQL db-custom-4-16384 to db-custom-2-8192", "savings_usd": 380, "priority": "high" },
{ "action": "Purchase 1-yr committed use discount for GKE nodes", "savings_usd": 290, "priority": "high" },
{ "action": "Move Cloud Storage objects >90 days to Nearline", "savings_usd": 75, "priority": "medium" }
],
"total_potential_savings_usd": 745
}
```
Output includes:
- Monthly cost breakdown by service
- Right-sizing recommendations
- Committed use discount opportunities
- Sustained use discount analysis
- Potential monthly savings
Use the [GCP Pricing Calculator](https://cloud.google.com/products/calculator) for detailed estimates.
### Step 4: Generate IaC
Create infrastructure-as-code for the selected pattern:
```bash
python scripts/deployment_manager.py --app-name my-app --pattern serverless_web --region us-central1
```
**Example Terraform HCL output (Cloud Run + Firestore):**
```hcl
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "~> 5.0"
}
}
}
provider "google" {
project = var.project_id
region = var.region
}
variable "project_id" {
description = "GCP project ID"
type = string
}
variable "region" {
description = "GCP region"
type = string
default = "us-central1"
}
resource "google_cloud_run_v2_service" "api" {
name = "var.environment-var.app_name-api"
location = var.region
template {
containers {
image = "gcr.io/var.project_id/var.app_name:latest"
resources {
limits = {
cpu = "1000m"
memory = "512Mi"
}
}
env {
name = "FIRESTORE_PROJECT"
value = var.project_id
}
}
scaling {
min_instance_count = 0
max_instance_count = 10
}
}
}
resource "google_firestore_database" "default" {
project = var.project_id
name = "(default)"
location_id = var.region
type = "FIRESTORE_NATIVE"
}
```
**Example gcloud CLI deployment:**
```bash
# Deploy Cloud Run service
gcloud run deploy my-app-api \
--image gcr.io/$PROJECT_ID/my-app:latest \
--region us-central1 \
--platform managed \
--allow-unauthenticated \
--memory 512Mi \
--cpu 1 \
--min-instances 0 \
--max-instances 10
# Create Firestore database
gcloud firestore databases create --location=us-central1
```
> Full templates including Cloud CDN, Identity Platform, IAM, and Cloud Monitoring are generated by `deployment_manager.py` and also available in `references/architecture_patterns.md`.
### Step 5: Configure CI/CD
Set up automated deployment with Cloud Build or GitHub Actions:
```yaml
# cloudbuild.yaml
steps:
- name: 'gcr.io/cloud-builders/docker'
args: ['build', '-t', 'gcr.io/$PROJECT_ID/my-app:$COMMIT_SHA', '.']
- name: 'gcr.io/cloud-builders/docker'
args: ['push', 'gcr.io/$PROJECT_ID/my-app:$COMMIT_SHA']
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
entrypoint: gcloud
args:
- 'run'
- 'deploy'
- 'my-app-api'
- '--image=gcr.io/$PROJECT_ID/my-app:$COMMIT_SHA'
- '--region=us-central1'
- '--platform=managed'
images:
- 'gcr.io/$PROJECT_ID/my-app:$COMMIT_SHA'
```
```bash
# Connect repo and create trigger
gcloud builds triggers create github \
--repo-name=my-app \
--repo-owner=my-org \
--branch-pattern="^main$" \
--build-config=cloudbuild.yaml
```
### Step 6: Security Review
Verify security configuration:
```bash
# Review IAM bindings
gcloud projects get-iam-policy $PROJECT_ID --format=json
# Check service account permissions
gcloud iam service-accounts list --project=$PROJECT_ID
# Verify VPC Service Controls (if applicable)
gcloud access-context-manager perimeters list --policy=$POLICY_ID
```
**Security checklist:**
- IAM roles follow least privilege (prefer predefined roles over basic roles)
- Service accounts use Workload Identity for GKE
- VPC Service Controls configured for sensitive APIs
- Cloud KMS encryption keys for customer-managed encryption
- Cloud Audit Logs enabled for all admin activity
- Organization policies restrict public access
- Secret Manager used for all credentials
**If deployment fails:**
1. Check the failure reason:
```bash
gcloud run services describe my-app-api --region us-central1
gcloud logging read "resource.type=cloud_run_revision" --limit=20
```
2. Review Cloud Logging for application errors.
3. Fix the configuration or container image.
4. Redeploy:
```bash
gcloud run deploy my-app-api --image gcr.io/$PROJECT_ID/my-app:latest --region us-central1
```
**Common failure causes:**
- IAM permission errors -- verify service account roles and `--allow-unauthenticated` flag
- Quota exceeded -- request quota increase via IAM & Admin > Quotas
- Container startup failure -- check container logs and health check configuration
- Region not enabled -- enable the required APIs with `gcloud services enable`
---
## Tools
### architecture_designer.py
Recommends GCP services based on workload 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
### cost_optimizer.py
Analyzes GCP resources for cost savings.
```bash
python scripts/cost_optimizer.py --resources inventory.json --monthly-spend 5000
```
**Output:** Recommendations for:
- Idle resource removal
- Machine type right-sizing
- Committed use discounts
- Storage class transitions
- Network egress optimization
### deployment_manager.py
Generates gcloud CLI deployment scripts and Terraform configurations.
```bash
python scripts/deployment_manager.py --app-name my-app --pattern serverless_web --region us-central1
```
**Output:** Production-ready deployment scripts with:
- Cloud Run or GKE deployment
- Firestore or Cloud SQL setup
- Identity Platform configuration
- IAM roles with least privilege
- Cloud Monitoring and Logging
---
## Quick Start
### Web App on Cloud Run (< $100/month)
```
Ask: "Design a serverless web backend for a mobile app with 1000 users"
Result:
- Cloud Run for API (auto-scaling, no cold start with min instances)
- Firestore for data (pay-per-operation)
- Identity Platform for authentication
- Cloud Storage + Cloud CDN for static assets
- Estimated: $15-40/month
```
### Microservices on GKE ($500-2000/month)
```
Ask: "Design a scalable architecture for a SaaS platform with 50k users"
Result:
- GKE Autopilot for containerized workloads
- Cloud SQL (PostgreSQL) with read replicas
- Memorystore (Redis) for session caching
- Cloud CDN for global delivery
- Cloud Build for CI/CD
- Multi-zone deployment
```
### Serverless Data Pipeline
```
Ask: "Design a real-time analytics pipeline for event data"
Result:
- Pub/Sub for event ingestion
- Dataflow (Apache Beam) for stream processing
- BigQuery for analytics and warehousing
- Looker for dashboards
- Cloud Functions for lightweight transforms
```
### ML Platform
```
Ask: "Design a machine learning platform for model training and serving"
Result:
- Vertex AI for training and prediction
- Cloud Storage for datasets and model artifacts
- BigQuery for feature store
- Cloud Functions for preprocessing triggers
- Cloud Monitoring for model drift detection
```
---
## 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 GCP limit | $500/month max |
| Team context | Size, GCP 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,
"gcp_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
- **Terraform HCL**: Production-ready Google provider configs
- **gcloud CLI**: Scripted deployment commands
- **Cloud Build YAML**: CI/CD pipeline definitions
### Cost Analysis
- Current spend breakdown with optimization recommendations
- Priority action list (high/medium/low) and implementation checklist
---
## Anti-Patterns
| Anti-Pattern | Why It Fails | Better Approach |
|---|---|---|
| Using default VPC for production | No isolation, shared firewall rules | Create custom VPC with private subnets |
| Over-provisioning GKE node pools | Wasted cost on idle capacity | Use GKE Autopilot or cluster autoscaler |
| Storing secrets in environment variables | Visible in Cloud Console, logs | Use Secret Manager with Workload Identity |
| Ignoring sustained use discounts | Missing 20-30% automatic savings | Right-size VMs for consistent baseline usage |
| Single-region deployment for SaaS | One region outage = full downtime | Multi-region with Cloud Load Balancing |
| BigQuery on-demand for heavy workloads | Unpredictable costs at scale | Use BigQuery slots (flat-rate) for consistent workloads |
| Running Cloud Functions for long tasks | 9-minute timeout, cold starts | Use Cloud Run for tasks > 60 seconds |
---
## Cross-References
| Skill | Relationship |
|-------|-------------|
| `engineering-team/aws-solution-architect` | AWS equivalent — same 6-step workflow, different services |
| `engineering-team/azure-cloud-architect` | Azure equivalent — completes the cloud trifecta |
| `engineering-team/senior-devops` | Broader DevOps scope — pipelines, monitoring, containerization |
| `engineering/terraform-patterns` | IaC implementation — use for Terraform modules targeting GCP |
| `engineering/ci-cd-pipeline-builder` | Pipeline construction — automates Cloud Build and deployment |
---
## Reference Documentation
| Document | Contents |
|----------|----------|
| `references/architecture_patterns.md` | 6 patterns: serverless, GKE microservices, three-tier, data pipeline, ML platform, multi-region |
| `references/service_selection.md` | Decision matrices for compute, database, storage, messaging |
| `references/best_practices.md` | Naming, labels, IAM, networking, monitoring, disaster recovery |
FILE:references/architecture_patterns.md
# GCP Architecture Patterns
Reference guide for selecting the right GCP 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: Microservices on GKE](#pattern-2-microservices-on-gke)
- [Pattern 3: Three-Tier Application](#pattern-3-three-tier-application)
- [Pattern 4: Serverless Data Pipeline](#pattern-4-serverless-data-pipeline)
- [Pattern 5: ML Platform](#pattern-5-ml-platform)
- [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 | $30-400 | Low |
| Microservices on GKE | Complex services, enterprise | 10K-500K | $400-2500 | Medium |
| Three-Tier | Traditional web, e-commerce | 10K-200K | $300-1500 | Medium |
| Data Pipeline | Analytics, ETL, streaming | Any | $100-2000 | Medium-High |
| ML Platform | Training, serving, MLOps | Any | $200-5000 | High |
| Multi-Region HA | Global apps, DR | >100K | 2x single | High |
---
## Pattern 1: Serverless Web Application
### Use Case
SaaS platforms, mobile backends, low-traffic websites, MVPs
### Architecture Diagram
```
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Cloud CDN │────▶│ Cloud │ │ Identity │
│ (CDN) │ │ Storage │ │ Platform │
└─────────────┘ │ (Static) │ │ (Auth) │
└─────────────┘ └──────┬──────┘
│
┌─────────────┐ ┌─────────────┐ ┌──────▼──────┐
│ Cloud DNS │────▶│ Cloud │────▶│ Cloud Run │
│ (DNS) │ │ Load Bal. │ │ (API) │
└─────────────┘ └─────────────┘ └──────┬──────┘
│
┌──────▼──────┐
│ Firestore │
│ (Database) │
└─────────────┘
```
### Service Stack
| Layer | Service | Configuration |
|-------|---------|---------------|
| Frontend | Cloud Storage + Cloud CDN | Static hosting with HTTPS |
| API | Cloud Run | Containerized API with auto-scaling |
| Database | Firestore | Native mode, pay-per-operation |
| Auth | Identity Platform | Multi-provider authentication |
| CI/CD | Cloud Build | Automated container deployments |
### Terraform Example
```hcl
resource "google_cloud_run_v2_service" "api" {
name = "my-app-api"
location = "us-central1"
template {
containers {
image = "gcr.io/my-project/my-app:latest"
resources {
limits = {
cpu = "1000m"
memory = "512Mi"
}
}
}
scaling {
min_instance_count = 0
max_instance_count = 10
}
}
}
```
### Cost Breakdown (10K users)
| Service | Monthly Cost |
|---------|-------------|
| Cloud Run | $5-25 |
| Firestore | $5-30 |
| Cloud CDN | $5-15 |
| Cloud Storage | $1-5 |
| Identity Platform | $0-10 |
| **Total** | **$16-85** |
### Pros and Cons
**Pros:**
- Scale-to-zero (pay nothing when idle)
- Container-based (no runtime restrictions)
- Built-in HTTPS and custom domains
- Auto-scaling with no configuration
**Cons:**
- Cold starts if min instances = 0
- Firestore query limitations vs SQL
- Vendor lock-in to GCP
---
## Pattern 2: Microservices on GKE
### Use Case
Complex business systems, enterprise applications, platform engineering
### Architecture Diagram
```
┌─────────────┐ ┌─────────────┐
│ Cloud CDN │────▶│ Global │
│ (CDN) │ │ Load Bal. │
└─────────────┘ └──────┬──────┘
│
┌──────▼──────┐
│ GKE │
│ Autopilot │
└──────┬──────┘
│
┌──────────────────┼──────────────────┐
│ │ │
┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ Cloud SQL │ │ Memorystore │ │ Pub/Sub │
│ (Postgres) │ │ (Redis) │ │ (Messaging) │
└─────────────┘ └─────────────┘ └─────────────┘
```
### Service Stack
| Layer | Service | Configuration |
|-------|---------|---------------|
| CDN | Cloud CDN | Edge caching, HTTPS |
| Load Balancer | Global Application LB | Backend services, health checks |
| Compute | GKE Autopilot | Managed node provisioning |
| Database | Cloud SQL PostgreSQL | Regional HA, read replicas |
| Cache | Memorystore Redis | Session, query caching |
| Messaging | Pub/Sub | Async service communication |
### GKE Autopilot Configuration
```yaml
# Deployment manifest
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-service
spec:
replicas: 2
selector:
matchLabels:
app: api-service
template:
metadata:
labels:
app: api-service
spec:
serviceAccountName: api-workload-sa
containers:
- name: api
image: us-central1-docker.pkg.dev/my-project/my-app/api:latest
ports:
- containerPort: 8080
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "1000m"
memory: "1Gi"
env:
- name: DB_HOST
valueFrom:
secretKeyRef:
name: db-credentials
key: host
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-service-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api-service
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
```
### Cost Breakdown (50K users)
| Service | Monthly Cost |
|---------|-------------|
| GKE Autopilot | $150-400 |
| Cloud Load Balancing | $25-50 |
| Cloud SQL | $100-300 |
| Memorystore | $40-80 |
| Pub/Sub | $5-20 |
| **Total** | **$320-850** |
---
## Pattern 3: Three-Tier Application
### Use Case
Traditional web apps, e-commerce, CMS, applications with complex queries
### Architecture Diagram
```
┌─────────────┐ ┌─────────────┐
│ Cloud CDN │────▶│ Global │
│ (CDN) │ │ Load Bal. │
└─────────────┘ └──────┬──────┘
│
┌──────▼──────┐
│ Cloud Run │
│ (or MIG) │
└──────┬──────┘
│
┌──────────────────┼──────────────────┐
│ │ │
┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ Cloud SQL │ │ Memorystore │ │ Cloud │
│ (Database) │ │ (Redis) │ │ Storage │
└─────────────┘ └─────────────┘ └─────────────┘
```
### Service Stack
| Layer | Service | Configuration |
|-------|---------|---------------|
| CDN | Cloud CDN | Edge caching, compression |
| Load Balancer | External Application LB | SSL termination, health checks |
| Compute | Cloud Run or Managed Instance Group | Auto-scaling containers or VMs |
| Database | Cloud SQL (MySQL/PostgreSQL) | Regional HA, automated backups |
| Cache | Memorystore Redis | Session store, query cache |
| Storage | Cloud Storage | Uploads, static assets, backups |
### Cost Breakdown (50K users)
| Service | Monthly Cost |
|---------|-------------|
| Cloud Run / MIG | $80-200 |
| Cloud Load Balancing | $25-50 |
| Cloud SQL | $100-250 |
| Memorystore | $30-60 |
| Cloud Storage | $10-30 |
| **Total** | **$245-590** |
---
## Pattern 4: Serverless Data Pipeline
### Use Case
Analytics, IoT data ingestion, log processing, real-time streaming, ETL
### Architecture Diagram
```
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Sources │────▶│ Pub/Sub │────▶│ Dataflow │
│ (Apps/IoT) │ │ (Ingest) │ │ (Process) │
└─────────────┘ └─────────────┘ └──────┬──────┘
│
┌─────────────┐ ┌─────────────┐ ┌──────▼──────┐
│ Looker │◀────│ BigQuery │◀────│ Cloud │
│ (Dashbd) │ │(Warehouse) │ │ Storage │
└─────────────┘ └─────────────┘ │ (Data Lake) │
└─────────────┘
```
### Service Stack
| Layer | Service | Purpose |
|-------|---------|---------|
| Ingestion | Pub/Sub | Real-time event capture |
| Processing | Dataflow (Apache Beam) | Stream/batch transforms |
| Warehouse | BigQuery | SQL analytics at scale |
| Storage | Cloud Storage | Raw data lake |
| Visualization | Looker / Looker Studio | Dashboards and reports |
| Orchestration | Cloud Composer (Airflow) | Pipeline scheduling |
### Dataflow Pipeline Example
```python
import apache_beam as beam
from apache_beam.options.pipeline_options import PipelineOptions
options = PipelineOptions([
'--runner=DataflowRunner',
'--project=my-project',
'--region=us-central1',
'--temp_location=gs://my-bucket/temp',
'--streaming'
])
with beam.Pipeline(options=options) as p:
(p
| 'ReadPubSub' >> beam.io.ReadFromPubSub(topic='projects/my-project/topics/events')
| 'ParseJSON' >> beam.Map(lambda x: json.loads(x))
| 'WindowInto' >> beam.WindowInto(beam.window.FixedWindows(60))
| 'WriteBQ' >> beam.io.WriteToBigQuery(
'my-project:analytics.events',
schema='event_id:STRING,event_type:STRING,timestamp:TIMESTAMP',
write_disposition=beam.io.BigQueryDisposition.WRITE_APPEND
))
```
### Cost Breakdown
| Service | Monthly Cost |
|---------|-------------|
| Pub/Sub | $5-30 |
| Dataflow | $30-200 |
| BigQuery (on-demand) | $10-100 |
| Cloud Storage | $5-30 |
| Looker Studio | $0 (free) |
| **Total** | **$50-360** |
---
## Pattern 5: ML Platform
### Use Case
Model training, serving, MLOps, feature engineering
### Architecture Diagram
```
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ BigQuery │────▶│ Vertex AI │────▶│ Vertex AI │
│ (Features) │ │ (Training) │ │ (Endpoints) │
└─────────────┘ └──────┬──────┘ └─────────────┘
│
┌─────────────┐ ┌──────▼──────┐ ┌─────────────┐
│ Cloud │◀────│ Cloud │────▶│ Vertex AI │
│ Functions │ │ Storage │ │ Pipelines │
│ (Triggers) │ │ (Artifacts) │ │ (MLOps) │
└─────────────┘ └─────────────┘ └─────────────┘
```
### Service Stack
| Layer | Service | Purpose |
|-------|---------|---------|
| Data | BigQuery | Feature engineering, exploration |
| Training | Vertex AI Training | Custom or AutoML training |
| Serving | Vertex AI Endpoints | Online/batch prediction |
| Storage | Cloud Storage | Datasets, model artifacts |
| Orchestration | Vertex AI Pipelines | ML workflow automation |
| Monitoring | Vertex AI Model Monitoring | Drift and skew detection |
### Vertex AI Training Example
```python
from google.cloud import aiplatform
aiplatform.init(project='my-project', location='us-central1')
job = aiplatform.CustomTrainingJob(
display_name='my-model-training',
script_path='train.py',
container_uri='us-docker.pkg.dev/vertex-ai/training/tf-gpu.2-12:latest',
requirements=['pandas', 'scikit-learn'],
)
model = job.run(
replica_count=1,
machine_type='n1-standard-8',
accelerator_type='NVIDIA_TESLA_T4',
accelerator_count=1,
)
endpoint = model.deploy(
deployed_model_display_name='my-model-v1',
machine_type='n1-standard-4',
min_replica_count=1,
max_replica_count=5,
)
```
### Cost Breakdown
| Service | Monthly Cost |
|---------|-------------|
| Vertex AI Training (T4 GPU) | $50-500 |
| Vertex AI Prediction | $30-200 |
| BigQuery | $10-50 |
| Cloud Storage | $5-30 |
| **Total** | **$95-780** |
---
## Pattern 6: Multi-Region High Availability
### Use Case
Global applications, disaster recovery, data sovereignty compliance
### Architecture Diagram
```
┌─────────────┐
│ Cloud DNS │
│(Geo routing)│
└──────┬──────┘
│
┌────────────────┼────────────────┐
│ │
┌──────▼──────┐ ┌──────▼──────┐
│us-central1 │ │europe-west1 │
│ Cloud Run │ │ Cloud Run │
└──────┬──────┘ └──────┬──────┘
│ │
┌──────▼──────┐ ┌──────▼──────┐
│Cloud Spanner│◀── Replication ──▶│Cloud Spanner│
│ (Region) │ │ (Region) │
└─────────────┘ └─────────────┘
```
### Service Stack
| Component | Service | Configuration |
|-----------|---------|---------------|
| DNS | Cloud DNS | Geolocation or latency routing |
| CDN | Cloud CDN | Multiple regional origins |
| Compute | Cloud Run (multi-region) | Deployed in each region |
| Database | Cloud Spanner (multi-region) | Strong global consistency |
| Storage | Cloud Storage (multi-region) | Automatic geo-redundancy |
### Cloud DNS Geolocation Policy
```bash
# Create geolocation routing policy
gcloud dns record-sets create api.example.com \
--zone=my-zone \
--type=A \
--routing-policy-type=GEO \
--routing-policy-data="us-central1=projects/my-project/regions/us-central1/addresses/api-us;europe-west1=projects/my-project/regions/europe-west1/addresses/api-eu"
```
### Cost Considerations
| Factor | Impact |
|--------|--------|
| Compute | 2x (each region) |
| Cloud Spanner | Multi-region 3x regional price |
| Data Transfer | Cross-region replication costs |
| Cloud DNS | Geolocation queries premium |
| **Total** | **2-3x single region** |
---
## Pattern Comparison Summary
### Latency
| Pattern | Typical Latency |
|---------|-----------------|
| Serverless Web | 30-150ms (Cloud Run) |
| GKE Microservices | 15-80ms |
| Three-Tier | 20-100ms |
| Multi-Region | <50ms (regional) |
### Scaling Characteristics
| Pattern | Scale Limit | Scale Speed |
|---------|-------------|-------------|
| Serverless Web | 1000 instances/service | Seconds |
| GKE Microservices | Cluster node limits | Minutes |
| Data Pipeline | Unlimited (Dataflow) | Seconds |
| Multi-Region | Regional limits | Seconds |
### Operational Complexity
| Pattern | Setup | Maintenance | Debugging |
|---------|-------|-------------|-----------|
| Serverless Web | Low | Low | Medium |
| GKE Microservices | Medium | Medium | Medium |
| Three-Tier | Medium | Medium | Low |
| Data Pipeline | High | Medium | High |
| ML Platform | High | High | High |
| Multi-Region | High | High | High |
FILE:references/best_practices.md
# GCP Best Practices
Production-ready practices for naming, labels, IAM, networking, monitoring, and disaster recovery.
---
## Table of Contents
- [Naming Conventions](#naming-conventions)
- [Labels and Organization](#labels-and-organization)
- [IAM and Security](#iam-and-security)
- [Networking](#networking)
- [Monitoring and Logging](#monitoring-and-logging)
- [Cost Optimization](#cost-optimization)
- [Disaster Recovery](#disaster-recovery)
- [Common Pitfalls](#common-pitfalls)
---
## Naming Conventions
### Resource Naming Pattern
```
{environment}-{project}-{resource-type}-{purpose}
Examples:
prod-myapp-gke-cluster
dev-myapp-sql-primary
staging-myapp-run-api
prod-myapp-gcs-uploads
```
### Project Naming
```
{org}-{team}-{environment}
Examples:
acme-platform-prod
acme-platform-dev
acme-data-prod
```
### Naming Rules
| Resource | Format | Max Length | Example |
|----------|--------|-----------|---------|
| Project ID | lowercase, hyphens | 30 chars | acme-platform-prod |
| GKE Cluster | lowercase, hyphens | 40 chars | prod-api-cluster |
| Cloud Run | lowercase, hyphens | 49 chars | prod-myapp-api |
| Cloud SQL | lowercase, hyphens | 84 chars | prod-myapp-sql-primary |
| GCS Bucket | lowercase, hyphens, dots | 63 chars | acme-prod-myapp-uploads |
| Service Account | lowercase, hyphens | 30 chars | myapp-run-sa |
---
## Labels and Organization
### Required Labels
Apply these labels to all resources:
```
labels:
environment: "prod" # dev, staging, prod
team: "platform" # team owning the resource
app: "myapp" # application name
cost-center: "eng-001" # billing allocation
managed-by: "terraform" # terraform, gcloud, console
```
### Label-Based Cost Reporting
```bash
# Export billing data to BigQuery with labels
# Then query by label:
SELECT
labels.value AS environment,
SUM(cost) AS total_cost
FROM `billing_export.gcp_billing_export_v1_*`
CROSS JOIN UNNEST(labels) AS labels
WHERE labels.key = 'environment'
GROUP BY environment
ORDER BY total_cost DESC
```
### Organization Hierarchy
```
Organization
├── Folder: Production
│ ├── Project: platform-prod
│ ├── Project: data-prod
│ └── Project: ml-prod
├── Folder: Non-Production
│ ├── Project: platform-dev
│ ├── Project: platform-staging
│ └── Project: data-dev
└── Folder: Shared Services
├── Project: shared-networking
├── Project: shared-security
└── Project: shared-monitoring
```
---
## IAM and Security
### Principle of Least Privilege
```bash
# BAD: Basic roles are too broad
gcloud projects add-iam-policy-binding my-project \
--member="user:dev@example.com" \
--role="roles/editor"
# GOOD: Use predefined roles
gcloud projects add-iam-policy-binding my-project \
--member="user:dev@example.com" \
--role="roles/run.developer"
```
### Service Account Best Practices
```bash
# 1. Create dedicated SA per workload
gcloud iam service-accounts create myapp-api-sa \
--display-name="MyApp API Service Account"
# 2. Grant only required roles
gcloud projects add-iam-policy-binding my-project \
--member="serviceAccount:myapp-api-sa@my-project.iam.gserviceaccount.com" \
--role="roles/datastore.user"
# 3. Use Workload Identity for GKE (no key files)
gcloud iam service-accounts add-iam-policy-binding \
myapp-api-sa@my-project.iam.gserviceaccount.com \
--role="roles/iam.workloadIdentityUser" \
--member="serviceAccount:my-project.svc.id.goog[default/myapp-api-ksa]"
# 4. NEVER download SA key files in production
# Instead, use attached service accounts or impersonation
```
### VPC Service Controls
```bash
# Create a service perimeter to restrict data exfiltration
gcloud access-context-manager perimeters create my-perimeter \
--title="Production Data Perimeter" \
--resources="projects/123456" \
--restricted-services="bigquery.googleapis.com,storage.googleapis.com" \
--policy=$POLICY_ID
```
### Organization Policies
```bash
# Restrict external IPs on VMs
gcloud resource-manager org-policies set-policy \
--project=my-project policy.yaml
# policy.yaml
constraint: compute.vmExternalIpAccess
listPolicy:
allValues: DENY
# Restrict public Cloud Storage
constraint: storage.publicAccessPrevention
booleanPolicy:
enforced: true
```
### Encryption
| Layer | Service | Default |
|-------|---------|---------|
| At rest | Google-managed keys | Always enabled |
| At rest | CMEK (Cloud KMS) | Optional, recommended |
| In transit | TLS 1.3 | Always enabled |
| Application | Cloud KMS | Encrypt sensitive fields |
```bash
# Create CMEK key for Cloud SQL
gcloud kms keys create myapp-sql-key \
--keyring=myapp-keyring \
--location=us-central1 \
--purpose=encryption
# Use CMEK with Cloud SQL
gcloud sql instances create myapp-db \
--disk-encryption-key=projects/my-project/locations/us-central1/keyRings/myapp-keyring/cryptoKeys/myapp-sql-key
```
---
## Networking
### VPC Design
```bash
# Create custom VPC (avoid default network)
gcloud compute networks create myapp-vpc \
--subnet-mode=custom
# Create subnets with secondary ranges for GKE
gcloud compute networks subnets create myapp-subnet \
--network=myapp-vpc \
--region=us-central1 \
--range=10.0.0.0/20 \
--secondary-range pods=10.4.0.0/14,services=10.8.0.0/20 \
--enable-private-google-access
```
### Shared VPC
Use Shared VPC for multi-project environments:
```
Host Project (shared-networking)
├── VPC: shared-vpc
│ ├── Subnet: prod-us-central1 → Service Project: platform-prod
│ ├── Subnet: prod-europe-west1 → Service Project: platform-prod
│ └── Subnet: dev-us-central1 → Service Project: platform-dev
```
### Firewall Rules
```bash
# Allow internal traffic
gcloud compute firewall-rules create allow-internal \
--network=myapp-vpc \
--allow=tcp,udp,icmp \
--source-ranges=10.0.0.0/8
# Allow health checks from Google load balancers
gcloud compute firewall-rules create allow-health-checks \
--network=myapp-vpc \
--allow=tcp:8080 \
--source-ranges=35.191.0.0/16,130.211.0.0/22 \
--target-tags=allow-health-check
# Deny all other ingress (implicit, but be explicit)
gcloud compute firewall-rules create deny-all-ingress \
--network=myapp-vpc \
--action=DENY \
--rules=all \
--direction=INGRESS \
--priority=65534
```
### Private Google Access
Always enable Private Google Access to reach GCP APIs without public IPs:
```bash
gcloud compute networks subnets update myapp-subnet \
--region=us-central1 \
--enable-private-google-access
```
---
## Monitoring and Logging
### Cloud Monitoring Setup
```bash
# Create uptime check
gcloud monitoring uptime create \
--display-name="API Health Check" \
--resource-type=cloud-run-revision \
--resource-labels="service_name=myapp-api,location=us-central1" \
--check-request-path="/health" \
--period=60s
# Create alerting policy
gcloud alpha monitoring policies create \
--display-name="High Error Rate" \
--condition-display-name="Cloud Run 5xx > 1%" \
--condition-filter='resource.type="cloud_run_revision" AND metric.type="run.googleapis.com/request_count" AND metric.labels.response_code_class="5xx"' \
--condition-threshold-value=1 \
--notification-channels="projects/my-project/notificationChannels/12345"
```
### Key Metrics to Monitor
| Service | Metric | Alert Threshold |
|---------|--------|-----------------|
| Cloud Run | request_latencies (p99) | >2s |
| Cloud Run | request_count (5xx) | >1% of total |
| Cloud SQL | cpu/utilization | >80% |
| Cloud SQL | disk/utilization | >85% |
| GKE | container/cpu/utilization | >80% |
| GKE | node/cpu/allocatable_utilization | >85% |
| Pub/Sub | subscription/oldest_unacked_message_age | >300s |
| BigQuery | query/execution_time | >60s |
### Log-Based Metrics
```bash
# Create a metric for application errors
gcloud logging metrics create app-errors \
--description="Application error count" \
--log-filter='resource.type="cloud_run_revision" AND severity>=ERROR'
# Create log sink to BigQuery for analysis
gcloud logging sinks create audit-logs-bq \
bigquery.googleapis.com/projects/my-project/datasets/audit_logs \
--log-filter='logName="projects/my-project/logs/cloudaudit.googleapis.com%2Factivity"'
```
### Log Exclusion (Cost Reduction)
```bash
# Exclude verbose debug logs to save on Cloud Logging costs
gcloud logging sinks create _Default \
--log-filter='NOT (severity="DEBUG" OR severity="DEFAULT")' \
--description="Exclude debug-level logs"
# Or create exclusion filters
gcloud logging exclusions create exclude-debug \
--log-filter='severity="DEBUG"' \
--description="Exclude debug logs to reduce costs"
```
---
## Cost Optimization
### Committed Use Discounts
| Term | Compute Discount | Memory Discount |
|------|-----------------|-----------------|
| 1 year | 37% | 37% |
| 3 years | 55% | 55% |
```bash
# Check recommendations
gcloud recommender recommendations list \
--project=my-project \
--location=us-central1 \
--recommender=google.compute.commitment.UsageCommitmentRecommender
```
### Sustained Use Discounts
Automatic discounts for resources running >25% of the month:
| Usage | Discount |
|-------|----------|
| 25-50% | 20% |
| 50-75% | 40% |
| 75-100% | 60% |
### BigQuery Cost Control
```sql
-- Use partitioning to limit data scanned
CREATE TABLE my_dataset.events
PARTITION BY DATE(timestamp)
CLUSTER BY event_type
AS SELECT * FROM raw_events;
-- Estimate query cost before running
-- Use --dry_run flag
bq query --dry_run --use_legacy_sql=false \
'SELECT * FROM my_dataset.events WHERE DATE(timestamp) = "2026-01-01"'
```
### Cloud Storage Optimization
```bash
# Enable Autoclass for automatic class management
gsutil mb -l us-central1 --autoclass gs://my-bucket/
# Set lifecycle policy
gsutil lifecycle set lifecycle.json gs://my-bucket/
```
---
## Disaster Recovery
### RPO/RTO Targets
| Tier | RPO | RTO | Strategy |
|------|-----|-----|----------|
| Tier 1 (Critical) | 0 | <1 hour | Multi-region active-active |
| Tier 2 (Important) | <1 hour | <4 hours | Regional HA + cross-region backup |
| Tier 3 (Standard) | <24 hours | <24 hours | Automated backups + restore |
### Backup Strategy
```bash
# Cloud SQL automated backups
gcloud sql instances patch myapp-db \
--backup-start-time=02:00 \
--enable-point-in-time-recovery
# Firestore scheduled exports
gcloud firestore export gs://myapp-backups/firestore/$(date +%Y%m%d)
# GKE cluster backup with Backup for GKE
gcloud beta container backup-restore backup-plans create myapp-plan \
--project=my-project \
--location=us-central1 \
--cluster=projects/my-project/locations/us-central1/clusters/myapp-cluster \
--all-namespaces \
--cron-schedule="0 2 * * *"
```
### Multi-Region Failover
```bash
# Cloud SQL cross-region replica for DR
gcloud sql instances create myapp-db-replica \
--master-instance-name=myapp-db \
--region=us-east1
# Promote replica during failover
gcloud sql instances promote-replica myapp-db-replica
```
---
## Common Pitfalls
### Technical Debt
| Pitfall | Solution |
|---------|----------|
| Using default VPC | Always create custom VPCs |
| Not enabling audit logs | Enable Cloud Audit Logs from day one |
| Single-region deployment | Plan for multi-zone at minimum |
| No IaC | Use Terraform from the start |
### Security Mistakes
| Mistake | Prevention |
|---------|------------|
| SA key files in code | Use Workload Identity, attached SAs |
| Public GCS buckets | Enable org policy for public access prevention |
| Basic roles (Owner/Editor) | Use predefined or custom roles |
| No encryption key management | Use CMEK for sensitive data |
| Default service account | Create dedicated SAs per workload |
### Performance Issues
| Issue | Solution |
|-------|----------|
| Cold starts on Cloud Run | Set min-instances=1 for latency-critical services |
| Slow BigQuery queries | Partition tables, use clustering, avoid SELECT * |
| GKE pod scheduling delays | Use PodDisruptionBudget, pre-provision with Autopilot |
| Firestore hotspots | Distribute writes across document IDs evenly |
### Cost Surprises
| Surprise | Prevention |
|----------|------------|
| Undeleted resources | Label everything, review weekly |
| Egress costs | Keep traffic in same region, use Private Google Access |
| Cloud NAT charges | Use Private Google Access for GCP service traffic |
| Log ingestion costs | Set exclusion filters for debug/verbose logs |
| BigQuery full scans | Always use partitioning and clustering |
| Idle GKE clusters | Delete dev clusters nightly, use Autopilot |
FILE:references/service_selection.md
# GCP Service Selection Guide
Quick reference for choosing the right GCP 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 |
|-------------|---------------------|
| HTTP-triggered containers, auto-scaling | Cloud Run |
| Event-driven, short tasks (<9 min) | Cloud Functions (2nd gen) |
| Kubernetes workloads, microservices | GKE Autopilot |
| Custom VMs, GPU/TPU | Compute Engine |
| Batch processing, HPC | Batch |
| Kubernetes with full control | GKE Standard |
### Cloud Run
**Best for:** Containerized HTTP services, APIs, web backends
```
Limits:
- vCPU: 1-8 per instance
- Memory: 128 MiB - 32 GiB
- Request timeout: 3600 seconds
- Concurrency: 1-1000 per instance
- Min instances: 0 (scale-to-zero)
- Max instances: 1000
Pricing: Per vCPU-second + GiB-second (free tier: 2M requests/month)
```
**Use when:**
- Containerized apps with HTTP endpoints
- Variable/unpredictable traffic
- Want scale-to-zero capability
- No Kubernetes expertise needed
**Avoid when:**
- Non-HTTP workloads (use Cloud Functions or GKE)
- Need GPU/TPU (use Compute Engine or GKE)
- Require persistent local storage
### Cloud Functions (2nd gen)
**Best for:** Event-driven functions, lightweight triggers, webhooks
```
Limits:
- Execution: 9 minutes max (2nd gen), 9 minutes (1st gen)
- Memory: 128 MB - 32 GB
- Concurrency: Up to 1000 per instance (2nd gen)
- Runtimes: Node.js, Python, Go, Java, .NET, Ruby, PHP
Pricing: $0.40 per million invocations + compute time
```
**Use when:**
- Event-driven processing (Pub/Sub, Cloud Storage, Firestore)
- Lightweight API endpoints
- Scheduled tasks (Cloud Scheduler triggers)
- Minimal infrastructure management
**Avoid when:**
- Long-running processes (>9 min)
- Complex multi-container apps
- Need fine-grained scaling control
### GKE Autopilot
**Best for:** Kubernetes workloads with managed node provisioning
```
Limits:
- Pod resources: 0.25-112 vCPU, 0.5-896 GiB memory
- GPU support: NVIDIA T4, L4, A100, H100
- Management fee: $0.10/hour per cluster ($74.40/month)
Pricing: Per pod vCPU-hour + GiB-hour (no node management)
```
**Use when:**
- Team has Kubernetes expertise
- Need pod-level resource control
- Multi-container services
- GPU workloads
### Compute Engine
**Best for:** Custom configurations, specialized hardware
```
Machine Types:
- General: e2, n2, n2d, c3
- Compute: c2, c2d
- Memory: m1, m2, m3
- Accelerator: a2 (GPU), a3 (GPU)
- Storage: z3
Pricing Options:
- On-demand, Spot (60-91% discount), Committed Use (37-55% discount)
```
**Use when:**
- Need GPU/TPU
- Windows workloads
- Specific hardware requirements
- Lift-and-shift migrations
---
## Database Services
### Decision Matrix
| Data Type | Query Pattern | Scale | Recommended |
|-----------|--------------|-------|-------------|
| Key-value, document | Simple lookups, real-time | Any | Firestore |
| Wide-column | High-throughput reads/writes | >1TB | Cloud Bigtable |
| Relational | Complex joins, ACID | Variable | Cloud SQL |
| Relational, global | Strong consistency, global | Large | Cloud Spanner |
| Time-series | Time-based queries | Any | Bigtable or BigQuery |
| Analytics, warehouse | SQL analytics | Petabytes | BigQuery |
### Firestore
**Best for:** Document data, mobile/web apps, real-time sync
```
Limits:
- Document size: 1 MiB max
- Field depth: 20 nested levels
- Write rate: 10,000 writes/sec per database
- Indexes: Automatic single-field, manual composite
Pricing:
- Reads: $0.036 per 100K reads
- Writes: $0.108 per 100K writes
- Storage: $0.108 per GiB/month
- Free tier: 50K reads, 20K writes, 1 GiB storage per day
```
**Use when:**
- Mobile/web apps needing offline sync
- Real-time data updates
- Flexible schema
- Serverless architecture
**Avoid when:**
- Complex SQL queries with joins
- Heavy analytics workloads
- Data >1 MiB per document
### Cloud SQL
**Best for:** Relational data with familiar SQL
| Engine | Version | Max Storage | Max Connections |
|--------|---------|-------------|-----------------|
| PostgreSQL | 15 | 64 TB | Instance-dependent |
| MySQL | 8.0 | 64 TB | Instance-dependent |
| SQL Server | 2022 | 64 TB | Instance-dependent |
```
Pricing:
- Machine type + storage + networking
- HA: 2x cost (regional instance)
- Read replicas: Per-replica pricing
```
**Use when:**
- Relational data with complex queries
- Existing SQL expertise
- Need ACID transactions
- Migration from on-premises databases
### Cloud Spanner
**Best for:** Globally distributed relational data
```
Limits:
- Storage: Unlimited
- Nodes: 1-100+ per instance
- Consistency: Strong global consistency
Pricing:
- Regional: $0.90/node-hour (~$657/month per node)
- Multi-region: $2.70/node-hour (~$1,971/month per node)
- Storage: $0.30/GiB/month
```
**Use when:**
- Global applications needing strong consistency
- Relational data at massive scale
- 99.999% availability requirement
- Horizontal scaling with SQL
### BigQuery
**Best for:** Analytics, data warehouse, SQL on massive datasets
```
Limits:
- Query: 6-hour timeout
- Concurrent queries: 100 default
- Streaming inserts: 100K rows/sec per table
Pricing:
- On-demand: $6.25 per TB queried (first 1 TB free/month)
- Editions: Autoscale slots starting at $0.04/slot-hour
- Storage: $0.02/GiB (active), $0.01/GiB (long-term)
```
### Firestore vs Cloud SQL vs Spanner
| Factor | Firestore | Cloud SQL | Cloud Spanner |
|--------|-----------|-----------|---------------|
| Query flexibility | Document-based | Full SQL | Full SQL |
| Scaling | Automatic | Vertical + read replicas | Horizontal |
| Consistency | Strong (single region) | ACID | Strong (global) |
| Cost model | Per-operation | Per-hour | Per-node-hour |
| Operational | Zero management | Managed (some ops) | Managed |
| Best for | Mobile/web apps | Traditional apps | Global scale |
---
## Storage Services
### Cloud Storage Classes
| Class | Access Pattern | Min Duration | Cost (GiB/mo) |
|-------|---------------|--------------|----------------|
| Standard | Frequent | None | $0.020 |
| Nearline | Monthly access | 30 days | $0.010 |
| Coldline | Quarterly access | 90 days | $0.004 |
| Archive | Annual access | 365 days | $0.0012 |
### Lifecycle Policy Example
```json
{
"lifecycle": {
"rule": [
{
"action": { "type": "SetStorageClass", "storageClass": "NEARLINE" },
"condition": { "age": 30, "matchesStorageClass": ["STANDARD"] }
},
{
"action": { "type": "SetStorageClass", "storageClass": "COLDLINE" },
"condition": { "age": 90, "matchesStorageClass": ["NEARLINE"] }
},
{
"action": { "type": "SetStorageClass", "storageClass": "ARCHIVE" },
"condition": { "age": 365, "matchesStorageClass": ["COLDLINE"] }
},
{
"action": { "type": "Delete" },
"condition": { "age": 2555 }
}
]
}
}
```
### Autoclass
Automatically transitions objects between storage classes based on access patterns. Recommended for mixed or unknown access patterns.
```bash
gsutil mb -l us-central1 --autoclass gs://my-bucket/
```
### Block and File Storage
| Service | Use Case | Access |
|---------|----------|--------|
| Persistent Disk | GCE/GKE block storage | Single instance (RW) or multi (RO) |
| Filestore | NFS shared file system | Multiple instances |
| Parallelstore | HPC parallel file system | High throughput |
| Cloud Storage FUSE | Mount GCS as filesystem | Any compute |
---
## Messaging and Events
### Decision Matrix
| Pattern | Service | Use Case |
|---------|---------|----------|
| Pub/sub messaging | Pub/Sub | Event streaming, microservice decoupling |
| Task queue | Cloud Tasks | Asynchronous task execution with retries |
| Workflow orchestration | Workflows | Multi-step service orchestration |
| Batch orchestration | Cloud Composer | Complex DAG-based pipelines (Airflow) |
| Event triggers | Eventarc | Route events to Cloud Run, GKE, Workflows |
### Pub/Sub
**Best for:** Event-driven architectures, stream processing
```
Limits:
- Message size: 10 MB max
- Throughput: Unlimited (auto-scaling)
- Retention: 7 days default (up to 31 days)
- Ordering: Per ordering key
Pricing: $40/TiB for message delivery
```
```python
# Pub/Sub publisher example
from google.cloud import pubsub_v1
import json
publisher = pubsub_v1.PublisherClient()
topic_path = publisher.topic_path('my-project', 'events')
def publish_event(event_type, payload):
data = json.dumps(payload).encode('utf-8')
future = publisher.publish(
topic_path,
data,
event_type=event_type
)
return future.result()
```
### Cloud Tasks
**Best for:** Asynchronous task execution with delivery guarantees
```
Features:
- Configurable retry policies
- Rate limiting
- Scheduled delivery
- HTTP and App Engine targets
Pricing: $0.40 per million operations
```
### Eventarc
**Best for:** Routing cloud events to services
```python
# Eventarc routes events from 130+ Google Cloud sources
# to Cloud Run, GKE, or Workflows
# Example: Trigger Cloud Run on Cloud Storage upload
# gcloud eventarc triggers create my-trigger \
# --destination-run-service=my-service \
# --event-filters="type=google.cloud.storage.object.v1.finalized" \
# --event-filters="bucket=my-bucket"
```
---
## API and Integration
### API Gateway vs Cloud Endpoints vs Cloud Run
| Factor | API Gateway | Cloud Endpoints | Cloud Run (direct) |
|--------|-------------|-----------------|---------------------|
| Protocol | REST, gRPC | REST, gRPC | Any HTTP |
| Auth | API keys, JWT, Firebase | API keys, JWT | IAM, custom |
| Rate limiting | Built-in | Built-in | Manual |
| Cost | Per-call pricing | Per-call pricing | Per-request |
| Best for | External APIs | Internal APIs | Simple services |
### Cloud Endpoints Configuration
```yaml
# openapi.yaml
swagger: "2.0"
info:
title: "My API"
version: "1.0.0"
host: "my-api-xyz.apigateway.my-project.cloud.goog"
schemes:
- "https"
paths:
/users:
get:
summary: "List users"
operationId: "listUsers"
x-google-backend:
address: "https://my-app-api-xyz.a.run.app"
security:
- api_key: []
securityDefinitions:
api_key:
type: "apiKey"
name: "key"
in: "query"
```
### Workflows
**Best for:** Orchestrating multi-service processes
```yaml
# workflow.yaml
main:
steps:
- processOrder:
call: http.post
args:
url: https://orders-service.run.app/process
body:
orderId: args.orderId
result: orderResult
- checkInventory:
switch:
- condition: orderResult.body.inStock
next: shipOrder
next: backOrder
- shipOrder:
call: http.post
args:
url: https://shipping-service.run.app/ship
body:
orderId: args.orderId
result: shipResult
- backOrder:
call: http.post
args:
url: https://inventory-service.run.app/backorder
body:
orderId: args.orderId
```
---
## Networking
### VPC Components
| Component | Purpose |
|-----------|---------|
| VPC | Isolated network (global resource) |
| Subnet | Regional network segment |
| Cloud NAT | Outbound internet for private instances |
| Cloud Router | Dynamic routing (BGP) |
| Private Google Access | Access GCP APIs without public IP |
| VPC Peering | Connect two VPC networks |
| Shared VPC | Share VPC across projects |
### VPC Design Pattern
```
VPC: 10.0.0.0/16 (global)
Subnet us-central1:
10.0.0.0/20 (primary)
10.4.0.0/14 (pods - secondary)
10.8.0.0/20 (services - secondary)
- GKE cluster, Cloud Run (VPC connector)
Subnet us-east1:
10.0.16.0/20 (primary)
- Cloud SQL (private IP), Memorystore
Subnet europe-west1:
10.0.32.0/20 (primary)
- DR / multi-region workloads
```
### Private Google Access
```bash
# Enable Private Google Access on a subnet
gcloud compute networks subnets update my-subnet \
--region=us-central1 \
--enable-private-google-access
```
---
## Security and Identity
### IAM Best Practices
```bash
# Prefer predefined roles over basic roles
# BAD: roles/editor (too broad)
# GOOD: roles/run.invoker (specific)
# Grant role to service account
gcloud projects add-iam-policy-binding my-project \
--member="serviceAccount:my-sa@my-project.iam.gserviceaccount.com" \
--role="roles/datastore.user" \
--condition='expression=resource.name.startsWith("projects/my-project/databases/(default)/documents/users"),title=firestore-users-only'
```
### Service Account Best Practices
| Practice | Description |
|----------|-------------|
| One SA per service | Separate service accounts per workload |
| Workload Identity | Bind K8s SAs to GCP SAs in GKE |
| Short-lived tokens | Use impersonation instead of key files |
| No SA keys | Avoid downloading JSON key files |
### Secret Manager vs Environment Variables
| Factor | Secret Manager | Env Variables |
|--------|---------------|---------------|
| Rotation | Automatic versioning | Manual redeploy |
| Audit | Cloud Audit Logs | No audit trail |
| Access control | IAM per-secret | Per-service |
| Pricing | $0.06/10K access ops | Free |
| Use case | Credentials, API keys | Non-sensitive config |
### Secret Manager Usage
```python
from google.cloud import secretmanager
def get_secret(project_id, secret_id, version="latest"):
client = secretmanager.SecretManagerServiceClient()
name = f"projects/{project_id}/secrets/{secret_id}/versions/{version}"
response = client.access_secret_version(request={"name": name})
return response.payload.data.decode("UTF-8")
# Usage
db_password = get_secret("my-project", "db-password")
```
FILE:scripts/architecture_designer.py
"""
GCP architecture design and service recommendation module.
Generates architecture patterns based on application requirements.
"""
import argparse
import json
import sys
from typing import Dict, List, Any
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"
ML_PLATFORM = "ml_platform"
class ArchitectureDesigner:
"""Design GCP 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.gcp_experience = requirements.get('gcp_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
"""
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._gke_microservices_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._data_pipeline_architecture()
elif self.app_type == 'microservices':
return self._gke_microservices_architecture()
elif self.app_type == 'ml_platform':
return self._ml_platform_architecture()
else:
return self._serverless_web_architecture()
def _serverless_web_architecture(self) -> Dict[str, Any]:
"""Serverless web application pattern using Cloud Run."""
return {
'pattern_name': 'Serverless Web Application',
'description': 'Fully serverless architecture with Cloud Run and Firestore',
'use_case': 'SaaS platforms, low to medium traffic websites, MVPs',
'services': {
'frontend': {
'service': 'Cloud Storage + Cloud CDN',
'purpose': 'Static website hosting with global CDN',
'configuration': {
'bucket': 'Website bucket with public access',
'cdn': 'Cloud CDN with custom domain and HTTPS',
'caching': 'Cache-Control headers, edge caching'
}
},
'api': {
'service': 'Cloud Run',
'purpose': 'Containerized API backend with auto-scaling',
'configuration': {
'cpu': '1 vCPU',
'memory': '512 Mi',
'min_instances': '0 (scale to zero)',
'max_instances': '10',
'concurrency': '80 requests per instance',
'timeout': '300 seconds'
}
},
'database': {
'service': 'Firestore',
'purpose': 'NoSQL document database with real-time sync',
'configuration': {
'mode': 'Native mode',
'location': 'Regional or multi-region',
'security_rules': 'Firestore security rules',
'backup': 'Scheduled exports to Cloud Storage'
}
},
'authentication': {
'service': 'Identity Platform',
'purpose': 'User authentication and authorization',
'configuration': {
'providers': 'Email/password, Google, Apple, OIDC',
'mfa': 'SMS or TOTP multi-factor authentication',
'token_expiration': '1 hour access, 30 days refresh'
}
},
'cicd': {
'service': 'Cloud Build',
'purpose': 'Automated build and deployment from Git',
'configuration': {
'source': 'GitHub or Cloud Source Repositories',
'build': 'Automatic on commit',
'environments': 'dev, staging, production'
}
}
},
'estimated_cost': {
'monthly_usd': self._calculate_serverless_cost(),
'breakdown': {
'Cloud CDN': '5-20 USD',
'Cloud Run': '5-25 USD',
'Firestore': '5-30 USD',
'Identity Platform': '0-10 USD (free tier: 50k MAU)',
'Cloud Storage': '1-5 USD'
}
},
'pros': [
'No server management',
'Auto-scaling with scale-to-zero',
'Pay only for what you use',
'No cold starts with min instances',
'Container-based (no runtime restrictions)'
],
'cons': [
'Vendor lock-in to GCP',
'Regional availability considerations',
'Debugging distributed systems complex',
'Firestore query limitations vs SQL'
],
'scaling_characteristics': {
'users_supported': '1k - 100k',
'requests_per_second': '100 - 10,000',
'scaling_method': 'Automatic (Cloud Run auto-scaling)'
}
}
def _gke_microservices_architecture(self) -> Dict[str, Any]:
"""GKE-based microservices architecture."""
return {
'pattern_name': 'Microservices on GKE',
'description': 'Kubernetes-native architecture with managed services',
'use_case': 'SaaS platforms, complex microservices, enterprise applications',
'services': {
'load_balancer': {
'service': 'Cloud Load Balancing',
'purpose': 'Global HTTP(S) load balancing',
'configuration': {
'type': 'External Application Load Balancer',
'ssl': 'Google-managed SSL certificate',
'health_checks': '/health endpoint, 10s interval',
'cdn': 'Cloud CDN enabled for static content'
}
},
'compute': {
'service': 'GKE Autopilot',
'purpose': 'Managed Kubernetes for containerized workloads',
'configuration': {
'mode': 'Autopilot (fully managed node provisioning)',
'scaling': 'Horizontal Pod Autoscaler',
'networking': 'VPC-native with Alias IPs',
'workload_identity': 'Enabled for secure service account binding'
}
},
'database': {
'service': 'Cloud SQL (PostgreSQL)',
'purpose': 'Managed relational database',
'configuration': {
'tier': 'db-custom-2-8192 (2 vCPU, 8 GB RAM)',
'high_availability': 'Regional with automatic failover',
'read_replicas': '1-2 for read scaling',
'backup': 'Automated daily backups, 7-day retention',
'encryption': 'Customer-managed encryption key (CMEK)'
}
},
'cache': {
'service': 'Memorystore (Redis)',
'purpose': 'Session storage, application caching',
'configuration': {
'tier': 'Basic (1 GB) or Standard (HA)',
'version': 'Redis 7.0',
'eviction_policy': 'allkeys-lru'
}
},
'messaging': {
'service': 'Pub/Sub',
'purpose': 'Asynchronous messaging between services',
'configuration': {
'topics': 'Per-domain event topics',
'subscriptions': 'Pull or push delivery',
'dead_letter': 'Dead letter topic after 5 retries',
'ordering': 'Ordering keys for ordered delivery'
}
},
'storage': {
'service': 'Cloud Storage',
'purpose': 'User uploads, backups, logs',
'configuration': {
'storage_class': 'Standard with lifecycle policies',
'versioning': 'Enabled for important buckets',
'lifecycle': 'Transition to Nearline after 30 days'
}
}
},
'estimated_cost': {
'monthly_usd': self._calculate_gke_cost(),
'breakdown': {
'Cloud Load Balancing': '20-40 USD',
'GKE Autopilot': '75-250 USD',
'Cloud SQL': '80-250 USD',
'Memorystore': '30-80 USD',
'Pub/Sub': '5-20 USD',
'Cloud Storage': '5-20 USD'
}
},
'pros': [
'Kubernetes ecosystem compatibility',
'Fine-grained scaling control',
'Multi-cloud portability',
'Rich service mesh (Anthos Service Mesh)',
'Managed node provisioning with Autopilot'
],
'cons': [
'Higher baseline costs than serverless',
'Kubernetes learning curve',
'More operational complexity',
'GKE management fee ($74.40/month per cluster)'
],
'scaling_characteristics': {
'users_supported': '10k - 500k',
'requests_per_second': '1,000 - 50,000',
'scaling_method': 'HPA + Cluster Autoscaler'
}
}
def _serverless_mobile_backend(self) -> Dict[str, Any]:
"""Serverless mobile backend with Firebase."""
return {
'pattern_name': 'Serverless Mobile Backend',
'description': 'Mobile-first backend with Firebase and Cloud Functions',
'use_case': 'Mobile apps, real-time applications, offline-first apps',
'services': {
'api': {
'service': 'Cloud Functions (2nd gen)',
'purpose': 'Event-driven API handlers',
'configuration': {
'runtime': 'Node.js 20 or Python 3.12',
'memory': '256 MB - 1 GB',
'timeout': '60 seconds',
'concurrency': 'Up to 1000 concurrent'
}
},
'database': {
'service': 'Firestore',
'purpose': 'Real-time NoSQL database with offline sync',
'configuration': {
'mode': 'Native mode',
'multi_region': 'nam5 or eur3 for HA',
'security_rules': 'Client-side access control',
'indexes': 'Composite indexes for queries'
}
},
'file_storage': {
'service': 'Cloud Storage (Firebase)',
'purpose': 'User uploads (images, videos, documents)',
'configuration': {
'access': 'Firebase Security Rules',
'resumable_uploads': 'Enabled for large files',
'cdn': 'Automatic via Firebase Hosting CDN'
}
},
'authentication': {
'service': 'Firebase Authentication',
'purpose': 'User management and federation',
'configuration': {
'providers': 'Email, Google, Apple, Phone',
'anonymous_auth': 'Enabled for guest access',
'custom_claims': 'Role-based access control',
'multi_tenancy': 'Supported via Identity Platform'
}
},
'push_notifications': {
'service': 'Firebase Cloud Messaging (FCM)',
'purpose': 'Push notifications to mobile devices',
'configuration': {
'platforms': 'iOS (APNs), Android, Web',
'topics': 'Topic-based group messaging',
'analytics': 'Notification delivery tracking'
}
},
'analytics': {
'service': 'Google Analytics (Firebase)',
'purpose': 'User analytics and event tracking',
'configuration': {
'events': 'Custom and automatic events',
'audiences': 'User segmentation',
'bigquery_export': 'Raw event export to BigQuery'
}
}
},
'estimated_cost': {
'monthly_usd': 40 + (self.expected_users * 0.004),
'breakdown': {
'Cloud Functions': '5-30 USD',
'Firestore': '10-50 USD',
'Cloud Storage': '5-20 USD',
'Identity Platform': '0-15 USD',
'FCM': '0 USD (free)',
'Analytics': '0 USD (free)'
}
},
'pros': [
'Real-time data sync built-in',
'Offline-first support',
'Firebase SDKs for iOS/Android/Web',
'Free tier covers most MVPs',
'Rapid development with Firebase console'
],
'cons': [
'Firestore query limitations',
'Vendor lock-in to Firebase/GCP',
'Cost scaling can be unpredictable',
'Limited server-side control'
],
'scaling_characteristics': {
'users_supported': '1k - 1M',
'requests_per_second': '100 - 100,000',
'scaling_method': 'Automatic (Firebase managed)'
}
}
def _data_pipeline_architecture(self) -> Dict[str, Any]:
"""Serverless data pipeline with BigQuery."""
return {
'pattern_name': 'Serverless Data Pipeline',
'description': 'Scalable data ingestion, processing, and analytics',
'use_case': 'Analytics, IoT data, log processing, ETL, data warehousing',
'services': {
'ingestion': {
'service': 'Pub/Sub',
'purpose': 'Real-time event and data ingestion',
'configuration': {
'throughput': 'Unlimited (auto-scaling)',
'retention': '7 days (configurable to 31 days)',
'ordering': 'Ordering keys for ordered delivery',
'dead_letter': 'Dead letter topic for failed messages'
}
},
'processing': {
'service': 'Dataflow (Apache Beam)',
'purpose': 'Stream and batch data processing',
'configuration': {
'mode': 'Streaming or batch',
'autoscaling': 'Horizontal autoscaling',
'workers': f'{max(1, self.data_size_gb // 20)} initial workers',
'sdk': 'Python or Java Apache Beam SDK'
}
},
'warehouse': {
'service': 'BigQuery',
'purpose': 'Serverless data warehouse and analytics',
'configuration': {
'pricing': 'On-demand ($6.25/TB queried) or slots',
'partitioning': 'By ingestion time or custom field',
'clustering': 'Up to 4 clustering columns',
'streaming_insert': 'Real-time data availability'
}
},
'storage': {
'service': 'Cloud Storage (Data Lake)',
'purpose': 'Raw data lake and archival storage',
'configuration': {
'format': 'Parquet or Avro (columnar)',
'partitioning': 'By date (year/month/day)',
'lifecycle': 'Transition to Coldline after 90 days',
'catalog': 'Dataplex for data governance'
}
},
'visualization': {
'service': 'Looker / Looker Studio',
'purpose': 'Business intelligence dashboards',
'configuration': {
'source': 'BigQuery direct connection',
'refresh': 'Real-time or scheduled',
'sharing': 'Embedded or web dashboards'
}
},
'orchestration': {
'service': 'Cloud Composer (Airflow)',
'purpose': 'Workflow orchestration for batch pipelines',
'configuration': {
'environment': 'Cloud Composer 2 (auto-scaling)',
'dags': 'Python DAG definitions',
'scheduling': 'Cron-based scheduling'
}
}
},
'estimated_cost': {
'monthly_usd': self._calculate_data_pipeline_cost(),
'breakdown': {
'Pub/Sub': '5-30 USD',
'Dataflow': '20-150 USD',
'BigQuery': '10-100 USD (on-demand)',
'Cloud Storage': '5-30 USD',
'Looker Studio': '0 USD (free)',
'Cloud Composer': '300+ USD (if used)'
}
},
'pros': [
'Fully serverless data stack',
'BigQuery scales to petabytes',
'Real-time and batch in same pipeline',
'Cost-effective with on-demand pricing',
'ML integration via BigQuery ML'
],
'cons': [
'Dataflow has steep learning curve (Beam SDK)',
'BigQuery costs based on data scanned',
'Cloud Composer expensive for small workloads',
'Schema evolution requires planning'
],
'scaling_characteristics': {
'events_per_second': '1,000 - 10,000,000',
'data_volume': '1 GB - 1 PB per day',
'scaling_method': 'Automatic (all services auto-scale)'
}
}
def _ml_platform_architecture(self) -> Dict[str, Any]:
"""ML platform architecture with Vertex AI."""
return {
'pattern_name': 'ML Platform',
'description': 'End-to-end machine learning platform',
'use_case': 'Model training, serving, MLOps, feature engineering',
'services': {
'ml_platform': {
'service': 'Vertex AI',
'purpose': 'Training, tuning, and serving ML models',
'configuration': {
'training': 'Custom or AutoML training jobs',
'prediction': 'Online or batch prediction endpoints',
'pipelines': 'Vertex AI Pipelines for MLOps',
'feature_store': 'Vertex AI Feature Store'
}
},
'data': {
'service': 'BigQuery',
'purpose': 'Feature engineering and data exploration',
'configuration': {
'ml': 'BigQuery ML for in-warehouse models',
'export': 'Export to Cloud Storage for training',
'feature_engineering': 'SQL-based transformations'
}
},
'storage': {
'service': 'Cloud Storage',
'purpose': 'Datasets, model artifacts, experiment logs',
'configuration': {
'buckets': 'Separate buckets for data/models/logs',
'versioning': 'Enabled for model artifacts',
'lifecycle': 'Archive old experiment data'
}
},
'triggers': {
'service': 'Cloud Functions',
'purpose': 'Event-driven preprocessing and triggers',
'configuration': {
'triggers': 'Cloud Storage, Pub/Sub, Scheduler',
'preprocessing': 'Data validation and transforms',
'notifications': 'Training completion alerts'
}
},
'monitoring': {
'service': 'Vertex AI Model Monitoring',
'purpose': 'Detect data drift and model degradation',
'configuration': {
'skew_detection': 'Training-serving skew alerts',
'drift_detection': 'Feature drift monitoring',
'alerting': 'Cloud Monitoring integration'
}
}
},
'estimated_cost': {
'monthly_usd': 200 + (self.data_size_gb * 2),
'breakdown': {
'Vertex AI Training': '50-500 USD (GPU dependent)',
'Vertex AI Prediction': '30-200 USD',
'BigQuery': '20-100 USD',
'Cloud Storage': '10-50 USD',
'Cloud Functions': '5-20 USD'
}
},
'pros': [
'End-to-end ML lifecycle management',
'AutoML for rapid prototyping',
'Integrated with BigQuery and Cloud Storage',
'Managed model serving with autoscaling',
'Built-in experiment tracking'
],
'cons': [
'GPU costs can escalate quickly',
'Vertex AI pricing is complex',
'Limited customization vs self-managed',
'Vendor lock-in for model artifacts'
],
'scaling_characteristics': {
'training': 'Multi-GPU, distributed training',
'prediction': '1 - 1000+ replicas',
'scaling_method': 'Automatic endpoint scaling'
}
}
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': 'Cloud DNS',
'purpose': 'Global DNS with health-checked routing',
'configuration': {
'routing_policy': 'Geolocation or weighted routing',
'health_checks': 'HTTP health checks per region',
'failover': 'Automatic DNS failover'
}
},
'cdn': {
'service': 'Cloud CDN',
'purpose': 'Edge caching and acceleration',
'configuration': {
'origins': 'Multiple regional backends',
'cache_modes': 'CACHE_ALL_STATIC or USE_ORIGIN_HEADERS',
'edge_locations': 'Global (100+ locations)'
}
},
'compute': {
'service': 'Multi-region GKE or Cloud Run',
'purpose': 'Active-active deployment across regions',
'configuration': {
'regions': 'us-central1 (primary), europe-west1 (secondary)',
'deployment': 'Cloud Deploy for multi-region rollout',
'traffic_split': 'Global Load Balancer with traffic management'
}
},
'database': {
'service': 'Cloud Spanner or Firestore multi-region',
'purpose': 'Globally consistent database',
'configuration': {
'spanner': 'Multi-region config (nam-eur-asia1)',
'firestore': 'Multi-region location (nam5, eur3)',
'consistency': 'Strong consistency (Spanner) or eventual (Firestore)',
'replication': 'Automatic cross-region replication'
}
},
'storage': {
'service': 'Cloud Storage (dual-region or multi-region)',
'purpose': 'Geo-redundant object storage',
'configuration': {
'location': 'Dual-region (us-central1+us-east1) or multi-region (US)',
'turbo_replication': '15-minute RPO with turbo replication',
'versioning': 'Enabled for critical data'
}
}
},
'estimated_cost': {
'monthly_usd': self._calculate_gke_cost() * 2.0,
'breakdown': {
'Cloud DNS': '5-15 USD',
'Cloud CDN': '20-100 USD',
'Compute (2 regions)': '150-500 USD',
'Cloud Spanner': '500-2000 USD (multi-region)',
'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': [
'2x+ costs vs single region',
'Cloud Spanner is expensive',
'Complex deployment pipeline',
'Cross-region data transfer costs',
'Operational overhead'
],
'scaling_characteristics': {
'users_supported': '100k - 100M',
'requests_per_second': '10,000 - 10,000,000',
'scaling_method': 'Per-region auto-scaling + global load balancing'
}
}
def _calculate_serverless_cost(self) -> float:
"""Estimate serverless architecture cost."""
requests_per_month = self.requests_per_second * 2_592_000
cloud_run_cost = max(5, (requests_per_month / 1_000_000) * 0.40)
firestore_cost = max(5, self.data_size_gb * 0.18)
cdn_cost = max(5, self.expected_users * 0.008)
storage_cost = max(1, self.data_size_gb * 0.02)
total = cloud_run_cost + firestore_cost + cdn_cost + storage_cost
return min(total, self.budget_monthly)
def _calculate_gke_cost(self) -> float:
"""Estimate GKE microservices architecture cost."""
gke_management = 74.40 # Autopilot cluster fee
pod_cost = max(2, self.expected_users // 5000) * 35
cloud_sql_cost = 120 # db-custom-2-8192 baseline
memorystore_cost = 35 # Basic 1 GB
lb_cost = 25
total = gke_management + pod_cost + cloud_sql_cost + memorystore_cost + lb_cost
return min(total, self.budget_monthly)
def _calculate_data_pipeline_cost(self) -> float:
"""Estimate data pipeline cost."""
pubsub_cost = max(5, self.data_size_gb * 0.5)
dataflow_cost = max(20, self.data_size_gb * 1.5)
bigquery_cost = max(10, self.data_size_gb * 0.02 * 6.25)
storage_cost = self.data_size_gb * 0.02
total = pubsub_cost + dataflow_cost + bigquery_cost + storage_cost
return min(total, self.budget_monthly)
def generate_service_checklist(self) -> list:
"""Generate implementation checklist for recommended architecture."""
architecture = self.recommend_architecture_pattern()
checklist = [
{
'phase': 'Planning',
'tasks': [
'Review architecture pattern and services',
'Estimate costs using GCP Pricing Calculator',
'Define environment strategy (dev, staging, prod)',
'Set up GCP Organization and projects',
'Define labeling strategy for resources'
]
},
{
'phase': 'Foundation',
'tasks': [
'Create VPC with subnets (if using GKE/Compute)',
'Configure Cloud NAT for private resources',
'Set up IAM roles and service accounts',
'Enable Cloud Audit Logs',
'Configure Organization policies'
]
},
{
'phase': 'Core Services',
'tasks': [
f"Deploy {service['service']}"
for service in architecture['services'].values()
]
},
{
'phase': 'Security',
'tasks': [
'Configure firewall rules and VPC Service Controls',
'Enable encryption (Cloud KMS) for all services',
'Set up Cloud Armor WAF rules',
'Configure Secret Manager for credentials',
'Enable Security Command Center'
]
},
{
'phase': 'Monitoring',
'tasks': [
'Create Cloud Monitoring dashboards',
'Set up alerting policies for critical metrics',
'Configure notification channels (email, Slack, PagerDuty)',
'Enable Cloud Trace for distributed tracing',
'Set up log-based metrics and log sinks'
]
},
{
'phase': 'CI/CD',
'tasks': [
'Set up Cloud Build triggers',
'Configure automated testing',
'Implement canary or rolling deployments',
'Set up rollback procedures',
'Document deployment process'
]
}
]
return checklist
def main():
parser = argparse.ArgumentParser(
description='GCP Architecture Designer - Recommends GCP services based on workload requirements'
)
parser.add_argument(
'--input', '-i',
type=str,
help='Path to JSON file with application requirements'
)
parser.add_argument(
'--output', '-o',
type=str,
help='Path to write design output JSON'
)
parser.add_argument(
'--json',
action='store_true',
help='Output as JSON format'
)
parser.add_argument(
'--app-type',
type=str,
choices=['web_application', 'mobile_backend', 'data_pipeline',
'microservices', 'saas_platform', 'ml_platform'],
default='web_application',
help='Application type (default: web_application)'
)
parser.add_argument(
'--users',
type=int,
default=1000,
help='Expected number of users (default: 1000)'
)
parser.add_argument(
'--budget',
type=float,
default=500,
help='Monthly budget in USD (default: 500)'
)
args = parser.parse_args()
if args.input:
try:
with open(args.input, 'r') as f:
requirements = json.load(f)
except FileNotFoundError:
print(f"Error: File '{args.input}' not found.", file=sys.stderr)
sys.exit(1)
except json.JSONDecodeError:
print(f"Error: File '{args.input}' is not valid JSON.", file=sys.stderr)
sys.exit(1)
else:
requirements = {
'application_type': args.app_type,
'expected_users': args.users,
'budget_monthly_usd': args.budget
}
designer = ArchitectureDesigner(requirements)
result = designer.recommend_architecture_pattern()
checklist = designer.generate_service_checklist()
output = {
'architecture': result,
'implementation_checklist': checklist
}
if args.output:
with open(args.output, 'w') as f:
json.dump(output, f, indent=2)
print(f"Design written to {args.output}")
elif args.json:
print(json.dumps(output, indent=2))
else:
print(f"\nRecommended Pattern: {result['pattern_name']}")
print(f"Description: {result['description']}")
print(f"Use Case: {result['use_case']}")
print(f"\nServices:")
for name, svc in result['services'].items():
print(f" - {name}: {svc['service']} ({svc['purpose']})")
print(f"\nEstimated Monthly Cost: .2f")
print(f"\nPros: {', '.join(result['pros'])}")
print(f"Cons: {', '.join(result['cons'])}")
if __name__ == '__main__':
main()
FILE:scripts/cost_optimizer.py
"""
GCP cost optimization analyzer.
Provides cost-saving recommendations for GCP resources.
"""
import argparse
import json
import sys
from typing import Dict, List, Any
class CostOptimizer:
"""Analyze GCP costs and provide optimization recommendations."""
def __init__(self, current_resources: Dict[str, Any], monthly_spend: float):
"""
Initialize with current GCP resources and spending.
Args:
current_resources: Dictionary of current GCP resources
monthly_spend: Current monthly GCP 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
compute_savings = self._analyze_compute()
potential_savings += compute_savings
storage_savings = self._analyze_storage()
potential_savings += storage_savings
database_savings = self._analyze_database()
potential_savings += database_savings
network_savings = self._analyze_networking()
potential_savings += network_savings
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 (GCE, GKE, Cloud Run)."""
savings = 0.0
gce_instances = self.resources.get('gce_instances', [])
if gce_instances:
idle_count = sum(1 for inst in gce_instances if inst.get('cpu_utilization', 100) < 10)
if idle_count > 0:
idle_cost = idle_count * 50
savings += idle_cost
self.recommendations.append({
'service': 'Compute Engine',
'type': 'Idle Resources',
'issue': f'{idle_count} GCE instances with <10% CPU utilization',
'recommendation': 'Stop or delete idle instances, or downsize to smaller machine types',
'potential_savings': idle_cost,
'priority': 'high'
})
# Check for committed use discounts
on_demand_count = sum(1 for inst in gce_instances if inst.get('pricing', 'on-demand') == 'on-demand')
if on_demand_count >= 2:
cud_savings = on_demand_count * 50 * 0.37 # 37% savings with 1-yr CUD
savings += cud_savings
self.recommendations.append({
'service': 'Compute Engine',
'type': 'Committed Use Discounts',
'issue': f'{on_demand_count} instances on on-demand pricing',
'recommendation': 'Purchase 1-year committed use discounts for predictable workloads (37% savings) or 3-year (55% savings)',
'potential_savings': cud_savings,
'priority': 'medium'
})
# Check for sustained use discounts awareness
short_lived = sum(1 for inst in gce_instances if inst.get('uptime_hours_month', 730) < 200)
if short_lived > 0:
self.recommendations.append({
'service': 'Compute Engine',
'type': 'Scheduling',
'issue': f'{short_lived} instances running <200 hours/month',
'recommendation': 'Use Instance Scheduler to stop dev/test instances outside business hours',
'potential_savings': short_lived * 20,
'priority': 'medium'
})
savings += short_lived * 20
# GKE optimization
gke_clusters = self.resources.get('gke_clusters', [])
for cluster in gke_clusters:
if cluster.get('mode', 'standard') == 'standard':
node_utilization = cluster.get('avg_node_utilization', 100)
if node_utilization < 40:
autopilot_savings = cluster.get('monthly_cost', 500) * 0.30
savings += autopilot_savings
self.recommendations.append({
'service': 'GKE',
'type': 'Cluster Mode',
'issue': f'Standard GKE cluster with <40% node utilization',
'recommendation': 'Migrate to GKE Autopilot to pay only for pod resources, or enable cluster autoscaler',
'potential_savings': autopilot_savings,
'priority': 'high'
})
# Cloud Run optimization
cloud_run_services = self.resources.get('cloud_run_services', [])
for svc in cloud_run_services:
if svc.get('min_instances', 0) > 0 and svc.get('avg_rps', 100) < 1:
min_inst_savings = svc.get('min_instances', 1) * 15
savings += min_inst_savings
self.recommendations.append({
'service': 'Cloud Run',
'type': 'Min Instances',
'issue': f'Service {svc.get("name", "unknown")} has min instances but very low traffic',
'recommendation': 'Set min-instances to 0 for low-traffic services to enable scale-to-zero',
'potential_savings': min_inst_savings,
'priority': 'medium'
})
return savings
def _analyze_storage(self) -> float:
"""Analyze Cloud Storage resources."""
savings = 0.0
gcs_buckets = self.resources.get('gcs_buckets', [])
for bucket in gcs_buckets:
size_gb = bucket.get('size_gb', 0)
storage_class = bucket.get('storage_class', 'STANDARD')
if not bucket.get('has_lifecycle_policy', False) and size_gb > 100:
lifecycle_savings = size_gb * 0.012
savings += lifecycle_savings
self.recommendations.append({
'service': 'Cloud Storage',
'type': 'Lifecycle Policy',
'issue': f'Bucket {bucket.get("name", "unknown")} ({size_gb} GB) has no lifecycle policy',
'recommendation': 'Add lifecycle rule: Transition to Nearline after 30 days, Coldline after 90 days, Archive after 365 days',
'potential_savings': lifecycle_savings,
'priority': 'medium'
})
if storage_class == 'STANDARD' and size_gb > 500:
class_savings = size_gb * 0.006
savings += class_savings
self.recommendations.append({
'service': 'Cloud Storage',
'type': 'Storage Class',
'issue': f'Large bucket ({size_gb} GB) using Standard class',
'recommendation': 'Enable Autoclass for automatic storage class management based on access patterns',
'potential_savings': class_savings,
'priority': 'high'
})
return savings
def _analyze_database(self) -> float:
"""Analyze Cloud SQL, Firestore, and BigQuery costs."""
savings = 0.0
cloud_sql_instances = self.resources.get('cloud_sql_instances', [])
for db in cloud_sql_instances:
if db.get('connections_per_day', 1000) < 10:
db_cost = db.get('monthly_cost', 100)
savings += db_cost * 0.8
self.recommendations.append({
'service': 'Cloud SQL',
'type': 'Idle Resource',
'issue': f'Database {db.get("name", "unknown")} has <10 connections/day',
'recommendation': 'Stop database if not needed, or take a backup and delete',
'potential_savings': db_cost * 0.8,
'priority': 'high'
})
if db.get('utilization', 100) < 30 and not db.get('has_ha', False):
rightsize_savings = db.get('monthly_cost', 200) * 0.35
savings += rightsize_savings
self.recommendations.append({
'service': 'Cloud SQL',
'type': 'Right-sizing',
'issue': f'Cloud SQL instance {db.get("name", "unknown")} has low utilization (<30%)',
'recommendation': 'Downsize to a smaller machine type (e.g., db-custom-2-8192 to db-f1-micro for dev)',
'potential_savings': rightsize_savings,
'priority': 'medium'
})
# BigQuery optimization
bigquery_datasets = self.resources.get('bigquery_datasets', [])
for dataset in bigquery_datasets:
if dataset.get('pricing_model', 'on_demand') == 'on_demand':
monthly_tb_scanned = dataset.get('monthly_tb_scanned', 0)
if monthly_tb_scanned > 10:
slot_savings = (monthly_tb_scanned * 6.25) * 0.30
savings += slot_savings
self.recommendations.append({
'service': 'BigQuery',
'type': 'Pricing Model',
'issue': f'Scanning {monthly_tb_scanned} TB/month on on-demand pricing',
'recommendation': 'Switch to BigQuery editions with slots for predictable costs (30%+ savings at this volume)',
'potential_savings': slot_savings,
'priority': 'high'
})
if not dataset.get('has_partitioning', False):
partition_savings = dataset.get('monthly_query_cost', 50) * 0.50
savings += partition_savings
self.recommendations.append({
'service': 'BigQuery',
'type': 'Table Partitioning',
'issue': f'Tables in {dataset.get("name", "unknown")} lack partitioning',
'recommendation': 'Partition tables by date and add clustering columns to reduce bytes scanned',
'potential_savings': partition_savings,
'priority': 'medium'
})
return savings
def _analyze_networking(self) -> float:
"""Analyze networking costs (egress, Cloud NAT, etc.)."""
savings = 0.0
cloud_nat_gateways = self.resources.get('cloud_nat_gateways', [])
if len(cloud_nat_gateways) > 1:
extra_nats = len(cloud_nat_gateways) - 1
nat_savings = extra_nats * 45
savings += nat_savings
self.recommendations.append({
'service': 'Cloud NAT',
'type': 'Resource Consolidation',
'issue': f'{len(cloud_nat_gateways)} Cloud NAT gateways deployed',
'recommendation': 'Consolidate NAT gateways in dev/staging, or use Private Google Access for GCP services',
'potential_savings': nat_savings,
'priority': 'high'
})
egress_gb = self.resources.get('monthly_egress_gb', 0)
if egress_gb > 1000:
cdn_savings = egress_gb * 0.04 # CDN is cheaper than direct egress
savings += cdn_savings
self.recommendations.append({
'service': 'Networking',
'type': 'CDN Optimization',
'issue': f'High egress volume ({egress_gb} GB/month)',
'recommendation': 'Enable Cloud CDN to serve cached content at lower egress rates',
'potential_savings': cdn_savings,
'priority': 'medium'
})
return savings
def _analyze_general_optimizations(self) -> float:
"""General GCP cost optimizations."""
savings = 0.0
# Log retention
log_sinks = self.resources.get('log_sinks', [])
if not log_sinks:
log_volume_gb = self.resources.get('monthly_log_volume_gb', 0)
if log_volume_gb > 50:
log_savings = log_volume_gb * 0.50 * 0.6
savings += log_savings
self.recommendations.append({
'service': 'Cloud Logging',
'type': 'Log Exclusion',
'issue': f'{log_volume_gb} GB/month of logs without exclusion filters',
'recommendation': 'Create log exclusion filters for verbose/debug logs and route remaining to Cloud Storage via log sinks',
'potential_savings': log_savings,
'priority': 'medium'
})
# Unattached persistent disks
persistent_disks = self.resources.get('persistent_disks', [])
unattached = sum(1 for disk in persistent_disks if not disk.get('attached', True))
if unattached > 0:
disk_savings = unattached * 10 # ~$10/month per 100 GB disk
savings += disk_savings
self.recommendations.append({
'service': 'Compute Engine',
'type': 'Unused Resources',
'issue': f'{unattached} unattached persistent disks',
'recommendation': 'Snapshot and delete unused persistent disks',
'potential_savings': disk_savings,
'priority': 'high'
})
# Static external IPs
static_ips = self.resources.get('static_ips', [])
unused_ips = sum(1 for ip in static_ips if not ip.get('in_use', True))
if unused_ips > 0:
ip_savings = unused_ips * 7.30 # $0.01/hour = $7.30/month
savings += ip_savings
self.recommendations.append({
'service': 'Networking',
'type': 'Unused Resources',
'issue': f'{unused_ips} unused static external IP addresses',
'recommendation': 'Release unused static IPs to avoid hourly charges',
'potential_savings': ip_savings,
'priority': 'high'
})
# Budget alerts
if not self.resources.get('has_budget_alerts', False):
self.recommendations.append({
'service': 'Cloud Billing',
'type': 'Cost Monitoring',
'issue': 'No budget alerts configured',
'recommendation': 'Set up Cloud Billing budgets with alerts at 50%, 80%, 100% of monthly budget',
'potential_savings': 0,
'priority': 'high'
})
# Recommender API
if not self.resources.get('uses_recommender', False):
self.recommendations.append({
'service': 'Active Assist',
'type': 'Visibility',
'issue': 'GCP Recommender not reviewed',
'recommendation': 'Review Active Assist recommendations for right-sizing, idle resources, and committed use discounts',
'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]
def generate_optimization_checklist(self) -> List[Dict[str, Any]]:
"""Generate actionable checklist for cost optimization."""
return [
{
'category': 'Immediate Actions (Today)',
'items': [
'Release unused static IPs',
'Delete unattached persistent disks',
'Stop idle Compute Engine instances',
'Set up billing budget alerts'
]
},
{
'category': 'This Week',
'items': [
'Add Cloud Storage lifecycle policies',
'Create log exclusion filters for verbose logs',
'Right-size Cloud SQL instances',
'Review Active Assist recommendations'
]
},
{
'category': 'This Month',
'items': [
'Evaluate committed use discounts',
'Migrate GKE Standard to Autopilot where applicable',
'Partition and cluster BigQuery tables',
'Enable Cloud CDN for high-egress services'
]
},
{
'category': 'Ongoing',
'items': [
'Review billing reports weekly',
'Label all resources for cost allocation',
'Monitor Active Assist recommendations monthly',
'Conduct quarterly cost optimization reviews'
]
}
]
def main():
parser = argparse.ArgumentParser(
description='GCP Cost Optimizer - Analyzes GCP resources and recommends cost savings'
)
parser.add_argument(
'--resources', '-r',
type=str,
help='Path to JSON file with current GCP resource inventory'
)
parser.add_argument(
'--monthly-spend', '-s',
type=float,
default=1000,
help='Current monthly GCP spend in USD (default: 1000)'
)
parser.add_argument(
'--output', '-o',
type=str,
help='Path to write optimization report JSON'
)
parser.add_argument(
'--json',
action='store_true',
help='Output as JSON format'
)
parser.add_argument(
'--checklist',
action='store_true',
help='Generate optimization checklist'
)
args = parser.parse_args()
if args.resources:
try:
with open(args.resources, 'r') as f:
resources = json.load(f)
except FileNotFoundError:
print(f"Error: File '{args.resources}' not found.", file=sys.stderr)
sys.exit(1)
except json.JSONDecodeError:
print(f"Error: File '{args.resources}' is not valid JSON.", file=sys.stderr)
sys.exit(1)
else:
resources = {}
optimizer = CostOptimizer(resources, args.monthly_spend)
result = optimizer.analyze_and_optimize()
if args.checklist:
result['checklist'] = optimizer.generate_optimization_checklist()
if args.output:
with open(args.output, 'w') as f:
json.dump(result, f, indent=2)
print(f"Report written to {args.output}")
elif args.json:
print(json.dumps(result, indent=2))
else:
print(f"\nGCP Cost Optimization Report")
print(f"{'=' * 40}")
print(f"Current Monthly Spend: .2f")
print(f"Potential Savings: .2f")
print(f"Optimized Spend: .2f")
print(f"Savings Percentage: {result['savings_percentage']}%")
print(f"\nTop Priority Actions:")
for i, action in enumerate(result['priority_actions'], 1):
print(f" {i}. [{action['service']}] {action['recommendation']}")
print(f" Savings: .2f/month")
print(f"\nTotal Recommendations: {len(result['recommendations'])}")
if __name__ == '__main__':
main()
FILE:scripts/deployment_manager.py
"""
GCP deployment script generator.
Creates gcloud CLI scripts and Terraform configurations for GCP architectures.
"""
import argparse
import json
import sys
from typing import Dict, Any
class DeploymentManager:
"""Generate GCP deployment scripts and IaC configurations."""
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 pattern, region, project requirements
"""
self.app_name = app_name.lower().replace(' ', '-')
self.requirements = requirements
self.region = requirements.get('region', 'us-central1')
self.project_id = requirements.get('project_id', 'my-project')
self.pattern = requirements.get('pattern', 'serverless_web')
def generate_gcloud_script(self) -> str:
"""
Generate gcloud CLI deployment script.
Returns:
Shell script as string
"""
if self.pattern == 'serverless_web':
return self._gcloud_serverless_web()
elif self.pattern == 'gke_microservices':
return self._gcloud_gke_microservices()
elif self.pattern == 'data_pipeline':
return self._gcloud_data_pipeline()
else:
return self._gcloud_serverless_web()
def _gcloud_serverless_web(self) -> str:
"""Generate gcloud script for serverless web pattern."""
return f"""#!/bin/bash
# GCP Serverless Web Deployment Script
# Application: {self.app_name}
# Region: {self.region}
# Pattern: Cloud Run + Firestore + Cloud Storage + Cloud CDN
set -euo pipefail
PROJECT_ID="{self.project_id}"
REGION="{self.region}"
APP_NAME="{self.app_name}"
ENVIRONMENT="-dev}"
echo "=== Deploying $APP_NAME to GCP ($ENVIRONMENT) ==="
# 1. Set project
gcloud config set project $PROJECT_ID
# 2. Enable required APIs
echo "Enabling required APIs..."
gcloud services enable \\
run.googleapis.com \\
firestore.googleapis.com \\
cloudbuild.googleapis.com \\
artifactregistry.googleapis.com \\
secretmanager.googleapis.com \\
compute.googleapis.com \\
monitoring.googleapis.com \\
logging.googleapis.com
# 3. Create Artifact Registry repository
echo "Creating Artifact Registry repository..."
gcloud artifacts repositories create $APP_NAME \\
--repository-format=docker \\
--location=$REGION \\
--description="Docker images for $APP_NAME" \\
|| echo "Repository already exists"
# 4. Build and push container image
echo "Building container image..."
gcloud builds submit \\
--tag $REGION-docker.pkg.dev/$PROJECT_ID/$APP_NAME/$APP_NAME:latest \\
.
# 5. Create Firestore database
echo "Creating Firestore database..."
gcloud firestore databases create \\
--location=$REGION \\
--type=firestore-native \\
|| echo "Firestore database already exists"
# 6. Create service account for Cloud Run
echo "Creating service account..."
SA_NAME="{APP_NAME}-run-sa"
gcloud iam service-accounts create $SA_NAME \\
--display-name="$APP_NAME Cloud Run Service Account" \\
|| echo "Service account already exists"
# Grant Firestore access
gcloud projects add-iam-policy-binding $PROJECT_ID \\
--member="serviceAccount:$SA_NAME@$PROJECT_ID.iam.gserviceaccount.com" \\
--role="roles/datastore.user" \\
--condition=None
# Grant Secret Manager access
gcloud projects add-iam-policy-binding $PROJECT_ID \\
--member="serviceAccount:$SA_NAME@$PROJECT_ID.iam.gserviceaccount.com" \\
--role="roles/secretmanager.secretAccessor" \\
--condition=None
# 7. Deploy Cloud Run service
echo "Deploying Cloud Run service..."
gcloud run deploy $APP_NAME-api \\
--image $REGION-docker.pkg.dev/$PROJECT_ID/$APP_NAME/$APP_NAME:latest \\
--region $REGION \\
--platform managed \\
--service-account $SA_NAME@$PROJECT_ID.iam.gserviceaccount.com \\
--memory 512Mi \\
--cpu 1 \\
--min-instances 0 \\
--max-instances 10 \\
--set-env-vars "PROJECT_ID=$PROJECT_ID,ENVIRONMENT=$ENVIRONMENT" \\
--allow-unauthenticated
# 8. Create Cloud Storage bucket for static assets
echo "Creating static assets bucket..."
BUCKET_NAME="{PROJECT_ID}-{APP_NAME}-static"
gsutil mb -l $REGION gs://$BUCKET_NAME/ || echo "Bucket already exists"
gsutil iam ch allUsers:objectViewer gs://$BUCKET_NAME
# 9. Set up Cloud Monitoring alerting
echo "Setting up monitoring..."
gcloud alpha monitoring policies create \\
--notification-channels="" \\
--display-name="$APP_NAME High Error Rate" \\
--condition-display-name="Cloud Run 5xx Error Rate" \\
--condition-filter='resource.type="cloud_run_revision" AND metric.type="run.googleapis.com/request_count" AND metric.labels.response_code_class="5xx"' \\
--condition-threshold-value=10 \\
--condition-threshold-duration=60s \\
|| echo "Alert policy creation requires additional configuration"
# 10. Output deployment info
echo ""
echo "=== Deployment Complete ==="
SERVICE_URL=$(gcloud run services describe $APP_NAME-api --region $REGION --format 'value(status.url)')
echo "Cloud Run URL: $SERVICE_URL"
echo "Static Bucket: gs://$BUCKET_NAME"
echo "Firestore: https://console.cloud.google.com/firestore?project=$PROJECT_ID"
echo "Monitoring: https://console.cloud.google.com/monitoring?project=$PROJECT_ID"
"""
def _gcloud_gke_microservices(self) -> str:
"""Generate gcloud script for GKE microservices pattern."""
return f"""#!/bin/bash
# GCP GKE Microservices Deployment Script
# Application: {self.app_name}
# Region: {self.region}
# Pattern: GKE Autopilot + Cloud SQL + Memorystore
set -euo pipefail
PROJECT_ID="{self.project_id}"
REGION="{self.region}"
APP_NAME="{self.app_name}"
ENVIRONMENT="-dev}"
CLUSTER_NAME="{APP_NAME}-cluster"
NETWORK_NAME="{APP_NAME}-vpc"
echo "=== Deploying $APP_NAME GKE Microservices ($ENVIRONMENT) ==="
# 1. Set project
gcloud config set project $PROJECT_ID
# 2. Enable required APIs
echo "Enabling required APIs..."
gcloud services enable \\
container.googleapis.com \\
sqladmin.googleapis.com \\
redis.googleapis.com \\
cloudbuild.googleapis.com \\
artifactregistry.googleapis.com \\
secretmanager.googleapis.com \\
servicenetworking.googleapis.com \\
compute.googleapis.com
# 3. Create VPC network
echo "Creating VPC network..."
gcloud compute networks create $NETWORK_NAME \\
--subnet-mode=auto \\
|| echo "Network already exists"
# Allocate IP range for private services
gcloud compute addresses create google-managed-services-$NETWORK_NAME \\
--global \\
--purpose=VPC_PEERING \\
--prefix-length=16 \\
--network=$NETWORK_NAME \\
|| echo "IP range already exists"
gcloud services vpc-peerings connect \\
--service=servicenetworking.googleapis.com \\
--ranges=google-managed-services-$NETWORK_NAME \\
--network=$NETWORK_NAME \\
|| echo "VPC peering already exists"
# 4. Create GKE Autopilot cluster
echo "Creating GKE Autopilot cluster..."
gcloud container clusters create-auto $CLUSTER_NAME \\
--region $REGION \\
--network $NETWORK_NAME \\
--release-channel regular \\
--enable-master-authorized-networks \\
--enable-private-nodes \\
|| echo "Cluster already exists"
# 5. Get cluster credentials
gcloud container clusters get-credentials $CLUSTER_NAME --region $REGION
# 6. Create Cloud SQL instance
echo "Creating Cloud SQL instance..."
gcloud sql instances create $APP_NAME-db \\
--database-version=POSTGRES_15 \\
--tier=db-custom-2-8192 \\
--region=$REGION \\
--network=$NETWORK_NAME \\
--no-assign-ip \\
--availability-type=regional \\
--backup-start-time=02:00 \\
--storage-auto-increase \\
|| echo "Cloud SQL instance already exists"
# Create database
gcloud sql databases create $APP_NAME \\
--instance=$APP_NAME-db \\
|| echo "Database already exists"
# 7. Create Memorystore Redis instance
echo "Creating Memorystore Redis instance..."
gcloud redis instances create $APP_NAME-cache \\
--size=1 \\
--region=$REGION \\
--redis-version=redis_7_0 \\
--network=$NETWORK_NAME \\
--tier=basic \\
|| echo "Redis instance already exists"
# 8. Configure Workload Identity
echo "Configuring Workload Identity..."
SA_NAME="{APP_NAME}-workload"
gcloud iam service-accounts create $SA_NAME \\
--display-name="$APP_NAME Workload Identity SA" \\
|| echo "Service account already exists"
gcloud projects add-iam-policy-binding $PROJECT_ID \\
--member="serviceAccount:$SA_NAME@$PROJECT_ID.iam.gserviceaccount.com" \\
--role="roles/cloudsql.client"
gcloud iam service-accounts add-iam-policy-binding \\
$SA_NAME@$PROJECT_ID.iam.gserviceaccount.com \\
--role="roles/iam.workloadIdentityUser" \\
--member="serviceAccount:$PROJECT_ID.svc.id.goog[default/$SA_NAME]"
echo ""
echo "=== GKE Cluster Ready ==="
echo "Cluster: $CLUSTER_NAME"
echo "Cloud SQL: $APP_NAME-db"
echo "Redis: $APP_NAME-cache"
echo ""
echo "Next: Apply Kubernetes manifests with 'kubectl apply -f k8s/'"
"""
def _gcloud_data_pipeline(self) -> str:
"""Generate gcloud script for data pipeline pattern."""
return f"""#!/bin/bash
# GCP Data Pipeline Deployment Script
# Application: {self.app_name}
# Region: {self.region}
# Pattern: Pub/Sub + Dataflow + BigQuery
set -euo pipefail
PROJECT_ID="{self.project_id}"
REGION="{self.region}"
APP_NAME="{self.app_name}"
echo "=== Deploying $APP_NAME Data Pipeline ==="
# 1. Set project
gcloud config set project $PROJECT_ID
# 2. Enable required APIs
echo "Enabling required APIs..."
gcloud services enable \\
pubsub.googleapis.com \\
dataflow.googleapis.com \\
bigquery.googleapis.com \\
storage.googleapis.com \\
monitoring.googleapis.com
# 3. Create Pub/Sub topic and subscription
echo "Creating Pub/Sub resources..."
gcloud pubsub topics create $APP_NAME-events \\
|| echo "Topic already exists"
gcloud pubsub subscriptions create $APP_NAME-events-sub \\
--topic=$APP_NAME-events \\
--ack-deadline=60 \\
--message-retention-duration=7d \\
|| echo "Subscription already exists"
# Dead letter topic
gcloud pubsub topics create $APP_NAME-events-dlq \\
|| echo "DLQ topic already exists"
gcloud pubsub subscriptions update $APP_NAME-events-sub \\
--dead-letter-topic=$APP_NAME-events-dlq \\
--max-delivery-attempts=5
# 4. Create BigQuery dataset and table
echo "Creating BigQuery resources..."
bq mk --dataset --location=$REGION $PROJECT_ID:{APP_NAME//-/_}_analytics \\
|| echo "Dataset already exists"
bq mk --table \\
$PROJECT_ID:{APP_NAME//-/_}_analytics.events \\
event_id:STRING,event_type:STRING,payload:STRING,timestamp:TIMESTAMP,processed_at:TIMESTAMP \\
--time_partitioning_type=DAY \\
--time_partitioning_field=timestamp \\
--clustering_fields=event_type \\
|| echo "Table already exists"
# 5. Create Cloud Storage bucket for Dataflow temp/staging
echo "Creating staging bucket..."
STAGING_BUCKET="{PROJECT_ID}-{APP_NAME}-dataflow"
gsutil mb -l $REGION gs://$STAGING_BUCKET/ || echo "Bucket already exists"
# 6. Create service account for Dataflow
echo "Creating Dataflow service account..."
SA_NAME="{APP_NAME}-dataflow-sa"
gcloud iam service-accounts create $SA_NAME \\
--display-name="$APP_NAME Dataflow Worker SA" \\
|| echo "Service account already exists"
for ROLE in roles/dataflow.worker roles/bigquery.dataEditor roles/pubsub.subscriber roles/storage.objectAdmin; do
gcloud projects add-iam-policy-binding $PROJECT_ID \\
--member="serviceAccount:$SA_NAME@$PROJECT_ID.iam.gserviceaccount.com" \\
--role="$ROLE" \\
--condition=None
done
echo ""
echo "=== Data Pipeline Infrastructure Ready ==="
echo "Pub/Sub Topic: $APP_NAME-events"
echo "BigQuery Dataset: {APP_NAME//-/_}_analytics"
echo "Staging Bucket: gs://$STAGING_BUCKET"
echo ""
echo "Next: Deploy Dataflow job with Apache Beam pipeline"
echo " python -m apache_beam.examples.streaming_wordcount \\\\"
echo " --runner DataflowRunner \\\\"
echo " --project $PROJECT_ID \\\\"
echo " --region $REGION \\\\"
echo " --temp_location gs://$STAGING_BUCKET/temp"
"""
def generate_terraform_configuration(self) -> str:
"""
Generate Terraform configuration for the selected pattern.
Returns:
Terraform HCL configuration as string
"""
if self.pattern == 'serverless_web':
return self._terraform_serverless_web()
elif self.pattern == 'gke_microservices':
return self._terraform_gke_microservices()
else:
return self._terraform_serverless_web()
def _terraform_serverless_web(self) -> str:
"""Generate Terraform for serverless web pattern."""
return f"""terraform {{
required_version = ">= 1.0"
required_providers {{
google = {{
source = "hashicorp/google"
version = "~> 5.0"
}}
}}
}}
provider "google" {{
project = var.project_id
region = var.region
}}
variable "project_id" {{
description = "GCP project ID"
type = string
}}
variable "region" {{
description = "GCP 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}"
}}
# Enable required APIs
resource "google_project_service" "apis" {{
for_each = toset([
"run.googleapis.com",
"firestore.googleapis.com",
"secretmanager.googleapis.com",
"artifactregistry.googleapis.com",
"monitoring.googleapis.com",
])
project = var.project_id
service = each.value
}}
# Service Account for Cloud Run
resource "google_service_account" "cloud_run" {{
account_id = "{var.app_name}-run-sa"
display_name = "{var.app_name} Cloud Run Service Account"
}}
resource "google_project_iam_member" "firestore_user" {{
project = var.project_id
role = "roles/datastore.user"
member = "serviceAccount:{google_service_account.cloud_run.email}"
}}
resource "google_project_iam_member" "secret_accessor" {{
project = var.project_id
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:{google_service_account.cloud_run.email}"
}}
# Firestore Database
resource "google_firestore_database" "default" {{
project = var.project_id
name = "(default)"
location_id = var.region
type = "FIRESTORE_NATIVE"
depends_on = [google_project_service.apis["firestore.googleapis.com"]]
}}
# Cloud Run Service
resource "google_cloud_run_v2_service" "api" {{
name = "{var.environment}-{var.app_name}-api"
location = var.region
template {{
service_account = google_service_account.cloud_run.email
containers {{
image = "{var.region}-docker.pkg.dev/{var.project_id}/{var.app_name}/{var.app_name}:latest"
resources {{
limits = {{
cpu = "1000m"
memory = "512Mi"
}}
}}
env {{
name = "PROJECT_ID"
value = var.project_id
}}
env {{
name = "ENVIRONMENT"
value = var.environment
}}
}}
scaling {{
min_instance_count = 0
max_instance_count = 10
}}
}}
depends_on = [google_project_service.apis["run.googleapis.com"]]
labels = {{
environment = var.environment
app = var.app_name
}}
}}
# Allow unauthenticated access (public API)
resource "google_cloud_run_v2_service_iam_member" "public" {{
project = var.project_id
location = var.region
name = google_cloud_run_v2_service.api.name
role = "roles/run.invoker"
member = "allUsers"
}}
# Cloud Storage bucket for static assets
resource "google_storage_bucket" "static" {{
name = "{var.project_id}-{var.app_name}-static"
location = var.region
uniform_bucket_level_access = true
website {{
main_page_suffix = "index.html"
not_found_page = "404.html"
}}
lifecycle_rule {{
condition {{
age = 30
}}
action {{
type = "SetStorageClass"
storage_class = "NEARLINE"
}}
}}
labels = {{
environment = var.environment
app = var.app_name
}}
}}
# Outputs
output "cloud_run_url" {{
description = "Cloud Run service URL"
value = google_cloud_run_v2_service.api.uri
}}
output "static_bucket" {{
description = "Static assets bucket name"
value = google_storage_bucket.static.name
}}
output "service_account" {{
description = "Cloud Run service account email"
value = google_service_account.cloud_run.email
}}
"""
def _terraform_gke_microservices(self) -> str:
"""Generate Terraform for GKE microservices pattern."""
return f"""terraform {{
required_version = ">= 1.0"
required_providers {{
google = {{
source = "hashicorp/google"
version = "~> 5.0"
}}
}}
}}
provider "google" {{
project = var.project_id
region = var.region
}}
variable "project_id" {{
description = "GCP project ID"
type = string
}}
variable "region" {{
description = "GCP 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}"
}}
# Enable required APIs
resource "google_project_service" "apis" {{
for_each = toset([
"container.googleapis.com",
"sqladmin.googleapis.com",
"redis.googleapis.com",
"servicenetworking.googleapis.com",
"secretmanager.googleapis.com",
])
project = var.project_id
service = each.value
}}
# VPC Network
resource "google_compute_network" "main" {{
name = "{var.app_name}-vpc"
auto_create_subnetworks = false
}}
resource "google_compute_subnetwork" "main" {{
name = "{var.app_name}-subnet"
ip_cidr_range = "10.0.0.0/20"
region = var.region
network = google_compute_network.main.id
secondary_ip_range {{
range_name = "pods"
ip_cidr_range = "10.4.0.0/14"
}}
secondary_ip_range {{
range_name = "services"
ip_cidr_range = "10.8.0.0/20"
}}
}}
# GKE Autopilot Cluster
resource "google_container_cluster" "main" {{
name = "{var.environment}-{var.app_name}-cluster"
location = var.region
enable_autopilot = true
network = google_compute_network.main.name
subnetwork = google_compute_subnetwork.main.name
ip_allocation_policy {{
cluster_secondary_range_name = "pods"
services_secondary_range_name = "services"
}}
release_channel {{
channel = "REGULAR"
}}
depends_on = [google_project_service.apis["container.googleapis.com"]]
}}
# Private Services Access for Cloud SQL
resource "google_compute_global_address" "private_ip" {{
name = "private-ip-range"
purpose = "VPC_PEERING"
address_type = "INTERNAL"
prefix_length = 16
network = google_compute_network.main.id
}}
resource "google_service_networking_connection" "private_vpc" {{
network = google_compute_network.main.id
service = "servicenetworking.googleapis.com"
reserved_peering_ranges = [google_compute_global_address.private_ip.name]
}}
# Cloud SQL PostgreSQL
resource "google_sql_database_instance" "main" {{
name = "{var.environment}-{var.app_name}-db"
database_version = "POSTGRES_15"
region = var.region
settings {{
tier = "db-custom-2-8192"
availability_type = "REGIONAL"
backup_configuration {{
enabled = true
start_time = "02:00"
point_in_time_recovery_enabled = true
}}
ip_configuration {{
ipv4_enabled = false
private_network = google_compute_network.main.id
}}
disk_autoresize = true
}}
depends_on = [google_service_networking_connection.private_vpc]
}}
resource "google_sql_database" "app" {{
name = var.app_name
instance = google_sql_database_instance.main.name
}}
# Memorystore Redis
resource "google_redis_instance" "cache" {{
name = "{var.environment}-{var.app_name}-cache"
tier = "BASIC"
memory_size_gb = 1
region = var.region
redis_version = "REDIS_7_0"
authorized_network = google_compute_network.main.id
depends_on = [google_project_service.apis["redis.googleapis.com"]]
labels = {{
environment = var.environment
app = var.app_name
}}
}}
# Outputs
output "cluster_name" {{
description = "GKE cluster name"
value = google_container_cluster.main.name
}}
output "cloud_sql_connection" {{
description = "Cloud SQL connection name"
value = google_sql_database_instance.main.connection_name
}}
output "redis_host" {{
description = "Memorystore Redis host"
value = google_redis_instance.cache.host
}}
"""
def main():
parser = argparse.ArgumentParser(
description='GCP Deployment Manager - Generates gcloud CLI scripts and Terraform configurations'
)
parser.add_argument(
'--app-name', '-a',
type=str,
required=True,
help='Application name'
)
parser.add_argument(
'--pattern', '-p',
type=str,
choices=['serverless_web', 'gke_microservices', 'data_pipeline'],
default='serverless_web',
help='Architecture pattern (default: serverless_web)'
)
parser.add_argument(
'--region', '-r',
type=str,
default='us-central1',
help='GCP region (default: us-central1)'
)
parser.add_argument(
'--project-id',
type=str,
default='my-project',
help='GCP project ID (default: my-project)'
)
parser.add_argument(
'--format', '-f',
type=str,
choices=['gcloud', 'terraform', 'both'],
default='both',
help='Output format (default: both)'
)
parser.add_argument(
'--output', '-o',
type=str,
help='Output directory for generated files'
)
parser.add_argument(
'--json',
action='store_true',
help='Output as JSON format'
)
args = parser.parse_args()
requirements = {
'pattern': args.pattern,
'region': args.region,
'project_id': args.project_id
}
manager = DeploymentManager(args.app_name, requirements)
if args.json:
output = {}
if args.format in ('gcloud', 'both'):
output['gcloud_script'] = manager.generate_gcloud_script()
if args.format in ('terraform', 'both'):
output['terraform_config'] = manager.generate_terraform_configuration()
print(json.dumps(output, indent=2))
elif args.output:
import os
os.makedirs(args.output, exist_ok=True)
if args.format in ('gcloud', 'both'):
gcloud_path = os.path.join(args.output, 'deploy.sh')
with open(gcloud_path, 'w') as f:
f.write(manager.generate_gcloud_script())
os.chmod(gcloud_path, 0o755)
print(f"gcloud script written to {gcloud_path}")
if args.format in ('terraform', 'both'):
tf_path = os.path.join(args.output, 'main.tf')
with open(tf_path, 'w') as f:
f.write(manager.generate_terraform_configuration())
print(f"Terraform config written to {tf_path}")
else:
if args.format in ('gcloud', 'both'):
print("# ===== gcloud CLI Script =====")
print(manager.generate_gcloud_script())
if args.format in ('terraform', 'both'):
print("# ===== Terraform Configuration =====")
print(manager.generate_terraform_configuration())
if __name__ == '__main__':
main()