Lập kế hoạch di chuyển không gián đoạn, kiểm tra tương thích và chiến lược rollback cho di chuyển hệ thống, cơ sở dữ liệu và hạ tầng.
---
name: "migration-architect"
description: "Zero-downtime migration planning, compatibility validation, and rollback strategy generation. Tools for system, database, and infrastructure migrations with minimal business impact. Use when planning a database migration, infrastructure cutover, system replacement, or any high-risk transition that needs explicit rollback paths."
---
# Migration Architect
**Tier:** POWERFUL
**Category:** Engineering - Migration Strategy
**Purpose:** Zero-downtime migration planning, compatibility validation, and rollback strategy generation
## Overview
The Migration Architect skill provides comprehensive tools and methodologies for planning, executing, and validating complex system migrations with minimal business impact. This skill combines proven migration patterns with automated planning tools to ensure successful transitions between systems, databases, and infrastructure.
## Core Capabilities
### 1. Migration Strategy Planning
- **Phased Migration Planning:** Break complex migrations into manageable phases with clear validation gates
- **Risk Assessment:** Identify potential failure points and mitigation strategies before execution
- **Timeline Estimation:** Generate realistic timelines based on migration complexity and resource constraints
- **Stakeholder Communication:** Create communication templates and progress dashboards
### 2. Compatibility Analysis
- **Schema Evolution:** Analyze database schema changes for backward compatibility issues
- **API Versioning:** Detect breaking changes in REST/GraphQL APIs and microservice interfaces
- **Data Type Validation:** Identify data format mismatches and conversion requirements
- **Constraint Analysis:** Validate referential integrity and business rule changes
### 3. Rollback Strategy Generation
- **Automated Rollback Plans:** Generate comprehensive rollback procedures for each migration phase
- **Data Recovery Scripts:** Create point-in-time data restoration procedures
- **Service Rollback:** Plan service version rollbacks with traffic management
- **Validation Checkpoints:** Define success criteria and rollback triggers
## Migration Patterns
### Database Migrations
#### Schema Evolution Patterns
1. **Expand-Contract Pattern**
- **Expand:** Add new columns/tables alongside existing schema
- **Dual Write:** Application writes to both old and new schema
- **Migration:** Backfill historical data to new schema
- **Contract:** Remove old columns/tables after validation
2. **Parallel Schema Pattern**
- Run new schema in parallel with existing schema
- Use feature flags to route traffic between schemas
- Validate data consistency between parallel systems
- Cutover when confidence is high
3. **Event Sourcing Migration**
- Capture all changes as events during migration window
- Apply events to new schema for consistency
- Enable replay capability for rollback scenarios
#### Data Migration Strategies
1. **Bulk Data Migration**
- **Snapshot Approach:** Full data copy during maintenance window
- **Incremental Sync:** Continuous data synchronization with change tracking
- **Stream Processing:** Real-time data transformation pipelines
2. **Dual-Write Pattern**
- Write to both source and target systems during migration
- Implement compensation patterns for write failures
- Use distributed transactions where consistency is critical
3. **Change Data Capture (CDC)**
- Stream database changes to target system
- Maintain eventual consistency during migration
- Enable zero-downtime migrations for large datasets
### Service Migrations
#### Strangler Fig Pattern
1. **Intercept Requests:** Route traffic through proxy/gateway
2. **Gradually Replace:** Implement new service functionality incrementally
3. **Legacy Retirement:** Remove old service components as new ones prove stable
4. **Monitoring:** Track performance and error rates throughout transition
```mermaid
graph TD
A[Client Requests] --> B[API Gateway]
B --> C{Route Decision}
C -->|Legacy Path| D[Legacy Service]
C -->|New Path| E[New Service]
D --> F[Legacy Database]
E --> G[New Database]
```
#### Parallel Run Pattern
1. **Dual Execution:** Run both old and new services simultaneously
2. **Shadow Traffic:** Route production traffic to both systems
3. **Result Comparison:** Compare outputs to validate correctness
4. **Gradual Cutover:** Shift traffic percentage based on confidence
#### Canary Deployment Pattern
1. **Limited Rollout:** Deploy new service to small percentage of users
2. **Monitoring:** Track key metrics (latency, errors, business KPIs)
3. **Gradual Increase:** Increase traffic percentage as confidence grows
4. **Full Rollout:** Complete migration once validation passes
### Infrastructure Migrations
#### Cloud-to-Cloud Migration
1. **Assessment Phase**
- Inventory existing resources and dependencies
- Map services to target cloud equivalents
- Identify vendor-specific features requiring refactoring
2. **Pilot Migration**
- Migrate non-critical workloads first
- Validate performance and cost models
- Refine migration procedures
3. **Production Migration**
- Use infrastructure as code for consistency
- Implement cross-cloud networking during transition
- Maintain disaster recovery capabilities
#### On-Premises to Cloud Migration
1. **Lift and Shift**
- Minimal changes to existing applications
- Quick migration with optimization later
- Use cloud migration tools and services
2. **Re-architecture**
- Redesign applications for cloud-native patterns
- Adopt microservices, containers, and serverless
- Implement cloud security and scaling practices
3. **Hybrid Approach**
- Keep sensitive data on-premises
- Migrate compute workloads to cloud
- Implement secure connectivity between environments
## Feature Flags for Migrations
### Progressive Feature Rollout
```python
# Example feature flag implementation
class MigrationFeatureFlag:
def __init__(self, flag_name, rollout_percentage=0):
self.flag_name = flag_name
self.rollout_percentage = rollout_percentage
def is_enabled_for_user(self, user_id):
hash_value = hash(f"{self.flag_name}:{user_id}")
return (hash_value % 100) < self.rollout_percentage
def gradual_rollout(self, target_percentage, step_size=10):
while self.rollout_percentage < target_percentage:
self.rollout_percentage = min(
self.rollout_percentage + step_size,
target_percentage
)
yield self.rollout_percentage
```
### Circuit Breaker Pattern
Implement automatic fallback to legacy systems when new systems show degraded performance:
```python
class MigrationCircuitBreaker:
def __init__(self, failure_threshold=5, timeout=60):
self.failure_count = 0
self.failure_threshold = failure_threshold
self.timeout = timeout
self.last_failure_time = None
self.state = 'CLOSED' # CLOSED, OPEN, HALF_OPEN
def call_new_service(self, request):
if self.state == 'OPEN':
if self.should_attempt_reset():
self.state = 'HALF_OPEN'
else:
return self.fallback_to_legacy(request)
try:
response = self.new_service.process(request)
self.on_success()
return response
except Exception as e:
self.on_failure()
return self.fallback_to_legacy(request)
```
## Data Validation and Reconciliation
### Validation Strategies
1. **Row Count Validation**
- Compare record counts between source and target
- Account for soft deletes and filtered records
- Implement threshold-based alerting
2. **Checksums and Hashing**
- Generate checksums for critical data subsets
- Compare hash values to detect data drift
- Use sampling for large datasets
3. **Business Logic Validation**
- Run critical business queries on both systems
- Compare aggregate results (sums, counts, averages)
- Validate derived data and calculations
### Reconciliation Patterns
1. **Delta Detection**
```sql
-- Example delta query for reconciliation
SELECT 'missing_in_target' as issue_type, source_id
FROM source_table s
WHERE NOT EXISTS (
SELECT 1 FROM target_table t
WHERE t.id = s.id
)
UNION ALL
SELECT 'extra_in_target' as issue_type, target_id
FROM target_table t
WHERE NOT EXISTS (
SELECT 1 FROM source_table s
WHERE s.id = t.id
);
```
2. **Automated Correction**
- Implement data repair scripts for common issues
- Use idempotent operations for safe re-execution
- Log all correction actions for audit trails
## Rollback Strategies
### Database Rollback
1. **Schema Rollback**
- Maintain schema version control
- Use backward-compatible migrations when possible
- Keep rollback scripts for each migration step
2. **Data Rollback**
- Point-in-time recovery using database backups
- Transaction log replay for precise rollback points
- Maintain data snapshots at migration checkpoints
### Service Rollback
1. **Blue-Green Deployment**
- Keep previous service version running during migration
- Switch traffic back to blue environment if issues arise
- Maintain parallel infrastructure during migration window
2. **Rolling Rollback**
- Gradually shift traffic back to previous version
- Monitor system health during rollback process
- Implement automated rollback triggers
### Infrastructure Rollback
1. **Infrastructure as Code**
- Version control all infrastructure definitions
- Maintain rollback terraform/CloudFormation templates
- Test rollback procedures in staging environments
2. **Data Persistence**
- Preserve data in original location during migration
- Implement data sync back to original systems
- Maintain backup strategies across both environments
## Risk Assessment Framework
### Risk Categories
1. **Technical Risks**
- Data loss or corruption
- Service downtime or degraded performance
- Integration failures with dependent systems
- Scalability issues under production load
2. **Business Risks**
- Revenue impact from service disruption
- Customer experience degradation
- Compliance and regulatory concerns
- Brand reputation impact
3. **Operational Risks**
- Team knowledge gaps
- Insufficient testing coverage
- Inadequate monitoring and alerting
- Communication breakdowns
### Risk Mitigation Strategies
1. **Technical Mitigations**
- Comprehensive testing (unit, integration, load, chaos)
- Gradual rollout with automated rollback triggers
- Data validation and reconciliation processes
- Performance monitoring and alerting
2. **Business Mitigations**
- Stakeholder communication plans
- Business continuity procedures
- Customer notification strategies
- Revenue protection measures
3. **Operational Mitigations**
- Team training and documentation
- Runbook creation and testing
- On-call rotation planning
- Post-migration review processes
## Migration Runbooks
### Pre-Migration Checklist
- [ ] Migration plan reviewed and approved
- [ ] Rollback procedures tested and validated
- [ ] Monitoring and alerting configured
- [ ] Team roles and responsibilities defined
- [ ] Stakeholder communication plan activated
- [ ] Backup and recovery procedures verified
- [ ] Test environment validation complete
- [ ] Performance benchmarks established
- [ ] Security review completed
- [ ] Compliance requirements verified
### During Migration
- [ ] Execute migration phases in planned order
- [ ] Monitor key performance indicators continuously
- [ ] Validate data consistency at each checkpoint
- [ ] Communicate progress to stakeholders
- [ ] Document any deviations from plan
- [ ] Execute rollback if success criteria not met
- [ ] Coordinate with dependent teams
- [ ] Maintain detailed execution logs
### Post-Migration
- [ ] Validate all success criteria met
- [ ] Perform comprehensive system health checks
- [ ] Execute data reconciliation procedures
- [ ] Monitor system performance over 72 hours
- [ ] Update documentation and runbooks
- [ ] Decommission legacy systems (if applicable)
- [ ] Conduct post-migration retrospective
- [ ] Archive migration artifacts
- [ ] Update disaster recovery procedures
## Communication Templates
### Executive Summary Template
```
Migration Status: [IN_PROGRESS | COMPLETED | ROLLED_BACK]
Start Time: [YYYY-MM-DD HH:MM UTC]
Current Phase: [X of Y]
Overall Progress: [X%]
Key Metrics:
- System Availability: [X.XX%]
- Data Migration Progress: [X.XX%]
- Performance Impact: [+/-X%]
- Issues Encountered: [X]
Next Steps:
1. [Action item 1]
2. [Action item 2]
Risk Assessment: [LOW | MEDIUM | HIGH]
Rollback Status: [AVAILABLE | NOT_AVAILABLE]
```
### Technical Team Update Template
```
Phase: [Phase Name] - [Status]
Duration: [Started] - [Expected End]
Completed Tasks:
✓ [Task 1]
✓ [Task 2]
In Progress:
🔄 [Task 3] - [X% complete]
Upcoming:
⏳ [Task 4] - [Expected start time]
Issues:
⚠️ [Issue description] - [Severity] - [ETA resolution]
Metrics:
- Migration Rate: [X records/minute]
- Error Rate: [X.XX%]
- System Load: [CPU/Memory/Disk]
```
## Success Metrics
### Technical Metrics
- **Migration Completion Rate:** Percentage of data/services successfully migrated
- **Downtime Duration:** Total system unavailability during migration
- **Data Consistency Score:** Percentage of data validation checks passing
- **Performance Delta:** Performance change compared to baseline
- **Error Rate:** Percentage of failed operations during migration
### Business Metrics
- **Customer Impact Score:** Measure of customer experience degradation
- **Revenue Protection:** Percentage of revenue maintained during migration
- **Time to Value:** Duration from migration start to business value realization
- **Stakeholder Satisfaction:** Post-migration stakeholder feedback scores
### Operational Metrics
- **Plan Adherence:** Percentage of migration executed according to plan
- **Issue Resolution Time:** Average time to resolve migration issues
- **Team Efficiency:** Resource utilization and productivity metrics
- **Knowledge Transfer Score:** Team readiness for post-migration operations
## Tools and Technologies
### Migration Planning Tools
- **migration_planner.py:** Automated migration plan generation
- **compatibility_checker.py:** Schema and API compatibility analysis
- **rollback_generator.py:** Comprehensive rollback procedure generation
### Validation Tools
- Database comparison utilities (schema and data)
- API contract testing frameworks
- Performance benchmarking tools
- Data quality validation pipelines
### Monitoring and Alerting
- Real-time migration progress dashboards
- Automated rollback trigger systems
- Business metric monitoring
- Stakeholder notification systems
## Best Practices
### Planning Phase
1. **Start with Risk Assessment:** Identify all potential failure modes before planning
2. **Design for Rollback:** Every migration step should have a tested rollback procedure
3. **Validate in Staging:** Execute full migration process in production-like environment
4. **Plan for Gradual Rollout:** Use feature flags and traffic routing for controlled migration
### Execution Phase
1. **Monitor Continuously:** Track both technical and business metrics throughout
2. **Communicate Proactively:** Keep all stakeholders informed of progress and issues
3. **Document Everything:** Maintain detailed logs for post-migration analysis
4. **Stay Flexible:** Be prepared to adjust timeline based on real-world performance
### Validation Phase
1. **Automate Validation:** Use automated tools for data consistency and performance checks
2. **Business Logic Testing:** Validate critical business processes end-to-end
3. **Load Testing:** Verify system performance under expected production load
4. **Security Validation:** Ensure security controls function properly in new environment
## Integration with Development Lifecycle
### CI/CD Integration
```yaml
# Example migration pipeline stage
migration_validation:
stage: test
script:
- python scripts/compatibility_checker.py --before=old_schema.json --after=new_schema.json
- python scripts/migration_planner.py --config=migration_config.json --validate
artifacts:
reports:
- compatibility_report.json
- migration_plan.json
```
### Infrastructure as Code
```terraform
# Example Terraform for blue-green infrastructure
resource "aws_instance" "blue_environment" {
count = var.migration_phase == "preparation" ? var.instance_count : 0
# Blue environment configuration
}
resource "aws_instance" "green_environment" {
count = var.migration_phase == "execution" ? var.instance_count : 0
# Green environment configuration
}
```
This Migration Architect skill provides a comprehensive framework for planning, executing, and validating complex system migrations while minimizing business impact and technical risk. The combination of automated tools, proven patterns, and detailed procedures enables organizations to confidently undertake even the most complex migration projects.
FILE:assets/database_schema_after.json
{
"schema_version": "2.0",
"database": "user_management_v2",
"tables": {
"users": {
"columns": {
"id": {
"type": "bigint",
"nullable": false,
"primary_key": true,
"auto_increment": true
},
"username": {
"type": "varchar",
"length": 50,
"nullable": false,
"unique": true
},
"email": {
"type": "varchar",
"length": 320,
"nullable": false,
"unique": true
},
"password_hash": {
"type": "varchar",
"length": 255,
"nullable": false
},
"first_name": {
"type": "varchar",
"length": 100,
"nullable": true
},
"last_name": {
"type": "varchar",
"length": 100,
"nullable": true
},
"created_at": {
"type": "timestamp",
"nullable": false,
"default": "CURRENT_TIMESTAMP"
},
"updated_at": {
"type": "timestamp",
"nullable": false,
"default": "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"
},
"is_active": {
"type": "boolean",
"nullable": false,
"default": true
},
"phone": {
"type": "varchar",
"length": 20,
"nullable": true
},
"email_verified_at": {
"type": "timestamp",
"nullable": true,
"comment": "When email was verified"
},
"phone_verified_at": {
"type": "timestamp",
"nullable": true,
"comment": "When phone was verified"
},
"two_factor_enabled": {
"type": "boolean",
"nullable": false,
"default": false
},
"last_login_at": {
"type": "timestamp",
"nullable": true
}
},
"constraints": {
"primary_key": ["id"],
"unique": [
"username",
"email"
],
"foreign_key": [],
"check": [
"email LIKE '%@%'",
"LENGTH(password_hash) >= 60",
"phone IS NULL OR LENGTH(phone) >= 10"
]
},
"indexes": [
{
"name": "idx_users_email",
"columns": ["email"],
"unique": true
},
{
"name": "idx_users_username",
"columns": ["username"],
"unique": true
},
{
"name": "idx_users_created_at",
"columns": ["created_at"]
},
{
"name": "idx_users_email_verified",
"columns": ["email_verified_at"]
},
{
"name": "idx_users_last_login",
"columns": ["last_login_at"]
}
]
},
"user_profiles": {
"columns": {
"id": {
"type": "bigint",
"nullable": false,
"primary_key": true,
"auto_increment": true
},
"user_id": {
"type": "bigint",
"nullable": false
},
"bio": {
"type": "text",
"nullable": true
},
"avatar_url": {
"type": "varchar",
"length": 500,
"nullable": true
},
"birth_date": {
"type": "date",
"nullable": true
},
"location": {
"type": "varchar",
"length": 100,
"nullable": true
},
"website": {
"type": "varchar",
"length": 255,
"nullable": true
},
"privacy_level": {
"type": "varchar",
"length": 20,
"nullable": false,
"default": "public"
},
"timezone": {
"type": "varchar",
"length": 50,
"nullable": true,
"default": "UTC"
},
"language": {
"type": "varchar",
"length": 10,
"nullable": false,
"default": "en"
},
"created_at": {
"type": "timestamp",
"nullable": false,
"default": "CURRENT_TIMESTAMP"
},
"updated_at": {
"type": "timestamp",
"nullable": false,
"default": "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"
}
},
"constraints": {
"primary_key": ["id"],
"unique": [],
"foreign_key": [
{
"columns": ["user_id"],
"references": "users(id)",
"on_delete": "CASCADE"
}
],
"check": [
"privacy_level IN ('public', 'private', 'friends_only')",
"bio IS NULL OR LENGTH(bio) <= 2000",
"language IN ('en', 'es', 'fr', 'de', 'it', 'pt', 'ru', 'ja', 'ko', 'zh')"
]
},
"indexes": [
{
"name": "idx_user_profiles_user_id",
"columns": ["user_id"],
"unique": true
},
{
"name": "idx_user_profiles_privacy",
"columns": ["privacy_level"]
},
{
"name": "idx_user_profiles_language",
"columns": ["language"]
}
]
},
"user_sessions": {
"columns": {
"id": {
"type": "varchar",
"length": 128,
"nullable": false,
"primary_key": true
},
"user_id": {
"type": "bigint",
"nullable": false
},
"ip_address": {
"type": "varchar",
"length": 45,
"nullable": true
},
"user_agent": {
"type": "text",
"nullable": true
},
"expires_at": {
"type": "timestamp",
"nullable": false
},
"created_at": {
"type": "timestamp",
"nullable": false,
"default": "CURRENT_TIMESTAMP"
},
"last_activity": {
"type": "timestamp",
"nullable": false,
"default": "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"
},
"session_type": {
"type": "varchar",
"length": 20,
"nullable": false,
"default": "web"
},
"is_mobile": {
"type": "boolean",
"nullable": false,
"default": false
}
},
"constraints": {
"primary_key": ["id"],
"unique": [],
"foreign_key": [
{
"columns": ["user_id"],
"references": "users(id)",
"on_delete": "CASCADE"
}
],
"check": [
"session_type IN ('web', 'mobile', 'api', 'admin')"
]
},
"indexes": [
{
"name": "idx_user_sessions_user_id",
"columns": ["user_id"]
},
{
"name": "idx_user_sessions_expires",
"columns": ["expires_at"]
},
{
"name": "idx_user_sessions_type",
"columns": ["session_type"]
}
]
},
"user_preferences": {
"columns": {
"id": {
"type": "bigint",
"nullable": false,
"primary_key": true,
"auto_increment": true
},
"user_id": {
"type": "bigint",
"nullable": false
},
"preference_key": {
"type": "varchar",
"length": 100,
"nullable": false
},
"preference_value": {
"type": "json",
"nullable": true
},
"created_at": {
"type": "timestamp",
"nullable": false,
"default": "CURRENT_TIMESTAMP"
},
"updated_at": {
"type": "timestamp",
"nullable": false,
"default": "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"
}
},
"constraints": {
"primary_key": ["id"],
"unique": [
["user_id", "preference_key"]
],
"foreign_key": [
{
"columns": ["user_id"],
"references": "users(id)",
"on_delete": "CASCADE"
}
],
"check": []
},
"indexes": [
{
"name": "idx_user_preferences_user_key",
"columns": ["user_id", "preference_key"],
"unique": true
}
]
}
},
"views": {
"active_users": {
"definition": "SELECT u.id, u.username, u.email, u.first_name, u.last_name, u.email_verified_at, u.last_login_at FROM users u WHERE u.is_active = true",
"columns": ["id", "username", "email", "first_name", "last_name", "email_verified_at", "last_login_at"]
},
"verified_users": {
"definition": "SELECT u.id, u.username, u.email FROM users u WHERE u.is_active = true AND u.email_verified_at IS NOT NULL",
"columns": ["id", "username", "email"]
}
},
"procedures": [
{
"name": "cleanup_expired_sessions",
"parameters": [],
"definition": "DELETE FROM user_sessions WHERE expires_at < NOW()"
},
{
"name": "get_user_with_profile",
"parameters": ["user_id BIGINT"],
"definition": "SELECT u.*, p.bio, p.avatar_url, p.privacy_level FROM users u LEFT JOIN user_profiles p ON u.id = p.user_id WHERE u.id = user_id"
}
]
}
FILE:assets/database_schema_before.json
{
"schema_version": "1.0",
"database": "user_management",
"tables": {
"users": {
"columns": {
"id": {
"type": "bigint",
"nullable": false,
"primary_key": true,
"auto_increment": true
},
"username": {
"type": "varchar",
"length": 50,
"nullable": false,
"unique": true
},
"email": {
"type": "varchar",
"length": 255,
"nullable": false,
"unique": true
},
"password_hash": {
"type": "varchar",
"length": 255,
"nullable": false
},
"first_name": {
"type": "varchar",
"length": 100,
"nullable": true
},
"last_name": {
"type": "varchar",
"length": 100,
"nullable": true
},
"created_at": {
"type": "timestamp",
"nullable": false,
"default": "CURRENT_TIMESTAMP"
},
"updated_at": {
"type": "timestamp",
"nullable": false,
"default": "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"
},
"is_active": {
"type": "boolean",
"nullable": false,
"default": true
},
"phone": {
"type": "varchar",
"length": 20,
"nullable": true
}
},
"constraints": {
"primary_key": ["id"],
"unique": [
"username",
"email"
],
"foreign_key": [],
"check": [
"email LIKE '%@%'",
"LENGTH(password_hash) >= 60"
]
},
"indexes": [
{
"name": "idx_users_email",
"columns": ["email"],
"unique": true
},
{
"name": "idx_users_username",
"columns": ["username"],
"unique": true
},
{
"name": "idx_users_created_at",
"columns": ["created_at"]
}
]
},
"user_profiles": {
"columns": {
"id": {
"type": "bigint",
"nullable": false,
"primary_key": true,
"auto_increment": true
},
"user_id": {
"type": "bigint",
"nullable": false
},
"bio": {
"type": "varchar",
"length": 255,
"nullable": true
},
"avatar_url": {
"type": "varchar",
"length": 500,
"nullable": true
},
"birth_date": {
"type": "date",
"nullable": true
},
"location": {
"type": "varchar",
"length": 100,
"nullable": true
},
"website": {
"type": "varchar",
"length": 255,
"nullable": true
},
"privacy_level": {
"type": "varchar",
"length": 20,
"nullable": false,
"default": "public"
},
"created_at": {
"type": "timestamp",
"nullable": false,
"default": "CURRENT_TIMESTAMP"
},
"updated_at": {
"type": "timestamp",
"nullable": false,
"default": "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"
}
},
"constraints": {
"primary_key": ["id"],
"unique": [],
"foreign_key": [
{
"columns": ["user_id"],
"references": "users(id)",
"on_delete": "CASCADE"
}
],
"check": [
"privacy_level IN ('public', 'private', 'friends_only')"
]
},
"indexes": [
{
"name": "idx_user_profiles_user_id",
"columns": ["user_id"],
"unique": true
},
{
"name": "idx_user_profiles_privacy",
"columns": ["privacy_level"]
}
]
},
"user_sessions": {
"columns": {
"id": {
"type": "varchar",
"length": 128,
"nullable": false,
"primary_key": true
},
"user_id": {
"type": "bigint",
"nullable": false
},
"ip_address": {
"type": "varchar",
"length": 45,
"nullable": true
},
"user_agent": {
"type": "varchar",
"length": 500,
"nullable": true
},
"expires_at": {
"type": "timestamp",
"nullable": false
},
"created_at": {
"type": "timestamp",
"nullable": false,
"default": "CURRENT_TIMESTAMP"
},
"last_activity": {
"type": "timestamp",
"nullable": false,
"default": "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"
}
},
"constraints": {
"primary_key": ["id"],
"unique": [],
"foreign_key": [
{
"columns": ["user_id"],
"references": "users(id)",
"on_delete": "CASCADE"
}
],
"check": []
},
"indexes": [
{
"name": "idx_user_sessions_user_id",
"columns": ["user_id"]
},
{
"name": "idx_user_sessions_expires",
"columns": ["expires_at"]
}
]
}
},
"views": {
"active_users": {
"definition": "SELECT u.id, u.username, u.email, u.first_name, u.last_name FROM users u WHERE u.is_active = true",
"columns": ["id", "username", "email", "first_name", "last_name"]
}
},
"procedures": [
{
"name": "cleanup_expired_sessions",
"parameters": [],
"definition": "DELETE FROM user_sessions WHERE expires_at < NOW()"
}
]
}
FILE:assets/sample_database_migration.json
{
"type": "database",
"pattern": "schema_change",
"source": "PostgreSQL 13 Production Database",
"target": "PostgreSQL 15 Cloud Database",
"description": "Migrate user management system from on-premises PostgreSQL to cloud with schema updates",
"constraints": {
"max_downtime_minutes": 30,
"data_volume_gb": 2500,
"dependencies": [
"user_service_api",
"authentication_service",
"notification_service",
"analytics_pipeline",
"backup_service"
],
"compliance_requirements": [
"GDPR",
"SOX"
],
"special_requirements": [
"zero_data_loss",
"referential_integrity",
"performance_baseline_maintained"
]
},
"tables_to_migrate": [
{
"name": "users",
"row_count": 1500000,
"size_mb": 450,
"critical": true
},
{
"name": "user_profiles",
"row_count": 1500000,
"size_mb": 890,
"critical": true
},
{
"name": "user_sessions",
"row_count": 25000000,
"size_mb": 1200,
"critical": false
},
{
"name": "audit_logs",
"row_count": 50000000,
"size_mb": 2800,
"critical": false
}
],
"schema_changes": [
{
"table": "users",
"changes": [
{
"type": "add_column",
"column": "email_verified_at",
"data_type": "timestamp",
"nullable": true
},
{
"type": "add_column",
"column": "phone_verified_at",
"data_type": "timestamp",
"nullable": true
}
]
},
{
"table": "user_profiles",
"changes": [
{
"type": "modify_column",
"column": "bio",
"old_type": "varchar(255)",
"new_type": "text"
},
{
"type": "add_constraint",
"constraint_type": "check",
"constraint_name": "bio_length_check",
"definition": "LENGTH(bio) <= 2000"
}
]
}
],
"performance_requirements": {
"max_query_response_time_ms": 100,
"concurrent_connections": 500,
"transactions_per_second": 1000
},
"business_continuity": {
"critical_business_hours": {
"start": "08:00",
"end": "18:00",
"timezone": "UTC"
},
"preferred_migration_window": {
"start": "02:00",
"end": "06:00",
"timezone": "UTC"
}
}
}
FILE:assets/sample_service_migration.json
{
"type": "service",
"pattern": "strangler_fig",
"source": "Legacy User Service (Java Spring Boot 2.x)",
"target": "New User Service (Node.js + TypeScript)",
"description": "Migrate legacy user management service to modern microservices architecture",
"constraints": {
"max_downtime_minutes": 0,
"data_volume_gb": 50,
"dependencies": [
"payment_service",
"order_service",
"notification_service",
"analytics_service",
"mobile_app_v1",
"mobile_app_v2",
"web_frontend",
"admin_dashboard"
],
"compliance_requirements": [
"PCI_DSS",
"GDPR"
],
"special_requirements": [
"api_backward_compatibility",
"session_continuity",
"rate_limit_preservation"
]
},
"service_details": {
"legacy_service": {
"endpoints": [
"GET /api/v1/users/{id}",
"POST /api/v1/users",
"PUT /api/v1/users/{id}",
"DELETE /api/v1/users/{id}",
"GET /api/v1/users/{id}/profile",
"PUT /api/v1/users/{id}/profile",
"POST /api/v1/users/{id}/verify-email",
"POST /api/v1/users/login",
"POST /api/v1/users/logout"
],
"current_load": {
"requests_per_second": 850,
"peak_requests_per_second": 2000,
"average_response_time_ms": 120,
"p95_response_time_ms": 300
},
"infrastructure": {
"instances": 4,
"cpu_cores_per_instance": 4,
"memory_gb_per_instance": 8,
"load_balancer": "AWS ELB Classic"
}
},
"new_service": {
"endpoints": [
"GET /api/v2/users/{id}",
"POST /api/v2/users",
"PUT /api/v2/users/{id}",
"DELETE /api/v2/users/{id}",
"GET /api/v2/users/{id}/profile",
"PUT /api/v2/users/{id}/profile",
"POST /api/v2/users/{id}/verify-email",
"POST /api/v2/users/{id}/verify-phone",
"POST /api/v2/auth/login",
"POST /api/v2/auth/logout",
"POST /api/v2/auth/refresh"
],
"target_performance": {
"requests_per_second": 1500,
"peak_requests_per_second": 3000,
"average_response_time_ms": 80,
"p95_response_time_ms": 200
},
"infrastructure": {
"container_platform": "Kubernetes",
"initial_replicas": 3,
"max_replicas": 10,
"cpu_request_millicores": 500,
"cpu_limit_millicores": 1000,
"memory_request_mb": 512,
"memory_limit_mb": 1024,
"load_balancer": "AWS ALB"
}
}
},
"migration_phases": [
{
"phase": "preparation",
"description": "Deploy new service and configure routing",
"estimated_duration_hours": 8
},
{
"phase": "intercept",
"description": "Configure API gateway to route to new service",
"estimated_duration_hours": 2
},
{
"phase": "gradual_migration",
"description": "Gradually increase traffic to new service",
"estimated_duration_hours": 48
},
{
"phase": "validation",
"description": "Validate new service performance and functionality",
"estimated_duration_hours": 24
},
{
"phase": "decommission",
"description": "Remove legacy service after validation",
"estimated_duration_hours": 4
}
],
"feature_flags": [
{
"name": "enable_new_user_service",
"description": "Route user service requests to new implementation",
"initial_percentage": 5,
"rollout_schedule": [
{"percentage": 5, "duration_hours": 24},
{"percentage": 25, "duration_hours": 24},
{"percentage": 50, "duration_hours": 24},
{"percentage": 100, "duration_hours": 0}
]
},
{
"name": "enable_new_auth_endpoints",
"description": "Enable new authentication endpoints",
"initial_percentage": 0,
"rollout_schedule": [
{"percentage": 10, "duration_hours": 12},
{"percentage": 50, "duration_hours": 12},
{"percentage": 100, "duration_hours": 0}
]
}
],
"monitoring": {
"critical_metrics": [
"request_rate",
"error_rate",
"response_time_p95",
"response_time_p99",
"cpu_utilization",
"memory_utilization",
"database_connection_pool"
],
"alert_thresholds": {
"error_rate": 0.05,
"response_time_p95": 250,
"cpu_utilization": 0.80,
"memory_utilization": 0.85
}
},
"rollback_triggers": [
{
"metric": "error_rate",
"threshold": 0.10,
"duration_minutes": 5,
"action": "automatic_rollback"
},
{
"metric": "response_time_p95",
"threshold": 500,
"duration_minutes": 10,
"action": "alert_team"
},
{
"metric": "cpu_utilization",
"threshold": 0.95,
"duration_minutes": 5,
"action": "scale_up"
}
]
}
FILE:expected_outputs/rollback_runbook.json
{
"runbook_id": "rb_921c0bca",
"migration_id": "23a52ed1507f",
"created_at": "2026-02-16T13:47:31.108500",
"rollback_phases": [
{
"phase_name": "rollback_cleanup",
"description": "Rollback changes made during cleanup phase",
"urgency_level": "medium",
"estimated_duration_minutes": 570,
"prerequisites": [
"Incident commander assigned and briefed",
"All team members notified of rollback initiation",
"Monitoring systems confirmed operational",
"Backup systems verified and accessible"
],
"steps": [
{
"step_id": "rb_validate_0_final",
"name": "Validate rollback completion",
"description": "Comprehensive validation that cleanup rollback completed successfully",
"script_type": "manual",
"script_content": "Execute validation checklist for this phase",
"estimated_duration_minutes": 10,
"dependencies": [],
"validation_commands": [
"SELECT COUNT(*) FROM {table_name};",
"SELECT COUNT(*) FROM information_schema.tables WHERE table_name = '{table_name}';",
"SELECT COUNT(*) FROM information_schema.columns WHERE table_name = '{table_name}' AND column_name = '{column_name}';",
"SELECT COUNT(DISTINCT {primary_key}) FROM {table_name};",
"SELECT MAX({timestamp_column}) FROM {table_name};"
],
"success_criteria": [
"cleanup fully rolled back",
"All validation checks pass"
],
"failure_escalation": "Investigate cleanup rollback failures",
"rollback_order": 99
}
],
"validation_checkpoints": [
"cleanup rollback steps completed",
"System health checks passing",
"No critical errors in logs",
"Key metrics within acceptable ranges",
"Validation command passed: SELECT COUNT(*) FROM {table_name};...",
"Validation command passed: SELECT COUNT(*) FROM information_schema.tables WHE...",
"Validation command passed: SELECT COUNT(*) FROM information_schema.columns WH..."
],
"communication_requirements": [
"Notify incident commander of phase start/completion",
"Update rollback status dashboard",
"Log all actions and decisions"
],
"risk_level": "medium"
},
{
"phase_name": "rollback_contract",
"description": "Rollback changes made during contract phase",
"urgency_level": "medium",
"estimated_duration_minutes": 570,
"prerequisites": [
"Incident commander assigned and briefed",
"All team members notified of rollback initiation",
"Monitoring systems confirmed operational",
"Backup systems verified and accessible",
"Previous rollback phase completed successfully"
],
"steps": [
{
"step_id": "rb_validate_1_final",
"name": "Validate rollback completion",
"description": "Comprehensive validation that contract rollback completed successfully",
"script_type": "manual",
"script_content": "Execute validation checklist for this phase",
"estimated_duration_minutes": 10,
"dependencies": [],
"validation_commands": [
"SELECT COUNT(*) FROM {table_name};",
"SELECT COUNT(*) FROM information_schema.tables WHERE table_name = '{table_name}';",
"SELECT COUNT(*) FROM information_schema.columns WHERE table_name = '{table_name}' AND column_name = '{column_name}';",
"SELECT COUNT(DISTINCT {primary_key}) FROM {table_name};",
"SELECT MAX({timestamp_column}) FROM {table_name};"
],
"success_criteria": [
"contract fully rolled back",
"All validation checks pass"
],
"failure_escalation": "Investigate contract rollback failures",
"rollback_order": 99
}
],
"validation_checkpoints": [
"contract rollback steps completed",
"System health checks passing",
"No critical errors in logs",
"Key metrics within acceptable ranges",
"Validation command passed: SELECT COUNT(*) FROM {table_name};...",
"Validation command passed: SELECT COUNT(*) FROM information_schema.tables WHE...",
"Validation command passed: SELECT COUNT(*) FROM information_schema.columns WH..."
],
"communication_requirements": [
"Notify incident commander of phase start/completion",
"Update rollback status dashboard",
"Log all actions and decisions"
],
"risk_level": "medium"
},
{
"phase_name": "rollback_migrate",
"description": "Rollback changes made during migrate phase",
"urgency_level": "medium",
"estimated_duration_minutes": 570,
"prerequisites": [
"Incident commander assigned and briefed",
"All team members notified of rollback initiation",
"Monitoring systems confirmed operational",
"Backup systems verified and accessible",
"Previous rollback phase completed successfully"
],
"steps": [
{
"step_id": "rb_validate_2_final",
"name": "Validate rollback completion",
"description": "Comprehensive validation that migrate rollback completed successfully",
"script_type": "manual",
"script_content": "Execute validation checklist for this phase",
"estimated_duration_minutes": 10,
"dependencies": [],
"validation_commands": [
"SELECT COUNT(*) FROM {table_name};",
"SELECT COUNT(*) FROM information_schema.tables WHERE table_name = '{table_name}';",
"SELECT COUNT(*) FROM information_schema.columns WHERE table_name = '{table_name}' AND column_name = '{column_name}';",
"SELECT COUNT(DISTINCT {primary_key}) FROM {table_name};",
"SELECT MAX({timestamp_column}) FROM {table_name};"
],
"success_criteria": [
"migrate fully rolled back",
"All validation checks pass"
],
"failure_escalation": "Investigate migrate rollback failures",
"rollback_order": 99
}
],
"validation_checkpoints": [
"migrate rollback steps completed",
"System health checks passing",
"No critical errors in logs",
"Key metrics within acceptable ranges",
"Validation command passed: SELECT COUNT(*) FROM {table_name};...",
"Validation command passed: SELECT COUNT(*) FROM information_schema.tables WHE...",
"Validation command passed: SELECT COUNT(*) FROM information_schema.columns WH..."
],
"communication_requirements": [
"Notify incident commander of phase start/completion",
"Update rollback status dashboard",
"Log all actions and decisions"
],
"risk_level": "medium"
},
{
"phase_name": "rollback_expand",
"description": "Rollback changes made during expand phase",
"urgency_level": "medium",
"estimated_duration_minutes": 570,
"prerequisites": [
"Incident commander assigned and briefed",
"All team members notified of rollback initiation",
"Monitoring systems confirmed operational",
"Backup systems verified and accessible",
"Previous rollback phase completed successfully"
],
"steps": [
{
"step_id": "rb_validate_3_final",
"name": "Validate rollback completion",
"description": "Comprehensive validation that expand rollback completed successfully",
"script_type": "manual",
"script_content": "Execute validation checklist for this phase",
"estimated_duration_minutes": 10,
"dependencies": [],
"validation_commands": [
"SELECT COUNT(*) FROM {table_name};",
"SELECT COUNT(*) FROM information_schema.tables WHERE table_name = '{table_name}';",
"SELECT COUNT(*) FROM information_schema.columns WHERE table_name = '{table_name}' AND column_name = '{column_name}';",
"SELECT COUNT(DISTINCT {primary_key}) FROM {table_name};",
"SELECT MAX({timestamp_column}) FROM {table_name};"
],
"success_criteria": [
"expand fully rolled back",
"All validation checks pass"
],
"failure_escalation": "Investigate expand rollback failures",
"rollback_order": 99
}
],
"validation_checkpoints": [
"expand rollback steps completed",
"System health checks passing",
"No critical errors in logs",
"Key metrics within acceptable ranges",
"Validation command passed: SELECT COUNT(*) FROM {table_name};...",
"Validation command passed: SELECT COUNT(*) FROM information_schema.tables WHE...",
"Validation command passed: SELECT COUNT(*) FROM information_schema.columns WH..."
],
"communication_requirements": [
"Notify incident commander of phase start/completion",
"Update rollback status dashboard",
"Log all actions and decisions"
],
"risk_level": "medium"
},
{
"phase_name": "rollback_preparation",
"description": "Rollback changes made during preparation phase",
"urgency_level": "medium",
"estimated_duration_minutes": 570,
"prerequisites": [
"Incident commander assigned and briefed",
"All team members notified of rollback initiation",
"Monitoring systems confirmed operational",
"Backup systems verified and accessible",
"Previous rollback phase completed successfully"
],
"steps": [
{
"step_id": "rb_schema_4_01",
"name": "Drop migration artifacts",
"description": "Remove temporary migration tables and procedures",
"script_type": "sql",
"script_content": "-- Drop migration artifacts\nDROP TABLE IF EXISTS migration_log;\nDROP PROCEDURE IF EXISTS migrate_data();",
"estimated_duration_minutes": 5,
"dependencies": [],
"validation_commands": [
"SELECT COUNT(*) FROM information_schema.tables WHERE table_name LIKE '%migration%';"
],
"success_criteria": [
"No migration artifacts remain"
],
"failure_escalation": "Manual cleanup required",
"rollback_order": 1
},
{
"step_id": "rb_validate_4_final",
"name": "Validate rollback completion",
"description": "Comprehensive validation that preparation rollback completed successfully",
"script_type": "manual",
"script_content": "Execute validation checklist for this phase",
"estimated_duration_minutes": 10,
"dependencies": [
"rb_schema_4_01"
],
"validation_commands": [
"SELECT COUNT(*) FROM {table_name};",
"SELECT COUNT(*) FROM information_schema.tables WHERE table_name = '{table_name}';",
"SELECT COUNT(*) FROM information_schema.columns WHERE table_name = '{table_name}' AND column_name = '{column_name}';",
"SELECT COUNT(DISTINCT {primary_key}) FROM {table_name};",
"SELECT MAX({timestamp_column}) FROM {table_name};"
],
"success_criteria": [
"preparation fully rolled back",
"All validation checks pass"
],
"failure_escalation": "Investigate preparation rollback failures",
"rollback_order": 99
}
],
"validation_checkpoints": [
"preparation rollback steps completed",
"System health checks passing",
"No critical errors in logs",
"Key metrics within acceptable ranges",
"Validation command passed: SELECT COUNT(*) FROM {table_name};...",
"Validation command passed: SELECT COUNT(*) FROM information_schema.tables WHE...",
"Validation command passed: SELECT COUNT(*) FROM information_schema.columns WH..."
],
"communication_requirements": [
"Notify incident commander of phase start/completion",
"Update rollback status dashboard",
"Log all actions and decisions"
],
"risk_level": "medium"
}
],
"trigger_conditions": [
{
"trigger_id": "error_rate_spike",
"name": "Error Rate Spike",
"condition": "error_rate > baseline * 5 for 5 minutes",
"metric_threshold": {
"metric": "error_rate",
"operator": "greater_than",
"value": "baseline_error_rate * 5",
"duration_minutes": 5
},
"evaluation_window_minutes": 5,
"auto_execute": true,
"escalation_contacts": [
"on_call_engineer",
"migration_lead"
]
},
{
"trigger_id": "response_time_degradation",
"name": "Response Time Degradation",
"condition": "p95_response_time > baseline * 3 for 10 minutes",
"metric_threshold": {
"metric": "p95_response_time",
"operator": "greater_than",
"value": "baseline_p95 * 3",
"duration_minutes": 10
},
"evaluation_window_minutes": 10,
"auto_execute": false,
"escalation_contacts": [
"performance_team",
"migration_lead"
]
},
{
"trigger_id": "availability_drop",
"name": "Service Availability Drop",
"condition": "availability < 95% for 2 minutes",
"metric_threshold": {
"metric": "availability",
"operator": "less_than",
"value": 0.95,
"duration_minutes": 2
},
"evaluation_window_minutes": 2,
"auto_execute": true,
"escalation_contacts": [
"sre_team",
"incident_commander"
]
},
{
"trigger_id": "data_integrity_failure",
"name": "Data Integrity Check Failure",
"condition": "data_validation_failures > 0",
"metric_threshold": {
"metric": "data_validation_failures",
"operator": "greater_than",
"value": 0,
"duration_minutes": 1
},
"evaluation_window_minutes": 1,
"auto_execute": true,
"escalation_contacts": [
"dba_team",
"data_team"
]
},
{
"trigger_id": "migration_progress_stalled",
"name": "Migration Progress Stalled",
"condition": "migration_progress unchanged for 30 minutes",
"metric_threshold": {
"metric": "migration_progress_rate",
"operator": "equals",
"value": 0,
"duration_minutes": 30
},
"evaluation_window_minutes": 30,
"auto_execute": false,
"escalation_contacts": [
"migration_team",
"dba_team"
]
}
],
"data_recovery_plan": {
"recovery_method": "point_in_time",
"backup_location": "/backups/pre_migration_{migration_id}_{timestamp}.sql",
"recovery_scripts": [
"pg_restore -d production -c /backups/pre_migration_backup.sql",
"SELECT pg_create_restore_point('rollback_point');",
"VACUUM ANALYZE; -- Refresh statistics after restore"
],
"data_validation_queries": [
"SELECT COUNT(*) FROM critical_business_table;",
"SELECT MAX(created_at) FROM audit_log;",
"SELECT COUNT(DISTINCT user_id) FROM user_sessions;",
"SELECT SUM(amount) FROM financial_transactions WHERE date = CURRENT_DATE;"
],
"estimated_recovery_time_minutes": 45,
"recovery_dependencies": [
"database_instance_running",
"backup_file_accessible"
]
},
"communication_templates": [
{
"template_type": "rollback_start",
"audience": "technical",
"subject": "ROLLBACK INITIATED: {migration_name}",
"body": "Team,\n\nWe have initiated rollback for migration: {migration_name}\nRollback ID: {rollback_id}\nStart Time: {start_time}\nEstimated Duration: {estimated_duration}\n\nReason: {rollback_reason}\n\nCurrent Status: Rolling back phase {current_phase}\n\nNext Updates: Every 15 minutes or upon phase completion\n\nActions Required:\n- Monitor system health dashboards\n- Stand by for escalation if needed\n- Do not make manual changes during rollback\n\nIncident Commander: {incident_commander}\n",
"urgency": "medium",
"delivery_methods": [
"email",
"slack"
]
},
{
"template_type": "rollback_start",
"audience": "business",
"subject": "System Rollback In Progress - {system_name}",
"body": "Business Stakeholders,\n\nWe are currently performing a planned rollback of the {system_name} migration due to {rollback_reason}.\n\nImpact: {business_impact}\nExpected Resolution: {estimated_completion_time}\nAffected Services: {affected_services}\n\nWe will provide updates every 30 minutes.\n\nContact: {business_contact}\n",
"urgency": "medium",
"delivery_methods": [
"email"
]
},
{
"template_type": "rollback_start",
"audience": "executive",
"subject": "EXEC ALERT: Critical System Rollback - {system_name}",
"body": "Executive Team,\n\nA critical rollback is in progress for {system_name}.\n\nSummary:\n- Rollback Reason: {rollback_reason}\n- Business Impact: {business_impact}\n- Expected Resolution: {estimated_completion_time}\n- Customer Impact: {customer_impact}\n\nWe are following established procedures and will update hourly.\n\nEscalation: {escalation_contact}\n",
"urgency": "high",
"delivery_methods": [
"email"
]
},
{
"template_type": "rollback_complete",
"audience": "technical",
"subject": "ROLLBACK COMPLETED: {migration_name}",
"body": "Team,\n\nRollback has been successfully completed for migration: {migration_name}\n\nSummary:\n- Start Time: {start_time}\n- End Time: {end_time}\n- Duration: {actual_duration}\n- Phases Completed: {completed_phases}\n\nValidation Results:\n{validation_results}\n\nSystem Status: {system_status}\n\nNext Steps:\n- Continue monitoring for 24 hours\n- Post-rollback review scheduled for {review_date}\n- Root cause analysis to begin\n\nAll clear to resume normal operations.\n\nIncident Commander: {incident_commander}\n",
"urgency": "medium",
"delivery_methods": [
"email",
"slack"
]
},
{
"template_type": "emergency_escalation",
"audience": "executive",
"subject": "CRITICAL: Rollback Emergency - {migration_name}",
"body": "CRITICAL SITUATION - IMMEDIATE ATTENTION REQUIRED\n\nMigration: {migration_name}\nIssue: Rollback procedure has encountered critical failures\n\nCurrent Status: {current_status}\nFailed Components: {failed_components}\nBusiness Impact: {business_impact}\nCustomer Impact: {customer_impact}\n\nImmediate Actions:\n1. Emergency response team activated\n2. {emergency_action_1}\n3. {emergency_action_2}\n\nWar Room: {war_room_location}\nBridge Line: {conference_bridge}\n\nNext Update: {next_update_time}\n\nIncident Commander: {incident_commander}\nExecutive On-Call: {executive_on_call}\n",
"urgency": "emergency",
"delivery_methods": [
"email",
"sms",
"phone_call"
]
}
],
"escalation_matrix": {
"level_1": {
"trigger": "Single component failure",
"response_time_minutes": 5,
"contacts": [
"on_call_engineer",
"migration_lead"
],
"actions": [
"Investigate issue",
"Attempt automated remediation",
"Monitor closely"
]
},
"level_2": {
"trigger": "Multiple component failures or single critical failure",
"response_time_minutes": 2,
"contacts": [
"senior_engineer",
"team_lead",
"devops_lead"
],
"actions": [
"Initiate rollback",
"Establish war room",
"Notify stakeholders"
]
},
"level_3": {
"trigger": "System-wide failure or data corruption",
"response_time_minutes": 1,
"contacts": [
"engineering_manager",
"cto",
"incident_commander"
],
"actions": [
"Emergency rollback",
"All hands on deck",
"Executive notification"
]
},
"emergency": {
"trigger": "Business-critical failure with customer impact",
"response_time_minutes": 0,
"contacts": [
"ceo",
"cto",
"head_of_operations"
],
"actions": [
"Emergency procedures",
"Customer communication",
"Media preparation if needed"
]
}
},
"validation_checklist": [
"Verify system is responding to health checks",
"Confirm error rates are within normal parameters",
"Validate response times meet SLA requirements",
"Check all critical business processes are functioning",
"Verify monitoring and alerting systems are operational",
"Confirm no data corruption has occurred",
"Validate security controls are functioning properly",
"Check backup systems are working correctly",
"Verify integration points with downstream systems",
"Confirm user authentication and authorization working",
"Validate database schema matches expected state",
"Confirm referential integrity constraints",
"Check database performance metrics",
"Verify data consistency across related tables",
"Validate indexes and statistics are optimal",
"Confirm transaction logs are clean",
"Check database connections and connection pooling"
],
"post_rollback_procedures": [
"Monitor system stability for 24-48 hours post-rollback",
"Conduct thorough post-rollback testing of all critical paths",
"Review and analyze rollback metrics and timing",
"Document lessons learned and rollback procedure improvements",
"Schedule post-mortem meeting with all stakeholders",
"Update rollback procedures based on actual experience",
"Communicate rollback completion to all stakeholders",
"Archive rollback logs and artifacts for future reference",
"Review and update monitoring thresholds if needed",
"Plan for next migration attempt with improved procedures",
"Conduct security review to ensure no vulnerabilities introduced",
"Update disaster recovery procedures if affected by rollback",
"Review capacity planning based on rollback resource usage",
"Update documentation with rollback experience and timings"
],
"emergency_contacts": [
{
"role": "Incident Commander",
"name": "TBD - Assigned during migration",
"primary_phone": "+1-XXX-XXX-XXXX",
"email": "incident.commander@company.com",
"backup_contact": "backup.commander@company.com"
},
{
"role": "Technical Lead",
"name": "TBD - Migration technical owner",
"primary_phone": "+1-XXX-XXX-XXXX",
"email": "tech.lead@company.com",
"backup_contact": "senior.engineer@company.com"
},
{
"role": "Business Owner",
"name": "TBD - Business stakeholder",
"primary_phone": "+1-XXX-XXX-XXXX",
"email": "business.owner@company.com",
"backup_contact": "product.manager@company.com"
},
{
"role": "On-Call Engineer",
"name": "Current on-call rotation",
"primary_phone": "+1-XXX-XXX-XXXX",
"email": "oncall@company.com",
"backup_contact": "backup.oncall@company.com"
},
{
"role": "Executive Escalation",
"name": "CTO/VP Engineering",
"primary_phone": "+1-XXX-XXX-XXXX",
"email": "cto@company.com",
"backup_contact": "vp.engineering@company.com"
}
]
}
FILE:expected_outputs/rollback_runbook.txt
================================================================================
ROLLBACK RUNBOOK: rb_921c0bca
================================================================================
Migration ID: 23a52ed1507f
Created: 2026-02-16T13:47:31.108500
EMERGENCY CONTACTS
----------------------------------------
Incident Commander: TBD - Assigned during migration
Phone: +1-XXX-XXX-XXXX
Email: incident.commander@company.com
Backup: backup.commander@company.com
Technical Lead: TBD - Migration technical owner
Phone: +1-XXX-XXX-XXXX
Email: tech.lead@company.com
Backup: senior.engineer@company.com
Business Owner: TBD - Business stakeholder
Phone: +1-XXX-XXX-XXXX
Email: business.owner@company.com
Backup: product.manager@company.com
On-Call Engineer: Current on-call rotation
Phone: +1-XXX-XXX-XXXX
Email: oncall@company.com
Backup: backup.oncall@company.com
Executive Escalation: CTO/VP Engineering
Phone: +1-XXX-XXX-XXXX
Email: cto@company.com
Backup: vp.engineering@company.com
ESCALATION MATRIX
----------------------------------------
LEVEL_1:
Trigger: Single component failure
Response Time: 5 minutes
Contacts: on_call_engineer, migration_lead
Actions: Investigate issue, Attempt automated remediation, Monitor closely
LEVEL_2:
Trigger: Multiple component failures or single critical failure
Response Time: 2 minutes
Contacts: senior_engineer, team_lead, devops_lead
Actions: Initiate rollback, Establish war room, Notify stakeholders
LEVEL_3:
Trigger: System-wide failure or data corruption
Response Time: 1 minutes
Contacts: engineering_manager, cto, incident_commander
Actions: Emergency rollback, All hands on deck, Executive notification
EMERGENCY:
Trigger: Business-critical failure with customer impact
Response Time: 0 minutes
Contacts: ceo, cto, head_of_operations
Actions: Emergency procedures, Customer communication, Media preparation if needed
AUTOMATIC ROLLBACK TRIGGERS
----------------------------------------
• Error Rate Spike
Condition: error_rate > baseline * 5 for 5 minutes
Auto-Execute: Yes
Evaluation Window: 5 minutes
Contacts: on_call_engineer, migration_lead
• Response Time Degradation
Condition: p95_response_time > baseline * 3 for 10 minutes
Auto-Execute: No
Evaluation Window: 10 minutes
Contacts: performance_team, migration_lead
• Service Availability Drop
Condition: availability < 95% for 2 minutes
Auto-Execute: Yes
Evaluation Window: 2 minutes
Contacts: sre_team, incident_commander
• Data Integrity Check Failure
Condition: data_validation_failures > 0
Auto-Execute: Yes
Evaluation Window: 1 minutes
Contacts: dba_team, data_team
• Migration Progress Stalled
Condition: migration_progress unchanged for 30 minutes
Auto-Execute: No
Evaluation Window: 30 minutes
Contacts: migration_team, dba_team
ROLLBACK PHASES
----------------------------------------
1. ROLLBACK_CLEANUP
Description: Rollback changes made during cleanup phase
Urgency: MEDIUM
Duration: 570 minutes
Risk Level: MEDIUM
Prerequisites:
✓ Incident commander assigned and briefed
✓ All team members notified of rollback initiation
✓ Monitoring systems confirmed operational
✓ Backup systems verified and accessible
Steps:
99. Validate rollback completion
Duration: 10 min
Type: manual
Success Criteria: cleanup fully rolled back, All validation checks pass
Validation Checkpoints:
☐ cleanup rollback steps completed
☐ System health checks passing
☐ No critical errors in logs
☐ Key metrics within acceptable ranges
☐ Validation command passed: SELECT COUNT(*) FROM {table_name};...
☐ Validation command passed: SELECT COUNT(*) FROM information_schema.tables WHE...
☐ Validation command passed: SELECT COUNT(*) FROM information_schema.columns WH...
2. ROLLBACK_CONTRACT
Description: Rollback changes made during contract phase
Urgency: MEDIUM
Duration: 570 minutes
Risk Level: MEDIUM
Prerequisites:
✓ Incident commander assigned and briefed
✓ All team members notified of rollback initiation
✓ Monitoring systems confirmed operational
✓ Backup systems verified and accessible
✓ Previous rollback phase completed successfully
Steps:
99. Validate rollback completion
Duration: 10 min
Type: manual
Success Criteria: contract fully rolled back, All validation checks pass
Validation Checkpoints:
☐ contract rollback steps completed
☐ System health checks passing
☐ No critical errors in logs
☐ Key metrics within acceptable ranges
☐ Validation command passed: SELECT COUNT(*) FROM {table_name};...
☐ Validation command passed: SELECT COUNT(*) FROM information_schema.tables WHE...
☐ Validation command passed: SELECT COUNT(*) FROM information_schema.columns WH...
3. ROLLBACK_MIGRATE
Description: Rollback changes made during migrate phase
Urgency: MEDIUM
Duration: 570 minutes
Risk Level: MEDIUM
Prerequisites:
✓ Incident commander assigned and briefed
✓ All team members notified of rollback initiation
✓ Monitoring systems confirmed operational
✓ Backup systems verified and accessible
✓ Previous rollback phase completed successfully
Steps:
99. Validate rollback completion
Duration: 10 min
Type: manual
Success Criteria: migrate fully rolled back, All validation checks pass
Validation Checkpoints:
☐ migrate rollback steps completed
☐ System health checks passing
☐ No critical errors in logs
☐ Key metrics within acceptable ranges
☐ Validation command passed: SELECT COUNT(*) FROM {table_name};...
☐ Validation command passed: SELECT COUNT(*) FROM information_schema.tables WHE...
☐ Validation command passed: SELECT COUNT(*) FROM information_schema.columns WH...
4. ROLLBACK_EXPAND
Description: Rollback changes made during expand phase
Urgency: MEDIUM
Duration: 570 minutes
Risk Level: MEDIUM
Prerequisites:
✓ Incident commander assigned and briefed
✓ All team members notified of rollback initiation
✓ Monitoring systems confirmed operational
✓ Backup systems verified and accessible
✓ Previous rollback phase completed successfully
Steps:
99. Validate rollback completion
Duration: 10 min
Type: manual
Success Criteria: expand fully rolled back, All validation checks pass
Validation Checkpoints:
☐ expand rollback steps completed
☐ System health checks passing
☐ No critical errors in logs
☐ Key metrics within acceptable ranges
☐ Validation command passed: SELECT COUNT(*) FROM {table_name};...
☐ Validation command passed: SELECT COUNT(*) FROM information_schema.tables WHE...
☐ Validation command passed: SELECT COUNT(*) FROM information_schema.columns WH...
5. ROLLBACK_PREPARATION
Description: Rollback changes made during preparation phase
Urgency: MEDIUM
Duration: 570 minutes
Risk Level: MEDIUM
Prerequisites:
✓ Incident commander assigned and briefed
✓ All team members notified of rollback initiation
✓ Monitoring systems confirmed operational
✓ Backup systems verified and accessible
✓ Previous rollback phase completed successfully
Steps:
1. Drop migration artifacts
Duration: 5 min
Type: sql
Script:
-- Drop migration artifacts
DROP TABLE IF EXISTS migration_log;
DROP PROCEDURE IF EXISTS migrate_data();
Success Criteria: No migration artifacts remain
99. Validate rollback completion
Duration: 10 min
Type: manual
Success Criteria: preparation fully rolled back, All validation checks pass
Validation Checkpoints:
☐ preparation rollback steps completed
☐ System health checks passing
☐ No critical errors in logs
☐ Key metrics within acceptable ranges
☐ Validation command passed: SELECT COUNT(*) FROM {table_name};...
☐ Validation command passed: SELECT COUNT(*) FROM information_schema.tables WHE...
☐ Validation command passed: SELECT COUNT(*) FROM information_schema.columns WH...
DATA RECOVERY PLAN
----------------------------------------
Recovery Method: point_in_time
Backup Location: /backups/pre_migration_{migration_id}_{timestamp}.sql
Estimated Recovery Time: 45 minutes
Recovery Scripts:
• pg_restore -d production -c /backups/pre_migration_backup.sql
• SELECT pg_create_restore_point('rollback_point');
• VACUUM ANALYZE; -- Refresh statistics after restore
Validation Queries:
• SELECT COUNT(*) FROM critical_business_table;
• SELECT MAX(created_at) FROM audit_log;
• SELECT COUNT(DISTINCT user_id) FROM user_sessions;
• SELECT SUM(amount) FROM financial_transactions WHERE date = CURRENT_DATE;
POST-ROLLBACK VALIDATION CHECKLIST
----------------------------------------
1. ☐ Verify system is responding to health checks
2. ☐ Confirm error rates are within normal parameters
3. ☐ Validate response times meet SLA requirements
4. ☐ Check all critical business processes are functioning
5. ☐ Verify monitoring and alerting systems are operational
6. ☐ Confirm no data corruption has occurred
7. ☐ Validate security controls are functioning properly
8. ☐ Check backup systems are working correctly
9. ☐ Verify integration points with downstream systems
10. ☐ Confirm user authentication and authorization working
11. ☐ Validate database schema matches expected state
12. ☐ Confirm referential integrity constraints
13. ☐ Check database performance metrics
14. ☐ Verify data consistency across related tables
15. ☐ Validate indexes and statistics are optimal
16. ☐ Confirm transaction logs are clean
17. ☐ Check database connections and connection pooling
POST-ROLLBACK PROCEDURES
----------------------------------------
1. Monitor system stability for 24-48 hours post-rollback
2. Conduct thorough post-rollback testing of all critical paths
3. Review and analyze rollback metrics and timing
4. Document lessons learned and rollback procedure improvements
5. Schedule post-mortem meeting with all stakeholders
6. Update rollback procedures based on actual experience
7. Communicate rollback completion to all stakeholders
8. Archive rollback logs and artifacts for future reference
9. Review and update monitoring thresholds if needed
10. Plan for next migration attempt with improved procedures
11. Conduct security review to ensure no vulnerabilities introduced
12. Update disaster recovery procedures if affected by rollback
13. Review capacity planning based on rollback resource usage
14. Update documentation with rollback experience and timings
FILE:expected_outputs/sample_database_migration_plan.json
{
"migration_id": "23a52ed1507f",
"source_system": "PostgreSQL 13 Production Database",
"target_system": "PostgreSQL 15 Cloud Database",
"migration_type": "database",
"complexity": "critical",
"estimated_duration_hours": 95,
"phases": [
{
"name": "preparation",
"description": "Prepare systems and teams for migration",
"duration_hours": 19,
"dependencies": [],
"validation_criteria": [
"All backups completed successfully",
"Monitoring systems operational",
"Team members briefed and ready",
"Rollback procedures tested"
],
"rollback_triggers": [
"Critical system failure",
"Data corruption detected",
"Performance degradation > 50%",
"Business process failure"
],
"tasks": [
"Backup source system",
"Set up monitoring and alerting",
"Prepare rollback procedures",
"Communicate migration timeline",
"Validate prerequisites"
],
"risk_level": "medium",
"resources_required": [
"Technical team availability",
"System access and permissions",
"Monitoring and alerting systems",
"Communication channels"
]
},
{
"name": "expand",
"description": "Execute expand phase",
"duration_hours": 19,
"dependencies": [
"preparation"
],
"validation_criteria": [
"Expand phase completed successfully"
],
"rollback_triggers": [
"Critical system failure",
"Data corruption detected",
"Performance degradation > 50%",
"Business process failure"
],
"tasks": [
"Complete expand activities"
],
"risk_level": "medium",
"resources_required": [
"Technical team availability",
"System access and permissions",
"Monitoring and alerting systems",
"Communication channels"
]
},
{
"name": "migrate",
"description": "Execute migrate phase",
"duration_hours": 19,
"dependencies": [
"expand"
],
"validation_criteria": [
"Migrate phase completed successfully"
],
"rollback_triggers": [
"Critical system failure",
"Data corruption detected",
"Performance degradation > 50%",
"Business process failure"
],
"tasks": [
"Complete migrate activities"
],
"risk_level": "medium",
"resources_required": [
"Technical team availability",
"System access and permissions",
"Monitoring and alerting systems",
"Communication channels"
]
},
{
"name": "contract",
"description": "Execute contract phase",
"duration_hours": 19,
"dependencies": [
"migrate"
],
"validation_criteria": [
"Contract phase completed successfully"
],
"rollback_triggers": [
"Critical system failure",
"Data corruption detected",
"Performance degradation > 50%",
"Business process failure"
],
"tasks": [
"Complete contract activities"
],
"risk_level": "medium",
"resources_required": [
"Technical team availability",
"System access and permissions",
"Monitoring and alerting systems",
"Communication channels"
]
},
{
"name": "cleanup",
"description": "Execute cleanup phase",
"duration_hours": 19,
"dependencies": [
"contract"
],
"validation_criteria": [
"Cleanup phase completed successfully"
],
"rollback_triggers": [
"Critical system failure",
"Data corruption detected",
"Performance degradation > 50%",
"Business process failure"
],
"tasks": [
"Complete cleanup activities"
],
"risk_level": "medium",
"resources_required": [
"Technical team availability",
"System access and permissions",
"Monitoring and alerting systems",
"Communication channels"
]
}
],
"risks": [
{
"category": "technical",
"description": "Data corruption during migration",
"probability": "low",
"impact": "critical",
"severity": "high",
"mitigation": "Implement comprehensive backup and validation procedures",
"owner": "DBA Team"
},
{
"category": "technical",
"description": "Extended downtime due to migration complexity",
"probability": "medium",
"impact": "high",
"severity": "high",
"mitigation": "Use blue-green deployment and phased migration approach",
"owner": "DevOps Team"
},
{
"category": "business",
"description": "Business process disruption",
"probability": "medium",
"impact": "high",
"severity": "high",
"mitigation": "Communicate timeline and provide alternate workflows",
"owner": "Business Owner"
},
{
"category": "operational",
"description": "Insufficient rollback testing",
"probability": "high",
"impact": "critical",
"severity": "critical",
"mitigation": "Execute full rollback procedures in staging environment",
"owner": "QA Team"
},
{
"category": "business",
"description": "Zero-downtime requirement increases complexity",
"probability": "high",
"impact": "medium",
"severity": "high",
"mitigation": "Implement blue-green deployment or rolling update strategy",
"owner": "DevOps Team"
},
{
"category": "compliance",
"description": "Regulatory compliance requirements",
"probability": "medium",
"impact": "high",
"severity": "high",
"mitigation": "Ensure all compliance checks are integrated into migration process",
"owner": "Compliance Team"
}
],
"success_criteria": [
"All data successfully migrated with 100% integrity",
"System performance meets or exceeds baseline",
"All business processes functioning normally",
"No critical security vulnerabilities introduced",
"Stakeholder acceptance criteria met",
"Documentation and runbooks updated"
],
"rollback_plan": {
"rollback_phases": [
{
"phase": "cleanup",
"rollback_actions": [
"Revert cleanup changes",
"Restore pre-cleanup state",
"Validate cleanup rollback success"
],
"validation_criteria": [
"System restored to pre-cleanup state",
"All cleanup changes successfully reverted",
"System functionality confirmed"
],
"estimated_time_minutes": 285
},
{
"phase": "contract",
"rollback_actions": [
"Revert contract changes",
"Restore pre-contract state",
"Validate contract rollback success"
],
"validation_criteria": [
"System restored to pre-contract state",
"All contract changes successfully reverted",
"System functionality confirmed"
],
"estimated_time_minutes": 285
},
{
"phase": "migrate",
"rollback_actions": [
"Revert migrate changes",
"Restore pre-migrate state",
"Validate migrate rollback success"
],
"validation_criteria": [
"System restored to pre-migrate state",
"All migrate changes successfully reverted",
"System functionality confirmed"
],
"estimated_time_minutes": 285
},
{
"phase": "expand",
"rollback_actions": [
"Revert expand changes",
"Restore pre-expand state",
"Validate expand rollback success"
],
"validation_criteria": [
"System restored to pre-expand state",
"All expand changes successfully reverted",
"System functionality confirmed"
],
"estimated_time_minutes": 285
},
{
"phase": "preparation",
"rollback_actions": [
"Revert preparation changes",
"Restore pre-preparation state",
"Validate preparation rollback success"
],
"validation_criteria": [
"System restored to pre-preparation state",
"All preparation changes successfully reverted",
"System functionality confirmed"
],
"estimated_time_minutes": 285
}
],
"rollback_triggers": [
"Critical system failure",
"Data corruption detected",
"Migration timeline exceeded by > 50%",
"Business-critical functionality unavailable",
"Security breach detected",
"Stakeholder decision to abort"
],
"rollback_decision_matrix": {
"low_severity": "Continue with monitoring",
"medium_severity": "Assess and decide within 15 minutes",
"high_severity": "Immediate rollback initiation",
"critical_severity": "Emergency rollback - all hands"
},
"rollback_contacts": [
"Migration Lead",
"Technical Lead",
"Business Owner",
"On-call Engineer"
]
},
"stakeholders": [
"Business Owner",
"Technical Lead",
"DevOps Team",
"QA Team",
"Security Team",
"End Users"
],
"created_at": "2026-02-16T13:47:23.704502"
}
FILE:expected_outputs/sample_database_migration_plan.txt
================================================================================
MIGRATION PLAN: 23a52ed1507f
================================================================================
Source System: PostgreSQL 13 Production Database
Target System: PostgreSQL 15 Cloud Database
Migration Type: DATABASE
Complexity Level: CRITICAL
Estimated Duration: 95 hours (4.0 days)
Created: 2026-02-16T13:47:23.704502
MIGRATION PHASES
----------------------------------------
1. PREPARATION (19h)
Description: Prepare systems and teams for migration
Risk Level: MEDIUM
Tasks:
• Backup source system
• Set up monitoring and alerting
• Prepare rollback procedures
• Communicate migration timeline
• Validate prerequisites
Success Criteria:
✓ All backups completed successfully
✓ Monitoring systems operational
✓ Team members briefed and ready
✓ Rollback procedures tested
2. EXPAND (19h)
Description: Execute expand phase
Risk Level: MEDIUM
Dependencies: preparation
Tasks:
• Complete expand activities
Success Criteria:
✓ Expand phase completed successfully
3. MIGRATE (19h)
Description: Execute migrate phase
Risk Level: MEDIUM
Dependencies: expand
Tasks:
• Complete migrate activities
Success Criteria:
✓ Migrate phase completed successfully
4. CONTRACT (19h)
Description: Execute contract phase
Risk Level: MEDIUM
Dependencies: migrate
Tasks:
• Complete contract activities
Success Criteria:
✓ Contract phase completed successfully
5. CLEANUP (19h)
Description: Execute cleanup phase
Risk Level: MEDIUM
Dependencies: contract
Tasks:
• Complete cleanup activities
Success Criteria:
✓ Cleanup phase completed successfully
RISK ASSESSMENT
----------------------------------------
CRITICAL SEVERITY RISKS:
• Insufficient rollback testing
Category: operational
Probability: high | Impact: critical
Mitigation: Execute full rollback procedures in staging environment
Owner: QA Team
HIGH SEVERITY RISKS:
• Data corruption during migration
Category: technical
Probability: low | Impact: critical
Mitigation: Implement comprehensive backup and validation procedures
Owner: DBA Team
• Extended downtime due to migration complexity
Category: technical
Probability: medium | Impact: high
Mitigation: Use blue-green deployment and phased migration approach
Owner: DevOps Team
• Business process disruption
Category: business
Probability: medium | Impact: high
Mitigation: Communicate timeline and provide alternate workflows
Owner: Business Owner
• Zero-downtime requirement increases complexity
Category: business
Probability: high | Impact: medium
Mitigation: Implement blue-green deployment or rolling update strategy
Owner: DevOps Team
• Regulatory compliance requirements
Category: compliance
Probability: medium | Impact: high
Mitigation: Ensure all compliance checks are integrated into migration process
Owner: Compliance Team
ROLLBACK STRATEGY
----------------------------------------
Rollback Triggers:
• Critical system failure
• Data corruption detected
• Migration timeline exceeded by > 50%
• Business-critical functionality unavailable
• Security breach detected
• Stakeholder decision to abort
Rollback Phases:
CLEANUP:
- Revert cleanup changes
- Restore pre-cleanup state
- Validate cleanup rollback success
Estimated Time: 285 minutes
CONTRACT:
- Revert contract changes
- Restore pre-contract state
- Validate contract rollback success
Estimated Time: 285 minutes
MIGRATE:
- Revert migrate changes
- Restore pre-migrate state
- Validate migrate rollback success
Estimated Time: 285 minutes
EXPAND:
- Revert expand changes
- Restore pre-expand state
- Validate expand rollback success
Estimated Time: 285 minutes
PREPARATION:
- Revert preparation changes
- Restore pre-preparation state
- Validate preparation rollback success
Estimated Time: 285 minutes
SUCCESS CRITERIA
----------------------------------------
✓ All data successfully migrated with 100% integrity
✓ System performance meets or exceeds baseline
✓ All business processes functioning normally
✓ No critical security vulnerabilities introduced
✓ Stakeholder acceptance criteria met
✓ Documentation and runbooks updated
STAKEHOLDERS
----------------------------------------
• Business Owner
• Technical Lead
• DevOps Team
• QA Team
• Security Team
• End Users
FILE:expected_outputs/sample_service_migration_plan.json
{
"migration_id": "21031930da18",
"source_system": "Legacy User Service (Java Spring Boot 2.x)",
"target_system": "New User Service (Node.js + TypeScript)",
"migration_type": "service",
"complexity": "critical",
"estimated_duration_hours": 500,
"phases": [
{
"name": "intercept",
"description": "Execute intercept phase",
"duration_hours": 100,
"dependencies": [],
"validation_criteria": [
"Intercept phase completed successfully"
],
"rollback_triggers": [
"Critical system failure",
"Data corruption detected",
"Performance degradation > 50%",
"Business process failure"
],
"tasks": [
"Complete intercept activities"
],
"risk_level": "medium",
"resources_required": [
"Technical team availability",
"System access and permissions",
"Monitoring and alerting systems",
"Communication channels"
]
},
{
"name": "implement",
"description": "Execute implement phase",
"duration_hours": 100,
"dependencies": [
"intercept"
],
"validation_criteria": [
"Implement phase completed successfully"
],
"rollback_triggers": [
"Critical system failure",
"Data corruption detected",
"Performance degradation > 50%",
"Business process failure"
],
"tasks": [
"Complete implement activities"
],
"risk_level": "medium",
"resources_required": [
"Technical team availability",
"System access and permissions",
"Monitoring and alerting systems",
"Communication channels"
]
},
{
"name": "redirect",
"description": "Execute redirect phase",
"duration_hours": 100,
"dependencies": [
"implement"
],
"validation_criteria": [
"Redirect phase completed successfully"
],
"rollback_triggers": [
"Critical system failure",
"Data corruption detected",
"Performance degradation > 50%",
"Business process failure"
],
"tasks": [
"Complete redirect activities"
],
"risk_level": "medium",
"resources_required": [
"Technical team availability",
"System access and permissions",
"Monitoring and alerting systems",
"Communication channels"
]
},
{
"name": "validate",
"description": "Execute validate phase",
"duration_hours": 100,
"dependencies": [
"redirect"
],
"validation_criteria": [
"Validate phase completed successfully"
],
"rollback_triggers": [
"Critical system failure",
"Data corruption detected",
"Performance degradation > 50%",
"Business process failure"
],
"tasks": [
"Complete validate activities"
],
"risk_level": "medium",
"resources_required": [
"Technical team availability",
"System access and permissions",
"Monitoring and alerting systems",
"Communication channels"
]
},
{
"name": "retire",
"description": "Execute retire phase",
"duration_hours": 100,
"dependencies": [
"validate"
],
"validation_criteria": [
"Retire phase completed successfully"
],
"rollback_triggers": [
"Critical system failure",
"Data corruption detected",
"Performance degradation > 50%",
"Business process failure"
],
"tasks": [
"Complete retire activities"
],
"risk_level": "medium",
"resources_required": [
"Technical team availability",
"System access and permissions",
"Monitoring and alerting systems",
"Communication channels"
]
}
],
"risks": [
{
"category": "technical",
"description": "Service compatibility issues",
"probability": "medium",
"impact": "high",
"severity": "high",
"mitigation": "Implement comprehensive integration testing",
"owner": "Development Team"
},
{
"category": "technical",
"description": "Performance degradation",
"probability": "medium",
"impact": "medium",
"severity": "medium",
"mitigation": "Conduct load testing and performance benchmarking",
"owner": "DevOps Team"
},
{
"category": "business",
"description": "Feature parity gaps",
"probability": "high",
"impact": "high",
"severity": "high",
"mitigation": "Document feature mapping and acceptance criteria",
"owner": "Product Owner"
},
{
"category": "operational",
"description": "Monitoring gap during transition",
"probability": "medium",
"impact": "medium",
"severity": "medium",
"mitigation": "Set up dual monitoring and alerting systems",
"owner": "SRE Team"
},
{
"category": "business",
"description": "Zero-downtime requirement increases complexity",
"probability": "high",
"impact": "medium",
"severity": "high",
"mitigation": "Implement blue-green deployment or rolling update strategy",
"owner": "DevOps Team"
},
{
"category": "compliance",
"description": "Regulatory compliance requirements",
"probability": "medium",
"impact": "high",
"severity": "high",
"mitigation": "Ensure all compliance checks are integrated into migration process",
"owner": "Compliance Team"
}
],
"success_criteria": [
"All data successfully migrated with 100% integrity",
"System performance meets or exceeds baseline",
"All business processes functioning normally",
"No critical security vulnerabilities introduced",
"Stakeholder acceptance criteria met",
"Documentation and runbooks updated"
],
"rollback_plan": {
"rollback_phases": [
{
"phase": "retire",
"rollback_actions": [
"Revert retire changes",
"Restore pre-retire state",
"Validate retire rollback success"
],
"validation_criteria": [
"System restored to pre-retire state",
"All retire changes successfully reverted",
"System functionality confirmed"
],
"estimated_time_minutes": 1500
},
{
"phase": "validate",
"rollback_actions": [
"Revert validate changes",
"Restore pre-validate state",
"Validate validate rollback success"
],
"validation_criteria": [
"System restored to pre-validate state",
"All validate changes successfully reverted",
"System functionality confirmed"
],
"estimated_time_minutes": 1500
},
{
"phase": "redirect",
"rollback_actions": [
"Revert redirect changes",
"Restore pre-redirect state",
"Validate redirect rollback success"
],
"validation_criteria": [
"System restored to pre-redirect state",
"All redirect changes successfully reverted",
"System functionality confirmed"
],
"estimated_time_minutes": 1500
},
{
"phase": "implement",
"rollback_actions": [
"Revert implement changes",
"Restore pre-implement state",
"Validate implement rollback success"
],
"validation_criteria": [
"System restored to pre-implement state",
"All implement changes successfully reverted",
"System functionality confirmed"
],
"estimated_time_minutes": 1500
},
{
"phase": "intercept",
"rollback_actions": [
"Revert intercept changes",
"Restore pre-intercept state",
"Validate intercept rollback success"
],
"validation_criteria": [
"System restored to pre-intercept state",
"All intercept changes successfully reverted",
"System functionality confirmed"
],
"estimated_time_minutes": 1500
}
],
"rollback_triggers": [
"Critical system failure",
"Data corruption detected",
"Migration timeline exceeded by > 50%",
"Business-critical functionality unavailable",
"Security breach detected",
"Stakeholder decision to abort"
],
"rollback_decision_matrix": {
"low_severity": "Continue with monitoring",
"medium_severity": "Assess and decide within 15 minutes",
"high_severity": "Immediate rollback initiation",
"critical_severity": "Emergency rollback - all hands"
},
"rollback_contacts": [
"Migration Lead",
"Technical Lead",
"Business Owner",
"On-call Engineer"
]
},
"stakeholders": [
"Business Owner",
"Technical Lead",
"DevOps Team",
"QA Team",
"Security Team",
"End Users"
],
"created_at": "2026-02-16T13:47:34.565896"
}
FILE:expected_outputs/sample_service_migration_plan.txt
================================================================================
MIGRATION PLAN: 21031930da18
================================================================================
Source System: Legacy User Service (Java Spring Boot 2.x)
Target System: New User Service (Node.js + TypeScript)
Migration Type: SERVICE
Complexity Level: CRITICAL
Estimated Duration: 500 hours (20.8 days)
Created: 2026-02-16T13:47:34.565896
MIGRATION PHASES
----------------------------------------
1. INTERCEPT (100h)
Description: Execute intercept phase
Risk Level: MEDIUM
Tasks:
• Complete intercept activities
Success Criteria:
✓ Intercept phase completed successfully
2. IMPLEMENT (100h)
Description: Execute implement phase
Risk Level: MEDIUM
Dependencies: intercept
Tasks:
• Complete implement activities
Success Criteria:
✓ Implement phase completed successfully
3. REDIRECT (100h)
Description: Execute redirect phase
Risk Level: MEDIUM
Dependencies: implement
Tasks:
• Complete redirect activities
Success Criteria:
✓ Redirect phase completed successfully
4. VALIDATE (100h)
Description: Execute validate phase
Risk Level: MEDIUM
Dependencies: redirect
Tasks:
• Complete validate activities
Success Criteria:
✓ Validate phase completed successfully
5. RETIRE (100h)
Description: Execute retire phase
Risk Level: MEDIUM
Dependencies: validate
Tasks:
• Complete retire activities
Success Criteria:
✓ Retire phase completed successfully
RISK ASSESSMENT
----------------------------------------
HIGH SEVERITY RISKS:
• Service compatibility issues
Category: technical
Probability: medium | Impact: high
Mitigation: Implement comprehensive integration testing
Owner: Development Team
• Feature parity gaps
Category: business
Probability: high | Impact: high
Mitigation: Document feature mapping and acceptance criteria
Owner: Product Owner
• Zero-downtime requirement increases complexity
Category: business
Probability: high | Impact: medium
Mitigation: Implement blue-green deployment or rolling update strategy
Owner: DevOps Team
• Regulatory compliance requirements
Category: compliance
Probability: medium | Impact: high
Mitigation: Ensure all compliance checks are integrated into migration process
Owner: Compliance Team
MEDIUM SEVERITY RISKS:
• Performance degradation
Category: technical
Probability: medium | Impact: medium
Mitigation: Conduct load testing and performance benchmarking
Owner: DevOps Team
• Monitoring gap during transition
Category: operational
Probability: medium | Impact: medium
Mitigation: Set up dual monitoring and alerting systems
Owner: SRE Team
ROLLBACK STRATEGY
----------------------------------------
Rollback Triggers:
• Critical system failure
• Data corruption detected
• Migration timeline exceeded by > 50%
• Business-critical functionality unavailable
• Security breach detected
• Stakeholder decision to abort
Rollback Phases:
RETIRE:
- Revert retire changes
- Restore pre-retire state
- Validate retire rollback success
Estimated Time: 1500 minutes
VALIDATE:
- Revert validate changes
- Restore pre-validate state
- Validate validate rollback success
Estimated Time: 1500 minutes
REDIRECT:
- Revert redirect changes
- Restore pre-redirect state
- Validate redirect rollback success
Estimated Time: 1500 minutes
IMPLEMENT:
- Revert implement changes
- Restore pre-implement state
- Validate implement rollback success
Estimated Time: 1500 minutes
INTERCEPT:
- Revert intercept changes
- Restore pre-intercept state
- Validate intercept rollback success
Estimated Time: 1500 minutes
SUCCESS CRITERIA
----------------------------------------
✓ All data successfully migrated with 100% integrity
✓ System performance meets or exceeds baseline
✓ All business processes functioning normally
✓ No critical security vulnerabilities introduced
✓ Stakeholder acceptance criteria met
✓ Documentation and runbooks updated
STAKEHOLDERS
----------------------------------------
• Business Owner
• Technical Lead
• DevOps Team
• QA Team
• Security Team
• End Users
FILE:expected_outputs/schema_compatibility_report.json
{
"schema_before": "{\n \"schema_version\": \"1.0\",\n \"database\": \"user_management\",\n \"tables\": {\n \"users\": {\n \"columns\": {\n \"id\": {\n \"type\": \"bigint\",\n \"nullable\": false,\n \"primary_key\": true,\n \"auto_increment\": true\n },\n \"username\": {\n \"type\": \"varchar\",\n \"length\": 50,\n \"nullable\": false,\n \"unique\": true\n },\n \"email\": {\n \"type\": \"varchar\",\n \"length\": 255,\n \"nullable\": false,\n...",
"schema_after": "{\n \"schema_version\": \"2.0\",\n \"database\": \"user_management_v2\",\n \"tables\": {\n \"users\": {\n \"columns\": {\n \"id\": {\n \"type\": \"bigint\",\n \"nullable\": false,\n \"primary_key\": true,\n \"auto_increment\": true\n },\n \"username\": {\n \"type\": \"varchar\",\n \"length\": 50,\n \"nullable\": false,\n \"unique\": true\n },\n \"email\": {\n \"type\": \"varchar\",\n \"length\": 320,\n \"nullable\": fals...",
"analysis_date": "2026-02-16T13:47:27.050459",
"overall_compatibility": "potentially_incompatible",
"breaking_changes_count": 0,
"potentially_breaking_count": 4,
"non_breaking_changes_count": 0,
"additive_changes_count": 0,
"issues": [
{
"type": "check_added",
"severity": "potentially_breaking",
"description": "New check constraint 'phone IS NULL OR LENGTH(phone) >= 10' added to table 'users'",
"field_path": "tables.users.constraints.check",
"old_value": null,
"new_value": "phone IS NULL OR LENGTH(phone) >= 10",
"impact": "New check constraint may reject existing data",
"suggested_migration": "Validate existing data complies with new constraint",
"affected_operations": [
"INSERT",
"UPDATE"
]
},
{
"type": "check_added",
"severity": "potentially_breaking",
"description": "New check constraint 'bio IS NULL OR LENGTH(bio) <= 2000' added to table 'user_profiles'",
"field_path": "tables.user_profiles.constraints.check",
"old_value": null,
"new_value": "bio IS NULL OR LENGTH(bio) <= 2000",
"impact": "New check constraint may reject existing data",
"suggested_migration": "Validate existing data complies with new constraint",
"affected_operations": [
"INSERT",
"UPDATE"
]
},
{
"type": "check_added",
"severity": "potentially_breaking",
"description": "New check constraint 'language IN ('en', 'es', 'fr', 'de', 'it', 'pt', 'ru', 'ja', 'ko', 'zh')' added to table 'user_profiles'",
"field_path": "tables.user_profiles.constraints.check",
"old_value": null,
"new_value": "language IN ('en', 'es', 'fr', 'de', 'it', 'pt', 'ru', 'ja', 'ko', 'zh')",
"impact": "New check constraint may reject existing data",
"suggested_migration": "Validate existing data complies with new constraint",
"affected_operations": [
"INSERT",
"UPDATE"
]
},
{
"type": "check_added",
"severity": "potentially_breaking",
"description": "New check constraint 'session_type IN ('web', 'mobile', 'api', 'admin')' added to table 'user_sessions'",
"field_path": "tables.user_sessions.constraints.check",
"old_value": null,
"new_value": "session_type IN ('web', 'mobile', 'api', 'admin')",
"impact": "New check constraint may reject existing data",
"suggested_migration": "Validate existing data complies with new constraint",
"affected_operations": [
"INSERT",
"UPDATE"
]
}
],
"migration_scripts": [
{
"script_type": "sql",
"description": "Create new table user_preferences",
"script_content": "CREATE TABLE user_preferences (\n id bigint NOT NULL,\n user_id bigint NOT NULL,\n preference_key varchar NOT NULL,\n preference_value json,\n created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP\n);",
"rollback_script": "DROP TABLE IF EXISTS user_preferences;",
"dependencies": [],
"validation_query": "SELECT COUNT(*) FROM information_schema.tables WHERE table_name = 'user_preferences';"
},
{
"script_type": "sql",
"description": "Add column email_verified_at to table users",
"script_content": "ALTER TABLE users ADD COLUMN email_verified_at timestamp;",
"rollback_script": "ALTER TABLE users DROP COLUMN email_verified_at;",
"dependencies": [],
"validation_query": "SELECT COUNT(*) FROM information_schema.columns WHERE table_name = 'users' AND column_name = 'email_verified_at';"
},
{
"script_type": "sql",
"description": "Add column phone_verified_at to table users",
"script_content": "ALTER TABLE users ADD COLUMN phone_verified_at timestamp;",
"rollback_script": "ALTER TABLE users DROP COLUMN phone_verified_at;",
"dependencies": [],
"validation_query": "SELECT COUNT(*) FROM information_schema.columns WHERE table_name = 'users' AND column_name = 'phone_verified_at';"
},
{
"script_type": "sql",
"description": "Add column two_factor_enabled to table users",
"script_content": "ALTER TABLE users ADD COLUMN two_factor_enabled boolean NOT NULL DEFAULT False;",
"rollback_script": "ALTER TABLE users DROP COLUMN two_factor_enabled;",
"dependencies": [],
"validation_query": "SELECT COUNT(*) FROM information_schema.columns WHERE table_name = 'users' AND column_name = 'two_factor_enabled';"
},
{
"script_type": "sql",
"description": "Add column last_login_at to table users",
"script_content": "ALTER TABLE users ADD COLUMN last_login_at timestamp;",
"rollback_script": "ALTER TABLE users DROP COLUMN last_login_at;",
"dependencies": [],
"validation_query": "SELECT COUNT(*) FROM information_schema.columns WHERE table_name = 'users' AND column_name = 'last_login_at';"
},
{
"script_type": "sql",
"description": "Add check constraint to users",
"script_content": "ALTER TABLE users ADD CONSTRAINT check_users CHECK (phone IS NULL OR LENGTH(phone) >= 10);",
"rollback_script": "ALTER TABLE users DROP CONSTRAINT check_users;",
"dependencies": [],
"validation_query": "SELECT COUNT(*) FROM information_schema.table_constraints WHERE table_name = 'users' AND constraint_type = 'CHECK';"
},
{
"script_type": "sql",
"description": "Add column timezone to table user_profiles",
"script_content": "ALTER TABLE user_profiles ADD COLUMN timezone varchar DEFAULT UTC;",
"rollback_script": "ALTER TABLE user_profiles DROP COLUMN timezone;",
"dependencies": [],
"validation_query": "SELECT COUNT(*) FROM information_schema.columns WHERE table_name = 'user_profiles' AND column_name = 'timezone';"
},
{
"script_type": "sql",
"description": "Add column language to table user_profiles",
"script_content": "ALTER TABLE user_profiles ADD COLUMN language varchar NOT NULL DEFAULT en;",
"rollback_script": "ALTER TABLE user_profiles DROP COLUMN language;",
"dependencies": [],
"validation_query": "SELECT COUNT(*) FROM information_schema.columns WHERE table_name = 'user_profiles' AND column_name = 'language';"
},
{
"script_type": "sql",
"description": "Add check constraint to user_profiles",
"script_content": "ALTER TABLE user_profiles ADD CONSTRAINT check_user_profiles CHECK (bio IS NULL OR LENGTH(bio) <= 2000);",
"rollback_script": "ALTER TABLE user_profiles DROP CONSTRAINT check_user_profiles;",
"dependencies": [],
"validation_query": "SELECT COUNT(*) FROM information_schema.table_constraints WHERE table_name = 'user_profiles' AND constraint_type = 'CHECK';"
},
{
"script_type": "sql",
"description": "Add check constraint to user_profiles",
"script_content": "ALTER TABLE user_profiles ADD CONSTRAINT check_user_profiles CHECK (language IN ('en', 'es', 'fr', 'de', 'it', 'pt', 'ru', 'ja', 'ko', 'zh'));",
"rollback_script": "ALTER TABLE user_profiles DROP CONSTRAINT check_user_profiles;",
"dependencies": [],
"validation_query": "SELECT COUNT(*) FROM information_schema.table_constraints WHERE table_name = 'user_profiles' AND constraint_type = 'CHECK';"
},
{
"script_type": "sql",
"description": "Add column session_type to table user_sessions",
"script_content": "ALTER TABLE user_sessions ADD COLUMN session_type varchar NOT NULL DEFAULT web;",
"rollback_script": "ALTER TABLE user_sessions DROP COLUMN session_type;",
"dependencies": [],
"validation_query": "SELECT COUNT(*) FROM information_schema.columns WHERE table_name = 'user_sessions' AND column_name = 'session_type';"
},
{
"script_type": "sql",
"description": "Add column is_mobile to table user_sessions",
"script_content": "ALTER TABLE user_sessions ADD COLUMN is_mobile boolean NOT NULL DEFAULT False;",
"rollback_script": "ALTER TABLE user_sessions DROP COLUMN is_mobile;",
"dependencies": [],
"validation_query": "SELECT COUNT(*) FROM information_schema.columns WHERE table_name = 'user_sessions' AND column_name = 'is_mobile';"
},
{
"script_type": "sql",
"description": "Add check constraint to user_sessions",
"script_content": "ALTER TABLE user_sessions ADD CONSTRAINT check_user_sessions CHECK (session_type IN ('web', 'mobile', 'api', 'admin'));",
"rollback_script": "ALTER TABLE user_sessions DROP CONSTRAINT check_user_sessions;",
"dependencies": [],
"validation_query": "SELECT COUNT(*) FROM information_schema.table_constraints WHERE table_name = 'user_sessions' AND constraint_type = 'CHECK';"
}
],
"risk_assessment": {
"overall_risk": "medium",
"deployment_risk": "safe_independent_deployment",
"rollback_complexity": "low",
"testing_requirements": [
"integration_testing",
"regression_testing",
"data_migration_testing"
]
},
"recommendations": [
"Conduct thorough testing with realistic data volumes",
"Implement monitoring for migration success metrics",
"Test all migration scripts in staging environment",
"Implement migration progress monitoring",
"Create detailed communication plan for stakeholders",
"Implement feature flags for gradual rollout"
]
}
FILE:expected_outputs/schema_compatibility_report.txt
================================================================================
COMPATIBILITY ANALYSIS REPORT
================================================================================
Analysis Date: 2026-02-16T13:47:27.050459
Overall Compatibility: POTENTIALLY_INCOMPATIBLE
SUMMARY
----------------------------------------
Breaking Changes: 0
Potentially Breaking: 4
Non-Breaking Changes: 0
Additive Changes: 0
Total Issues Found: 4
RISK ASSESSMENT
----------------------------------------
Overall Risk: medium
Deployment Risk: safe_independent_deployment
Rollback Complexity: low
Testing Requirements: ['integration_testing', 'regression_testing', 'data_migration_testing']
POTENTIALLY BREAKING ISSUES
----------------------------------------
• New check constraint 'phone IS NULL OR LENGTH(phone) >= 10' added to table 'users'
Field: tables.users.constraints.check
Impact: New check constraint may reject existing data
Migration: Validate existing data complies with new constraint
Affected Operations: INSERT, UPDATE
• New check constraint 'bio IS NULL OR LENGTH(bio) <= 2000' added to table 'user_profiles'
Field: tables.user_profiles.constraints.check
Impact: New check constraint may reject existing data
Migration: Validate existing data complies with new constraint
Affected Operations: INSERT, UPDATE
• New check constraint 'language IN ('en', 'es', 'fr', 'de', 'it', 'pt', 'ru', 'ja', 'ko', 'zh')' added to table 'user_profiles'
Field: tables.user_profiles.constraints.check
Impact: New check constraint may reject existing data
Migration: Validate existing data complies with new constraint
Affected Operations: INSERT, UPDATE
• New check constraint 'session_type IN ('web', 'mobile', 'api', 'admin')' added to table 'user_sessions'
Field: tables.user_sessions.constraints.check
Impact: New check constraint may reject existing data
Migration: Validate existing data complies with new constraint
Affected Operations: INSERT, UPDATE
SUGGESTED MIGRATION SCRIPTS
----------------------------------------
1. Create new table user_preferences
Type: sql
Script:
CREATE TABLE user_preferences (
id bigint NOT NULL,
user_id bigint NOT NULL,
preference_key varchar NOT NULL,
preference_value json,
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
2. Add column email_verified_at to table users
Type: sql
Script:
ALTER TABLE users ADD COLUMN email_verified_at timestamp;
3. Add column phone_verified_at to table users
Type: sql
Script:
ALTER TABLE users ADD COLUMN phone_verified_at timestamp;
4. Add column two_factor_enabled to table users
Type: sql
Script:
ALTER TABLE users ADD COLUMN two_factor_enabled boolean NOT NULL DEFAULT False;
5. Add column last_login_at to table users
Type: sql
Script:
ALTER TABLE users ADD COLUMN last_login_at timestamp;
6. Add check constraint to users
Type: sql
Script:
ALTER TABLE users ADD CONSTRAINT check_users CHECK (phone IS NULL OR LENGTH(phone) >= 10);
7. Add column timezone to table user_profiles
Type: sql
Script:
ALTER TABLE user_profiles ADD COLUMN timezone varchar DEFAULT UTC;
8. Add column language to table user_profiles
Type: sql
Script:
ALTER TABLE user_profiles ADD COLUMN language varchar NOT NULL DEFAULT en;
9. Add check constraint to user_profiles
Type: sql
Script:
ALTER TABLE user_profiles ADD CONSTRAINT check_user_profiles CHECK (bio IS NULL OR LENGTH(bio) <= 2000);
10. Add check constraint to user_profiles
Type: sql
Script:
ALTER TABLE user_profiles ADD CONSTRAINT check_user_profiles CHECK (language IN ('en', 'es', 'fr', 'de', 'it', 'pt', 'ru', 'ja', 'ko', 'zh'));
11. Add column session_type to table user_sessions
Type: sql
Script:
ALTER TABLE user_sessions ADD COLUMN session_type varchar NOT NULL DEFAULT web;
12. Add column is_mobile to table user_sessions
Type: sql
Script:
ALTER TABLE user_sessions ADD COLUMN is_mobile boolean NOT NULL DEFAULT False;
13. Add check constraint to user_sessions
Type: sql
Script:
ALTER TABLE user_sessions ADD CONSTRAINT check_user_sessions CHECK (session_type IN ('web', 'mobile', 'api', 'admin'));
RECOMMENDATIONS
----------------------------------------
1. Conduct thorough testing with realistic data volumes
2. Implement monitoring for migration success metrics
3. Test all migration scripts in staging environment
4. Implement migration progress monitoring
5. Create detailed communication plan for stakeholders
6. Implement feature flags for gradual rollout
FILE:README.md
# Migration Architect
**Tier:** POWERFUL
**Category:** Engineering - Migration Strategy
**Purpose:** Zero-downtime migration planning, compatibility validation, and rollback strategy generation
## Overview
The Migration Architect skill provides comprehensive tools and methodologies for planning, executing, and validating complex system migrations with minimal business impact. This skill combines proven migration patterns with automated planning tools to ensure successful transitions between systems, databases, and infrastructure.
## Components
### Core Scripts
1. **migration_planner.py** - Automated migration plan generation
2. **compatibility_checker.py** - Schema and API compatibility analysis
3. **rollback_generator.py** - Comprehensive rollback procedure generation
### Reference Documentation
- **migration_patterns_catalog.md** - Detailed catalog of proven migration patterns
- **zero_downtime_techniques.md** - Comprehensive zero-downtime migration techniques
- **data_reconciliation_strategies.md** - Advanced data consistency and reconciliation strategies
### Sample Assets
- **sample_database_migration.json** - Example database migration specification
- **sample_service_migration.json** - Example service migration specification
- **database_schema_before.json** - Sample "before" database schema
- **database_schema_after.json** - Sample "after" database schema
## Quick Start
### 1. Generate a Migration Plan
```bash
python3 scripts/migration_planner.py \
--input assets/sample_database_migration.json \
--output migration_plan.json \
--format both
```
**Input:** Migration specification with source, target, constraints, and requirements
**Output:** Detailed phased migration plan with risk assessment, timeline, and validation gates
### 2. Check Compatibility
```bash
python3 scripts/compatibility_checker.py \
--before assets/database_schema_before.json \
--after assets/database_schema_after.json \
--type database \
--output compatibility_report.json \
--format both
```
**Input:** Before and after schema definitions
**Output:** Compatibility report with breaking changes, migration scripts, and recommendations
### 3. Generate Rollback Procedures
```bash
python3 scripts/rollback_generator.py \
--input migration_plan.json \
--output rollback_runbook.json \
--format both
```
**Input:** Migration plan from step 1
**Output:** Comprehensive rollback runbook with procedures, triggers, and communication templates
## Script Details
### Migration Planner (`migration_planner.py`)
Generates comprehensive migration plans with:
- **Phased approach** with dependencies and validation gates
- **Risk assessment** with mitigation strategies
- **Timeline estimation** based on complexity and constraints
- **Rollback triggers** and success criteria
- **Stakeholder communication** templates
**Usage:**
```bash
python3 scripts/migration_planner.py [OPTIONS]
Options:
--input, -i Input migration specification file (JSON) [required]
--output, -o Output file for migration plan (JSON)
--format, -f Output format: json, text, both (default: both)
--validate Validate migration specification only
```
**Input Format:**
```json
{
"type": "database|service|infrastructure",
"pattern": "schema_change|strangler_fig|blue_green",
"source": "Source system description",
"target": "Target system description",
"constraints": {
"max_downtime_minutes": 30,
"data_volume_gb": 2500,
"dependencies": ["service1", "service2"],
"compliance_requirements": ["GDPR", "SOX"]
}
}
```
### Compatibility Checker (`compatibility_checker.py`)
Analyzes compatibility between schema versions:
- **Breaking change detection** (removed fields, type changes, constraint additions)
- **Data migration requirements** identification
- **Suggested migration scripts** generation
- **Risk assessment** for each change
**Usage:**
```bash
python3 scripts/compatibility_checker.py [OPTIONS]
Options:
--before Before schema file (JSON) [required]
--after After schema file (JSON) [required]
--type Schema type: database, api (default: database)
--output, -o Output file for compatibility report (JSON)
--format, -f Output format: json, text, both (default: both)
```
**Exit Codes:**
- `0`: No compatibility issues
- `1`: Potentially breaking changes found
- `2`: Breaking changes found
### Rollback Generator (`rollback_generator.py`)
Creates comprehensive rollback procedures:
- **Phase-by-phase rollback** steps
- **Automated trigger conditions** for rollback
- **Data recovery procedures**
- **Communication templates** for different audiences
- **Validation checklists** for rollback success
**Usage:**
```bash
python3 scripts/rollback_generator.py [OPTIONS]
Options:
--input, -i Input migration plan file (JSON) [required]
--output, -o Output file for rollback runbook (JSON)
--format, -f Output format: json, text, both (default: both)
```
## Migration Patterns Supported
### Database Migrations
- **Expand-Contract Pattern** - Zero-downtime schema evolution
- **Parallel Schema Pattern** - Side-by-side schema migration
- **Event Sourcing Migration** - Event-driven data migration
### Service Migrations
- **Strangler Fig Pattern** - Gradual legacy system replacement
- **Parallel Run Pattern** - Risk mitigation through dual execution
- **Blue-Green Deployment** - Zero-downtime service updates
### Infrastructure Migrations
- **Lift and Shift** - Quick cloud migration with minimal changes
- **Hybrid Cloud Migration** - Gradual cloud adoption
- **Multi-Cloud Migration** - Distribution across multiple providers
## Sample Workflow
### 1. Database Schema Migration
```bash
# Generate migration plan
python3 scripts/migration_planner.py \
--input assets/sample_database_migration.json \
--output db_migration_plan.json
# Check schema compatibility
python3 scripts/compatibility_checker.py \
--before assets/database_schema_before.json \
--after assets/database_schema_after.json \
--type database \
--output schema_compatibility.json
# Generate rollback procedures
python3 scripts/rollback_generator.py \
--input db_migration_plan.json \
--output db_rollback_runbook.json
```
### 2. Service Migration
```bash
# Generate service migration plan
python3 scripts/migration_planner.py \
--input assets/sample_service_migration.json \
--output service_migration_plan.json
# Generate rollback procedures
python3 scripts/rollback_generator.py \
--input service_migration_plan.json \
--output service_rollback_runbook.json
```
## Output Examples
### Migration Plan Structure
```json
{
"migration_id": "abc123def456",
"source_system": "Legacy User Service",
"target_system": "New User Service",
"migration_type": "service",
"complexity": "medium",
"estimated_duration_hours": 72,
"phases": [
{
"name": "preparation",
"description": "Prepare systems and teams for migration",
"duration_hours": 8,
"validation_criteria": ["All backups completed successfully"],
"rollback_triggers": ["Critical system failure"],
"risk_level": "medium"
}
],
"risks": [
{
"category": "technical",
"description": "Service compatibility issues",
"severity": "high",
"mitigation": "Comprehensive integration testing"
}
]
}
```
### Compatibility Report Structure
```json
{
"overall_compatibility": "potentially_incompatible",
"breaking_changes_count": 2,
"potentially_breaking_count": 3,
"issues": [
{
"type": "required_column_added",
"severity": "breaking",
"description": "Required column 'email_verified_at' added",
"suggested_migration": "Add default value initially"
}
],
"migration_scripts": [
{
"script_type": "sql",
"description": "Add email verification columns",
"script_content": "ALTER TABLE users ADD COLUMN email_verified_at TIMESTAMP;",
"rollback_script": "ALTER TABLE users DROP COLUMN email_verified_at;"
}
]
}
```
## Best Practices
### Planning Phase
1. **Start with risk assessment** - Identify failure modes before planning
2. **Design for rollback** - Every step should have a tested rollback procedure
3. **Validate in staging** - Execute full migration in production-like environment
4. **Plan gradual rollout** - Use feature flags and traffic routing
### Execution Phase
1. **Monitor continuously** - Track technical and business metrics
2. **Communicate proactively** - Keep stakeholders informed
3. **Document everything** - Maintain detailed logs for analysis
4. **Stay flexible** - Be prepared to adjust based on real-world performance
### Validation Phase
1. **Automate validation** - Use automated consistency and performance checks
2. **Test business logic** - Validate critical business processes end-to-end
3. **Load test** - Verify performance under expected production load
4. **Security validation** - Ensure security controls function properly
## Integration
### CI/CD Pipeline Integration
```yaml
# Example GitHub Actions workflow
name: Migration Validation
on: [push, pull_request]
jobs:
validate-migration:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Validate Migration Plan
run: |
python3 scripts/migration_planner.py \
--input migration_spec.json \
--validate
- name: Check Compatibility
run: |
python3 scripts/compatibility_checker.py \
--before schema_before.json \
--after schema_after.json \
--type database
```
### Monitoring Integration
The tools generate metrics and alerts that can be integrated with:
- **Prometheus** - For metrics collection
- **Grafana** - For visualization and dashboards
- **PagerDuty** - For incident management
- **Slack** - For team notifications
## Advanced Features
### Machine Learning Integration
- Anomaly detection for data consistency issues
- Predictive analysis for migration success probability
- Automated pattern recognition for migration optimization
### Performance Optimization
- Parallel processing for large-scale migrations
- Incremental reconciliation strategies
- Statistical sampling for validation
### Compliance Support
- GDPR compliance tracking
- SOX audit trail generation
- HIPAA security validation
## Troubleshooting
### Common Issues
**"Migration plan validation failed"**
- Check JSON syntax in migration specification
- Ensure all required fields are present
- Validate constraint values are realistic
**"Compatibility checker reports false positives"**
- Review excluded fields configuration
- Check data type mapping compatibility
- Adjust tolerance settings for numerical comparisons
**"Rollback procedures seem incomplete"**
- Ensure migration plan includes all phases
- Verify database backup locations are specified
- Check that all dependencies are documented
### Getting Help
1. **Review documentation** - Check reference docs for patterns and techniques
2. **Examine sample files** - Use provided assets as templates
3. **Check expected outputs** - Compare your results with sample outputs
4. **Validate inputs** - Ensure input files match expected format
## Contributing
To extend or modify the Migration Architect skill:
1. **Add new patterns** - Extend pattern templates in migration_planner.py
2. **Enhance compatibility checks** - Add new validation rules in compatibility_checker.py
3. **Improve rollback procedures** - Add specialized rollback steps in rollback_generator.py
4. **Update documentation** - Keep reference docs current with new patterns
## License
This skill is part of the claude-skills repository and follows the same license terms.
FILE:references/data_reconciliation_strategies.md
# Data Reconciliation Strategies
## Overview
Data reconciliation is the process of ensuring data consistency and integrity across systems during and after migrations. This document provides comprehensive strategies, tools, and implementation patterns for detecting, measuring, and correcting data discrepancies in migration scenarios.
## Core Principles
### 1. Eventually Consistent
Accept that perfect real-time consistency may not be achievable during migrations, but ensure eventual consistency through reconciliation processes.
### 2. Idempotent Operations
All reconciliation operations must be safe to run multiple times without causing additional issues.
### 3. Audit Trail
Maintain detailed logs of all reconciliation actions for compliance and debugging.
### 4. Non-Destructive
Reconciliation should prefer addition over deletion, and always maintain backups before corrections.
## Types of Data Inconsistencies
### 1. Missing Records
Records that exist in source but not in target system.
### 2. Extra Records
Records that exist in target but not in source system.
### 3. Field Mismatches
Records exist in both systems but with different field values.
### 4. Referential Integrity Violations
Foreign key relationships that are broken during migration.
### 5. Temporal Inconsistencies
Data with incorrect timestamps or ordering.
### 6. Schema Drift
Structural differences between source and target schemas.
## Detection Strategies
### 1. Row Count Validation
#### Simple Count Comparison
```sql
-- Compare total row counts
SELECT
'source' as system,
COUNT(*) as row_count
FROM source_table
UNION ALL
SELECT
'target' as system,
COUNT(*) as row_count
FROM target_table;
```
#### Filtered Count Comparison
```sql
-- Compare counts with business logic filters
WITH source_counts AS (
SELECT
status,
created_date::date as date,
COUNT(*) as count
FROM source_orders
WHERE created_date >= '2024-01-01'
GROUP BY status, created_date::date
),
target_counts AS (
SELECT
status,
created_date::date as date,
COUNT(*) as count
FROM target_orders
WHERE created_date >= '2024-01-01'
GROUP BY status, created_date::date
)
SELECT
COALESCE(s.status, t.status) as status,
COALESCE(s.date, t.date) as date,
COALESCE(s.count, 0) as source_count,
COALESCE(t.count, 0) as target_count,
COALESCE(s.count, 0) - COALESCE(t.count, 0) as difference
FROM source_counts s
FULL OUTER JOIN target_counts t
ON s.status = t.status AND s.date = t.date
WHERE COALESCE(s.count, 0) != COALESCE(t.count, 0);
```
### 2. Checksum-Based Validation
#### Record-Level Checksums
```python
import hashlib
import json
class RecordChecksum:
def __init__(self, exclude_fields=None):
self.exclude_fields = exclude_fields or ['updated_at', 'version']
def calculate_checksum(self, record):
"""Calculate MD5 checksum for a database record"""
# Remove excluded fields and sort for consistency
filtered_record = {
k: v for k, v in record.items()
if k not in self.exclude_fields
}
# Convert to sorted JSON string for consistent hashing
normalized = json.dumps(filtered_record, sort_keys=True, default=str)
return hashlib.md5(normalized.encode('utf-8')).hexdigest()
def compare_records(self, source_record, target_record):
"""Compare two records using checksums"""
source_checksum = self.calculate_checksum(source_record)
target_checksum = self.calculate_checksum(target_record)
return {
'match': source_checksum == target_checksum,
'source_checksum': source_checksum,
'target_checksum': target_checksum
}
# Usage example
checksum_calculator = RecordChecksum(exclude_fields=['updated_at', 'migration_flag'])
source_records = fetch_records_from_source()
target_records = fetch_records_from_target()
mismatches = []
for source_id, source_record in source_records.items():
if source_id in target_records:
comparison = checksum_calculator.compare_records(
source_record, target_records[source_id]
)
if not comparison['match']:
mismatches.append({
'record_id': source_id,
'source_checksum': comparison['source_checksum'],
'target_checksum': comparison['target_checksum']
})
```
#### Aggregate Checksums
```sql
-- Calculate aggregate checksums for data validation
WITH source_aggregates AS (
SELECT
DATE_TRUNC('day', created_at) as day,
status,
COUNT(*) as record_count,
SUM(amount) as total_amount,
MD5(STRING_AGG(CAST(id AS VARCHAR) || ':' || CAST(amount AS VARCHAR), '|' ORDER BY id)) as checksum
FROM source_transactions
GROUP BY DATE_TRUNC('day', created_at), status
),
target_aggregates AS (
SELECT
DATE_TRUNC('day', created_at) as day,
status,
COUNT(*) as record_count,
SUM(amount) as total_amount,
MD5(STRING_AGG(CAST(id AS VARCHAR) || ':' || CAST(amount AS VARCHAR), '|' ORDER BY id)) as checksum
FROM target_transactions
GROUP BY DATE_TRUNC('day', created_at), status
)
SELECT
COALESCE(s.day, t.day) as day,
COALESCE(s.status, t.status) as status,
COALESCE(s.record_count, 0) as source_count,
COALESCE(t.record_count, 0) as target_count,
COALESCE(s.total_amount, 0) as source_amount,
COALESCE(t.total_amount, 0) as target_amount,
s.checksum as source_checksum,
t.checksum as target_checksum,
CASE WHEN s.checksum = t.checksum THEN 'MATCH' ELSE 'MISMATCH' END as status
FROM source_aggregates s
FULL OUTER JOIN target_aggregates t
ON s.day = t.day AND s.status = t.status
WHERE s.checksum != t.checksum OR s.checksum IS NULL OR t.checksum IS NULL;
```
### 3. Delta Detection
#### Change Data Capture (CDC) Based
```python
class CDCReconciler:
def __init__(self, kafka_client, database_client):
self.kafka = kafka_client
self.db = database_client
self.processed_changes = set()
def process_cdc_stream(self, topic_name):
"""Process CDC events and track changes for reconciliation"""
consumer = self.kafka.consumer(topic_name)
for message in consumer:
change_event = json.loads(message.value)
change_id = f"{change_event['table']}:{change_event['key']}:{change_event['timestamp']}"
if change_id in self.processed_changes:
continue # Skip duplicate events
try:
self.apply_change(change_event)
self.processed_changes.add(change_id)
# Commit offset only after successful processing
consumer.commit()
except Exception as e:
# Log failure and continue - will be caught by reconciliation
self.log_processing_failure(change_id, str(e))
def apply_change(self, change_event):
"""Apply CDC change to target system"""
table = change_event['table']
operation = change_event['operation']
key = change_event['key']
data = change_event.get('data', {})
if operation == 'INSERT':
self.db.insert(table, data)
elif operation == 'UPDATE':
self.db.update(table, key, data)
elif operation == 'DELETE':
self.db.delete(table, key)
def reconcile_missed_changes(self, start_timestamp, end_timestamp):
"""Find and apply changes that may have been missed"""
# Query source database for changes in time window
source_changes = self.db.get_changes_in_window(
start_timestamp, end_timestamp
)
missed_changes = []
for change in source_changes:
change_id = f"{change['table']}:{change['key']}:{change['timestamp']}"
if change_id not in self.processed_changes:
missed_changes.append(change)
# Apply missed changes
for change in missed_changes:
try:
self.apply_change(change)
print(f"Applied missed change: {change['table']}:{change['key']}")
except Exception as e:
print(f"Failed to apply missed change: {e}")
```
### 4. Business Logic Validation
#### Critical Business Rules Validation
```python
class BusinessLogicValidator:
def __init__(self, source_db, target_db):
self.source_db = source_db
self.target_db = target_db
def validate_financial_consistency(self):
"""Validate critical financial calculations"""
validation_rules = [
{
'name': 'daily_transaction_totals',
'source_query': """
SELECT DATE(created_at) as date, SUM(amount) as total
FROM source_transactions
WHERE created_at >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY DATE(created_at)
""",
'target_query': """
SELECT DATE(created_at) as date, SUM(amount) as total
FROM target_transactions
WHERE created_at >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY DATE(created_at)
""",
'tolerance': 0.01 # Allow $0.01 difference for rounding
},
{
'name': 'customer_balance_totals',
'source_query': """
SELECT customer_id, SUM(balance) as total_balance
FROM source_accounts
GROUP BY customer_id
HAVING SUM(balance) > 0
""",
'target_query': """
SELECT customer_id, SUM(balance) as total_balance
FROM target_accounts
GROUP BY customer_id
HAVING SUM(balance) > 0
""",
'tolerance': 0.01
}
]
validation_results = []
for rule in validation_rules:
source_data = self.source_db.execute_query(rule['source_query'])
target_data = self.target_db.execute_query(rule['target_query'])
differences = self.compare_financial_data(
source_data, target_data, rule['tolerance']
)
validation_results.append({
'rule_name': rule['name'],
'differences_found': len(differences),
'differences': differences[:10], # First 10 differences
'status': 'PASS' if len(differences) == 0 else 'FAIL'
})
return validation_results
def compare_financial_data(self, source_data, target_data, tolerance):
"""Compare financial data with tolerance for rounding differences"""
source_dict = {
tuple(row[:-1]): row[-1] for row in source_data
} # Last column is the amount
target_dict = {
tuple(row[:-1]): row[-1] for row in target_data
}
differences = []
# Check for missing records and value differences
for key, source_value in source_dict.items():
if key not in target_dict:
differences.append({
'key': key,
'source_value': source_value,
'target_value': None,
'difference_type': 'MISSING_IN_TARGET'
})
else:
target_value = target_dict[key]
if abs(float(source_value) - float(target_value)) > tolerance:
differences.append({
'key': key,
'source_value': source_value,
'target_value': target_value,
'difference': float(source_value) - float(target_value),
'difference_type': 'VALUE_MISMATCH'
})
# Check for extra records in target
for key, target_value in target_dict.items():
if key not in source_dict:
differences.append({
'key': key,
'source_value': None,
'target_value': target_value,
'difference_type': 'EXTRA_IN_TARGET'
})
return differences
```
## Correction Strategies
### 1. Automated Correction
#### Missing Record Insertion
```python
class AutoCorrector:
def __init__(self, source_db, target_db, dry_run=True):
self.source_db = source_db
self.target_db = target_db
self.dry_run = dry_run
self.correction_log = []
def correct_missing_records(self, table_name, key_field):
"""Add missing records from source to target"""
# Find records in source but not in target
missing_query = f"""
SELECT s.*
FROM source_{table_name} s
LEFT JOIN target_{table_name} t ON s.{key_field} = t.{key_field}
WHERE t.{key_field} IS NULL
"""
missing_records = self.source_db.execute_query(missing_query)
for record in missing_records:
correction = {
'table': table_name,
'operation': 'INSERT',
'key': record[key_field],
'data': record,
'timestamp': datetime.utcnow()
}
if not self.dry_run:
try:
self.target_db.insert(table_name, record)
correction['status'] = 'SUCCESS'
except Exception as e:
correction['status'] = 'FAILED'
correction['error'] = str(e)
else:
correction['status'] = 'DRY_RUN'
self.correction_log.append(correction)
return len(missing_records)
def correct_field_mismatches(self, table_name, key_field, fields_to_correct):
"""Correct field value mismatches"""
mismatch_query = f"""
SELECT s.{key_field}, {', '.join([f's.{f} as source_{f}, t.{f} as target_{f}' for f in fields_to_correct])}
FROM source_{table_name} s
JOIN target_{table_name} t ON s.{key_field} = t.{key_field}
WHERE {' OR '.join([f's.{f} != t.{f}' for f in fields_to_correct])}
"""
mismatched_records = self.source_db.execute_query(mismatch_query)
for record in mismatched_records:
key_value = record[key_field]
updates = {}
for field in fields_to_correct:
source_value = record[f'source_{field}']
target_value = record[f'target_{field}']
if source_value != target_value:
updates[field] = source_value
if updates:
correction = {
'table': table_name,
'operation': 'UPDATE',
'key': key_value,
'updates': updates,
'timestamp': datetime.utcnow()
}
if not self.dry_run:
try:
self.target_db.update(table_name, {key_field: key_value}, updates)
correction['status'] = 'SUCCESS'
except Exception as e:
correction['status'] = 'FAILED'
correction['error'] = str(e)
else:
correction['status'] = 'DRY_RUN'
self.correction_log.append(correction)
return len(mismatched_records)
```
### 2. Manual Review Process
#### Correction Workflow
```python
class ManualReviewSystem:
def __init__(self, database_client):
self.db = database_client
self.review_queue = []
def queue_for_review(self, discrepancy):
"""Add discrepancy to manual review queue"""
review_item = {
'id': str(uuid.uuid4()),
'discrepancy_type': discrepancy['type'],
'table': discrepancy['table'],
'record_key': discrepancy['key'],
'source_data': discrepancy.get('source_data'),
'target_data': discrepancy.get('target_data'),
'description': discrepancy['description'],
'severity': discrepancy.get('severity', 'medium'),
'status': 'PENDING',
'created_at': datetime.utcnow(),
'reviewed_by': None,
'reviewed_at': None,
'resolution': None
}
self.review_queue.append(review_item)
# Persist to review database
self.db.insert('manual_review_queue', review_item)
return review_item['id']
def process_review(self, review_id, reviewer, action, notes=None):
"""Process manual review decision"""
review_item = self.get_review_item(review_id)
if not review_item:
raise ValueError(f"Review item {review_id} not found")
review_item.update({
'status': 'REVIEWED',
'reviewed_by': reviewer,
'reviewed_at': datetime.utcnow(),
'resolution': {
'action': action, # 'APPLY_SOURCE', 'KEEP_TARGET', 'CUSTOM_FIX'
'notes': notes
}
})
# Apply the resolution
if action == 'APPLY_SOURCE':
self.apply_source_data(review_item)
elif action == 'KEEP_TARGET':
pass # No action needed
elif action == 'CUSTOM_FIX':
# Custom fix would be applied separately
pass
# Update review record
self.db.update('manual_review_queue',
{'id': review_id},
review_item)
return review_item
def generate_review_report(self):
"""Generate summary report of manual reviews"""
reviews = self.db.query("""
SELECT
discrepancy_type,
severity,
status,
COUNT(*) as count,
MIN(created_at) as oldest_review,
MAX(created_at) as newest_review
FROM manual_review_queue
GROUP BY discrepancy_type, severity, status
ORDER BY severity DESC, discrepancy_type
""")
return reviews
```
### 3. Reconciliation Scheduling
#### Automated Reconciliation Jobs
```python
import schedule
import time
from datetime import datetime, timedelta
class ReconciliationScheduler:
def __init__(self, reconciler):
self.reconciler = reconciler
self.job_history = []
def setup_schedules(self):
"""Set up automated reconciliation schedules"""
# Quick reconciliation every 15 minutes during migration
schedule.every(15).minutes.do(self.quick_reconciliation)
# Comprehensive reconciliation every 4 hours
schedule.every(4).hours.do(self.comprehensive_reconciliation)
# Deep validation daily
schedule.every().day.at("02:00").do(self.deep_validation)
# Weekly business logic validation
schedule.every().sunday.at("03:00").do(self.business_logic_validation)
def quick_reconciliation(self):
"""Quick count-based reconciliation"""
job_start = datetime.utcnow()
try:
# Check critical tables only
critical_tables = [
'transactions', 'orders', 'customers', 'accounts'
]
results = []
for table in critical_tables:
count_diff = self.reconciler.check_row_counts(table)
if abs(count_diff) > 0:
results.append({
'table': table,
'count_difference': count_diff,
'severity': 'high' if abs(count_diff) > 100 else 'medium'
})
job_result = {
'job_type': 'quick_reconciliation',
'start_time': job_start,
'end_time': datetime.utcnow(),
'status': 'completed',
'issues_found': len(results),
'details': results
}
# Alert if significant issues found
if any(r['severity'] == 'high' for r in results):
self.send_alert(job_result)
except Exception as e:
job_result = {
'job_type': 'quick_reconciliation',
'start_time': job_start,
'end_time': datetime.utcnow(),
'status': 'failed',
'error': str(e)
}
self.job_history.append(job_result)
def comprehensive_reconciliation(self):
"""Comprehensive checksum-based reconciliation"""
job_start = datetime.utcnow()
try:
tables_to_check = self.get_migration_tables()
issues = []
for table in tables_to_check:
# Sample-based checksum validation
sample_issues = self.reconciler.validate_sample_checksums(
table, sample_size=1000
)
issues.extend(sample_issues)
# Auto-correct simple issues
auto_corrections = 0
for issue in issues:
if issue['auto_correctable']:
self.reconciler.auto_correct_issue(issue)
auto_corrections += 1
else:
# Queue for manual review
self.reconciler.queue_for_manual_review(issue)
job_result = {
'job_type': 'comprehensive_reconciliation',
'start_time': job_start,
'end_time': datetime.utcnow(),
'status': 'completed',
'total_issues': len(issues),
'auto_corrections': auto_corrections,
'manual_reviews_queued': len(issues) - auto_corrections
}
except Exception as e:
job_result = {
'job_type': 'comprehensive_reconciliation',
'start_time': job_start,
'end_time': datetime.utcnow(),
'status': 'failed',
'error': str(e)
}
self.job_history.append(job_result)
def run_scheduler(self):
"""Run the reconciliation scheduler"""
print("Starting reconciliation scheduler...")
while True:
schedule.run_pending()
time.sleep(60) # Check every minute
```
## Monitoring and Reporting
### 1. Reconciliation Metrics
```python
class ReconciliationMetrics:
def __init__(self, prometheus_client):
self.prometheus = prometheus_client
# Define metrics
self.inconsistencies_found = Counter(
'reconciliation_inconsistencies_total',
'Number of inconsistencies found',
['table', 'type', 'severity']
)
self.reconciliation_duration = Histogram(
'reconciliation_duration_seconds',
'Time spent on reconciliation jobs',
['job_type']
)
self.auto_corrections = Counter(
'reconciliation_auto_corrections_total',
'Number of automatically corrected inconsistencies',
['table', 'correction_type']
)
self.data_drift_gauge = Gauge(
'data_drift_percentage',
'Percentage of records with inconsistencies',
['table']
)
def record_inconsistency(self, table, inconsistency_type, severity):
"""Record a found inconsistency"""
self.inconsistencies_found.labels(
table=table,
type=inconsistency_type,
severity=severity
).inc()
def record_auto_correction(self, table, correction_type):
"""Record an automatic correction"""
self.auto_corrections.labels(
table=table,
correction_type=correction_type
).inc()
def update_data_drift(self, table, drift_percentage):
"""Update data drift gauge"""
self.data_drift_gauge.labels(table=table).set(drift_percentage)
def record_job_duration(self, job_type, duration_seconds):
"""Record reconciliation job duration"""
self.reconciliation_duration.labels(job_type=job_type).observe(duration_seconds)
```
### 2. Alerting Rules
```yaml
# Prometheus alerting rules for data reconciliation
groups:
- name: data_reconciliation
rules:
- alert: HighDataInconsistency
expr: reconciliation_inconsistencies_total > 100
for: 5m
labels:
severity: critical
annotations:
summary: "High number of data inconsistencies detected"
description: "{{ $value }} inconsistencies found in the last 5 minutes"
- alert: DataDriftHigh
expr: data_drift_percentage > 5
for: 10m
labels:
severity: warning
annotations:
summary: "Data drift percentage is high"
description: "{{ $labels.table }} has {{ $value }}% data drift"
- alert: ReconciliationJobFailed
expr: up{job="reconciliation"} == 0
for: 2m
labels:
severity: critical
annotations:
summary: "Reconciliation job is down"
description: "The data reconciliation service is not responding"
- alert: AutoCorrectionRateHigh
expr: rate(reconciliation_auto_corrections_total[10m]) > 10
for: 5m
labels:
severity: warning
annotations:
summary: "High rate of automatic corrections"
description: "Auto-correction rate is {{ $value }} per second"
```
### 3. Dashboard and Reporting
```python
class ReconciliationDashboard:
def __init__(self, database_client, metrics_client):
self.db = database_client
self.metrics = metrics_client
def generate_daily_report(self, date=None):
"""Generate daily reconciliation report"""
if not date:
date = datetime.utcnow().date()
# Query reconciliation results for the day
daily_stats = self.db.query("""
SELECT
table_name,
inconsistency_type,
COUNT(*) as count,
AVG(CASE WHEN resolution = 'AUTO_CORRECTED' THEN 1 ELSE 0 END) as auto_correction_rate
FROM reconciliation_log
WHERE DATE(created_at) = %s
GROUP BY table_name, inconsistency_type
""", (date,))
# Generate summary
summary = {
'date': date.isoformat(),
'total_inconsistencies': sum(row['count'] for row in daily_stats),
'auto_correction_rate': sum(row['auto_correction_rate'] * row['count'] for row in daily_stats) / max(sum(row['count'] for row in daily_stats), 1),
'tables_affected': len(set(row['table_name'] for row in daily_stats)),
'details_by_table': {}
}
# Group by table
for row in daily_stats:
table = row['table_name']
if table not in summary['details_by_table']:
summary['details_by_table'][table] = []
summary['details_by_table'][table].append({
'inconsistency_type': row['inconsistency_type'],
'count': row['count'],
'auto_correction_rate': row['auto_correction_rate']
})
return summary
def generate_trend_analysis(self, days=7):
"""Generate trend analysis for reconciliation metrics"""
end_date = datetime.utcnow().date()
start_date = end_date - timedelta(days=days)
trends = self.db.query("""
SELECT
DATE(created_at) as date,
table_name,
COUNT(*) as inconsistencies,
AVG(CASE WHEN resolution = 'AUTO_CORRECTED' THEN 1 ELSE 0 END) as auto_correction_rate
FROM reconciliation_log
WHERE DATE(created_at) BETWEEN %s AND %s
GROUP BY DATE(created_at), table_name
ORDER BY date, table_name
""", (start_date, end_date))
# Calculate trends
trend_analysis = {
'period': f"{start_date} to {end_date}",
'trends': {},
'overall_trend': 'stable'
}
for table in set(row['table_name'] for row in trends):
table_data = [row for row in trends if row['table_name'] == table]
if len(table_data) >= 2:
first_count = table_data[0]['inconsistencies']
last_count = table_data[-1]['inconsistencies']
if last_count > first_count * 1.2:
trend = 'increasing'
elif last_count < first_count * 0.8:
trend = 'decreasing'
else:
trend = 'stable'
trend_analysis['trends'][table] = {
'direction': trend,
'first_day_count': first_count,
'last_day_count': last_count,
'change_percentage': ((last_count - first_count) / max(first_count, 1)) * 100
}
return trend_analysis
```
## Advanced Reconciliation Techniques
### 1. Machine Learning-Based Anomaly Detection
```python
from sklearn.isolation import IsolationForest
from sklearn.preprocessing import StandardScaler
import numpy as np
class MLAnomalyDetector:
def __init__(self):
self.models = {}
self.scalers = {}
def train_anomaly_detector(self, table_name, training_data):
"""Train anomaly detection model for a specific table"""
# Prepare features (convert records to numerical features)
features = self.extract_features(training_data)
# Scale features
scaler = StandardScaler()
scaled_features = scaler.fit_transform(features)
# Train isolation forest
model = IsolationForest(contamination=0.05, random_state=42)
model.fit(scaled_features)
# Store model and scaler
self.models[table_name] = model
self.scalers[table_name] = scaler
def detect_anomalies(self, table_name, data):
"""Detect anomalous records that may indicate reconciliation issues"""
if table_name not in self.models:
raise ValueError(f"No trained model for table {table_name}")
# Extract features
features = self.extract_features(data)
# Scale features
scaled_features = self.scalers[table_name].transform(features)
# Predict anomalies
anomaly_scores = self.models[table_name].decision_function(scaled_features)
anomaly_predictions = self.models[table_name].predict(scaled_features)
# Return anomalous records with scores
anomalies = []
for i, (record, score, is_anomaly) in enumerate(zip(data, anomaly_scores, anomaly_predictions)):
if is_anomaly == -1: # Isolation forest returns -1 for anomalies
anomalies.append({
'record_index': i,
'record': record,
'anomaly_score': score,
'severity': 'high' if score < -0.5 else 'medium'
})
return anomalies
def extract_features(self, data):
"""Extract numerical features from database records"""
features = []
for record in data:
record_features = []
for key, value in record.items():
if isinstance(value, (int, float)):
record_features.append(value)
elif isinstance(value, str):
# Convert string to hash-based feature
record_features.append(hash(value) % 10000)
elif isinstance(value, datetime):
# Convert datetime to timestamp
record_features.append(value.timestamp())
else:
# Default value for other types
record_features.append(0)
features.append(record_features)
return np.array(features)
```
### 2. Probabilistic Reconciliation
```python
import random
from typing import List, Dict, Tuple
class ProbabilisticReconciler:
def __init__(self, confidence_threshold=0.95):
self.confidence_threshold = confidence_threshold
def statistical_sampling_validation(self, table_name: str, population_size: int) -> Dict:
"""Use statistical sampling to validate large datasets"""
# Calculate sample size for 95% confidence, 5% margin of error
confidence_level = 0.95
margin_of_error = 0.05
z_score = 1.96 # for 95% confidence
p = 0.5 # assume 50% error rate for maximum sample size
sample_size = (z_score ** 2 * p * (1 - p)) / (margin_of_error ** 2)
if population_size < 10000:
# Finite population correction
sample_size = sample_size / (1 + (sample_size - 1) / population_size)
sample_size = min(int(sample_size), population_size)
# Generate random sample
sample_ids = self.generate_random_sample(table_name, sample_size)
# Validate sample
sample_results = self.validate_sample_records(table_name, sample_ids)
# Calculate population estimates
error_rate = sample_results['errors'] / sample_size
estimated_errors = int(population_size * error_rate)
# Calculate confidence interval
standard_error = (error_rate * (1 - error_rate) / sample_size) ** 0.5
margin_of_error_actual = z_score * standard_error
confidence_interval = (
max(0, error_rate - margin_of_error_actual),
min(1, error_rate + margin_of_error_actual)
)
return {
'table_name': table_name,
'population_size': population_size,
'sample_size': sample_size,
'sample_error_rate': error_rate,
'estimated_total_errors': estimated_errors,
'confidence_interval': confidence_interval,
'confidence_level': confidence_level,
'recommendation': self.generate_recommendation(error_rate, confidence_interval)
}
def generate_random_sample(self, table_name: str, sample_size: int) -> List[int]:
"""Generate random sample of record IDs"""
# Get total record count and ID range
id_range = self.db.query(f"SELECT MIN(id), MAX(id) FROM {table_name}")[0]
min_id, max_id = id_range
# Generate random IDs
sample_ids = []
attempts = 0
max_attempts = sample_size * 10 # Avoid infinite loop
while len(sample_ids) < sample_size and attempts < max_attempts:
candidate_id = random.randint(min_id, max_id)
# Check if ID exists
exists = self.db.query(f"SELECT 1 FROM {table_name} WHERE id = %s", (candidate_id,))
if exists and candidate_id not in sample_ids:
sample_ids.append(candidate_id)
attempts += 1
return sample_ids
def validate_sample_records(self, table_name: str, sample_ids: List[int]) -> Dict:
"""Validate a sample of records"""
validation_results = {
'total_checked': len(sample_ids),
'errors': 0,
'error_details': []
}
for record_id in sample_ids:
# Get record from both source and target
source_record = self.source_db.get_record(table_name, record_id)
target_record = self.target_db.get_record(table_name, record_id)
if not target_record:
validation_results['errors'] += 1
validation_results['error_details'].append({
'id': record_id,
'error_type': 'MISSING_IN_TARGET'
})
elif not self.records_match(source_record, target_record):
validation_results['errors'] += 1
validation_results['error_details'].append({
'id': record_id,
'error_type': 'DATA_MISMATCH',
'differences': self.find_differences(source_record, target_record)
})
return validation_results
def generate_recommendation(self, error_rate: float, confidence_interval: Tuple[float, float]) -> str:
"""Generate recommendation based on error rate and confidence"""
if confidence_interval[1] < 0.01: # Less than 1% error rate with confidence
return "Data quality is excellent. Continue with normal reconciliation schedule."
elif confidence_interval[1] < 0.05: # Less than 5% error rate with confidence
return "Data quality is acceptable. Monitor closely and investigate sample errors."
elif confidence_interval[0] > 0.1: # More than 10% error rate with confidence
return "Data quality is poor. Immediate comprehensive reconciliation required."
else:
return "Data quality is uncertain. Increase sample size for better estimates."
```
## Performance Optimization
### 1. Parallel Processing
```python
import asyncio
import multiprocessing as mp
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
class ParallelReconciler:
def __init__(self, max_workers=None):
self.max_workers = max_workers or mp.cpu_count()
async def parallel_table_reconciliation(self, tables: List[str]):
"""Reconcile multiple tables in parallel"""
async with asyncio.Semaphore(self.max_workers):
tasks = [
self.reconcile_table_async(table)
for table in tables
]
results = await asyncio.gather(*tasks, return_exceptions=True)
# Process results
summary = {
'total_tables': len(tables),
'successful': 0,
'failed': 0,
'results': {}
}
for table, result in zip(tables, results):
if isinstance(result, Exception):
summary['failed'] += 1
summary['results'][table] = {
'status': 'failed',
'error': str(result)
}
else:
summary['successful'] += 1
summary['results'][table] = result
return summary
def parallel_chunk_processing(self, table_name: str, chunk_size: int = 10000):
"""Process table reconciliation in parallel chunks"""
# Get total record count
total_records = self.db.get_record_count(table_name)
num_chunks = (total_records + chunk_size - 1) // chunk_size
# Create chunk specifications
chunks = []
for i in range(num_chunks):
start_id = i * chunk_size
end_id = min((i + 1) * chunk_size - 1, total_records - 1)
chunks.append({
'table': table_name,
'start_id': start_id,
'end_id': end_id,
'chunk_number': i + 1
})
# Process chunks in parallel
with ProcessPoolExecutor(max_workers=self.max_workers) as executor:
chunk_results = list(executor.map(self.process_chunk, chunks))
# Aggregate results
total_inconsistencies = sum(r['inconsistencies'] for r in chunk_results)
total_corrections = sum(r['corrections'] for r in chunk_results)
return {
'table': table_name,
'total_records': total_records,
'chunks_processed': len(chunks),
'total_inconsistencies': total_inconsistencies,
'total_corrections': total_corrections,
'chunk_details': chunk_results
}
def process_chunk(self, chunk_spec: Dict) -> Dict:
"""Process a single chunk of records"""
# This runs in a separate process
table = chunk_spec['table']
start_id = chunk_spec['start_id']
end_id = chunk_spec['end_id']
# Initialize database connections for this process
local_source_db = SourceDatabase()
local_target_db = TargetDatabase()
# Get records in chunk
source_records = local_source_db.get_records_range(table, start_id, end_id)
target_records = local_target_db.get_records_range(table, start_id, end_id)
# Reconcile chunk
inconsistencies = 0
corrections = 0
for source_record in source_records:
target_record = target_records.get(source_record['id'])
if not target_record:
inconsistencies += 1
# Auto-correct if possible
try:
local_target_db.insert(table, source_record)
corrections += 1
except Exception:
pass # Log error in production
elif not self.records_match(source_record, target_record):
inconsistencies += 1
# Auto-correct field mismatches
try:
updates = self.calculate_updates(source_record, target_record)
local_target_db.update(table, source_record['id'], updates)
corrections += 1
except Exception:
pass # Log error in production
return {
'chunk_number': chunk_spec['chunk_number'],
'start_id': start_id,
'end_id': end_id,
'records_processed': len(source_records),
'inconsistencies': inconsistencies,
'corrections': corrections
}
```
### 2. Incremental Reconciliation
```python
class IncrementalReconciler:
def __init__(self, source_db, target_db):
self.source_db = source_db
self.target_db = target_db
self.last_reconciliation_times = {}
def incremental_reconciliation(self, table_name: str):
"""Reconcile only records changed since last reconciliation"""
last_reconciled = self.get_last_reconciliation_time(table_name)
# Get records modified since last reconciliation
modified_source = self.source_db.get_records_modified_since(
table_name, last_reconciled
)
modified_target = self.target_db.get_records_modified_since(
table_name, last_reconciled
)
# Create lookup dictionaries
source_dict = {r['id']: r for r in modified_source}
target_dict = {r['id']: r for r in modified_target}
# Find all record IDs to check
all_ids = set(source_dict.keys()) | set(target_dict.keys())
inconsistencies = []
for record_id in all_ids:
source_record = source_dict.get(record_id)
target_record = target_dict.get(record_id)
if source_record and not target_record:
inconsistencies.append({
'type': 'missing_in_target',
'table': table_name,
'id': record_id,
'source_record': source_record
})
elif not source_record and target_record:
inconsistencies.append({
'type': 'extra_in_target',
'table': table_name,
'id': record_id,
'target_record': target_record
})
elif source_record and target_record:
if not self.records_match(source_record, target_record):
inconsistencies.append({
'type': 'data_mismatch',
'table': table_name,
'id': record_id,
'source_record': source_record,
'target_record': target_record,
'differences': self.find_differences(source_record, target_record)
})
# Update last reconciliation time
self.update_last_reconciliation_time(table_name, datetime.utcnow())
return {
'table': table_name,
'reconciliation_time': datetime.utcnow(),
'records_checked': len(all_ids),
'inconsistencies_found': len(inconsistencies),
'inconsistencies': inconsistencies
}
def get_last_reconciliation_time(self, table_name: str) -> datetime:
"""Get the last reconciliation timestamp for a table"""
result = self.source_db.query("""
SELECT last_reconciled_at
FROM reconciliation_metadata
WHERE table_name = %s
""", (table_name,))
if result:
return result[0]['last_reconciled_at']
else:
# First time reconciliation - start from beginning of migration
return self.get_migration_start_time()
def update_last_reconciliation_time(self, table_name: str, timestamp: datetime):
"""Update the last reconciliation timestamp"""
self.source_db.execute("""
INSERT INTO reconciliation_metadata (table_name, last_reconciled_at)
VALUES (%s, %s)
ON CONFLICT (table_name)
DO UPDATE SET last_reconciled_at = %s
""", (table_name, timestamp, timestamp))
```
This comprehensive guide provides the framework and tools necessary for implementing robust data reconciliation strategies during migrations, ensuring data integrity and consistency while minimizing business disruption.
FILE:references/migration_patterns_catalog.md
# Migration Patterns Catalog
## Overview
This catalog provides detailed descriptions of proven migration patterns, their use cases, implementation guidelines, and best practices. Each pattern includes code examples, diagrams, and lessons learned from real-world implementations.
## Database Migration Patterns
### 1. Expand-Contract Pattern
**Use Case:** Schema evolution with zero downtime
**Complexity:** Medium
**Risk Level:** Low-Medium
#### Description
The Expand-Contract pattern allows for schema changes without downtime by following a three-phase approach:
1. **Expand:** Add new schema elements alongside existing ones
2. **Migrate:** Dual-write to both old and new schema during transition
3. **Contract:** Remove old schema elements after validation
#### Implementation Steps
```sql
-- Phase 1: Expand
ALTER TABLE users ADD COLUMN email_new VARCHAR(255);
CREATE INDEX CONCURRENTLY idx_users_email_new ON users(email_new);
-- Phase 2: Migrate (Application Code)
-- Write to both columns during transition period
INSERT INTO users (name, email, email_new) VALUES (?, ?, ?);
-- Backfill existing data
UPDATE users SET email_new = email WHERE email_new IS NULL;
-- Phase 3: Contract (after validation)
ALTER TABLE users DROP COLUMN email;
ALTER TABLE users RENAME COLUMN email_new TO email;
```
#### Pros and Cons
**Pros:**
- Zero downtime deployments
- Safe rollback at any point
- Gradual transition with validation
**Cons:**
- Increased storage during transition
- More complex application logic
- Extended migration timeline
### 2. Parallel Schema Pattern
**Use Case:** Major database restructuring
**Complexity:** High
**Risk Level:** Medium
#### Description
Run new and old schemas in parallel, using feature flags to gradually route traffic to the new schema while maintaining the ability to rollback quickly.
#### Implementation Example
```python
class DatabaseRouter:
def __init__(self, feature_flag_service):
self.feature_flags = feature_flag_service
self.old_db = OldDatabaseConnection()
self.new_db = NewDatabaseConnection()
def route_query(self, user_id, query_type):
if self.feature_flags.is_enabled("new_schema", user_id):
return self.new_db.execute(query_type)
else:
return self.old_db.execute(query_type)
def dual_write(self, data):
# Write to both databases for consistency
success_old = self.old_db.write(data)
success_new = self.new_db.write(transform_data(data))
if not (success_old and success_new):
# Handle partial failures
self.handle_dual_write_failure(data, success_old, success_new)
```
#### Best Practices
- Implement data consistency checks between schemas
- Use circuit breakers for automatic failover
- Monitor performance impact of dual writes
- Plan for data reconciliation processes
### 3. Event Sourcing Migration
**Use Case:** Migrating systems with complex business logic
**Complexity:** High
**Risk Level:** Medium-High
#### Description
Capture all changes as events during migration, enabling replay and reconciliation capabilities.
#### Event Store Schema
```sql
CREATE TABLE migration_events (
event_id UUID PRIMARY KEY,
aggregate_id UUID NOT NULL,
event_type VARCHAR(100) NOT NULL,
event_data JSONB NOT NULL,
event_version INTEGER NOT NULL,
occurred_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
processed_at TIMESTAMP WITH TIME ZONE
);
```
#### Migration Event Handler
```python
class MigrationEventHandler:
def __init__(self, old_store, new_store):
self.old_store = old_store
self.new_store = new_store
self.event_log = []
def handle_update(self, entity_id, old_data, new_data):
# Log the change as an event
event = MigrationEvent(
entity_id=entity_id,
event_type="entity_migrated",
old_data=old_data,
new_data=new_data,
timestamp=datetime.now()
)
self.event_log.append(event)
# Apply to new store
success = self.new_store.update(entity_id, new_data)
if not success:
# Mark for retry
event.status = "failed"
self.schedule_retry(event)
return success
def replay_events(self, from_timestamp=None):
"""Replay events for reconciliation"""
events = self.get_events_since(from_timestamp)
for event in events:
self.apply_event(event)
```
## Service Migration Patterns
### 1. Strangler Fig Pattern
**Use Case:** Legacy system replacement
**Complexity:** Medium-High
**Risk Level:** Medium
#### Description
Gradually replace legacy functionality by intercepting calls and routing them to new services, eventually "strangling" the legacy system.
#### Implementation Architecture
```yaml
# API Gateway Configuration
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: user-service-migration
spec:
http:
- match:
- headers:
migration-flag:
exact: "new"
route:
- destination:
host: user-service-v2
- route:
- destination:
host: user-service-v1
```
#### Strangler Proxy Implementation
```python
class StranglerProxy:
def __init__(self):
self.legacy_service = LegacyUserService()
self.new_service = NewUserService()
self.feature_flags = FeatureFlagService()
def handle_request(self, request):
route = self.determine_route(request)
if route == "new":
return self.handle_with_new_service(request)
elif route == "both":
return self.handle_with_both_services(request)
else:
return self.handle_with_legacy_service(request)
def determine_route(self, request):
user_id = request.get('user_id')
if self.feature_flags.is_enabled("new_user_service", user_id):
if self.feature_flags.is_enabled("dual_write", user_id):
return "both"
else:
return "new"
else:
return "legacy"
```
### 2. Parallel Run Pattern
**Use Case:** Risk mitigation for critical services
**Complexity:** Medium
**Risk Level:** Low-Medium
#### Description
Run both old and new services simultaneously, comparing outputs to validate correctness before switching traffic.
#### Implementation
```python
class ParallelRunManager:
def __init__(self):
self.primary_service = PrimaryService()
self.candidate_service = CandidateService()
self.comparator = ResponseComparator()
self.metrics = MetricsCollector()
async def parallel_execute(self, request):
# Execute both services concurrently
primary_task = asyncio.create_task(
self.primary_service.process(request)
)
candidate_task = asyncio.create_task(
self.candidate_service.process(request)
)
# Always wait for primary
primary_result = await primary_task
try:
# Wait for candidate with timeout
candidate_result = await asyncio.wait_for(
candidate_task, timeout=5.0
)
# Compare results
comparison = self.comparator.compare(
primary_result, candidate_result
)
# Record metrics
self.metrics.record_comparison(comparison)
except asyncio.TimeoutError:
self.metrics.record_timeout("candidate")
except Exception as e:
self.metrics.record_error("candidate", str(e))
# Always return primary result
return primary_result
```
### 3. Blue-Green Deployment Pattern
**Use Case:** Zero-downtime service updates
**Complexity:** Low-Medium
**Risk Level:** Low
#### Description
Maintain two identical production environments (blue and green), switching traffic between them for deployments.
#### Kubernetes Implementation
```yaml
# Blue Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-blue
labels:
version: blue
spec:
replicas: 3
selector:
matchLabels:
app: myapp
version: blue
template:
metadata:
labels:
app: myapp
version: blue
spec:
containers:
- name: app
image: myapp:v1.0.0
---
# Green Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-green
labels:
version: green
spec:
replicas: 3
selector:
matchLabels:
app: myapp
version: green
template:
metadata:
labels:
app: myapp
version: green
spec:
containers:
- name: app
image: myapp:v2.0.0
---
# Service (switches between blue and green)
apiVersion: v1
kind: Service
metadata:
name: app-service
spec:
selector:
app: myapp
version: blue # Change to green for deployment
ports:
- port: 80
targetPort: 8080
```
## Infrastructure Migration Patterns
### 1. Lift and Shift Pattern
**Use Case:** Quick cloud migration with minimal changes
**Complexity:** Low-Medium
**Risk Level:** Low
#### Description
Migrate applications to cloud infrastructure with minimal or no code changes, focusing on infrastructure compatibility.
#### Migration Checklist
```yaml
Pre-Migration Assessment:
- inventory_current_infrastructure:
- servers_and_specifications
- network_configuration
- storage_requirements
- security_configurations
- identify_dependencies:
- database_connections
- external_service_integrations
- file_system_dependencies
- assess_compatibility:
- operating_system_versions
- runtime_dependencies
- license_requirements
Migration Execution:
- provision_target_infrastructure:
- compute_instances
- storage_volumes
- network_configuration
- security_groups
- migrate_data:
- database_backup_restore
- file_system_replication
- configuration_files
- update_configurations:
- connection_strings
- environment_variables
- dns_records
- validate_functionality:
- application_health_checks
- end_to_end_testing
- performance_validation
```
### 2. Hybrid Cloud Migration
**Use Case:** Gradual cloud adoption with on-premises integration
**Complexity:** High
**Risk Level:** Medium-High
#### Description
Maintain some components on-premises while migrating others to cloud, requiring secure connectivity and data synchronization.
#### Network Architecture
```hcl
# Terraform configuration for hybrid connectivity
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
}
resource "aws_vpn_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "hybrid-vpn-gateway"
}
}
resource "aws_customer_gateway" "main" {
bgp_asn = 65000
ip_address = var.on_premises_public_ip
type = "ipsec.1"
tags = {
Name = "on-premises-gateway"
}
}
resource "aws_vpn_connection" "main" {
vpn_gateway_id = aws_vpn_gateway.main.id
customer_gateway_id = aws_customer_gateway.main.id
type = "ipsec.1"
static_routes_only = true
}
```
#### Data Synchronization Pattern
```python
class HybridDataSync:
def __init__(self):
self.on_prem_db = OnPremiseDatabase()
self.cloud_db = CloudDatabase()
self.sync_log = SyncLogManager()
async def bidirectional_sync(self):
"""Synchronize data between on-premises and cloud"""
# Get last sync timestamp
last_sync = self.sync_log.get_last_sync_time()
# Sync on-prem changes to cloud
on_prem_changes = self.on_prem_db.get_changes_since(last_sync)
for change in on_prem_changes:
await self.apply_change_to_cloud(change)
# Sync cloud changes to on-prem
cloud_changes = self.cloud_db.get_changes_since(last_sync)
for change in cloud_changes:
await self.apply_change_to_on_prem(change)
# Handle conflicts
conflicts = self.detect_conflicts(on_prem_changes, cloud_changes)
for conflict in conflicts:
await self.resolve_conflict(conflict)
# Update sync timestamp
self.sync_log.record_sync_completion()
async def apply_change_to_cloud(self, change):
"""Apply on-premises change to cloud database"""
try:
if change.operation == "INSERT":
await self.cloud_db.insert(change.table, change.data)
elif change.operation == "UPDATE":
await self.cloud_db.update(change.table, change.key, change.data)
elif change.operation == "DELETE":
await self.cloud_db.delete(change.table, change.key)
self.sync_log.record_success(change.id, "cloud")
except Exception as e:
self.sync_log.record_failure(change.id, "cloud", str(e))
raise
```
### 3. Multi-Cloud Migration
**Use Case:** Avoiding vendor lock-in or regulatory requirements
**Complexity:** Very High
**Risk Level:** High
#### Description
Distribute workloads across multiple cloud providers for resilience, compliance, or cost optimization.
#### Service Mesh Configuration
```yaml
# Istio configuration for multi-cloud service mesh
apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
name: aws-service
spec:
hosts:
- aws-service.company.com
ports:
- number: 443
name: https
protocol: HTTPS
location: MESH_EXTERNAL
resolution: DNS
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: multi-cloud-routing
spec:
hosts:
- user-service
http:
- match:
- headers:
region:
exact: "us-east"
route:
- destination:
host: aws-service.company.com
weight: 100
- match:
- headers:
region:
exact: "eu-west"
route:
- destination:
host: gcp-service.company.com
weight: 100
- route: # Default routing
- destination:
host: user-service
subset: local
weight: 80
- destination:
host: aws-service.company.com
weight: 20
```
## Feature Flag Patterns
### 1. Progressive Rollout Pattern
**Use Case:** Gradual feature deployment with risk mitigation
**Implementation:**
```python
class ProgressiveRollout:
def __init__(self, feature_name):
self.feature_name = feature_name
self.rollout_percentage = 0
self.user_buckets = {}
def is_enabled_for_user(self, user_id):
# Consistent user bucketing
user_hash = hashlib.md5(f"{self.feature_name}:{user_id}".encode()).hexdigest()
bucket = int(user_hash, 16) % 100
return bucket < self.rollout_percentage
def increase_rollout(self, target_percentage, step_size=10):
"""Gradually increase rollout percentage"""
while self.rollout_percentage < target_percentage:
self.rollout_percentage = min(
self.rollout_percentage + step_size,
target_percentage
)
# Monitor metrics before next increase
yield self.rollout_percentage
time.sleep(300) # Wait 5 minutes between increases
```
### 2. Circuit Breaker Pattern
**Use Case:** Automatic fallback during migration issues
```python
class MigrationCircuitBreaker:
def __init__(self, failure_threshold=5, timeout=60):
self.failure_count = 0
self.failure_threshold = failure_threshold
self.timeout = timeout
self.last_failure_time = None
self.state = 'CLOSED' # CLOSED, OPEN, HALF_OPEN
def call_new_service(self, request):
if self.state == 'OPEN':
if self.should_attempt_reset():
self.state = 'HALF_OPEN'
else:
return self.fallback_to_legacy(request)
try:
response = self.new_service.process(request)
self.on_success()
return response
except Exception as e:
self.on_failure()
return self.fallback_to_legacy(request)
def on_success(self):
self.failure_count = 0
self.state = 'CLOSED'
def on_failure(self):
self.failure_count += 1
self.last_failure_time = time.time()
if self.failure_count >= self.failure_threshold:
self.state = 'OPEN'
def should_attempt_reset(self):
return (time.time() - self.last_failure_time) >= self.timeout
```
## Migration Anti-Patterns
### 1. Big Bang Migration (Anti-Pattern)
**Why to Avoid:**
- High risk of complete system failure
- Difficult to rollback
- Extended downtime
- All-or-nothing deployment
**Better Alternative:** Use incremental migration patterns like Strangler Fig or Parallel Run.
### 2. No Rollback Plan (Anti-Pattern)
**Why to Avoid:**
- Cannot recover from failures
- Increases business risk
- Panic-driven decisions during issues
**Better Alternative:** Always implement comprehensive rollback procedures before migration.
### 3. Insufficient Testing (Anti-Pattern)
**Why to Avoid:**
- Unknown compatibility issues
- Performance degradation
- Data corruption risks
**Better Alternative:** Implement comprehensive testing at each migration phase.
## Pattern Selection Matrix
| Migration Type | Complexity | Downtime Tolerance | Recommended Pattern |
|---------------|------------|-------------------|-------------------|
| Schema Change | Low | Zero | Expand-Contract |
| Schema Change | High | Zero | Parallel Schema |
| Service Replace | Medium | Zero | Strangler Fig |
| Service Update | Low | Zero | Blue-Green |
| Data Migration | High | Some | Event Sourcing |
| Infrastructure | Low | Some | Lift and Shift |
| Infrastructure | High | Zero | Hybrid Cloud |
## Success Metrics
### Technical Metrics
- Migration completion rate
- System availability during migration
- Performance impact (response time, throughput)
- Error rate changes
- Rollback execution time
### Business Metrics
- Customer impact score
- Revenue protection
- Time to value realization
- Stakeholder satisfaction
### Operational Metrics
- Team efficiency
- Knowledge transfer effectiveness
- Post-migration support requirements
- Documentation completeness
## Lessons Learned
### Common Pitfalls
1. **Underestimating data dependencies** - Always map all data relationships
2. **Insufficient monitoring** - Implement comprehensive observability before migration
3. **Poor communication** - Keep all stakeholders informed throughout the process
4. **Rushed timelines** - Allow adequate time for testing and validation
5. **Ignoring performance impact** - Benchmark before and after migration
### Best Practices
1. **Start with low-risk migrations** - Build confidence and experience
2. **Automate everything possible** - Reduce human error and increase repeatability
3. **Test rollback procedures** - Ensure you can recover from any failure
4. **Monitor continuously** - Use real-time dashboards and alerting
5. **Document everything** - Create comprehensive runbooks and documentation
This catalog serves as a reference for selecting appropriate migration patterns based on specific requirements, risk tolerance, and technical constraints.
FILE:references/zero_downtime_techniques.md
# Zero-Downtime Migration Techniques
## Overview
Zero-downtime migrations are critical for maintaining business continuity and user experience during system changes. This guide provides comprehensive techniques, patterns, and implementation strategies for achieving true zero-downtime migrations across different system components.
## Core Principles
### 1. Backward Compatibility
Every change must be backward compatible until all clients have migrated to the new version.
### 2. Incremental Changes
Break large changes into smaller, independent increments that can be deployed and validated separately.
### 3. Feature Flags
Use feature toggles to control the rollout of new functionality without code deployments.
### 4. Graceful Degradation
Ensure systems continue to function even when some components are unavailable or degraded.
## Database Zero-Downtime Techniques
### Schema Evolution Without Downtime
#### 1. Additive Changes Only
**Principle:** Only add new elements; never remove or modify existing ones directly.
```sql
-- ✅ Good: Additive change
ALTER TABLE users ADD COLUMN middle_name VARCHAR(50);
-- ❌ Bad: Breaking change
ALTER TABLE users DROP COLUMN email;
```
#### 2. Multi-Phase Schema Evolution
**Phase 1: Expand**
```sql
-- Add new column alongside existing one
ALTER TABLE users ADD COLUMN email_address VARCHAR(255);
-- Add index concurrently (PostgreSQL)
CREATE INDEX CONCURRENTLY idx_users_email_address ON users(email_address);
```
**Phase 2: Dual Write (Application Code)**
```python
class UserService:
def create_user(self, name, email):
# Write to both old and new columns
user = User(
name=name,
email=email, # Old column
email_address=email # New column
)
return user.save()
def update_email(self, user_id, new_email):
# Update both columns
user = User.objects.get(id=user_id)
user.email = new_email
user.email_address = new_email
user.save()
return user
```
**Phase 3: Backfill Data**
```sql
-- Backfill existing data (in batches)
UPDATE users
SET email_address = email
WHERE email_address IS NULL
AND id BETWEEN ? AND ?;
```
**Phase 4: Switch Reads**
```python
class UserService:
def get_user_email(self, user_id):
user = User.objects.get(id=user_id)
# Switch to reading from new column
return user.email_address or user.email
```
**Phase 5: Contract**
```sql
-- After validation, remove old column
ALTER TABLE users DROP COLUMN email;
-- Rename new column if needed
ALTER TABLE users RENAME COLUMN email_address TO email;
```
### 3. Online Schema Changes
#### PostgreSQL Techniques
```sql
-- Safe column addition
ALTER TABLE orders ADD COLUMN status_new VARCHAR(20) DEFAULT 'pending';
-- Safe index creation
CREATE INDEX CONCURRENTLY idx_orders_status_new ON orders(status_new);
-- Safe constraint addition (after data validation)
ALTER TABLE orders ADD CONSTRAINT check_status_new
CHECK (status_new IN ('pending', 'processing', 'completed', 'cancelled'));
```
#### MySQL Techniques
```sql
-- Use pt-online-schema-change for large tables
pt-online-schema-change \
--alter "ADD COLUMN status VARCHAR(20) DEFAULT 'pending'" \
--execute \
D=mydb,t=orders
-- Online DDL (MySQL 5.6+)
ALTER TABLE orders
ADD COLUMN priority INT DEFAULT 1,
ALGORITHM=INPLACE,
LOCK=NONE;
```
### 4. Data Migration Strategies
#### Chunked Data Migration
```python
class DataMigrator:
def __init__(self, source_table, target_table, chunk_size=1000):
self.source_table = source_table
self.target_table = target_table
self.chunk_size = chunk_size
def migrate_data(self):
last_id = 0
total_migrated = 0
while True:
# Get next chunk
chunk = self.get_chunk(last_id, self.chunk_size)
if not chunk:
break
# Transform and migrate chunk
for record in chunk:
transformed = self.transform_record(record)
self.insert_or_update(transformed)
last_id = chunk[-1]['id']
total_migrated += len(chunk)
# Brief pause to avoid overwhelming the database
time.sleep(0.1)
self.log_progress(total_migrated)
return total_migrated
def get_chunk(self, last_id, limit):
return db.execute(f"""
SELECT * FROM {self.source_table}
WHERE id > %s
ORDER BY id
LIMIT %s
""", (last_id, limit))
```
#### Change Data Capture (CDC)
```python
class CDCProcessor:
def __init__(self):
self.kafka_consumer = KafkaConsumer('db_changes')
self.target_db = TargetDatabase()
def process_changes(self):
for message in self.kafka_consumer:
change = json.loads(message.value)
if change['operation'] == 'INSERT':
self.handle_insert(change)
elif change['operation'] == 'UPDATE':
self.handle_update(change)
elif change['operation'] == 'DELETE':
self.handle_delete(change)
def handle_insert(self, change):
transformed_data = self.transform_data(change['after'])
self.target_db.insert(change['table'], transformed_data)
def handle_update(self, change):
key = change['key']
transformed_data = self.transform_data(change['after'])
self.target_db.update(change['table'], key, transformed_data)
```
## Application Zero-Downtime Techniques
### 1. Blue-Green Deployments
#### Infrastructure Setup
```yaml
# Blue Environment (Current Production)
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-blue
labels:
version: blue
app: myapp
spec:
replicas: 3
selector:
matchLabels:
app: myapp
version: blue
template:
metadata:
labels:
app: myapp
version: blue
spec:
containers:
- name: app
image: myapp:1.0.0
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 15
periodSeconds: 10
---
# Green Environment (New Version)
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-green
labels:
version: green
app: myapp
spec:
replicas: 3
selector:
matchLabels:
app: myapp
version: green
template:
metadata:
labels:
app: myapp
version: green
spec:
containers:
- name: app
image: myapp:2.0.0
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
```
#### Service Switching
```yaml
# Service (switches between blue and green)
apiVersion: v1
kind: Service
metadata:
name: app-service
spec:
selector:
app: myapp
version: blue # Switch to 'green' for deployment
ports:
- port: 80
targetPort: 8080
type: LoadBalancer
```
#### Automated Deployment Script
```bash
#!/bin/bash
# Blue-Green Deployment Script
NAMESPACE="production"
APP_NAME="myapp"
NEW_IMAGE="myapp:2.0.0"
# Determine current and target environments
CURRENT_VERSION=$(kubectl get service $APP_NAME-service -o jsonpath='{.spec.selector.version}')
if [ "$CURRENT_VERSION" = "blue" ]; then
TARGET_VERSION="green"
else
TARGET_VERSION="blue"
fi
echo "Current version: $CURRENT_VERSION"
echo "Target version: $TARGET_VERSION"
# Update target environment with new image
kubectl set image deployment/$APP_NAME-$TARGET_VERSION app=$NEW_IMAGE
# Wait for rollout to complete
kubectl rollout status deployment/$APP_NAME-$TARGET_VERSION --timeout=300s
# Run health checks
echo "Running health checks..."
TARGET_IP=$(kubectl get service $APP_NAME-$TARGET_VERSION -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
for i in {1..30}; do
if curl -f http://$TARGET_IP/health; then
echo "Health check passed"
break
fi
if [ $i -eq 30 ]; then
echo "Health check failed after 30 attempts"
exit 1
fi
sleep 2
done
# Switch traffic to new version
kubectl patch service $APP_NAME-service -p '{"spec":{"selector":{"version":"'$TARGET_VERSION'"}}}'
echo "Traffic switched to $TARGET_VERSION"
# Monitor for 5 minutes
echo "Monitoring new version..."
sleep 300
# Check if rollback is needed
ERROR_RATE=$(curl -s "http://monitoring.company.com/api/error_rate?service=$APP_NAME" | jq '.error_rate')
if (( $(echo "$ERROR_RATE > 0.05" | bc -l) )); then
echo "Error rate too high ($ERROR_RATE), rolling back..."
kubectl patch service $APP_NAME-service -p '{"spec":{"selector":{"version":"'$CURRENT_VERSION'"}}}'
exit 1
fi
echo "Deployment successful!"
```
### 2. Canary Deployments
#### Progressive Canary with Istio
```yaml
# Destination Rule
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: myapp-destination
spec:
host: myapp
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
---
# Virtual Service for Canary
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: myapp-canary
spec:
hosts:
- myapp
http:
- match:
- headers:
canary:
exact: "true"
route:
- destination:
host: myapp
subset: v2
- route:
- destination:
host: myapp
subset: v1
weight: 95
- destination:
host: myapp
subset: v2
weight: 5
```
#### Automated Canary Controller
```python
class CanaryController:
def __init__(self, istio_client, prometheus_client):
self.istio = istio_client
self.prometheus = prometheus_client
self.canary_weight = 5
self.max_weight = 100
self.weight_increment = 5
self.validation_window = 300 # 5 minutes
async def deploy_canary(self, app_name, new_version):
"""Deploy new version using canary strategy"""
# Start with small percentage
await self.update_traffic_split(app_name, self.canary_weight)
while self.canary_weight < self.max_weight:
# Monitor metrics for validation window
await asyncio.sleep(self.validation_window)
# Check canary health
if not await self.is_canary_healthy(app_name, new_version):
await self.rollback_canary(app_name)
raise Exception("Canary deployment failed health checks")
# Increase traffic to canary
self.canary_weight = min(
self.canary_weight + self.weight_increment,
self.max_weight
)
await self.update_traffic_split(app_name, self.canary_weight)
print(f"Canary traffic increased to {self.canary_weight}%")
print("Canary deployment completed successfully")
async def is_canary_healthy(self, app_name, version):
"""Check if canary version is healthy"""
# Check error rate
error_rate = await self.prometheus.query(
f'rate(http_requests_total{{app="{app_name}", version="{version}", status=~"5.."}}'
f'[5m]) / rate(http_requests_total{{app="{app_name}", version="{version}"}}[5m])'
)
if error_rate > 0.05: # 5% error rate threshold
return False
# Check response time
p95_latency = await self.prometheus.query(
f'histogram_quantile(0.95, rate(http_request_duration_seconds_bucket'
f'{{app="{app_name}", version="{version}"}}[5m]))'
)
if p95_latency > 2.0: # 2 second p95 threshold
return False
return True
async def update_traffic_split(self, app_name, canary_weight):
"""Update Istio virtual service with new traffic split"""
stable_weight = 100 - canary_weight
virtual_service = {
"apiVersion": "networking.istio.io/v1beta1",
"kind": "VirtualService",
"metadata": {"name": f"{app_name}-canary"},
"spec": {
"hosts": [app_name],
"http": [{
"route": [
{
"destination": {"host": app_name, "subset": "stable"},
"weight": stable_weight
},
{
"destination": {"host": app_name, "subset": "canary"},
"weight": canary_weight
}
]
}]
}
}
await self.istio.apply_virtual_service(virtual_service)
```
### 3. Rolling Updates
#### Kubernetes Rolling Update Strategy
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: rolling-update-app
spec:
replicas: 10
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 2 # Can have 2 extra pods during update
maxUnavailable: 1 # At most 1 pod can be unavailable
selector:
matchLabels:
app: rolling-update-app
template:
metadata:
labels:
app: rolling-update-app
spec:
containers:
- name: app
image: myapp:2.0.0
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 2
timeoutSeconds: 1
successThreshold: 1
failureThreshold: 3
livenessProbe:
httpGet:
path: /live
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
```
#### Custom Rolling Update Controller
```python
class RollingUpdateController:
def __init__(self, k8s_client):
self.k8s = k8s_client
self.max_surge = 2
self.max_unavailable = 1
async def rolling_update(self, deployment_name, new_image):
"""Perform rolling update with custom logic"""
deployment = await self.k8s.get_deployment(deployment_name)
total_replicas = deployment.spec.replicas
# Calculate batch size
batch_size = min(self.max_surge, total_replicas // 5) # Update 20% at a time
updated_pods = []
for i in range(0, total_replicas, batch_size):
batch_end = min(i + batch_size, total_replicas)
# Update batch of pods
for pod_index in range(i, batch_end):
old_pod = await self.get_pod_by_index(deployment_name, pod_index)
# Create new pod with new image
new_pod = await self.create_updated_pod(old_pod, new_image)
# Wait for new pod to be ready
await self.wait_for_pod_ready(new_pod.metadata.name)
# Remove old pod
await self.k8s.delete_pod(old_pod.metadata.name)
updated_pods.append(new_pod)
# Brief pause between pod updates
await asyncio.sleep(2)
# Validate batch health before continuing
if not await self.validate_batch_health(updated_pods[-batch_size:]):
# Rollback batch
await self.rollback_batch(updated_pods[-batch_size:])
raise Exception("Rolling update failed validation")
print(f"Updated {batch_end}/{total_replicas} pods")
print("Rolling update completed successfully")
```
## Load Balancer and Traffic Management
### 1. Weighted Routing
#### NGINX Configuration
```nginx
upstream backend {
# Old version - 80% traffic
server old-app-1:8080 weight=4;
server old-app-2:8080 weight=4;
# New version - 20% traffic
server new-app-1:8080 weight=1;
server new-app-2:8080 weight=1;
}
server {
listen 80;
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Health check headers
proxy_set_header X-Health-Check-Timeout 5s;
}
}
```
#### HAProxy Configuration
```haproxy
backend app_servers
balance roundrobin
option httpchk GET /health
# Old version servers
server old-app-1 old-app-1:8080 check weight 80
server old-app-2 old-app-2:8080 check weight 80
# New version servers
server new-app-1 new-app-1:8080 check weight 20
server new-app-2 new-app-2:8080 check weight 20
frontend app_frontend
bind *:80
default_backend app_servers
# Custom health check endpoint
acl health_check path_beg /health
http-request return status 200 content-type text/plain string "OK" if health_check
```
### 2. Circuit Breaker Implementation
```python
class CircuitBreaker:
def __init__(self, failure_threshold=5, recovery_timeout=60, expected_exception=Exception):
self.failure_threshold = failure_threshold
self.recovery_timeout = recovery_timeout
self.expected_exception = expected_exception
self.failure_count = 0
self.last_failure_time = None
self.state = 'CLOSED' # CLOSED, OPEN, HALF_OPEN
def call(self, func, *args, **kwargs):
"""Execute function with circuit breaker protection"""
if self.state == 'OPEN':
if self._should_attempt_reset():
self.state = 'HALF_OPEN'
else:
raise CircuitBreakerOpenException("Circuit breaker is OPEN")
try:
result = func(*args, **kwargs)
self._on_success()
return result
except self.expected_exception as e:
self._on_failure()
raise
def _should_attempt_reset(self):
return (
self.last_failure_time and
time.time() - self.last_failure_time >= self.recovery_timeout
)
def _on_success(self):
self.failure_count = 0
self.state = 'CLOSED'
def _on_failure(self):
self.failure_count += 1
self.last_failure_time = time.time()
if self.failure_count >= self.failure_threshold:
self.state = 'OPEN'
# Usage with service migration
@CircuitBreaker(failure_threshold=3, recovery_timeout=30)
def call_new_service(request):
return new_service.process(request)
def handle_request(request):
try:
return call_new_service(request)
except CircuitBreakerOpenException:
# Fallback to old service
return old_service.process(request)
```
## Monitoring and Validation
### 1. Health Check Implementation
```python
class HealthChecker:
def __init__(self):
self.checks = []
def add_check(self, name, check_func, timeout=5):
self.checks.append({
'name': name,
'func': check_func,
'timeout': timeout
})
async def run_checks(self):
"""Run all health checks and return status"""
results = {}
overall_status = 'healthy'
for check in self.checks:
try:
result = await asyncio.wait_for(
check['func'](),
timeout=check['timeout']
)
results[check['name']] = {
'status': 'healthy',
'result': result
}
except asyncio.TimeoutError:
results[check['name']] = {
'status': 'unhealthy',
'error': 'timeout'
}
overall_status = 'unhealthy'
except Exception as e:
results[check['name']] = {
'status': 'unhealthy',
'error': str(e)
}
overall_status = 'unhealthy'
return {
'status': overall_status,
'checks': results,
'timestamp': datetime.utcnow().isoformat()
}
# Example health checks
health_checker = HealthChecker()
async def database_check():
"""Check database connectivity"""
result = await db.execute("SELECT 1")
return result is not None
async def external_api_check():
"""Check external API availability"""
response = await http_client.get("https://api.example.com/health")
return response.status_code == 200
async def memory_check():
"""Check memory usage"""
memory_usage = psutil.virtual_memory().percent
if memory_usage > 90:
raise Exception(f"Memory usage too high: {memory_usage}%")
return f"Memory usage: {memory_usage}%"
health_checker.add_check("database", database_check)
health_checker.add_check("external_api", external_api_check)
health_checker.add_check("memory", memory_check)
```
### 2. Readiness vs Liveness Probes
```yaml
# Kubernetes Pod with proper health checks
apiVersion: v1
kind: Pod
metadata:
name: app-pod
spec:
containers:
- name: app
image: myapp:2.0.0
ports:
- containerPort: 8080
# Readiness probe - determines if pod should receive traffic
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 3
timeoutSeconds: 2
successThreshold: 1
failureThreshold: 3
# Liveness probe - determines if pod should be restarted
livenessProbe:
httpGet:
path: /live
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
successThreshold: 1
failureThreshold: 3
# Startup probe - gives app time to start before other probes
startupProbe:
httpGet:
path: /startup
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
successThreshold: 1
failureThreshold: 30 # Allow up to 150 seconds for startup
```
### 3. Metrics and Alerting
```python
class MigrationMetrics:
def __init__(self, prometheus_client):
self.prometheus = prometheus_client
# Define custom metrics
self.migration_progress = Counter(
'migration_progress_total',
'Total migration operations completed',
['operation', 'status']
)
self.migration_duration = Histogram(
'migration_operation_duration_seconds',
'Time spent on migration operations',
['operation']
)
self.system_health = Gauge(
'system_health_score',
'Overall system health score (0-1)',
['component']
)
self.traffic_split = Gauge(
'traffic_split_percentage',
'Percentage of traffic going to each version',
['version']
)
def record_migration_step(self, operation, status, duration=None):
"""Record completion of a migration step"""
self.migration_progress.labels(operation=operation, status=status).inc()
if duration:
self.migration_duration.labels(operation=operation).observe(duration)
def update_health_score(self, component, score):
"""Update health score for a component"""
self.system_health.labels(component=component).set(score)
def update_traffic_split(self, version_weights):
"""Update traffic split metrics"""
for version, weight in version_weights.items():
self.traffic_split.labels(version=version).set(weight)
# Usage in migration
metrics = MigrationMetrics(prometheus_client)
def perform_migration_step(operation):
start_time = time.time()
try:
# Perform migration operation
result = execute_migration_operation(operation)
# Record success
duration = time.time() - start_time
metrics.record_migration_step(operation, 'success', duration)
return result
except Exception as e:
# Record failure
duration = time.time() - start_time
metrics.record_migration_step(operation, 'failure', duration)
raise
```
## Rollback Strategies
### 1. Immediate Rollback Triggers
```python
class AutoRollbackSystem:
def __init__(self, metrics_client, deployment_client):
self.metrics = metrics_client
self.deployment = deployment_client
self.rollback_triggers = {
'error_rate_spike': {
'threshold': 0.05, # 5% error rate
'window': 300, # 5 minutes
'auto_rollback': True
},
'latency_increase': {
'threshold': 2.0, # 2x baseline latency
'window': 600, # 10 minutes
'auto_rollback': False # Manual confirmation required
},
'availability_drop': {
'threshold': 0.95, # Below 95% availability
'window': 120, # 2 minutes
'auto_rollback': True
}
}
async def monitor_and_rollback(self, deployment_name):
"""Monitor deployment and trigger rollback if needed"""
while True:
for trigger_name, config in self.rollback_triggers.items():
if await self.check_trigger(trigger_name, config):
if config['auto_rollback']:
await self.execute_rollback(deployment_name, trigger_name)
else:
await self.alert_for_manual_rollback(deployment_name, trigger_name)
await asyncio.sleep(30) # Check every 30 seconds
async def check_trigger(self, trigger_name, config):
"""Check if rollback trigger condition is met"""
current_value = await self.metrics.get_current_value(trigger_name)
baseline_value = await self.metrics.get_baseline_value(trigger_name)
if trigger_name == 'error_rate_spike':
return current_value > config['threshold']
elif trigger_name == 'latency_increase':
return current_value > baseline_value * config['threshold']
elif trigger_name == 'availability_drop':
return current_value < config['threshold']
return False
async def execute_rollback(self, deployment_name, reason):
"""Execute automatic rollback"""
print(f"Executing automatic rollback for {deployment_name}. Reason: {reason}")
# Get previous revision
previous_revision = await self.deployment.get_previous_revision(deployment_name)
# Perform rollback
await self.deployment.rollback_to_revision(deployment_name, previous_revision)
# Notify stakeholders
await self.notify_rollback_executed(deployment_name, reason)
```
### 2. Data Rollback Strategies
```sql
-- Point-in-time recovery setup
-- Create restore point before migration
SELECT pg_create_restore_point('pre_migration_' || to_char(now(), 'YYYYMMDD_HH24MISS'));
-- Rollback using point-in-time recovery
-- (This would be executed on a separate recovery instance)
-- recovery.conf:
-- recovery_target_name = 'pre_migration_20240101_120000'
-- recovery_target_action = 'promote'
```
```python
class DataRollbackManager:
def __init__(self, database_client, backup_service):
self.db = database_client
self.backup = backup_service
async def create_rollback_point(self, migration_id):
"""Create a rollback point before migration"""
rollback_point = {
'migration_id': migration_id,
'timestamp': datetime.utcnow(),
'backup_location': None,
'schema_snapshot': None
}
# Create database backup
backup_path = await self.backup.create_backup(
f"pre_migration_{migration_id}_{int(time.time())}"
)
rollback_point['backup_location'] = backup_path
# Capture schema snapshot
schema_snapshot = await self.capture_schema_snapshot()
rollback_point['schema_snapshot'] = schema_snapshot
# Store rollback point metadata
await self.store_rollback_metadata(rollback_point)
return rollback_point
async def execute_rollback(self, migration_id):
"""Execute data rollback to specified point"""
rollback_point = await self.get_rollback_metadata(migration_id)
if not rollback_point:
raise Exception(f"No rollback point found for migration {migration_id}")
# Stop application traffic
await self.stop_application_traffic()
try:
# Restore from backup
await self.backup.restore_from_backup(
rollback_point['backup_location']
)
# Validate data integrity
await self.validate_data_integrity(
rollback_point['schema_snapshot']
)
# Update application configuration
await self.update_application_config(rollback_point)
# Resume application traffic
await self.resume_application_traffic()
print(f"Data rollback completed successfully for migration {migration_id}")
except Exception as e:
# If rollback fails, we have a serious problem
await self.escalate_rollback_failure(migration_id, str(e))
raise
```
## Best Practices Summary
### 1. Pre-Migration Checklist
- [ ] Comprehensive backup strategy in place
- [ ] Rollback procedures tested in staging
- [ ] Monitoring and alerting configured
- [ ] Health checks implemented
- [ ] Feature flags configured
- [ ] Team communication plan established
- [ ] Load balancer configuration prepared
- [ ] Database connection pooling optimized
### 2. During Migration
- [ ] Monitor key metrics continuously
- [ ] Validate each phase before proceeding
- [ ] Maintain detailed logs of all actions
- [ ] Keep stakeholders informed of progress
- [ ] Have rollback trigger ready
- [ ] Monitor user experience metrics
- [ ] Watch for performance degradation
- [ ] Validate data consistency
### 3. Post-Migration
- [ ] Continue monitoring for 24-48 hours
- [ ] Validate all business processes
- [ ] Update documentation
- [ ] Conduct post-migration retrospective
- [ ] Archive migration artifacts
- [ ] Update disaster recovery procedures
- [ ] Plan for legacy system decommissioning
### 4. Common Pitfalls to Avoid
- Don't skip testing rollback procedures
- Don't ignore performance impact
- Don't rush through validation phases
- Don't forget to communicate with stakeholders
- Don't assume health checks are sufficient
- Don't neglect data consistency validation
- Don't underestimate time requirements
- Don't overlook dependency impacts
This comprehensive guide provides the foundation for implementing zero-downtime migrations across various system components while maintaining high availability and data integrity.
FILE:scripts/compatibility_checker.py
#!/usr/bin/env python3
"""
Compatibility Checker - Analyze schema and API compatibility between versions
This tool analyzes schema and API changes between versions and identifies backward
compatibility issues including breaking changes, data type mismatches, missing fields,
constraint violations, and generates migration scripts suggestions.
Author: Migration Architect Skill
Version: 1.0.0
License: MIT
"""
import json
import argparse
import sys
import re
import datetime
from typing import Dict, List, Any, Optional, Tuple, Set
from dataclasses import dataclass, asdict
from enum import Enum
class ChangeType(Enum):
"""Types of changes detected"""
BREAKING = "breaking"
POTENTIALLY_BREAKING = "potentially_breaking"
NON_BREAKING = "non_breaking"
ADDITIVE = "additive"
class CompatibilityLevel(Enum):
"""Compatibility assessment levels"""
FULLY_COMPATIBLE = "fully_compatible"
BACKWARD_COMPATIBLE = "backward_compatible"
POTENTIALLY_INCOMPATIBLE = "potentially_incompatible"
BREAKING_CHANGES = "breaking_changes"
@dataclass
class CompatibilityIssue:
"""Individual compatibility issue"""
type: str
severity: str
description: str
field_path: str
old_value: Any
new_value: Any
impact: str
suggested_migration: str
affected_operations: List[str]
@dataclass
class MigrationScript:
"""Migration script suggestion"""
script_type: str # sql, api, config
description: str
script_content: str
rollback_script: str
dependencies: List[str]
validation_query: str
@dataclass
class CompatibilityReport:
"""Complete compatibility analysis report"""
schema_before: str
schema_after: str
analysis_date: str
overall_compatibility: str
breaking_changes_count: int
potentially_breaking_count: int
non_breaking_changes_count: int
additive_changes_count: int
issues: List[CompatibilityIssue]
migration_scripts: List[MigrationScript]
risk_assessment: Dict[str, Any]
recommendations: List[str]
class SchemaCompatibilityChecker:
"""Main schema compatibility checker class"""
def __init__(self):
self.type_compatibility_matrix = self._build_type_compatibility_matrix()
self.constraint_implications = self._build_constraint_implications()
def _build_type_compatibility_matrix(self) -> Dict[str, Dict[str, str]]:
"""Build data type compatibility matrix"""
return {
# SQL data types compatibility
"varchar": {
"text": "compatible",
"char": "potentially_breaking", # length might be different
"nvarchar": "compatible",
"int": "breaking",
"bigint": "breaking",
"decimal": "breaking",
"datetime": "breaking",
"boolean": "breaking"
},
"int": {
"bigint": "compatible",
"smallint": "potentially_breaking", # range reduction
"decimal": "compatible",
"float": "potentially_breaking", # precision loss
"varchar": "breaking",
"boolean": "breaking"
},
"bigint": {
"int": "potentially_breaking", # range reduction
"decimal": "compatible",
"varchar": "breaking",
"boolean": "breaking"
},
"decimal": {
"float": "potentially_breaking", # precision loss
"int": "potentially_breaking", # precision loss
"bigint": "potentially_breaking", # precision loss
"varchar": "breaking",
"boolean": "breaking"
},
"datetime": {
"timestamp": "compatible",
"date": "potentially_breaking", # time component lost
"varchar": "breaking",
"int": "breaking"
},
"boolean": {
"tinyint": "compatible",
"varchar": "breaking",
"int": "breaking"
},
# JSON/API field types
"string": {
"number": "breaking",
"boolean": "breaking",
"array": "breaking",
"object": "breaking",
"null": "potentially_breaking"
},
"number": {
"string": "breaking",
"boolean": "breaking",
"array": "breaking",
"object": "breaking",
"null": "potentially_breaking"
},
"boolean": {
"string": "breaking",
"number": "breaking",
"array": "breaking",
"object": "breaking",
"null": "potentially_breaking"
},
"array": {
"string": "breaking",
"number": "breaking",
"boolean": "breaking",
"object": "breaking",
"null": "potentially_breaking"
},
"object": {
"string": "breaking",
"number": "breaking",
"boolean": "breaking",
"array": "breaking",
"null": "potentially_breaking"
}
}
def _build_constraint_implications(self) -> Dict[str, Dict[str, str]]:
"""Build constraint change implications"""
return {
"required": {
"added": "breaking", # Previously optional field now required
"removed": "non_breaking" # Previously required field now optional
},
"not_null": {
"added": "breaking", # Previously nullable now NOT NULL
"removed": "non_breaking" # Previously NOT NULL now nullable
},
"unique": {
"added": "potentially_breaking", # May fail if duplicates exist
"removed": "non_breaking" # No longer enforcing uniqueness
},
"primary_key": {
"added": "breaking", # Major structural change
"removed": "breaking", # Major structural change
"modified": "breaking" # Primary key change is always breaking
},
"foreign_key": {
"added": "potentially_breaking", # May fail if referential integrity violated
"removed": "potentially_breaking", # May allow orphaned records
"modified": "breaking" # Reference change is breaking
},
"check": {
"added": "potentially_breaking", # May fail if existing data violates check
"removed": "non_breaking", # No longer enforcing check
"modified": "potentially_breaking" # Different validation rules
},
"index": {
"added": "non_breaking", # Performance improvement
"removed": "non_breaking", # Performance impact only
"modified": "non_breaking" # Performance impact only
}
}
def analyze_database_schema(self, before_schema: Dict[str, Any],
after_schema: Dict[str, Any]) -> CompatibilityReport:
"""Analyze database schema compatibility"""
issues = []
migration_scripts = []
before_tables = before_schema.get("tables", {})
after_tables = after_schema.get("tables", {})
# Check for removed tables
for table_name in before_tables:
if table_name not in after_tables:
issues.append(CompatibilityIssue(
type="table_removed",
severity="breaking",
description=f"Table '{table_name}' has been removed",
field_path=f"tables.{table_name}",
old_value=before_tables[table_name],
new_value=None,
impact="All operations on this table will fail",
suggested_migration=f"CREATE VIEW {table_name} AS SELECT * FROM replacement_table;",
affected_operations=["SELECT", "INSERT", "UPDATE", "DELETE"]
))
# Check for added tables
for table_name in after_tables:
if table_name not in before_tables:
migration_scripts.append(MigrationScript(
script_type="sql",
description=f"Create new table {table_name}",
script_content=self._generate_create_table_sql(table_name, after_tables[table_name]),
rollback_script=f"DROP TABLE IF EXISTS {table_name};",
dependencies=[],
validation_query=f"SELECT COUNT(*) FROM information_schema.tables WHERE table_name = '{table_name}';"
))
# Check for modified tables
for table_name in set(before_tables.keys()) & set(after_tables.keys()):
table_issues, table_scripts = self._analyze_table_changes(
table_name, before_tables[table_name], after_tables[table_name]
)
issues.extend(table_issues)
migration_scripts.extend(table_scripts)
return self._build_compatibility_report(
before_schema, after_schema, issues, migration_scripts
)
def analyze_api_schema(self, before_schema: Dict[str, Any],
after_schema: Dict[str, Any]) -> CompatibilityReport:
"""Analyze REST API schema compatibility"""
issues = []
migration_scripts = []
# Analyze endpoints
before_paths = before_schema.get("paths", {})
after_paths = after_schema.get("paths", {})
# Check for removed endpoints
for path in before_paths:
if path not in after_paths:
for method in before_paths[path]:
issues.append(CompatibilityIssue(
type="endpoint_removed",
severity="breaking",
description=f"Endpoint {method.upper()} {path} has been removed",
field_path=f"paths.{path}.{method}",
old_value=before_paths[path][method],
new_value=None,
impact="Client requests to this endpoint will fail with 404",
suggested_migration=f"Implement redirect to replacement endpoint or maintain backward compatibility stub",
affected_operations=[f"{method.upper()} {path}"]
))
# Check for modified endpoints
for path in set(before_paths.keys()) & set(after_paths.keys()):
path_issues, path_scripts = self._analyze_endpoint_changes(
path, before_paths[path], after_paths[path]
)
issues.extend(path_issues)
migration_scripts.extend(path_scripts)
# Analyze data models
before_components = before_schema.get("components", {}).get("schemas", {})
after_components = after_schema.get("components", {}).get("schemas", {})
for model_name in set(before_components.keys()) & set(after_components.keys()):
model_issues, model_scripts = self._analyze_model_changes(
model_name, before_components[model_name], after_components[model_name]
)
issues.extend(model_issues)
migration_scripts.extend(model_scripts)
return self._build_compatibility_report(
before_schema, after_schema, issues, migration_scripts
)
def _analyze_table_changes(self, table_name: str, before_table: Dict[str, Any],
after_table: Dict[str, Any]) -> Tuple[List[CompatibilityIssue], List[MigrationScript]]:
"""Analyze changes to a specific table"""
issues = []
scripts = []
before_columns = before_table.get("columns", {})
after_columns = after_table.get("columns", {})
# Check for removed columns
for col_name in before_columns:
if col_name not in after_columns:
issues.append(CompatibilityIssue(
type="column_removed",
severity="breaking",
description=f"Column '{col_name}' removed from table '{table_name}'",
field_path=f"tables.{table_name}.columns.{col_name}",
old_value=before_columns[col_name],
new_value=None,
impact="SELECT statements including this column will fail",
suggested_migration=f"ALTER TABLE {table_name} ADD COLUMN {col_name}_deprecated AS computed_value;",
affected_operations=["SELECT", "INSERT", "UPDATE"]
))
# Check for added columns
for col_name in after_columns:
if col_name not in before_columns:
col_def = after_columns[col_name]
is_required = col_def.get("nullable", True) == False and col_def.get("default") is None
if is_required:
issues.append(CompatibilityIssue(
type="required_column_added",
severity="breaking",
description=f"Required column '{col_name}' added to table '{table_name}'",
field_path=f"tables.{table_name}.columns.{col_name}",
old_value=None,
new_value=col_def,
impact="INSERT statements without this column will fail",
suggested_migration=f"Add default value or make column nullable initially",
affected_operations=["INSERT"]
))
scripts.append(MigrationScript(
script_type="sql",
description=f"Add column {col_name} to table {table_name}",
script_content=f"ALTER TABLE {table_name} ADD COLUMN {self._generate_column_definition(col_name, col_def)};",
rollback_script=f"ALTER TABLE {table_name} DROP COLUMN {col_name};",
dependencies=[],
validation_query=f"SELECT COUNT(*) FROM information_schema.columns WHERE table_name = '{table_name}' AND column_name = '{col_name}';"
))
# Check for modified columns
for col_name in set(before_columns.keys()) & set(after_columns.keys()):
col_issues, col_scripts = self._analyze_column_changes(
table_name, col_name, before_columns[col_name], after_columns[col_name]
)
issues.extend(col_issues)
scripts.extend(col_scripts)
# Check constraint changes
before_constraints = before_table.get("constraints", {})
after_constraints = after_table.get("constraints", {})
constraint_issues, constraint_scripts = self._analyze_constraint_changes(
table_name, before_constraints, after_constraints
)
issues.extend(constraint_issues)
scripts.extend(constraint_scripts)
return issues, scripts
def _analyze_column_changes(self, table_name: str, col_name: str,
before_col: Dict[str, Any], after_col: Dict[str, Any]) -> Tuple[List[CompatibilityIssue], List[MigrationScript]]:
"""Analyze changes to a specific column"""
issues = []
scripts = []
# Check data type changes
before_type = before_col.get("type", "").lower()
after_type = after_col.get("type", "").lower()
if before_type != after_type:
compatibility = self.type_compatibility_matrix.get(before_type, {}).get(after_type, "breaking")
if compatibility == "breaking":
issues.append(CompatibilityIssue(
type="incompatible_type_change",
severity="breaking",
description=f"Column '{col_name}' type changed from {before_type} to {after_type}",
field_path=f"tables.{table_name}.columns.{col_name}.type",
old_value=before_type,
new_value=after_type,
impact="Data conversion may fail or lose precision",
suggested_migration=f"Add conversion logic and validate data integrity",
affected_operations=["SELECT", "INSERT", "UPDATE", "WHERE clauses"]
))
scripts.append(MigrationScript(
script_type="sql",
description=f"Convert column {col_name} from {before_type} to {after_type}",
script_content=f"ALTER TABLE {table_name} ALTER COLUMN {col_name} TYPE {after_type} USING {col_name}::{after_type};",
rollback_script=f"ALTER TABLE {table_name} ALTER COLUMN {col_name} TYPE {before_type};",
dependencies=[f"backup_{table_name}"],
validation_query=f"SELECT COUNT(*) FROM {table_name} WHERE {col_name} IS NOT NULL;"
))
elif compatibility == "potentially_breaking":
issues.append(CompatibilityIssue(
type="risky_type_change",
severity="potentially_breaking",
description=f"Column '{col_name}' type changed from {before_type} to {after_type} - may lose data",
field_path=f"tables.{table_name}.columns.{col_name}.type",
old_value=before_type,
new_value=after_type,
impact="Potential data loss or precision reduction",
suggested_migration=f"Validate all existing data can be converted safely",
affected_operations=["Data integrity"]
))
# Check nullability changes
before_nullable = before_col.get("nullable", True)
after_nullable = after_col.get("nullable", True)
if before_nullable != after_nullable:
if before_nullable and not after_nullable: # null -> not null
issues.append(CompatibilityIssue(
type="nullability_restriction",
severity="breaking",
description=f"Column '{col_name}' changed from nullable to NOT NULL",
field_path=f"tables.{table_name}.columns.{col_name}.nullable",
old_value=before_nullable,
new_value=after_nullable,
impact="Existing NULL values will cause constraint violations",
suggested_migration=f"Update NULL values to valid defaults before applying NOT NULL constraint",
affected_operations=["INSERT", "UPDATE"]
))
scripts.append(MigrationScript(
script_type="sql",
description=f"Make column {col_name} NOT NULL",
script_content=f"""
-- Update NULL values first
UPDATE {table_name} SET {col_name} = 'DEFAULT_VALUE' WHERE {col_name} IS NULL;
-- Add NOT NULL constraint
ALTER TABLE {table_name} ALTER COLUMN {col_name} SET NOT NULL;
""",
rollback_script=f"ALTER TABLE {table_name} ALTER COLUMN {col_name} DROP NOT NULL;",
dependencies=[],
validation_query=f"SELECT COUNT(*) FROM {table_name} WHERE {col_name} IS NULL;"
))
# Check length/precision changes
before_length = before_col.get("length")
after_length = after_col.get("length")
if before_length and after_length and before_length != after_length:
if after_length < before_length:
issues.append(CompatibilityIssue(
type="length_reduction",
severity="potentially_breaking",
description=f"Column '{col_name}' length reduced from {before_length} to {after_length}",
field_path=f"tables.{table_name}.columns.{col_name}.length",
old_value=before_length,
new_value=after_length,
impact="Data truncation may occur for values exceeding new length",
suggested_migration=f"Validate no existing data exceeds new length limit",
affected_operations=["INSERT", "UPDATE"]
))
return issues, scripts
def _analyze_constraint_changes(self, table_name: str, before_constraints: Dict[str, Any],
after_constraints: Dict[str, Any]) -> Tuple[List[CompatibilityIssue], List[MigrationScript]]:
"""Analyze constraint changes"""
issues = []
scripts = []
for constraint_type in ["primary_key", "foreign_key", "unique", "check"]:
before_constraint = before_constraints.get(constraint_type, [])
after_constraint = after_constraints.get(constraint_type, [])
# Convert to sets for comparison
before_set = set(str(c) for c in before_constraint) if isinstance(before_constraint, list) else {str(before_constraint)} if before_constraint else set()
after_set = set(str(c) for c in after_constraint) if isinstance(after_constraint, list) else {str(after_constraint)} if after_constraint else set()
# Check for removed constraints
for constraint in before_set - after_set:
implication = self.constraint_implications.get(constraint_type, {}).get("removed", "non_breaking")
issues.append(CompatibilityIssue(
type=f"{constraint_type}_removed",
severity=implication,
description=f"{constraint_type.replace('_', ' ').title()} constraint '{constraint}' removed from table '{table_name}'",
field_path=f"tables.{table_name}.constraints.{constraint_type}",
old_value=constraint,
new_value=None,
impact=f"No longer enforcing {constraint_type} constraint",
suggested_migration=f"Consider application-level validation for removed constraint",
affected_operations=["INSERT", "UPDATE", "DELETE"]
))
# Check for added constraints
for constraint in after_set - before_set:
implication = self.constraint_implications.get(constraint_type, {}).get("added", "potentially_breaking")
issues.append(CompatibilityIssue(
type=f"{constraint_type}_added",
severity=implication,
description=f"New {constraint_type.replace('_', ' ')} constraint '{constraint}' added to table '{table_name}'",
field_path=f"tables.{table_name}.constraints.{constraint_type}",
old_value=None,
new_value=constraint,
impact=f"New {constraint_type} constraint may reject existing data",
suggested_migration=f"Validate existing data complies with new constraint",
affected_operations=["INSERT", "UPDATE"]
))
scripts.append(MigrationScript(
script_type="sql",
description=f"Add {constraint_type} constraint to {table_name}",
script_content=f"ALTER TABLE {table_name} ADD CONSTRAINT {constraint_type}_{table_name} {constraint_type.upper()} ({constraint});",
rollback_script=f"ALTER TABLE {table_name} DROP CONSTRAINT {constraint_type}_{table_name};",
dependencies=[],
validation_query=f"SELECT COUNT(*) FROM information_schema.table_constraints WHERE table_name = '{table_name}' AND constraint_type = '{constraint_type.upper()}';"
))
return issues, scripts
def _analyze_endpoint_changes(self, path: str, before_endpoint: Dict[str, Any],
after_endpoint: Dict[str, Any]) -> Tuple[List[CompatibilityIssue], List[MigrationScript]]:
"""Analyze changes to an API endpoint"""
issues = []
scripts = []
for method in set(before_endpoint.keys()) & set(after_endpoint.keys()):
before_method = before_endpoint[method]
after_method = after_endpoint[method]
# Check parameter changes
before_params = before_method.get("parameters", [])
after_params = after_method.get("parameters", [])
before_param_names = {p["name"] for p in before_params}
after_param_names = {p["name"] for p in after_params}
# Check for removed required parameters
for param_name in before_param_names - after_param_names:
param = next(p for p in before_params if p["name"] == param_name)
if param.get("required", False):
issues.append(CompatibilityIssue(
type="required_parameter_removed",
severity="breaking",
description=f"Required parameter '{param_name}' removed from {method.upper()} {path}",
field_path=f"paths.{path}.{method}.parameters",
old_value=param,
new_value=None,
impact="Client requests with this parameter will fail",
suggested_migration="Implement parameter validation with backward compatibility",
affected_operations=[f"{method.upper()} {path}"]
))
# Check for added required parameters
for param_name in after_param_names - before_param_names:
param = next(p for p in after_params if p["name"] == param_name)
if param.get("required", False):
issues.append(CompatibilityIssue(
type="required_parameter_added",
severity="breaking",
description=f"New required parameter '{param_name}' added to {method.upper()} {path}",
field_path=f"paths.{path}.{method}.parameters",
old_value=None,
new_value=param,
impact="Client requests without this parameter will fail",
suggested_migration="Provide default value or make parameter optional initially",
affected_operations=[f"{method.upper()} {path}"]
))
# Check response schema changes
before_responses = before_method.get("responses", {})
after_responses = after_method.get("responses", {})
for status_code in before_responses:
if status_code in after_responses:
before_schema = before_responses[status_code].get("content", {}).get("application/json", {}).get("schema", {})
after_schema = after_responses[status_code].get("content", {}).get("application/json", {}).get("schema", {})
if before_schema != after_schema:
issues.append(CompatibilityIssue(
type="response_schema_changed",
severity="potentially_breaking",
description=f"Response schema changed for {method.upper()} {path} (status {status_code})",
field_path=f"paths.{path}.{method}.responses.{status_code}",
old_value=before_schema,
new_value=after_schema,
impact="Client response parsing may fail",
suggested_migration="Implement versioned API responses",
affected_operations=[f"{method.upper()} {path}"]
))
return issues, scripts
def _analyze_model_changes(self, model_name: str, before_model: Dict[str, Any],
after_model: Dict[str, Any]) -> Tuple[List[CompatibilityIssue], List[MigrationScript]]:
"""Analyze changes to an API data model"""
issues = []
scripts = []
before_props = before_model.get("properties", {})
after_props = after_model.get("properties", {})
before_required = set(before_model.get("required", []))
after_required = set(after_model.get("required", []))
# Check for removed properties
for prop_name in set(before_props.keys()) - set(after_props.keys()):
issues.append(CompatibilityIssue(
type="property_removed",
severity="breaking",
description=f"Property '{prop_name}' removed from model '{model_name}'",
field_path=f"components.schemas.{model_name}.properties.{prop_name}",
old_value=before_props[prop_name],
new_value=None,
impact="Client code expecting this property will fail",
suggested_migration="Use API versioning to maintain backward compatibility",
affected_operations=["Serialization", "Deserialization"]
))
# Check for newly required properties
for prop_name in after_required - before_required:
issues.append(CompatibilityIssue(
type="property_made_required",
severity="breaking",
description=f"Property '{prop_name}' is now required in model '{model_name}'",
field_path=f"components.schemas.{model_name}.required",
old_value=list(before_required),
new_value=list(after_required),
impact="Client requests without this property will fail validation",
suggested_migration="Provide default values or implement gradual rollout",
affected_operations=["Request validation"]
))
# Check for property type changes
for prop_name in set(before_props.keys()) & set(after_props.keys()):
before_type = before_props[prop_name].get("type")
after_type = after_props[prop_name].get("type")
if before_type != after_type:
compatibility = self.type_compatibility_matrix.get(before_type, {}).get(after_type, "breaking")
issues.append(CompatibilityIssue(
type="property_type_changed",
severity=compatibility,
description=f"Property '{prop_name}' type changed from {before_type} to {after_type} in model '{model_name}'",
field_path=f"components.schemas.{model_name}.properties.{prop_name}.type",
old_value=before_type,
new_value=after_type,
impact="Client serialization/deserialization may fail",
suggested_migration="Implement type coercion or API versioning",
affected_operations=["Serialization", "Deserialization"]
))
return issues, scripts
def _build_compatibility_report(self, before_schema: Dict[str, Any], after_schema: Dict[str, Any],
issues: List[CompatibilityIssue], migration_scripts: List[MigrationScript]) -> CompatibilityReport:
"""Build the final compatibility report"""
# Count issues by severity
breaking_count = sum(1 for issue in issues if issue.severity == "breaking")
potentially_breaking_count = sum(1 for issue in issues if issue.severity == "potentially_breaking")
non_breaking_count = sum(1 for issue in issues if issue.severity == "non_breaking")
additive_count = sum(1 for issue in issues if issue.type == "additive")
# Determine overall compatibility
if breaking_count > 0:
overall_compatibility = "breaking_changes"
elif potentially_breaking_count > 0:
overall_compatibility = "potentially_incompatible"
elif non_breaking_count > 0:
overall_compatibility = "backward_compatible"
else:
overall_compatibility = "fully_compatible"
# Generate risk assessment
risk_assessment = {
"overall_risk": "high" if breaking_count > 0 else "medium" if potentially_breaking_count > 0 else "low",
"deployment_risk": "requires_coordinated_deployment" if breaking_count > 0 else "safe_independent_deployment",
"rollback_complexity": "high" if breaking_count > 3 else "medium" if breaking_count > 0 else "low",
"testing_requirements": ["integration_testing", "regression_testing"] +
(["data_migration_testing"] if any(s.script_type == "sql" for s in migration_scripts) else [])
}
# Generate recommendations
recommendations = []
if breaking_count > 0:
recommendations.append("Implement API versioning to maintain backward compatibility")
recommendations.append("Plan for coordinated deployment with all clients")
recommendations.append("Implement comprehensive rollback procedures")
if potentially_breaking_count > 0:
recommendations.append("Conduct thorough testing with realistic data volumes")
recommendations.append("Implement monitoring for migration success metrics")
if migration_scripts:
recommendations.append("Test all migration scripts in staging environment")
recommendations.append("Implement migration progress monitoring")
recommendations.append("Create detailed communication plan for stakeholders")
recommendations.append("Implement feature flags for gradual rollout")
return CompatibilityReport(
schema_before=json.dumps(before_schema, indent=2)[:500] + "..." if len(json.dumps(before_schema)) > 500 else json.dumps(before_schema, indent=2),
schema_after=json.dumps(after_schema, indent=2)[:500] + "..." if len(json.dumps(after_schema)) > 500 else json.dumps(after_schema, indent=2),
analysis_date=datetime.datetime.now().isoformat(),
overall_compatibility=overall_compatibility,
breaking_changes_count=breaking_count,
potentially_breaking_count=potentially_breaking_count,
non_breaking_changes_count=non_breaking_count,
additive_changes_count=additive_count,
issues=issues,
migration_scripts=migration_scripts,
risk_assessment=risk_assessment,
recommendations=recommendations
)
def _generate_create_table_sql(self, table_name: str, table_def: Dict[str, Any]) -> str:
"""Generate CREATE TABLE SQL statement"""
columns = []
for col_name, col_def in table_def.get("columns", {}).items():
columns.append(self._generate_column_definition(col_name, col_def))
return f"CREATE TABLE {table_name} (\n " + ",\n ".join(columns) + "\n);"
def _generate_column_definition(self, col_name: str, col_def: Dict[str, Any]) -> str:
"""Generate column definition for SQL"""
col_type = col_def.get("type", "VARCHAR(255)")
nullable = "" if col_def.get("nullable", True) else " NOT NULL"
default = f" DEFAULT {col_def.get('default')}" if col_def.get("default") is not None else ""
return f"{col_name} {col_type}{nullable}{default}"
def generate_human_readable_report(self, report: CompatibilityReport) -> str:
"""Generate human-readable compatibility report"""
output = []
output.append("=" * 80)
output.append("COMPATIBILITY ANALYSIS REPORT")
output.append("=" * 80)
output.append(f"Analysis Date: {report.analysis_date}")
output.append(f"Overall Compatibility: {report.overall_compatibility.upper()}")
output.append("")
# Summary
output.append("SUMMARY")
output.append("-" * 40)
output.append(f"Breaking Changes: {report.breaking_changes_count}")
output.append(f"Potentially Breaking: {report.potentially_breaking_count}")
output.append(f"Non-Breaking Changes: {report.non_breaking_changes_count}")
output.append(f"Additive Changes: {report.additive_changes_count}")
output.append(f"Total Issues Found: {len(report.issues)}")
output.append("")
# Risk Assessment
output.append("RISK ASSESSMENT")
output.append("-" * 40)
for key, value in report.risk_assessment.items():
output.append(f"{key.replace('_', ' ').title()}: {value}")
output.append("")
# Issues by Severity
issues_by_severity = {}
for issue in report.issues:
if issue.severity not in issues_by_severity:
issues_by_severity[issue.severity] = []
issues_by_severity[issue.severity].append(issue)
for severity in ["breaking", "potentially_breaking", "non_breaking"]:
if severity in issues_by_severity:
output.append(f"{severity.upper().replace('_', ' ')} ISSUES")
output.append("-" * 40)
for issue in issues_by_severity[severity]:
output.append(f"• {issue.description}")
output.append(f" Field: {issue.field_path}")
output.append(f" Impact: {issue.impact}")
output.append(f" Migration: {issue.suggested_migration}")
if issue.affected_operations:
output.append(f" Affected Operations: {', '.join(issue.affected_operations)}")
output.append("")
# Migration Scripts
if report.migration_scripts:
output.append("SUGGESTED MIGRATION SCRIPTS")
output.append("-" * 40)
for i, script in enumerate(report.migration_scripts, 1):
output.append(f"{i}. {script.description}")
output.append(f" Type: {script.script_type}")
output.append(" Script:")
for line in script.script_content.split('\n'):
output.append(f" {line}")
output.append("")
# Recommendations
output.append("RECOMMENDATIONS")
output.append("-" * 40)
for i, rec in enumerate(report.recommendations, 1):
output.append(f"{i}. {rec}")
output.append("")
return "\n".join(output)
def main():
"""Main function with command line interface"""
parser = argparse.ArgumentParser(description="Analyze schema and API compatibility between versions")
parser.add_argument("--before", required=True, help="Before schema file (JSON)")
parser.add_argument("--after", required=True, help="After schema file (JSON)")
parser.add_argument("--type", choices=["database", "api"], default="database", help="Schema type to analyze")
parser.add_argument("--output", "-o", help="Output file for compatibility report (JSON)")
parser.add_argument("--format", "-f", choices=["json", "text", "both"], default="both", help="Output format")
args = parser.parse_args()
try:
# Load schemas
with open(args.before, 'r') as f:
before_schema = json.load(f)
with open(args.after, 'r') as f:
after_schema = json.load(f)
# Analyze compatibility
checker = SchemaCompatibilityChecker()
if args.type == "database":
report = checker.analyze_database_schema(before_schema, after_schema)
else: # api
report = checker.analyze_api_schema(before_schema, after_schema)
# Output results
if args.format in ["json", "both"]:
report_dict = asdict(report)
if args.output:
with open(args.output, 'w') as f:
json.dump(report_dict, f, indent=2)
print(f"Compatibility report saved to {args.output}")
else:
print(json.dumps(report_dict, indent=2))
if args.format in ["text", "both"]:
human_report = checker.generate_human_readable_report(report)
text_output = args.output.replace('.json', '.txt') if args.output else None
if text_output:
with open(text_output, 'w') as f:
f.write(human_report)
print(f"Human-readable report saved to {text_output}")
else:
print("\n" + "="*80)
print("HUMAN-READABLE COMPATIBILITY REPORT")
print("="*80)
print(human_report)
# Return exit code based on compatibility
if report.breaking_changes_count > 0:
return 2 # Breaking changes found
elif report.potentially_breaking_count > 0:
return 1 # Potentially breaking changes found
else:
return 0 # No compatibility issues
except FileNotFoundError as e:
print(f"Error: File not found: {e}", file=sys.stderr)
return 1
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON: {e}", file=sys.stderr)
return 1
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
return 1
if __name__ == "__main__":
sys.exit(main())
FILE:scripts/migration_planner.py
#!/usr/bin/env python3
"""
Migration Planner - Generate comprehensive migration plans with risk assessment
This tool analyzes migration specifications and generates detailed, phased migration plans
including pre-migration checklists, validation gates, rollback triggers, timeline estimates,
and risk matrices.
Author: Migration Architect Skill
Version: 1.0.0
License: MIT
"""
import json
import argparse
import sys
import datetime
import hashlib
import math
from typing import Dict, List, Any, Optional, Tuple
from dataclasses import dataclass, asdict
from enum import Enum
class MigrationType(Enum):
"""Migration type enumeration"""
DATABASE = "database"
SERVICE = "service"
INFRASTRUCTURE = "infrastructure"
DATA = "data"
API = "api"
class MigrationComplexity(Enum):
"""Migration complexity levels"""
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
CRITICAL = "critical"
class RiskLevel(Enum):
"""Risk assessment levels"""
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
CRITICAL = "critical"
@dataclass
class MigrationConstraint:
"""Migration constraint definition"""
type: str
description: str
impact: str
mitigation: str
@dataclass
class MigrationPhase:
"""Individual migration phase"""
name: str
description: str
duration_hours: int
dependencies: List[str]
validation_criteria: List[str]
rollback_triggers: List[str]
tasks: List[str]
risk_level: str
resources_required: List[str]
@dataclass
class RiskItem:
"""Individual risk assessment item"""
category: str
description: str
probability: str # low, medium, high
impact: str # low, medium, high
severity: str # low, medium, high, critical
mitigation: str
owner: str
@dataclass
class MigrationPlan:
"""Complete migration plan structure"""
migration_id: str
source_system: str
target_system: str
migration_type: str
complexity: str
estimated_duration_hours: int
phases: List[MigrationPhase]
risks: List[RiskItem]
success_criteria: List[str]
rollback_plan: Dict[str, Any]
stakeholders: List[str]
created_at: str
class MigrationPlanner:
"""Main migration planner class"""
def __init__(self):
self.migration_patterns = self._load_migration_patterns()
self.risk_templates = self._load_risk_templates()
def _load_migration_patterns(self) -> Dict[str, Any]:
"""Load predefined migration patterns"""
return {
"database": {
"schema_change": {
"phases": ["preparation", "expand", "migrate", "contract", "cleanup"],
"base_duration": 24,
"complexity_multiplier": {"low": 1.0, "medium": 1.5, "high": 2.5, "critical": 4.0}
},
"data_migration": {
"phases": ["assessment", "setup", "bulk_copy", "delta_sync", "validation", "cutover"],
"base_duration": 48,
"complexity_multiplier": {"low": 1.2, "medium": 2.0, "high": 3.0, "critical": 5.0}
}
},
"service": {
"strangler_fig": {
"phases": ["intercept", "implement", "redirect", "validate", "retire"],
"base_duration": 168, # 1 week
"complexity_multiplier": {"low": 0.8, "medium": 1.0, "high": 1.8, "critical": 3.0}
},
"parallel_run": {
"phases": ["setup", "deploy", "shadow", "compare", "cutover", "cleanup"],
"base_duration": 72,
"complexity_multiplier": {"low": 1.0, "medium": 1.3, "high": 2.0, "critical": 3.5}
}
},
"infrastructure": {
"cloud_migration": {
"phases": ["assessment", "design", "pilot", "migration", "optimization", "decommission"],
"base_duration": 720, # 30 days
"complexity_multiplier": {"low": 0.6, "medium": 1.0, "high": 1.5, "critical": 2.5}
},
"on_prem_to_cloud": {
"phases": ["discovery", "planning", "pilot", "migration", "validation", "cutover"],
"base_duration": 480, # 20 days
"complexity_multiplier": {"low": 0.8, "medium": 1.2, "high": 2.0, "critical": 3.0}
}
}
}
def _load_risk_templates(self) -> Dict[str, List[RiskItem]]:
"""Load risk templates for different migration types"""
return {
"database": [
RiskItem("technical", "Data corruption during migration", "low", "critical", "high",
"Implement comprehensive backup and validation procedures", "DBA Team"),
RiskItem("technical", "Extended downtime due to migration complexity", "medium", "high", "high",
"Use blue-green deployment and phased migration approach", "DevOps Team"),
RiskItem("business", "Business process disruption", "medium", "high", "high",
"Communicate timeline and provide alternate workflows", "Business Owner"),
RiskItem("operational", "Insufficient rollback testing", "high", "critical", "critical",
"Execute full rollback procedures in staging environment", "QA Team")
],
"service": [
RiskItem("technical", "Service compatibility issues", "medium", "high", "high",
"Implement comprehensive integration testing", "Development Team"),
RiskItem("technical", "Performance degradation", "medium", "medium", "medium",
"Conduct load testing and performance benchmarking", "DevOps Team"),
RiskItem("business", "Feature parity gaps", "high", "high", "high",
"Document feature mapping and acceptance criteria", "Product Owner"),
RiskItem("operational", "Monitoring gap during transition", "medium", "medium", "medium",
"Set up dual monitoring and alerting systems", "SRE Team")
],
"infrastructure": [
RiskItem("technical", "Network connectivity issues", "medium", "critical", "high",
"Implement redundant network paths and monitoring", "Network Team"),
RiskItem("technical", "Security configuration drift", "high", "high", "high",
"Automated security scanning and compliance checks", "Security Team"),
RiskItem("business", "Cost overrun during transition", "high", "medium", "medium",
"Implement cost monitoring and budget alerts", "Finance Team"),
RiskItem("operational", "Team knowledge gaps", "high", "medium", "medium",
"Provide training and create detailed documentation", "Platform Team")
]
}
def _calculate_complexity(self, spec: Dict[str, Any]) -> str:
"""Calculate migration complexity based on specification"""
complexity_score = 0
# Data volume complexity
data_volume = spec.get("constraints", {}).get("data_volume_gb", 0)
if data_volume > 10000:
complexity_score += 3
elif data_volume > 1000:
complexity_score += 2
elif data_volume > 100:
complexity_score += 1
# System dependencies
dependencies = len(spec.get("constraints", {}).get("dependencies", []))
if dependencies > 10:
complexity_score += 3
elif dependencies > 5:
complexity_score += 2
elif dependencies > 2:
complexity_score += 1
# Downtime constraints
max_downtime = spec.get("constraints", {}).get("max_downtime_minutes", 480)
if max_downtime < 60:
complexity_score += 3
elif max_downtime < 240:
complexity_score += 2
elif max_downtime < 480:
complexity_score += 1
# Special requirements
special_reqs = spec.get("constraints", {}).get("special_requirements", [])
complexity_score += len(special_reqs)
if complexity_score >= 8:
return "critical"
elif complexity_score >= 5:
return "high"
elif complexity_score >= 3:
return "medium"
else:
return "low"
def _estimate_duration(self, migration_type: str, migration_pattern: str, complexity: str) -> int:
"""Estimate migration duration based on type, pattern, and complexity"""
pattern_info = self.migration_patterns.get(migration_type, {}).get(migration_pattern, {})
base_duration = pattern_info.get("base_duration", 48)
multiplier = pattern_info.get("complexity_multiplier", {}).get(complexity, 1.5)
return int(base_duration * multiplier)
def _generate_phases(self, spec: Dict[str, Any]) -> List[MigrationPhase]:
"""Generate migration phases based on specification"""
migration_type = spec.get("type")
migration_pattern = spec.get("pattern", "")
complexity = self._calculate_complexity(spec)
pattern_info = self.migration_patterns.get(migration_type, {})
if migration_pattern in pattern_info:
phase_names = pattern_info[migration_pattern]["phases"]
else:
# Default phases based on migration type
phase_names = {
"database": ["preparation", "migration", "validation", "cutover"],
"service": ["preparation", "deployment", "testing", "cutover"],
"infrastructure": ["assessment", "preparation", "migration", "validation"]
}.get(migration_type, ["preparation", "execution", "validation", "cleanup"])
phases = []
total_duration = self._estimate_duration(migration_type, migration_pattern, complexity)
phase_duration = total_duration // len(phase_names)
for i, phase_name in enumerate(phase_names):
phase = self._create_phase(phase_name, phase_duration, complexity, i, phase_names)
phases.append(phase)
return phases
def _create_phase(self, phase_name: str, duration: int, complexity: str,
phase_index: int, all_phases: List[str]) -> MigrationPhase:
"""Create a detailed migration phase"""
phase_templates = {
"preparation": {
"description": "Prepare systems and teams for migration",
"tasks": [
"Backup source system",
"Set up monitoring and alerting",
"Prepare rollback procedures",
"Communicate migration timeline",
"Validate prerequisites"
],
"validation_criteria": [
"All backups completed successfully",
"Monitoring systems operational",
"Team members briefed and ready",
"Rollback procedures tested"
],
"risk_level": "medium"
},
"assessment": {
"description": "Assess current state and migration requirements",
"tasks": [
"Inventory existing systems and dependencies",
"Analyze data volumes and complexity",
"Identify integration points",
"Document current architecture",
"Create migration mapping"
],
"validation_criteria": [
"Complete system inventory documented",
"Dependencies mapped and validated",
"Migration scope clearly defined",
"Resource requirements identified"
],
"risk_level": "low"
},
"migration": {
"description": "Execute core migration processes",
"tasks": [
"Begin data/service migration",
"Monitor migration progress",
"Validate data consistency",
"Handle migration errors",
"Update configuration"
],
"validation_criteria": [
"Migration progress within expected parameters",
"Data consistency checks passing",
"Error rates within acceptable limits",
"Performance metrics stable"
],
"risk_level": "high"
},
"validation": {
"description": "Validate migration success and system health",
"tasks": [
"Execute comprehensive testing",
"Validate business processes",
"Check system performance",
"Verify data integrity",
"Confirm security controls"
],
"validation_criteria": [
"All critical tests passing",
"Performance within acceptable range",
"Security controls functioning",
"Business processes operational"
],
"risk_level": "medium"
},
"cutover": {
"description": "Switch production traffic to new system",
"tasks": [
"Update DNS/load balancer configuration",
"Redirect production traffic",
"Monitor system performance",
"Validate end-user experience",
"Confirm business operations"
],
"validation_criteria": [
"Traffic successfully redirected",
"System performance stable",
"User experience satisfactory",
"Business operations normal"
],
"risk_level": "critical"
}
}
template = phase_templates.get(phase_name, {
"description": f"Execute {phase_name} phase",
"tasks": [f"Complete {phase_name} activities"],
"validation_criteria": [f"{phase_name.title()} phase completed successfully"],
"risk_level": "medium"
})
dependencies = []
if phase_index > 0:
dependencies.append(all_phases[phase_index - 1])
rollback_triggers = [
"Critical system failure",
"Data corruption detected",
"Performance degradation > 50%",
"Business process failure"
]
resources_required = [
"Technical team availability",
"System access and permissions",
"Monitoring and alerting systems",
"Communication channels"
]
return MigrationPhase(
name=phase_name,
description=template["description"],
duration_hours=duration,
dependencies=dependencies,
validation_criteria=template["validation_criteria"],
rollback_triggers=rollback_triggers,
tasks=template["tasks"],
risk_level=template["risk_level"],
resources_required=resources_required
)
def _assess_risks(self, spec: Dict[str, Any]) -> List[RiskItem]:
"""Generate risk assessment for migration"""
migration_type = spec.get("type")
base_risks = self.risk_templates.get(migration_type, [])
# Add specification-specific risks
additional_risks = []
constraints = spec.get("constraints", {})
if constraints.get("max_downtime_minutes", 480) < 60:
additional_risks.append(
RiskItem("business", "Zero-downtime requirement increases complexity", "high", "medium", "high",
"Implement blue-green deployment or rolling update strategy", "DevOps Team")
)
if constraints.get("data_volume_gb", 0) > 5000:
additional_risks.append(
RiskItem("technical", "Large data volumes may cause extended migration time", "high", "medium", "medium",
"Implement parallel processing and progress monitoring", "Data Team")
)
compliance_reqs = constraints.get("compliance_requirements", [])
if compliance_reqs:
additional_risks.append(
RiskItem("compliance", "Regulatory compliance requirements", "medium", "high", "high",
"Ensure all compliance checks are integrated into migration process", "Compliance Team")
)
return base_risks + additional_risks
def _generate_rollback_plan(self, phases: List[MigrationPhase]) -> Dict[str, Any]:
"""Generate comprehensive rollback plan"""
rollback_phases = []
for phase in reversed(phases):
rollback_phase = {
"phase": phase.name,
"rollback_actions": [
f"Revert {phase.name} changes",
f"Restore pre-{phase.name} state",
f"Validate {phase.name} rollback success"
],
"validation_criteria": [
f"System restored to pre-{phase.name} state",
f"All {phase.name} changes successfully reverted",
"System functionality confirmed"
],
"estimated_time_minutes": phase.duration_hours * 15 # 25% of original phase time
}
rollback_phases.append(rollback_phase)
return {
"rollback_phases": rollback_phases,
"rollback_triggers": [
"Critical system failure",
"Data corruption detected",
"Migration timeline exceeded by > 50%",
"Business-critical functionality unavailable",
"Security breach detected",
"Stakeholder decision to abort"
],
"rollback_decision_matrix": {
"low_severity": "Continue with monitoring",
"medium_severity": "Assess and decide within 15 minutes",
"high_severity": "Immediate rollback initiation",
"critical_severity": "Emergency rollback - all hands"
},
"rollback_contacts": [
"Migration Lead",
"Technical Lead",
"Business Owner",
"On-call Engineer"
]
}
def generate_plan(self, spec: Dict[str, Any]) -> MigrationPlan:
"""Generate complete migration plan from specification"""
migration_id = hashlib.md5(json.dumps(spec, sort_keys=True).encode()).hexdigest()[:12]
complexity = self._calculate_complexity(spec)
phases = self._generate_phases(spec)
risks = self._assess_risks(spec)
total_duration = sum(phase.duration_hours for phase in phases)
rollback_plan = self._generate_rollback_plan(phases)
success_criteria = [
"All data successfully migrated with 100% integrity",
"System performance meets or exceeds baseline",
"All business processes functioning normally",
"No critical security vulnerabilities introduced",
"Stakeholder acceptance criteria met",
"Documentation and runbooks updated"
]
stakeholders = [
"Business Owner",
"Technical Lead",
"DevOps Team",
"QA Team",
"Security Team",
"End Users"
]
return MigrationPlan(
migration_id=migration_id,
source_system=spec.get("source", "Unknown"),
target_system=spec.get("target", "Unknown"),
migration_type=spec.get("type", "Unknown"),
complexity=complexity,
estimated_duration_hours=total_duration,
phases=phases,
risks=risks,
success_criteria=success_criteria,
rollback_plan=rollback_plan,
stakeholders=stakeholders,
created_at=datetime.datetime.now().isoformat()
)
def generate_human_readable_plan(self, plan: MigrationPlan) -> str:
"""Generate human-readable migration plan"""
output = []
output.append("=" * 80)
output.append(f"MIGRATION PLAN: {plan.migration_id}")
output.append("=" * 80)
output.append(f"Source System: {plan.source_system}")
output.append(f"Target System: {plan.target_system}")
output.append(f"Migration Type: {plan.migration_type.upper()}")
output.append(f"Complexity Level: {plan.complexity.upper()}")
output.append(f"Estimated Duration: {plan.estimated_duration_hours} hours ({plan.estimated_duration_hours/24:.1f} days)")
output.append(f"Created: {plan.created_at}")
output.append("")
# Phases
output.append("MIGRATION PHASES")
output.append("-" * 40)
for i, phase in enumerate(plan.phases, 1):
output.append(f"{i}. {phase.name.upper()} ({phase.duration_hours}h)")
output.append(f" Description: {phase.description}")
output.append(f" Risk Level: {phase.risk_level.upper()}")
if phase.dependencies:
output.append(f" Dependencies: {', '.join(phase.dependencies)}")
output.append(" Tasks:")
for task in phase.tasks:
output.append(f" • {task}")
output.append(" Success Criteria:")
for criteria in phase.validation_criteria:
output.append(f" ✓ {criteria}")
output.append("")
# Risk Assessment
output.append("RISK ASSESSMENT")
output.append("-" * 40)
risk_by_severity = {}
for risk in plan.risks:
if risk.severity not in risk_by_severity:
risk_by_severity[risk.severity] = []
risk_by_severity[risk.severity].append(risk)
for severity in ["critical", "high", "medium", "low"]:
if severity in risk_by_severity:
output.append(f"{severity.upper()} SEVERITY RISKS:")
for risk in risk_by_severity[severity]:
output.append(f" • {risk.description}")
output.append(f" Category: {risk.category}")
output.append(f" Probability: {risk.probability} | Impact: {risk.impact}")
output.append(f" Mitigation: {risk.mitigation}")
output.append(f" Owner: {risk.owner}")
output.append("")
# Rollback Plan
output.append("ROLLBACK STRATEGY")
output.append("-" * 40)
output.append("Rollback Triggers:")
for trigger in plan.rollback_plan["rollback_triggers"]:
output.append(f" • {trigger}")
output.append("")
output.append("Rollback Phases:")
for rb_phase in plan.rollback_plan["rollback_phases"]:
output.append(f" {rb_phase['phase'].upper()}:")
for action in rb_phase["rollback_actions"]:
output.append(f" - {action}")
output.append(f" Estimated Time: {rb_phase['estimated_time_minutes']} minutes")
output.append("")
# Success Criteria
output.append("SUCCESS CRITERIA")
output.append("-" * 40)
for criteria in plan.success_criteria:
output.append(f"✓ {criteria}")
output.append("")
# Stakeholders
output.append("STAKEHOLDERS")
output.append("-" * 40)
for stakeholder in plan.stakeholders:
output.append(f"• {stakeholder}")
output.append("")
return "\n".join(output)
def main():
"""Main function with command line interface"""
parser = argparse.ArgumentParser(description="Generate comprehensive migration plans")
parser.add_argument("--input", "-i", required=True, help="Input migration specification file (JSON)")
parser.add_argument("--output", "-o", help="Output file for migration plan (JSON)")
parser.add_argument("--format", "-f", choices=["json", "text", "both"], default="both",
help="Output format")
parser.add_argument("--validate", action="store_true", help="Validate migration specification only")
args = parser.parse_args()
try:
# Load migration specification
with open(args.input, 'r') as f:
spec = json.load(f)
# Validate required fields
required_fields = ["type", "source", "target"]
for field in required_fields:
if field not in spec:
print(f"Error: Missing required field '{field}' in specification", file=sys.stderr)
return 1
if args.validate:
print("Migration specification is valid")
return 0
# Generate migration plan
planner = MigrationPlanner()
plan = planner.generate_plan(spec)
# Output results
if args.format in ["json", "both"]:
plan_dict = asdict(plan)
if args.output:
with open(args.output, 'w') as f:
json.dump(plan_dict, f, indent=2)
print(f"Migration plan saved to {args.output}")
else:
print(json.dumps(plan_dict, indent=2))
if args.format in ["text", "both"]:
human_plan = planner.generate_human_readable_plan(plan)
text_output = args.output.replace('.json', '.txt') if args.output else None
if text_output:
with open(text_output, 'w') as f:
f.write(human_plan)
print(f"Human-readable plan saved to {text_output}")
else:
print("\n" + "="*80)
print("HUMAN-READABLE MIGRATION PLAN")
print("="*80)
print(human_plan)
except FileNotFoundError:
print(f"Error: Input file '{args.input}' not found", file=sys.stderr)
return 1
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON in input file: {e}", file=sys.stderr)
return 1
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
sys.exit(main())
FILE:scripts/rollback_generator.py
#!/usr/bin/env python3
"""
Rollback Generator - Generate comprehensive rollback procedures for migrations
This tool takes a migration plan and generates detailed rollback procedures for each phase,
including data rollback scripts, service rollback steps, validation checks, and communication
templates to ensure safe and reliable migration reversals.
Author: Migration Architect Skill
Version: 1.0.0
License: MIT
"""
import json
import argparse
import sys
import datetime
import hashlib
from typing import Dict, List, Any, Optional, Tuple
from dataclasses import dataclass, asdict
from enum import Enum
class RollbackTrigger(Enum):
"""Types of rollback triggers"""
MANUAL = "manual"
AUTOMATED = "automated"
THRESHOLD_BASED = "threshold_based"
TIME_BASED = "time_based"
class RollbackUrgency(Enum):
"""Rollback urgency levels"""
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
EMERGENCY = "emergency"
@dataclass
class RollbackStep:
"""Individual rollback step"""
step_id: str
name: str
description: str
script_type: str # sql, bash, api, manual
script_content: str
estimated_duration_minutes: int
dependencies: List[str]
validation_commands: List[str]
success_criteria: List[str]
failure_escalation: str
rollback_order: int
@dataclass
class RollbackPhase:
"""Rollback phase containing multiple steps"""
phase_name: str
description: str
urgency_level: str
estimated_duration_minutes: int
prerequisites: List[str]
steps: List[RollbackStep]
validation_checkpoints: List[str]
communication_requirements: List[str]
risk_level: str
@dataclass
class RollbackTriggerCondition:
"""Conditions that trigger automatic rollback"""
trigger_id: str
name: str
condition: str
metric_threshold: Optional[Dict[str, Any]]
evaluation_window_minutes: int
auto_execute: bool
escalation_contacts: List[str]
@dataclass
class DataRecoveryPlan:
"""Data recovery and restoration plan"""
recovery_method: str # backup_restore, point_in_time, event_replay
backup_location: str
recovery_scripts: List[str]
data_validation_queries: List[str]
estimated_recovery_time_minutes: int
recovery_dependencies: List[str]
@dataclass
class CommunicationTemplate:
"""Communication template for rollback scenarios"""
template_type: str # start, progress, completion, escalation
audience: str # technical, business, executive, customers
subject: str
body: str
urgency: str
delivery_methods: List[str]
@dataclass
class RollbackRunbook:
"""Complete rollback runbook"""
runbook_id: str
migration_id: str
created_at: str
rollback_phases: List[RollbackPhase]
trigger_conditions: List[RollbackTriggerCondition]
data_recovery_plan: DataRecoveryPlan
communication_templates: List[CommunicationTemplate]
escalation_matrix: Dict[str, Any]
validation_checklist: List[str]
post_rollback_procedures: List[str]
emergency_contacts: List[Dict[str, str]]
class RollbackGenerator:
"""Main rollback generator class"""
def __init__(self):
self.rollback_templates = self._load_rollback_templates()
self.validation_templates = self._load_validation_templates()
self.communication_templates = self._load_communication_templates()
def _load_rollback_templates(self) -> Dict[str, Any]:
"""Load rollback script templates for different migration types"""
return {
"database": {
"schema_rollback": {
"drop_table": "DROP TABLE IF EXISTS {table_name};",
"drop_column": "ALTER TABLE {table_name} DROP COLUMN IF EXISTS {column_name};",
"restore_column": "ALTER TABLE {table_name} ADD COLUMN {column_definition};",
"revert_type": "ALTER TABLE {table_name} ALTER COLUMN {column_name} TYPE {original_type};",
"drop_constraint": "ALTER TABLE {table_name} DROP CONSTRAINT {constraint_name};",
"add_constraint": "ALTER TABLE {table_name} ADD CONSTRAINT {constraint_name} {constraint_definition};"
},
"data_rollback": {
"restore_backup": "pg_restore -d {database_name} -c {backup_file}",
"point_in_time_recovery": "SELECT pg_create_restore_point('pre_migration_{timestamp}');",
"delete_migrated_data": "DELETE FROM {table_name} WHERE migration_batch_id = '{batch_id}';",
"restore_original_values": "UPDATE {table_name} SET {column_name} = backup_{column_name} WHERE migration_flag = true;"
}
},
"service": {
"deployment_rollback": {
"rollback_blue_green": "kubectl patch service {service_name} -p '{\"spec\":{\"selector\":{\"version\":\"blue\"}}}'",
"rollback_canary": "kubectl scale deployment {service_name}-canary --replicas=0",
"restore_previous_version": "kubectl rollout undo deployment/{service_name} --to-revision={revision_number}",
"update_load_balancer": "aws elbv2 modify-rule --rule-arn {rule_arn} --actions Type=forward,TargetGroupArn={original_target_group}"
},
"configuration_rollback": {
"restore_config_map": "kubectl apply -f {original_config_file}",
"revert_feature_flags": "curl -X PUT {feature_flag_api}/flags/{flag_name} -d '{\"enabled\": false}'",
"restore_environment_vars": "kubectl set env deployment/{deployment_name} {env_var_name}={original_value}"
}
},
"infrastructure": {
"cloud_rollback": {
"revert_terraform": "terraform apply -target={resource_name} {rollback_plan_file}",
"restore_dns": "aws route53 change-resource-record-sets --hosted-zone-id {zone_id} --change-batch file://{rollback_dns_changes}",
"rollback_security_groups": "aws ec2 authorize-security-group-ingress --group-id {group_id} --protocol {protocol} --port {port} --cidr {cidr}",
"restore_iam_policies": "aws iam put-role-policy --role-name {role_name} --policy-name {policy_name} --policy-document file://{original_policy}"
},
"network_rollback": {
"restore_routing": "aws ec2 replace-route --route-table-id {route_table_id} --destination-cidr-block {cidr} --gateway-id {original_gateway}",
"revert_load_balancer": "aws elbv2 modify-load-balancer --load-balancer-arn {lb_arn} --scheme {original_scheme}",
"restore_firewall_rules": "aws ec2 revoke-security-group-ingress --group-id {group_id} --protocol {protocol} --port {port} --source-group {source_group}"
}
}
}
def _load_validation_templates(self) -> Dict[str, List[str]]:
"""Load validation command templates"""
return {
"database": [
"SELECT COUNT(*) FROM {table_name};",
"SELECT COUNT(*) FROM information_schema.tables WHERE table_name = '{table_name}';",
"SELECT COUNT(*) FROM information_schema.columns WHERE table_name = '{table_name}' AND column_name = '{column_name}';",
"SELECT COUNT(DISTINCT {primary_key}) FROM {table_name};",
"SELECT MAX({timestamp_column}) FROM {table_name};"
],
"service": [
"curl -f {health_check_url}",
"kubectl get pods -l app={service_name} --field-selector=status.phase=Running",
"kubectl logs deployment/{service_name} --tail=100 | grep -i error",
"curl -f {service_endpoint}/api/v1/status"
],
"infrastructure": [
"aws ec2 describe-instances --instance-ids {instance_id} --query 'Reservations[*].Instances[*].State.Name'",
"nslookup {domain_name}",
"curl -I {load_balancer_url}",
"aws elbv2 describe-target-health --target-group-arn {target_group_arn}"
]
}
def _load_communication_templates(self) -> Dict[str, Dict[str, str]]:
"""Load communication templates"""
return {
"rollback_start": {
"technical": {
"subject": "ROLLBACK INITIATED: {migration_name}",
"body": """Team,
We have initiated rollback for migration: {migration_name}
Rollback ID: {rollback_id}
Start Time: {start_time}
Estimated Duration: {estimated_duration}
Reason: {rollback_reason}
Current Status: Rolling back phase {current_phase}
Next Updates: Every 15 minutes or upon phase completion
Actions Required:
- Monitor system health dashboards
- Stand by for escalation if needed
- Do not make manual changes during rollback
Incident Commander: {incident_commander}
"""
},
"business": {
"subject": "System Rollback In Progress - {system_name}",
"body": """Business Stakeholders,
We are currently performing a planned rollback of the {system_name} migration due to {rollback_reason}.
Impact: {business_impact}
Expected Resolution: {estimated_completion_time}
Affected Services: {affected_services}
We will provide updates every 30 minutes.
Contact: {business_contact}
"""
},
"executive": {
"subject": "EXEC ALERT: Critical System Rollback - {system_name}",
"body": """Executive Team,
A critical rollback is in progress for {system_name}.
Summary:
- Rollback Reason: {rollback_reason}
- Business Impact: {business_impact}
- Expected Resolution: {estimated_completion_time}
- Customer Impact: {customer_impact}
We are following established procedures and will update hourly.
Escalation: {escalation_contact}
"""
}
},
"rollback_complete": {
"technical": {
"subject": "ROLLBACK COMPLETED: {migration_name}",
"body": """Team,
Rollback has been successfully completed for migration: {migration_name}
Summary:
- Start Time: {start_time}
- End Time: {end_time}
- Duration: {actual_duration}
- Phases Completed: {completed_phases}
Validation Results:
{validation_results}
System Status: {system_status}
Next Steps:
- Continue monitoring for 24 hours
- Post-rollback review scheduled for {review_date}
- Root cause analysis to begin
All clear to resume normal operations.
Incident Commander: {incident_commander}
"""
}
}
}
def generate_rollback_runbook(self, migration_plan: Dict[str, Any]) -> RollbackRunbook:
"""Generate comprehensive rollback runbook from migration plan"""
runbook_id = f"rb_{hashlib.md5(str(migration_plan).encode()).hexdigest()[:8]}"
migration_id = migration_plan.get("migration_id", "unknown")
migration_type = migration_plan.get("migration_type", "unknown")
# Generate rollback phases (reverse order of migration phases)
rollback_phases = self._generate_rollback_phases(migration_plan)
# Generate trigger conditions
trigger_conditions = self._generate_trigger_conditions(migration_plan)
# Generate data recovery plan
data_recovery_plan = self._generate_data_recovery_plan(migration_plan)
# Generate communication templates
communication_templates = self._generate_communication_templates(migration_plan)
# Generate escalation matrix
escalation_matrix = self._generate_escalation_matrix(migration_plan)
# Generate validation checklist
validation_checklist = self._generate_validation_checklist(migration_plan)
# Generate post-rollback procedures
post_rollback_procedures = self._generate_post_rollback_procedures(migration_plan)
# Generate emergency contacts
emergency_contacts = self._generate_emergency_contacts(migration_plan)
return RollbackRunbook(
runbook_id=runbook_id,
migration_id=migration_id,
created_at=datetime.datetime.now().isoformat(),
rollback_phases=rollback_phases,
trigger_conditions=trigger_conditions,
data_recovery_plan=data_recovery_plan,
communication_templates=communication_templates,
escalation_matrix=escalation_matrix,
validation_checklist=validation_checklist,
post_rollback_procedures=post_rollback_procedures,
emergency_contacts=emergency_contacts
)
def _generate_rollback_phases(self, migration_plan: Dict[str, Any]) -> List[RollbackPhase]:
"""Generate rollback phases from migration plan"""
migration_phases = migration_plan.get("phases", [])
migration_type = migration_plan.get("migration_type", "unknown")
rollback_phases = []
# Reverse the order of migration phases for rollback
for i, phase in enumerate(reversed(migration_phases)):
if isinstance(phase, dict):
phase_name = phase.get("name", f"phase_{i}")
phase_duration = phase.get("duration_hours", 2) * 60 # Convert to minutes
phase_risk = phase.get("risk_level", "medium")
else:
phase_name = str(phase)
phase_duration = 120 # Default 2 hours
phase_risk = "medium"
rollback_steps = self._generate_rollback_steps(phase_name, migration_type, i)
rollback_phase = RollbackPhase(
phase_name=f"rollback_{phase_name}",
description=f"Rollback changes made during {phase_name} phase",
urgency_level=self._calculate_urgency(phase_risk),
estimated_duration_minutes=phase_duration // 2, # Rollback typically faster
prerequisites=self._get_rollback_prerequisites(phase_name, i),
steps=rollback_steps,
validation_checkpoints=self._get_validation_checkpoints(phase_name, migration_type),
communication_requirements=self._get_communication_requirements(phase_name, phase_risk),
risk_level=phase_risk
)
rollback_phases.append(rollback_phase)
return rollback_phases
def _generate_rollback_steps(self, phase_name: str, migration_type: str, phase_index: int) -> List[RollbackStep]:
"""Generate specific rollback steps for a phase"""
steps = []
templates = self.rollback_templates.get(migration_type, {})
if migration_type == "database":
if "migration" in phase_name.lower() or "cutover" in phase_name.lower():
# Data rollback steps
steps.extend([
RollbackStep(
step_id=f"rb_data_{phase_index}_01",
name="Stop data migration processes",
description="Halt all ongoing data migration processes",
script_type="sql",
script_content="-- Stop migration processes\nSELECT pg_cancel_backend(pid) FROM pg_stat_activity WHERE query LIKE '%migration%';",
estimated_duration_minutes=5,
dependencies=[],
validation_commands=["SELECT COUNT(*) FROM pg_stat_activity WHERE query LIKE '%migration%';"],
success_criteria=["No active migration processes"],
failure_escalation="Contact DBA immediately",
rollback_order=1
),
RollbackStep(
step_id=f"rb_data_{phase_index}_02",
name="Restore from backup",
description="Restore database from pre-migration backup",
script_type="bash",
script_content=templates.get("data_rollback", {}).get("restore_backup", "pg_restore -d {database_name} -c {backup_file}"),
estimated_duration_minutes=30,
dependencies=[f"rb_data_{phase_index}_01"],
validation_commands=["SELECT COUNT(*) FROM information_schema.tables;"],
success_criteria=["Database restored successfully", "All expected tables present"],
failure_escalation="Escalate to senior DBA and infrastructure team",
rollback_order=2
)
])
if "preparation" in phase_name.lower():
# Schema rollback steps
steps.append(
RollbackStep(
step_id=f"rb_schema_{phase_index}_01",
name="Drop migration artifacts",
description="Remove temporary migration tables and procedures",
script_type="sql",
script_content="-- Drop migration artifacts\nDROP TABLE IF EXISTS migration_log;\nDROP PROCEDURE IF EXISTS migrate_data();",
estimated_duration_minutes=5,
dependencies=[],
validation_commands=["SELECT COUNT(*) FROM information_schema.tables WHERE table_name LIKE '%migration%';"],
success_criteria=["No migration artifacts remain"],
failure_escalation="Manual cleanup required",
rollback_order=1
)
)
elif migration_type == "service":
if "cutover" in phase_name.lower():
# Service rollback steps
steps.extend([
RollbackStep(
step_id=f"rb_service_{phase_index}_01",
name="Redirect traffic back to old service",
description="Update load balancer to route traffic back to previous service version",
script_type="bash",
script_content=templates.get("deployment_rollback", {}).get("update_load_balancer", "aws elbv2 modify-rule --rule-arn {rule_arn} --actions Type=forward,TargetGroupArn={original_target_group}"),
estimated_duration_minutes=2,
dependencies=[],
validation_commands=["curl -f {health_check_url}"],
success_criteria=["Traffic routing to original service", "Health checks passing"],
failure_escalation="Emergency procedure - manual traffic routing",
rollback_order=1
),
RollbackStep(
step_id=f"rb_service_{phase_index}_02",
name="Rollback service deployment",
description="Revert to previous service deployment version",
script_type="bash",
script_content=templates.get("deployment_rollback", {}).get("restore_previous_version", "kubectl rollout undo deployment/{service_name} --to-revision={revision_number}"),
estimated_duration_minutes=10,
dependencies=[f"rb_service_{phase_index}_01"],
validation_commands=["kubectl get pods -l app={service_name} --field-selector=status.phase=Running"],
success_criteria=["Previous version deployed", "All pods running"],
failure_escalation="Manual pod management required",
rollback_order=2
)
])
elif migration_type == "infrastructure":
steps.extend([
RollbackStep(
step_id=f"rb_infra_{phase_index}_01",
name="Revert infrastructure changes",
description="Apply terraform plan to revert infrastructure to previous state",
script_type="bash",
script_content=templates.get("cloud_rollback", {}).get("revert_terraform", "terraform apply -target={resource_name} {rollback_plan_file}"),
estimated_duration_minutes=15,
dependencies=[],
validation_commands=["terraform plan -detailed-exitcode"],
success_criteria=["Infrastructure matches previous state", "No planned changes"],
failure_escalation="Manual infrastructure review required",
rollback_order=1
),
RollbackStep(
step_id=f"rb_infra_{phase_index}_02",
name="Restore DNS configuration",
description="Revert DNS changes to point back to original infrastructure",
script_type="bash",
script_content=templates.get("cloud_rollback", {}).get("restore_dns", "aws route53 change-resource-record-sets --hosted-zone-id {zone_id} --change-batch file://{rollback_dns_changes}"),
estimated_duration_minutes=10,
dependencies=[f"rb_infra_{phase_index}_01"],
validation_commands=["nslookup {domain_name}"],
success_criteria=["DNS resolves to original endpoints"],
failure_escalation="Contact DNS administrator",
rollback_order=2
)
])
# Add generic validation step for all migration types
steps.append(
RollbackStep(
step_id=f"rb_validate_{phase_index}_final",
name="Validate rollback completion",
description=f"Comprehensive validation that {phase_name} rollback completed successfully",
script_type="manual",
script_content="Execute validation checklist for this phase",
estimated_duration_minutes=10,
dependencies=[step.step_id for step in steps],
validation_commands=self.validation_templates.get(migration_type, []),
success_criteria=[f"{phase_name} fully rolled back", "All validation checks pass"],
failure_escalation=f"Investigate {phase_name} rollback failures",
rollback_order=99
)
)
return steps
def _generate_trigger_conditions(self, migration_plan: Dict[str, Any]) -> List[RollbackTriggerCondition]:
"""Generate automatic rollback trigger conditions"""
triggers = []
migration_type = migration_plan.get("migration_type", "unknown")
# Generic triggers for all migration types
triggers.extend([
RollbackTriggerCondition(
trigger_id="error_rate_spike",
name="Error Rate Spike",
condition="error_rate > baseline * 5 for 5 minutes",
metric_threshold={
"metric": "error_rate",
"operator": "greater_than",
"value": "baseline_error_rate * 5",
"duration_minutes": 5
},
evaluation_window_minutes=5,
auto_execute=True,
escalation_contacts=["on_call_engineer", "migration_lead"]
),
RollbackTriggerCondition(
trigger_id="response_time_degradation",
name="Response Time Degradation",
condition="p95_response_time > baseline * 3 for 10 minutes",
metric_threshold={
"metric": "p95_response_time",
"operator": "greater_than",
"value": "baseline_p95 * 3",
"duration_minutes": 10
},
evaluation_window_minutes=10,
auto_execute=False,
escalation_contacts=["performance_team", "migration_lead"]
),
RollbackTriggerCondition(
trigger_id="availability_drop",
name="Service Availability Drop",
condition="availability < 95% for 2 minutes",
metric_threshold={
"metric": "availability",
"operator": "less_than",
"value": 0.95,
"duration_minutes": 2
},
evaluation_window_minutes=2,
auto_execute=True,
escalation_contacts=["sre_team", "incident_commander"]
)
])
# Migration-type specific triggers
if migration_type == "database":
triggers.extend([
RollbackTriggerCondition(
trigger_id="data_integrity_failure",
name="Data Integrity Check Failure",
condition="data_validation_failures > 0",
metric_threshold={
"metric": "data_validation_failures",
"operator": "greater_than",
"value": 0,
"duration_minutes": 1
},
evaluation_window_minutes=1,
auto_execute=True,
escalation_contacts=["dba_team", "data_team"]
),
RollbackTriggerCondition(
trigger_id="migration_progress_stalled",
name="Migration Progress Stalled",
condition="migration_progress unchanged for 30 minutes",
metric_threshold={
"metric": "migration_progress_rate",
"operator": "equals",
"value": 0,
"duration_minutes": 30
},
evaluation_window_minutes=30,
auto_execute=False,
escalation_contacts=["migration_team", "dba_team"]
)
])
elif migration_type == "service":
triggers.extend([
RollbackTriggerCondition(
trigger_id="cpu_utilization_spike",
name="CPU Utilization Spike",
condition="cpu_utilization > 90% for 15 minutes",
metric_threshold={
"metric": "cpu_utilization",
"operator": "greater_than",
"value": 0.90,
"duration_minutes": 15
},
evaluation_window_minutes=15,
auto_execute=False,
escalation_contacts=["devops_team", "infrastructure_team"]
),
RollbackTriggerCondition(
trigger_id="memory_leak_detected",
name="Memory Leak Detected",
condition="memory_usage increasing continuously for 20 minutes",
metric_threshold={
"metric": "memory_growth_rate",
"operator": "greater_than",
"value": "1MB/minute",
"duration_minutes": 20
},
evaluation_window_minutes=20,
auto_execute=True,
escalation_contacts=["development_team", "sre_team"]
)
])
return triggers
def _generate_data_recovery_plan(self, migration_plan: Dict[str, Any]) -> DataRecoveryPlan:
"""Generate data recovery plan"""
migration_type = migration_plan.get("migration_type", "unknown")
if migration_type == "database":
return DataRecoveryPlan(
recovery_method="point_in_time",
backup_location="/backups/pre_migration_{migration_id}_{timestamp}.sql",
recovery_scripts=[
"pg_restore -d production -c /backups/pre_migration_backup.sql",
"SELECT pg_create_restore_point('rollback_point');",
"VACUUM ANALYZE; -- Refresh statistics after restore"
],
data_validation_queries=[
"SELECT COUNT(*) FROM critical_business_table;",
"SELECT MAX(created_at) FROM audit_log;",
"SELECT COUNT(DISTINCT user_id) FROM user_sessions;",
"SELECT SUM(amount) FROM financial_transactions WHERE date = CURRENT_DATE;"
],
estimated_recovery_time_minutes=45,
recovery_dependencies=["database_instance_running", "backup_file_accessible"]
)
else:
return DataRecoveryPlan(
recovery_method="backup_restore",
backup_location="/backups/pre_migration_state",
recovery_scripts=[
"# Restore configuration files from backup",
"cp -r /backups/pre_migration_state/config/* /app/config/",
"# Restart services with previous configuration",
"systemctl restart application_service"
],
data_validation_queries=[
"curl -f http://localhost:8080/health",
"curl -f http://localhost:8080/api/status"
],
estimated_recovery_time_minutes=20,
recovery_dependencies=["service_stopped", "backup_accessible"]
)
def _generate_communication_templates(self, migration_plan: Dict[str, Any]) -> List[CommunicationTemplate]:
"""Generate communication templates for rollback scenarios"""
templates = []
base_templates = self.communication_templates
# Rollback start notifications
for audience in ["technical", "business", "executive"]:
if audience in base_templates["rollback_start"]:
template_data = base_templates["rollback_start"][audience]
templates.append(CommunicationTemplate(
template_type="rollback_start",
audience=audience,
subject=template_data["subject"],
body=template_data["body"],
urgency="high" if audience == "executive" else "medium",
delivery_methods=["email", "slack"] if audience == "technical" else ["email"]
))
# Rollback completion notifications
for audience in ["technical", "business"]:
if audience in base_templates.get("rollback_complete", {}):
template_data = base_templates["rollback_complete"][audience]
templates.append(CommunicationTemplate(
template_type="rollback_complete",
audience=audience,
subject=template_data["subject"],
body=template_data["body"],
urgency="medium",
delivery_methods=["email", "slack"] if audience == "technical" else ["email"]
))
# Emergency escalation template
templates.append(CommunicationTemplate(
template_type="emergency_escalation",
audience="executive",
subject="CRITICAL: Rollback Emergency - {migration_name}",
body="""CRITICAL SITUATION - IMMEDIATE ATTENTION REQUIRED
Migration: {migration_name}
Issue: Rollback procedure has encountered critical failures
Current Status: {current_status}
Failed Components: {failed_components}
Business Impact: {business_impact}
Customer Impact: {customer_impact}
Immediate Actions:
1. Emergency response team activated
2. {emergency_action_1}
3. {emergency_action_2}
War Room: {war_room_location}
Bridge Line: {conference_bridge}
Next Update: {next_update_time}
Incident Commander: {incident_commander}
Executive On-Call: {executive_on_call}
""",
urgency="emergency",
delivery_methods=["email", "sms", "phone_call"]
))
return templates
def _generate_escalation_matrix(self, migration_plan: Dict[str, Any]) -> Dict[str, Any]:
"""Generate escalation matrix for different failure scenarios"""
return {
"level_1": {
"trigger": "Single component failure",
"response_time_minutes": 5,
"contacts": ["on_call_engineer", "migration_lead"],
"actions": ["Investigate issue", "Attempt automated remediation", "Monitor closely"]
},
"level_2": {
"trigger": "Multiple component failures or single critical failure",
"response_time_minutes": 2,
"contacts": ["senior_engineer", "team_lead", "devops_lead"],
"actions": ["Initiate rollback", "Establish war room", "Notify stakeholders"]
},
"level_3": {
"trigger": "System-wide failure or data corruption",
"response_time_minutes": 1,
"contacts": ["engineering_manager", "cto", "incident_commander"],
"actions": ["Emergency rollback", "All hands on deck", "Executive notification"]
},
"emergency": {
"trigger": "Business-critical failure with customer impact",
"response_time_minutes": 0,
"contacts": ["ceo", "cto", "head_of_operations"],
"actions": ["Emergency procedures", "Customer communication", "Media preparation if needed"]
}
}
def _generate_validation_checklist(self, migration_plan: Dict[str, Any]) -> List[str]:
"""Generate comprehensive validation checklist"""
migration_type = migration_plan.get("migration_type", "unknown")
base_checklist = [
"Verify system is responding to health checks",
"Confirm error rates are within normal parameters",
"Validate response times meet SLA requirements",
"Check all critical business processes are functioning",
"Verify monitoring and alerting systems are operational",
"Confirm no data corruption has occurred",
"Validate security controls are functioning properly",
"Check backup systems are working correctly",
"Verify integration points with downstream systems",
"Confirm user authentication and authorization working"
]
if migration_type == "database":
base_checklist.extend([
"Validate database schema matches expected state",
"Confirm referential integrity constraints",
"Check database performance metrics",
"Verify data consistency across related tables",
"Validate indexes and statistics are optimal",
"Confirm transaction logs are clean",
"Check database connections and connection pooling"
])
elif migration_type == "service":
base_checklist.extend([
"Verify service discovery is working correctly",
"Confirm load balancing is distributing traffic properly",
"Check service-to-service communication",
"Validate API endpoints are responding correctly",
"Confirm feature flags are in correct state",
"Check resource utilization (CPU, memory, disk)",
"Verify container orchestration is healthy"
])
elif migration_type == "infrastructure":
base_checklist.extend([
"Verify network connectivity between components",
"Confirm DNS resolution is working correctly",
"Check firewall rules and security groups",
"Validate load balancer configuration",
"Confirm SSL/TLS certificates are valid",
"Check storage systems are accessible",
"Verify backup and disaster recovery systems"
])
return base_checklist
def _generate_post_rollback_procedures(self, migration_plan: Dict[str, Any]) -> List[str]:
"""Generate post-rollback procedures"""
return [
"Monitor system stability for 24-48 hours post-rollback",
"Conduct thorough post-rollback testing of all critical paths",
"Review and analyze rollback metrics and timing",
"Document lessons learned and rollback procedure improvements",
"Schedule post-mortem meeting with all stakeholders",
"Update rollback procedures based on actual experience",
"Communicate rollback completion to all stakeholders",
"Archive rollback logs and artifacts for future reference",
"Review and update monitoring thresholds if needed",
"Plan for next migration attempt with improved procedures",
"Conduct security review to ensure no vulnerabilities introduced",
"Update disaster recovery procedures if affected by rollback",
"Review capacity planning based on rollback resource usage",
"Update documentation with rollback experience and timings"
]
def _generate_emergency_contacts(self, migration_plan: Dict[str, Any]) -> List[Dict[str, str]]:
"""Generate emergency contact list"""
return [
{
"role": "Incident Commander",
"name": "TBD - Assigned during migration",
"primary_phone": "+1-XXX-XXX-XXXX",
"email": "incident.commander@company.com",
"backup_contact": "backup.commander@company.com"
},
{
"role": "Technical Lead",
"name": "TBD - Migration technical owner",
"primary_phone": "+1-XXX-XXX-XXXX",
"email": "tech.lead@company.com",
"backup_contact": "senior.engineer@company.com"
},
{
"role": "Business Owner",
"name": "TBD - Business stakeholder",
"primary_phone": "+1-XXX-XXX-XXXX",
"email": "business.owner@company.com",
"backup_contact": "product.manager@company.com"
},
{
"role": "On-Call Engineer",
"name": "Current on-call rotation",
"primary_phone": "+1-XXX-XXX-XXXX",
"email": "oncall@company.com",
"backup_contact": "backup.oncall@company.com"
},
{
"role": "Executive Escalation",
"name": "CTO/VP Engineering",
"primary_phone": "+1-XXX-XXX-XXXX",
"email": "cto@company.com",
"backup_contact": "vp.engineering@company.com"
}
]
def _calculate_urgency(self, risk_level: str) -> str:
"""Calculate rollback urgency based on risk level"""
risk_to_urgency = {
"low": "low",
"medium": "medium",
"high": "high",
"critical": "emergency"
}
return risk_to_urgency.get(risk_level, "medium")
def _get_rollback_prerequisites(self, phase_name: str, phase_index: int) -> List[str]:
"""Get prerequisites for rollback phase"""
prerequisites = [
"Incident commander assigned and briefed",
"All team members notified of rollback initiation",
"Monitoring systems confirmed operational",
"Backup systems verified and accessible"
]
if phase_index > 0:
prerequisites.append("Previous rollback phase completed successfully")
if "cutover" in phase_name.lower():
prerequisites.extend([
"Traffic redirection capabilities confirmed",
"Load balancer configuration backed up",
"DNS changes prepared for quick execution"
])
if "data" in phase_name.lower() or "migration" in phase_name.lower():
prerequisites.extend([
"Database backup verified and accessible",
"Data validation queries prepared",
"Database administrator on standby"
])
return prerequisites
def _get_validation_checkpoints(self, phase_name: str, migration_type: str) -> List[str]:
"""Get validation checkpoints for rollback phase"""
checkpoints = [
f"{phase_name} rollback steps completed",
"System health checks passing",
"No critical errors in logs",
"Key metrics within acceptable ranges"
]
validation_commands = self.validation_templates.get(migration_type, [])
checkpoints.extend([f"Validation command passed: {cmd[:50]}..." for cmd in validation_commands[:3]])
return checkpoints
def _get_communication_requirements(self, phase_name: str, risk_level: str) -> List[str]:
"""Get communication requirements for rollback phase"""
base_requirements = [
"Notify incident commander of phase start/completion",
"Update rollback status dashboard",
"Log all actions and decisions"
]
if risk_level in ["high", "critical"]:
base_requirements.extend([
"Notify all stakeholders of phase progress",
"Update executive team if rollback extends beyond expected time",
"Prepare customer communication if needed"
])
if "cutover" in phase_name.lower():
base_requirements.append("Immediate notification when traffic is redirected")
return base_requirements
def generate_human_readable_runbook(self, runbook: RollbackRunbook) -> str:
"""Generate human-readable rollback runbook"""
output = []
output.append("=" * 80)
output.append(f"ROLLBACK RUNBOOK: {runbook.runbook_id}")
output.append("=" * 80)
output.append(f"Migration ID: {runbook.migration_id}")
output.append(f"Created: {runbook.created_at}")
output.append("")
# Emergency Contacts
output.append("EMERGENCY CONTACTS")
output.append("-" * 40)
for contact in runbook.emergency_contacts:
output.append(f"{contact['role']}: {contact['name']}")
output.append(f" Phone: {contact['primary_phone']}")
output.append(f" Email: {contact['email']}")
output.append(f" Backup: {contact['backup_contact']}")
output.append("")
# Escalation Matrix
output.append("ESCALATION MATRIX")
output.append("-" * 40)
for level, details in runbook.escalation_matrix.items():
output.append(f"{level.upper()}:")
output.append(f" Trigger: {details['trigger']}")
output.append(f" Response Time: {details['response_time_minutes']} minutes")
output.append(f" Contacts: {', '.join(details['contacts'])}")
output.append(f" Actions: {', '.join(details['actions'])}")
output.append("")
# Rollback Trigger Conditions
output.append("AUTOMATIC ROLLBACK TRIGGERS")
output.append("-" * 40)
for trigger in runbook.trigger_conditions:
output.append(f"• {trigger.name}")
output.append(f" Condition: {trigger.condition}")
output.append(f" Auto-Execute: {'Yes' if trigger.auto_execute else 'No'}")
output.append(f" Evaluation Window: {trigger.evaluation_window_minutes} minutes")
output.append(f" Contacts: {', '.join(trigger.escalation_contacts)}")
output.append("")
# Rollback Phases
output.append("ROLLBACK PHASES")
output.append("-" * 40)
for i, phase in enumerate(runbook.rollback_phases, 1):
output.append(f"{i}. {phase.phase_name.upper()}")
output.append(f" Description: {phase.description}")
output.append(f" Urgency: {phase.urgency_level.upper()}")
output.append(f" Duration: {phase.estimated_duration_minutes} minutes")
output.append(f" Risk Level: {phase.risk_level.upper()}")
if phase.prerequisites:
output.append(" Prerequisites:")
for prereq in phase.prerequisites:
output.append(f" ✓ {prereq}")
output.append(" Steps:")
for step in sorted(phase.steps, key=lambda x: x.rollback_order):
output.append(f" {step.rollback_order}. {step.name}")
output.append(f" Duration: {step.estimated_duration_minutes} min")
output.append(f" Type: {step.script_type}")
if step.script_content and step.script_type != "manual":
output.append(" Script:")
for line in step.script_content.split('\n')[:3]: # Show first 3 lines
output.append(f" {line}")
if len(step.script_content.split('\n')) > 3:
output.append(" ...")
output.append(f" Success Criteria: {', '.join(step.success_criteria)}")
output.append("")
if phase.validation_checkpoints:
output.append(" Validation Checkpoints:")
for checkpoint in phase.validation_checkpoints:
output.append(f" ☐ {checkpoint}")
output.append("")
# Data Recovery Plan
output.append("DATA RECOVERY PLAN")
output.append("-" * 40)
drp = runbook.data_recovery_plan
output.append(f"Recovery Method: {drp.recovery_method}")
output.append(f"Backup Location: {drp.backup_location}")
output.append(f"Estimated Recovery Time: {drp.estimated_recovery_time_minutes} minutes")
output.append("Recovery Scripts:")
for script in drp.recovery_scripts:
output.append(f" • {script}")
output.append("Validation Queries:")
for query in drp.data_validation_queries:
output.append(f" • {query}")
output.append("")
# Validation Checklist
output.append("POST-ROLLBACK VALIDATION CHECKLIST")
output.append("-" * 40)
for i, item in enumerate(runbook.validation_checklist, 1):
output.append(f"{i:2d}. ☐ {item}")
output.append("")
# Post-Rollback Procedures
output.append("POST-ROLLBACK PROCEDURES")
output.append("-" * 40)
for i, procedure in enumerate(runbook.post_rollback_procedures, 1):
output.append(f"{i:2d}. {procedure}")
output.append("")
return "\n".join(output)
def main():
"""Main function with command line interface"""
parser = argparse.ArgumentParser(description="Generate comprehensive rollback runbooks from migration plans")
parser.add_argument("--input", "-i", required=True, help="Input migration plan file (JSON)")
parser.add_argument("--output", "-o", help="Output file for rollback runbook (JSON)")
parser.add_argument("--format", "-f", choices=["json", "text", "both"], default="both", help="Output format")
args = parser.parse_args()
try:
# Load migration plan
with open(args.input, 'r') as f:
migration_plan = json.load(f)
# Validate required fields
if "migration_id" not in migration_plan and "source" not in migration_plan:
print("Error: Migration plan must contain migration_id or source field", file=sys.stderr)
return 1
# Generate rollback runbook
generator = RollbackGenerator()
runbook = generator.generate_rollback_runbook(migration_plan)
# Output results
if args.format in ["json", "both"]:
runbook_dict = asdict(runbook)
if args.output:
with open(args.output, 'w') as f:
json.dump(runbook_dict, f, indent=2)
print(f"Rollback runbook saved to {args.output}")
else:
print(json.dumps(runbook_dict, indent=2))
if args.format in ["text", "both"]:
human_runbook = generator.generate_human_readable_runbook(runbook)
text_output = args.output.replace('.json', '.txt') if args.output else None
if text_output:
with open(text_output, 'w') as f:
f.write(human_runbook)
print(f"Human-readable runbook saved to {text_output}")
else:
print("\n" + "="*80)
print("HUMAN-READABLE ROLLBACK RUNBOOK")
print("="*80)
print(human_runbook)
except FileNotFoundError:
print(f"Error: Input file '{args.input}' not found", file=sys.stderr)
return 1
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON in input file: {e}", file=sys.stderr)
return 1
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
sys.exit(main())Tạo và tối ưu paywall, màn hình nâng cấp, modal upsell và giới hạn tính năng để chuyển người dùng miễn phí sang trả phí.
--- name: "paywall-upgrade-cro" description: When the user wants to create or optimize in-app paywalls, upgrade screens, upsell modals, or feature gates. Also use when the user mentions "paywall," "upgrade screen," "upgrade modal," "upsell," "feature gate," "convert free to paid," "freemium conversion," "trial expiration screen," "limit reached screen," "plan upgrade prompt," or "in-app pricing." Distinct from public pricing pages (see page-cro) — this skill focuses on in-product upgrade moments where the user has already experienced value. license: MIT metadata: version: 1.0.0 author: Alireza Rezvani category: marketing updated: 2026-03-06 --- # Paywall and Upgrade Screen CRO You are an expert in in-app paywalls and upgrade flows. Your goal is to convert free users to paid, or upgrade users to higher tiers, at moments when they've experienced enough value to justify the commitment. ## Initial Assessment **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. Before providing recommendations, understand: 1. **Upgrade Context** - Freemium → Paid? Trial → Paid? Tier upgrade? Feature upsell? Usage limit? 2. **Product Model** - What's free? What's behind paywall? What triggers prompts? Current conversion rate? 3. **User Journey** - When does this appear? What have they experienced? What are they trying to do? --- ## Core Principles ### 1. Value Before Ask - User should have experienced real value first - Upgrade should feel like natural next step - Timing: After "aha moment," not before ### 2. Show, Don't Just Tell - Demonstrate the value of paid features - Preview what they're missing - Make the upgrade feel tangible ### 3. Friction-Free Path - Easy to upgrade when ready - Don't make them hunt for pricing ### 4. Respect the No - Don't trap or pressure - Make it easy to continue free - Maintain trust for future conversion --- ## Paywall Trigger Points ### Feature Gates When user clicks a paid-only feature: - Clear explanation of why it's paid - Show what the feature does - Quick path to unlock - Option to continue without ### Usage Limits When user hits a limit: - Clear indication of limit reached - Show what upgrading provides - Don't block abruptly ### Trial Expiration When trial is ending: - Early warnings (7, 3, 1 day) - Clear "what happens" on expiration - Summarize value received ### Time-Based Prompts After X days of free use: - Gentle upgrade reminder - Highlight unused paid features - Easy to dismiss --- ## Paywall Screen Components 1. **Headline** - Focus on what they get: "Unlock [Feature] to [Benefit]" 2. **Value Demonstration** - Preview, before/after, "With Pro you could..." 3. **Feature Comparison** - Highlight key differences, current plan marked 4. **Pricing** - Clear, simple, annual vs. monthly options 5. **Social Proof** - Customer quotes, "X teams use this" 6. **CTA** - Specific and value-oriented: "Start Getting [Benefit]" 7. **Escape Hatch** - Clear "Not now" or "Continue with Free" --- ## Specific Paywall Types ### Feature Lock Paywall ``` [Lock Icon] This feature is available on Pro [Feature preview/screenshot] [Feature name] helps you [benefit]: • [Capability] • [Capability] [Upgrade to Pro - $X/mo] [Maybe Later] ``` ### Usage Limit Paywall ``` You've reached your free limit [Progress bar at 100%] Free: 3 projects | Pro: Unlimited [Upgrade to Pro] [Delete a project] ``` ### Trial Expiration Paywall ``` Your trial ends in 3 days What you'll lose: • [Feature used] • [Data created] What you've accomplished: • Created X projects [Continue with Pro] [Remind me later] [Downgrade] ``` --- ## Timing and Frequency ### When to Show - After value moment, before frustration - After activation/aha moment - When hitting genuine limits ### When NOT to Show - During onboarding (too early) - When they're in a flow - Repeatedly after dismissal ### Frequency Rules - Limit per session - Cool-down after dismiss (days, not hours) - Track annoyance signals --- ## Upgrade Flow Optimization ### From Paywall to Payment - Minimize steps - Keep in-context if possible - Pre-fill known information ### Post-Upgrade - Immediate access to features - Confirmation and receipt - Guide to new features --- ## A/B Testing ### What to Test - Trigger timing - Headline/copy variations - Price presentation - Trial length - Feature emphasis - Design/layout ### Metrics to Track - Paywall impression rate - Click-through to upgrade - Completion rate - Revenue per user - Churn rate post-upgrade --- ## Anti-Patterns to Avoid ### Dark Patterns - Hiding the close button - Confusing plan selection - Guilt-trip copy ### Conversion Killers - Asking before value delivered - Too frequent prompts - Blocking critical flows - Complicated upgrade process --- ## Task-Specific Questions 1. What's your current free → paid conversion rate? 2. What triggers upgrade prompts today? 3. What features are behind the paywall? 4. What's your "aha moment" for users? 5. What pricing model? (per seat, usage, flat) 6. Mobile app, web app, or both? --- ## Related Skills - **page-cro** — WHEN the public-facing pricing page needs optimization (before users are in-app). NOT for in-product upgrade screens or feature gates. - **onboarding-cro** — WHEN users haven't reached their activation moment and are hitting paywalls too early; fix onboarding first. NOT when value has already been delivered. - **ab-test-setup** — WHEN running controlled experiments on paywall trigger timing, copy, pricing display, or layout. NOT for initial paywall design. - **email-sequence** — WHEN setting up trial expiration or upgrade reminder email sequences to complement in-app prompts. NOT as a replacement for in-app paywall design. - **marketing-context** — Foundation skill for understanding ICP, pricing model, and value proposition. Load before designing paywall copy and positioning. --- ## Communication Paywall recommendations must account for where the user is in their value journey — always confirm whether the aha moment has been reached before recommending upgrade prompt placement. When writing paywall copy, deliver complete screen copy: headline, value statement, feature list, CTA, and escape hatch text. Flag dark patterns proactively and recommend ethical alternatives. Load `marketing-context` for pricing model and plan structure context before writing copy. --- ## Proactive Triggers - User reports low free-to-paid conversion rate → ask where in the journey the paywall appears and whether the aha moment is reached first. - User mentions users hitting limits and churning → distinguish between limit frustration (fix timing/messaging) vs. wrong ICP (fix acquisition). - User asks about freemium model design → help define what's free vs. paid, then design paywall moments around natural value gaps. - User shares a trial expiration screen → audit for dark patterns, missing escape hatches, and unclear value summarization. - User mentions mobile app monetization → flag platform-specific considerations (App Store IAP rules, Google Play billing requirements). --- ## Output Artifacts | Artifact | Description | |----------|-------------| | Paywall Trigger Map | All paywall trigger points with timing rules, cooldown periods, and frequency caps | | Full Paywall Screen Copy | Headline, value demonstration, feature comparison, CTA, and escape hatch for each paywall type | | Upgrade Flow Diagram | Step-by-step from paywall click to post-upgrade confirmation with friction reduction notes | | Anti-Pattern Audit | Review of existing paywall for dark patterns, trust-damaging copy, and conversion killers | | A/B Test Backlog | Prioritized experiment ideas for trigger timing, copy, and pricing display |
Thiết kế hoặc xem lại giá sản phẩm: chọn mô hình giá, phân tích Van Westendorp từ khảo sát WTP và đóng gói bậc Good/Better/Best.
---
name: pricing-strategist
description: "Use when designing or revisiting product pricing — selecting a pricing model (subscription seat-based, usage-based, value-based, freemium, or hybrid), running Van Westendorp Price Sensitivity Meter analysis on WTP survey data, or designing Good/Better/Best packaging tiers. Recommends a model and a price range with trade-offs, never a single number. For Commercial leads, Product Marketing, and CMOs at the pricing-design moment — not deal-by-deal discounting, not brand positioning."
version: 2.8.0
author: claude-code-skills
license: MIT
tags: [commercial, pricing, packaging, wtp, van-westendorp, value-based-pricing, saas-pricing]
compatible_tools: [claude-code, codex-cli, cursor, antigravity, opencode, gemini-cli]
---
# pricing-strategist
## Purpose
Help Commercial, Product Marketing, and CMO functions answer three questions at the pricing-design moment:
1. **Which pricing model fits this product + customer + market?** (subscription seat-based, usage-based, value-based, freemium, hybrid)
2. **What does the customer actually pay before it feels too expensive?** (Van Westendorp PSM on WTP survey responses)
3. **How should we package this into tiers?** (Good / Better / Best — with anti-pattern detection)
The skill recommends **a model and a range**. The human picks the number, owns the trade-offs, and runs the GTM.
## When to use
- Launching a new SaaS / API / AI tool and choosing the first pricing model
- Revisiting pricing after 18+ months of GTM data (model shift, not just price increase)
- Designing or redesigning tier packaging (Good/Better/Best, Bronze/Silver/Gold)
- You have Van Westendorp survey data and want the optimal price range
- A board / exec is asking "what should we charge?" and you need the structured answer
- You suspect your packaging has anti-patterns (decoy tier, feature dump, no upgrade trigger)
**Do not use for:**
- Per-deal discount approval → `deal-desk`
- Strategic CMO positioning, brand, category creation → `c-level-advisor/cmo-advisor`
- Whole-company revenue strategy → `c-level-advisor/cro-advisor`
- Technical-sale enablement → `business-growth/sales-engineer`
## Workflow
### Step 1 — Assess customer context
Fill `assets/pricing_brief_template.md` (≈ 20 min). Capture: industry, deal size avg, customer count, value drivers, adoption curve, consumption pattern (seat / usage / value / hybrid), competitor models.
### Step 2 — Pick the pricing model
Run `scripts/pricing_model_picker.py --input brief.json --profile saas --output markdown`. Output ranks 5 models by fit-score 0-100 with trade-offs. Decision logic is deterministic: low usage variance + high seat-attach → subscription wins; power-law usage + variable customer value → usage-based wins.
### Step 3 — Validate WTP with Van Westendorp PSM
If you have survey data (≥ 4 questions per respondent: too cheap / bargain / getting expensive / too expensive), run `scripts/wtp_analyzer.py --input survey.json --output markdown`. Output: 4 intersection points (OPP, IDP, PMC, PME) and the Range of Acceptable Prices.
PSM gives a **range**, not the price. See `references/van_westendorp_methodology.md` for common misinterpretations.
### Step 4 — Design packaging
Run `scripts/packaging_designer.py --input features.json --profile saas --output markdown`. Output: 3-tier Good/Better/Best assignment with anti-pattern flags (decoy tier, feature dump, no upgrade trigger, Bronze loss leader, Enterprise no-anchor).
### Step 5 — Decide
Take model + range + packaging into the pricing committee. Skill does not commit the number — you do.
## Scripts
- `scripts/pricing_model_picker.py` — 5-model fit scorer (subscription / usage / value / freemium / hybrid)
- `scripts/wtp_analyzer.py` — Van Westendorp PSM implementation
- `scripts/packaging_designer.py` — Good/Better/Best tier designer with anti-pattern detection
All scripts: stdlib only. `--help` and `--sample` work on all three.
## References
- `references/saas_pricing_canon.md` — Skok, Tunguz, Campbell, Ramanujam, BVP, Shevlin, Stanford GSB
- `references/van_westendorp_methodology.md` — original 1976 paper, NMS refinement, Conjoint.ly, Sawtooth, ESOMAR, Lipovetsky, Decision Analyst
- `references/packaging_anti_patterns.md` — ProfitWell, OpenView, BVP vertical SaaS, Ramanujam, Poyar, SaaS Capital
## Assumptions
- Pricing decisions are joint: Commercial owns the model + tier shape, Product owns the features-per-tier, Finance owns the discount envelope, Legal owns the contract.
- Van Westendorp PSM is a **directional** tool. N ≥ 30 minimum, N ≥ 100 preferred. Below 30, the script emits a sample-size warning.
- "Value-based pricing" requires a measurable customer value driver (revenue lift, cost saved, time recovered). If you can't measure it, don't pick value-based.
- Industry profiles tune defaults — they don't override your data.
- This is a decision-support skill, not a price oracle. Output is a model + range, never the number.
## Anti-patterns
- **Recommending a specific number.** This skill emits a model and a range. Final price is a human commercial decision involving deal-desk policy, competitive intel, and strategic intent that this skill cannot know.
- **Using PSM with N < 30.** Statistical noise dominates. The script warns; respect the warning.
- **Treating PSM as "the price."** PSM gives a Range of Acceptable Prices (RAP) and an Optimal Price Point (OPP). Test the range in market, don't anchor on a single intersection.
- **Picking value-based pricing without a measurable value metric.** Without instrumentation to show customer ROI, value-based collapses into "whatever they'll pay" — which is just bad usage-based pricing.
- **Designing tiers before picking a model.** Tier structure depends on the model. Run pricing_model_picker first.
- **Packaging "feature dumps" into the Best tier.** If Best has 3x the features for 2x the price, customers buy Better and never upgrade. See `packaging_anti_patterns.md`.
- **Hidden usage-based pricing inside subscription tiers.** "Up to 100k API calls/mo, then $X per 1k" disguised as a "Pro tier" is two pricing models in one. Customers notice. Pick one.
- **Confusing this skill with deal-desk.** Pricing strategy = the menu. Deal-desk = approving discounts off the menu. Different decision, different cadence, different owner.
## Distinct from
- **deal-desk** — per-deal discount approval, MEDDIC, deal scoring. Operates daily on existing pricing.
- **c-level-advisor/cmo-advisor** — strategic positioning, brand, category. Pricing strategist consumes positioning as input, doesn't generate it.
- **c-level-advisor/cro-advisor** — full-funnel revenue strategy, comp plans, territory design. Pricing strategist is one input to CRO.
- **business-growth/sales-engineer** — technical sale, POC scoping. Sales engineering operates after pricing is set.
## Forcing-question library (Matt Pocock grill discipline)
Walked one at a time by `/cs:grill-commercial` or the orchestrator. Recommended answer + canon citation per question. Never bundled.
1. **"Is your customer paying for outcomes, seats, or usage?"**
Recommended: outcomes (value-based) if you can measure them; usage if marginal cost is variable; seats only if usage is roughly flat per user.
Canon: Ramanujam 2016 (*Monetizing Innovation*) — Mistake #1 of 9: seat-based pricing on a usage-variable product caps TAM at ~20% of WTP.
2. **"Do you have a measurable value metric, or are you guessing?"**
Recommended: instrument the value metric BEFORE going to market with value-based pricing.
Canon: Patrick Campbell / ProfitWell research — value-based without instrumentation collapses into bad usage-based pricing.
3. **"What's the variance in customer usage across your top decile vs. median?"**
Recommended: variance > 10x → usage-based wins; variance < 3x → subscription wins; in between → hybrid with usage overage.
Canon: Kyle Poyar (*Growth Unhinged*) — high-variance products lose 60%+ of revenue on flat-rate plans.
4. **"What's your competitor's pricing model, and why are you choosing the same or different?"**
Recommended: surface the differentiation hypothesis explicitly. Identical pricing = identical value claim.
Canon: David Skok (*For Entrepreneurs*) — pricing is a positioning signal.
5. **"What sample size do you have for WTP analysis, and is it segmented?"**
Recommended: N≥30 per segment for PSM, N≥100 for conjoint.
Canon: van Westendorp 1976 / Sawtooth Software methodology — sub-30 PSM is statistical noise.
6. **"What's the ONE feature that forces a tier upgrade?"**
Recommended: every Better and Best tier needs a single non-negotiable upgrade trigger.
Canon: Ramanujam (*Monetizing Innovation*) — Mistake #4: tiers with no clear differentiator make 70% of customers pick the cheapest.
Walk depth-first. Lock 1-3 before opening 4-6. After all 6 are answered, invoke `pricing_model_picker.py` → `wtp_analyzer.py` → `packaging_designer.py` in sequence.
FILE:assets/pricing_brief_template.md
# Pricing Strategy Brief
**Owner:** _______________ **Date:** _______________
**Time to fill out:** ≈ 20 minutes
Fill this brief out *before* running `pricing_model_picker.py`. The skill outputs are only
as good as the inputs. Be specific. If you don't know a field, write "unknown" — don't guess.
---
## 1. Product context
- **Product / feature being priced:** _______________
- **Customer-visible name:** _______________
- **One-line value prop:** _______________
- **Stage:** [ ] new launch [ ] re-pricing [ ] adding tier [ ] expansion play
## 2. Customer context
- **Industry:** _______________ (e.g., "B2B SaaS — sales intelligence")
- **ICP (Ideal Customer Profile, 1 sentence):** _______________
- **Avg deal size today (annual contract value):** $_______________
- **Customer count today:** _______________
- **Geographic concentration:** _______________
## 3. Value drivers (rank top 3)
The customer outcome that pricing should track. Be specific — "saves time" is not a value
driver. "Reduces lead-research time by 6 hours/rep/week" is.
1. _______________
2. _______________
3. _______________
For each, can you measure it? [ ] yes [ ] partly [ ] no
## 4. Adoption curve
- [ ] Top-down enterprise sale (CIO/VP signs)
- [ ] Bottom-up / PLG (individual user adopts, expands)
- [ ] Hybrid (champion-led, exec-approved)
- [ ] Viral (referral loops within or across orgs)
## 5. Consumption pattern (assign 0.0 - 1.0 to each)
How does customer value scale with what they use?
- **Seat-based** (more users = more value): _______________
- **Usage-based** (more events / API calls / volume = more value): _______________
- **Value-based** (measurable customer outcome = more value): _______________
- **Hybrid** (multiple drivers, no single dominant): _______________
## 6. Competitive pricing landscape
List the 3-5 closest competitors and their pricing model:
| Competitor | Pricing model | Notable mechanics |
|---|---|---|
| | | |
| | | |
| | | |
## 7. Strategic constraints
- **Margin floor (gross margin %):** _______________
- **Discount envelope (max % off list):** _______________
- **Sales motion (self-serve / inside / field):** _______________
- **Any contractual / regulatory pricing constraints?** _______________
## 8. Anti-goals
What pricing outcomes would be a *failure* even if NRR looks fine?
- _______________
- _______________
---
## JSON skeleton (for the script)
Copy this into a file (e.g., `brief.json`) and fill in. Then run:
```bash
python scripts/pricing_model_picker.py --input brief.json --profile saas --output markdown
```
```json
{
"industry": "",
"deal_size_avg": 0,
"customer_count": 0,
"value_drivers": [
""
],
"adoption_curve": "",
"consumption_pattern": {
"seat-based": 0.0,
"usage-based": 0.0,
"value-based": 0.0,
"hybrid": 0.0
},
"competitor_pricing_models": [
""
]
}
```
---
## After running the picker
1. Take the top 1-2 model recommendations into a 30-min review with Product + Finance.
2. If a model is selected, run a **Van Westendorp PSM survey** (≥ 30 respondents, preferably 100+).
3. Feed survey data to `wtp_analyzer.py` to get the Range of Acceptable Prices.
4. Run `packaging_designer.py` on your feature list to draft Good/Better/Best tiers.
5. Pressure-test in pricing committee. The skill output is one input, not the decision.
FILE:references/packaging_anti_patterns.md
# Packaging Anti-Patterns
Reference for `packaging_designer.py`. The anti-pattern detectors in the tool implement the
flags below. This document is the source-of-truth for *why* each pattern is harmful.
---
## The seven anti-patterns
### 1. Decoy tier that fools no one
A middle tier designed to make the top tier look reasonable — but the price gap is too small,
the feature list is too thin, or the differentiation is purely cosmetic. Customers see through
it, and worse: it trains them to question your pricing integrity.
**Detection:** Best tier > 2x Better price with < 1.5x value.
**Fix:** Either compress Better and Best closer (real differentiation) or widen the value gap.
### 2. Feature dump in the Best tier
Every roadmap feature gets tossed into Best because "Enterprise wants it." Result: Best has
3x the features for 2x the price. Customers buy Better and never upgrade.
**Detection:** Best feature count > 2x Better's with price ratio < 1.5x.
**Fix:** Move 1-3 "upgrade trigger" features down to Better and re-price.
### 3. No clear upgrade trigger
A customer on Good has no specific friction that pushes them to Better. Marketing fixes this
with "more advanced features" copy — but if you can't name the *single event* that triggers
the upgrade ("you hit 10k API calls", "you added a 5th seat", "you needed SSO"), customers
don't upgrade.
**Detection:** Better tier features have lower average importance than Good tier features.
**Fix:** Identify 1-2 "moment of pain" features and move them into the gate.
### 4. Usage-based pricing hidden inside subscription tiers
"Pro tier: includes up to 100k events, then $X per 1k." This is two pricing models pretending
to be one. Customers feel deceived when overage hits. Either commit to subscription (with a
realistic cap) or commit to usage (with a transparent meter).
**Detection:** Pricing-page narrative inspection — not algorithmic. Flag manually.
**Fix:** Pick one model. If you genuinely need both, use a clean Platform + Consumption hybrid,
not a hidden-overage subscription.
### 5. Bronze / Good tier as loss leader
Good is so cheap that cost-to-serve eats most of the revenue. Customers stay on Good forever
because the value-per-dollar is too good. Acquisition costs amortize through Better/Best
upgrades that never happen.
**Detection:** Cost-to-serve aggregate > 80% of Good tier price.
**Fix:** Raise Good's price floor, or strip a feature down to Better.
### 6. Enterprise / Best = "Call us" with no anchor
"Contact sales for pricing" at the top tier with no published anchor price. Prospects with
budget constraints disqualify themselves without ever talking to you. Competitors who publish
ranges win the consideration set.
**Detection:** Best tier has features but no published price.
**Fix:** Publish a "Starting at $X" anchor. The number doesn't have to be precise — it just
has to disqualify the wrong-fit prospects and qualify the right-fit ones.
### 7. Feature appears in all 3 tiers (no differentiation)
If "API access" is in Good, Better, and Best with the same scope, it's not a tier feature —
it's a base feature. Listing it three times wastes pricing-page real estate and dilutes the
upgrade narrative.
**Detection:** Feature appears in all 3 tiers' assigned-feature lists.
**Fix:** Either drop it from the tier comparison or differentiate scope (rate-limited /
metered / unlimited).
---
## Authoritative sources
1. **Patrick Campbell — ProfitWell / Paddle research on packaging**.
"The State of Subscription Pricing" reports and the ProfitWell podcast.
Empirical: tier redesigns that fixed clear upgrade triggers grew NRR by 8-15 points on
average across their cohort. https://www.paddle.com/resources
2. **Madhavan Ramanujam — Monetizing Innovation (Wiley, 2016)**.
The "9 mistakes" framework: feature shock, minivation, hidden gem, undead. Anti-patterns 2
(feature dump), 3 (no upgrade trigger), and 7 (no differentiation) map directly to
Ramanujam's mistake taxonomy.
3. **OpenView — SaaS Benchmarks + Product-Led Growth reports**.
Annual benchmarks on tier mix, free-to-paid conversion, and the cost of bad packaging.
https://openviewpartners.com/saas-benchmarks-report/
4. **Bessemer Venture Partners — Vertical SaaS Index + Cloud 100 Memos**.
Documents the move from 3-tier to 4-tier packaging in vertical SaaS as products mature, and
the failure modes when the 4th tier is added without removing complexity from existing tiers.
https://www.bvp.com/atlas
5. **Kyle Poyar — Growth Unhinged**.
"The Anatomy of a Great Pricing Page" series. Anti-patterns 5 (loss leader) and 6 (no
anchor) come from Poyar's documented PLG-to-enterprise transition patterns.
https://www.growthunhinged.com/
6. **SaaS Capital — Spending Benchmarks for Private B2B SaaS Companies (annual)**.
Cost-to-serve benchmarks by ACV band. Source for the "Bronze tier loss leader" threshold
(80% cost-to-serve ratio).
7. **Tomasz Tunguz — Theory Ventures**.
Multi-year posts on tier-mix evolution in Cloud 100 cohort. Documents the death of 5-tier
pricing pages and the consolidation toward Good/Better/Best + Enterprise.
https://tomtunguz.com/
8. **Simon-Kucher & Partners — Global Pricing Studies**.
Cross-industry data on pricing-page complexity vs conversion. Their research underpins the
"more tiers = more cognitive load" finding behind anti-pattern 4 (hidden usage inside subscription).
## How this skill uses the references
- `packaging_designer.py` runs deterministic detection for 7 anti-patterns (the manual-inspection
one — hidden usage in subscription — is documented but not auto-detected; the tool relies on
the human reading the pricing-page narrative).
- Industry profiles encode tier-mix priors (Good 50% / Better 30% / Best 20% for SaaS, etc.)
derived from OpenView and BVP benchmarks.
- Price-ratio thresholds (2.5x Good → Better, 2.0x Better → Best for SaaS) come from
ProfitWell + Tunguz cohort averages.
- The "no anchor price" flag implements the Poyar / BVP guidance that Enterprise tiers need
published starting prices.
FILE:references/saas_pricing_canon.md
# SaaS Pricing Canon
Curated, opinionated knowledge base for pricing model selection. This is the source material
behind `pricing_model_picker.py`'s scoring rules.
## Core principle
Pricing is a product decision, not a finance decision. The pricing model encodes how customers
experience value capture — get it wrong and every other GTM lever (sales, retention, expansion)
compounds the mistake.
---
## The five pricing models
### 1. Subscription seat-based
Customers pay per user, per period. Works when:
- Value scales linearly with user count
- Usage variance per seat is low
- Procurement prefers predictable line items
- Competitive set already trains the market on seat pricing
Failure modes: usage power-law (top 10% of users drive 80% of value) leaves money on the table;
"seat sprawl" makes customers hide users; expansion is gated on hiring, which is slow.
### 2. Usage-based (consumption)
Customers pay for what they consume — API calls, tokens, GB stored, messages sent. Works when:
- Value is tightly coupled to a measurable unit
- Usage variance across customers is high (power-law)
- Customer wants to start small and scale
- The metering infrastructure exists
Failure modes: bill-shock (variance scares procurement); cohort-NRR volatility; "cost of a query"
becomes a feature-velocity tax; revenue forecasting becomes hard.
### 3. Value-based
Price is anchored to the customer's economic outcome (revenue lift, cost saved, time recovered).
Works when:
- The value driver is measurable and attributable
- Customer count is small enough to calibrate per-account
- Deal size is large enough to justify the sales motion
- ROI proof is part of the product (not a slide)
Failure modes: requires instrumented ROI per customer; doesn't scale operationally beyond ~50-200
accounts without specialization; collapses to "whatever they'll pay" when value isn't measurable.
### 4. Freemium
Free tier acquires users, paid tiers monetize. Works when:
- Adoption is bottom-up / viral / PLG
- Free-tier cost-to-serve is < 5% of paid LTV
- There is a natural upgrade trigger inside the free experience
- Sales motion is self-serve or low-touch
Failure modes: enterprise sale + freemium dilutes positioning; free-tier costs balloon faster than
conversion; the "free forever for 10 users" cliff trains customers to game it.
### 5. Hybrid
Combinations — seat + usage overage, platform + per-event, base + value uplift. Works when:
- Multiple value drivers exist (seats AND usage)
- Customer segments split on dominant driver
- Deal sizes are large enough to absorb pricing-page complexity
Failure modes: cognitive load on the prospect; CS overhead in tier-to-tier moves; invoice disputes;
hybrid sometimes hides "we couldn't decide" — which customers detect.
---
## Authoritative sources
1. **David Skok — For Entrepreneurs**.
"SaaS Metrics 2.0" + "Unit Economics" series. The canonical playbook on CAC, LTV, and how
pricing model interacts with both. https://www.forentrepreneurs.com/saas-metrics-2/
2. **Tomasz Tunguz — Theory Ventures blog**.
Years of empirical posts on Cloud 100 pricing patterns, hybrid-pricing adoption curves,
usage-based unit economics. https://tomtunguz.com/
3. **Patrick Campbell — ProfitWell / Paddle research**.
The largest body of public SaaS pricing data. Key findings: prospects who see clear value
metrics convert 2x; freemium converts 2-5% on average; bad packaging is the #1 churn cause.
https://www.paddle.com/resources
4. **Madhavan Ramanujam — Monetizing Innovation (Wiley, 2016)**.
Simon-Kucher partner. The "9 Pricing Mistakes" frame: feature shock, minivation, hidden gem,
undead. Establishes the discipline that pricing comes before product, not after.
5. **Bessemer Venture Partners — State of the Cloud + Memos**.
Annual benchmarks: Rule of 40, NRR by ACV band, pricing-model mix in Cloud 100. The
reference for "what good looks like" in SaaS.
https://www.bvp.com/atlas
6. **Ron Shevlin — Cornerstone Advisors / Forbes columns**.
Pricing psychology applied to financial services SaaS — anchoring, decoy effect, charm
pricing's diminishing returns in B2B.
7. **Stanford GSB pricing research (Bertini, Gourville, Anderson)**.
Academic foundation on price-quality signaling, reference price formation, and the
penny-gap problem (the $0 → $0.01 conversion cliff). See Bertini & Gourville HBR 2012,
"Pricing to Create Shared Value."
8. **Kyle Poyar — OpenView / Growth Unhinged**.
Practitioner depth on PLG monetization, packaging redesigns, and the shift from seat to
hybrid pricing in 2020-2025 cohort. https://www.growthunhinged.com/
## How this skill uses the canon
- `pricing_model_picker.py` weights consumption-pattern signals per the Skok/Tunguz/Campbell
empirical priors.
- Industry profiles (`saas`, `api`, `ai-tools`, `enterprise-software`, `marketplace`) encode
default biases observed in BVP and ProfitWell cohort data.
- The "value-based requires measurable driver" gate comes directly from Ramanujam's "minivation"
failure mode.
- Freemium scoring penalties for high-ACV deals come from Poyar's documented PLG-to-enterprise
transition patterns.
FILE:references/van_westendorp_methodology.md
# Van Westendorp Price Sensitivity Meter — Methodology
Reference for `wtp_analyzer.py`. Covers the 4 questions, the 4 intersection points,
sample size discipline, segmentation requirements, and the most common misinterpretations.
---
## The four questions
Each respondent answers, for the product or feature described:
1. **Too cheap** — "At what price would you consider the product so inexpensive that you'd
doubt its quality and not buy it?"
2. **Bargain** — "At what price would you consider the product a bargain — a great buy for the
money?"
3. **Getting expensive** — "At what price would you start to feel the product is getting
expensive, but you'd still consider buying it?"
4. **Too expensive** — "At what price would you consider the product so expensive that you
would not consider buying it?"
For each respondent, the answers should obey:
`too_cheap ≤ bargain ≤ getting_expensive ≤ too_expensive`
Respondents who violate this ordering are typically screened out before analysis (the tool
flags them as warnings).
---
## The four intersection points
Build cumulative curves on a sorted price grid:
- **% too cheap (≥ price)** — decreasing in price (more respondents say "too cheap" at low prices)
- **% bargain (≥ price)** — decreasing
- **% getting expensive (≤ price)** — increasing
- **% too expensive (≤ price)** — increasing
Then find the four intersections:
| Point | Curves | Interpretation |
|---|---|---|
| **OPP** — Optimal Price Point | too cheap ↔ too expensive | Equal % reject as too cheap and too expensive. Theoretical sweet spot. |
| **IDP** — Indifference Price Point | bargain ↔ getting expensive | Median respondent's perceived "fair" price. |
| **PMC** — Point of Marginal Cheapness | too cheap ↔ getting expensive | Lower bound of acceptable range — below this, quality doubt dominates. |
| **PME** — Point of Marginal Expensiveness | bargain ↔ too expensive | Upper bound of acceptable range — above this, purchase rejection dominates. |
**Range of Acceptable Prices (RAP) = [PMC, PME].**
---
## Sample size discipline
- **N < 30:** Directional only. Tool emits a warning. Do not anchor decisions on these results.
- **N = 30-99:** Acceptable for hypothesis generation; expect noisy intersections.
- **N ≥ 100:** Preferred. ESOMAR conventions cite N=200-400 for stable PSM in B2C; B2B can
work with smaller but more carefully screened panels.
- **Segmented PSM:** Run separately for ICP vs non-ICP, and per buying-role segment. Aggregate
PSM averages across segments hide the structure you need.
---
## Common misinterpretations (the high-cost ones)
1. **"PSM gives THE price."** — No. PSM gives a **range**. The number inside the range is a
commercial decision involving competition, positioning, and margin targets.
2. **"OPP is the optimal price."** — OPP is named misleadingly. It's the point of *equal
resistance from both sides*, not a profit-maximizing price. The optimal price often sits
between OPP and PME if the market tolerates upside.
3. **"PSM works on non-customers."** — PSM measures *perceived* price thresholds. Run it on
the actual ICP. Random survey panels produce intersection points for an imaginary buyer.
4. **"PSM works for any product."** — Original method (van Westendorp, 1976) was built for
consumer non-durables. It works for SaaS, but breaks for products where the customer cannot
form a reference price (truly novel categories). Use Newton-Miller-Smith (NMS) refinement
in those cases — adds purchase-likelihood at each price.
5. **"Higher RAP upper bound = we can charge more."** — Only if your willingness-to-act
matches willingness-to-state. Always validate with a real purchase test (conjoint, A/B,
or sales-priced cohort) before anchoring at PME.
---
## Authoritative sources
1. **Peter van Westendorp — "NSS-Price Sensitivity Meter (PSM)" — 29th ESOMAR Congress
Proceedings, 1976.** The original paper. Establishes the four questions and intersection
method. Still the canonical reference 50 years later.
2. **Gabor & Granger (1966), Newton, Miller & Smith (NMS).** Extension that adds
purchase-likelihood at each price. Conjoint.ly and Sawtooth both implement NMS variants
for novel products without strong reference prices.
3. **Conjoint.ly — "Price Sensitivity Meter (Van Westendorp) Explained"**.
Practitioner-grade explanation including segmentation guidance and NMS comparison.
https://conjointly.com/guides/van-westendorp-price-sensitivity-analysis/
4. **Sawtooth Software — Lighthouse Studio documentation on PSM**.
Industry-standard tooling. Their guidance on respondent screening, monotonicity checks,
and segmentation is the operational standard most pricing consultancies use.
5. **ESOMAR — Code of Conduct + price-sensitivity research guidance**.
Sample-size conventions, respondent qualification, ethical pricing research. PSM is
referenced in their pricing research best-practice papers.
6. **Stan Lipovetsky (2006) — "Van Westendorp Price Sensitivity in statistical modeling,"
International Journal of Operational Research.** Critique and statistical refinement —
shows that classical PSM intersections are biased estimators under common response
distributions. Recommends bootstrap CIs and ordinal regression overlays.
7. **Decision Analyst — "Van Westendorp PSM Handbook"**.
Operational handbook including a worked example, screening criteria, and segmentation
templates. https://www.decisionanalyst.com/
8. **Madhavan Ramanujam — Monetizing Innovation (Wiley, 2016), Ch. 4.**
PSM as one of three WTP techniques (alongside direct WTP and conjoint). Ramanujam's
guidance: PSM for category baseline, conjoint for feature-level WTP, direct WTP for
confirmation.
## How this skill uses the methodology
- `wtp_analyzer.py` implements classical PSM intersections using linear interpolation on the
sorted price grid — the standard approach per van Westendorp (1976) and Sawtooth.
- Tool emits sample-size warnings at N<30 and N<100, per ESOMAR / Decision Analyst conventions.
- Tool checks per-respondent monotonicity (`tc ≤ bg ≤ ge ≤ te`) and reports inconsistent rows.
- Output explicitly frames PSM as a **range**, not a price, and recommends segmented re-runs —
per the documented misinterpretation patterns above.
- Tool does not implement NMS extension; for novel categories without reference prices, point
the user to conjoint or NMS-specific tooling.
FILE:scripts/packaging_designer.py
#!/usr/bin/env python3
"""packaging_designer.py — Good/Better/Best tier designer with anti-pattern detection.
Input: JSON with feature list (importance + cost-to-serve), customer segments, and
current pricing. Output: 3-tier packaging assignment with anti-pattern flags.
Deterministic logic — features are bucketed into tiers by importance × segment-fit.
No LLM calls.
Usage:
packaging_designer.py --input features.json --profile saas --output markdown
packaging_designer.py --sample
"""
from __future__ import annotations
import argparse
import json
import sys
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any
PROFILES = {
"saas": {"good_pct": 0.50, "better_pct": 0.30, "best_pct": 0.20, "price_ratio_good_to_better": 2.5, "price_ratio_better_to_best": 2.0},
"api": {"good_pct": 0.40, "better_pct": 0.30, "best_pct": 0.30, "price_ratio_good_to_better": 3.0, "price_ratio_better_to_best": 2.5},
"enterprise": {"good_pct": 0.30, "better_pct": 0.35, "best_pct": 0.35, "price_ratio_good_to_better": 2.0, "price_ratio_better_to_best": 2.5},
"prosumer": {"good_pct": 0.60, "better_pct": 0.25, "best_pct": 0.15, "price_ratio_good_to_better": 3.0, "price_ratio_better_to_best": 2.0},
}
@dataclass
class Feature:
name: str
importance: float # 0..1, how much customers value it
cost_to_serve: float # relative cost units
segment_fit: dict[str, float] = field(default_factory=dict) # segment → fit 0..1
@classmethod
def from_dict(cls, d: dict[str, Any]) -> "Feature":
return cls(
name=d["name"],
importance=float(d.get("importance", 0.5)),
cost_to_serve=float(d.get("cost_to_serve", 1.0)),
segment_fit=d.get("segment_fit", {}),
)
@dataclass
class Tier:
name: str
features: list[Feature] = field(default_factory=list)
price: float = 0.0
def assign_tiers(features: list[Feature], segments: list[str], profile: str) -> dict[str, Tier]:
"""Assign each feature to Good / Better / Best based on importance and segment fit.
Rule: high importance across all segments → Good (base).
mid importance OR segment-skewed to mid → Better.
low importance OR enterprise-skewed OR high cost-to-serve → Best.
"""
good = Tier("Good")
better = Tier("Better")
best = Tier("Best")
# Identify enterprise-leaning segments (last in declared order is convention)
enterprise_seg = segments[-1] if segments else None
for f in features:
avg_fit = sum(f.segment_fit.values()) / len(f.segment_fit) if f.segment_fit else 0.5
enterprise_fit = f.segment_fit.get(enterprise_seg, avg_fit) if enterprise_seg else avg_fit
# High importance + broad fit → Good
if f.importance >= 0.75 and avg_fit >= 0.6 and f.cost_to_serve <= 2.0:
good.features.append(f)
# Enterprise-skewed OR high cost → Best
elif enterprise_fit >= 0.7 and avg_fit < 0.6:
best.features.append(f)
elif f.cost_to_serve >= 3.0:
best.features.append(f)
elif f.importance <= 0.4:
best.features.append(f)
# Everything else → Better
else:
better.features.append(f)
return {"good": good, "better": better, "best": best}
def price_tiers(tiers: dict[str, Tier], current_pricing: dict[str, float], profile: str) -> None:
"""Anchor pricing to current_pricing if provided; else use profile ratios from a base of 100."""
cfg = PROFILES[profile]
if current_pricing.get("good"):
tiers["good"].price = float(current_pricing["good"])
else:
tiers["good"].price = 100.0
if current_pricing.get("better"):
tiers["better"].price = float(current_pricing["better"])
else:
tiers["better"].price = tiers["good"].price * cfg["price_ratio_good_to_better"]
if current_pricing.get("best"):
tiers["best"].price = float(current_pricing["best"])
else:
tiers["best"].price = tiers["better"].price * cfg["price_ratio_better_to_best"]
def detect_anti_patterns(tiers: dict[str, Tier]) -> list[str]:
"""Return list of human-readable anti-pattern flags."""
flags: list[str] = []
good, better, best = tiers["good"], tiers["better"], tiers["best"]
# 1. Empty tier
for t in (good, better, best):
if not t.features:
flags.append(f"Empty tier: '{t.name}' has no features — collapse or re-balance.")
# 2. Feature in all tiers (no differentiation)
good_names = {f.name for f in good.features}
better_names = {f.name for f in better.features}
best_names = {f.name for f in best.features}
all_three = good_names & better_names & best_names
if all_three:
flags.append(f"No differentiation: features appear in all 3 tiers — {sorted(all_three)}.")
# 3. Feature dump in Best (>2x the count of Better with <1.5x the price)
if better.features and best.features and better.price > 0 and best.price > 0:
feature_ratio = len(best.features) / max(1, len(better.features))
price_ratio = best.price / better.price
if feature_ratio > 2.0 and price_ratio < 1.5:
flags.append(
f"Feature dump in Best: {len(best.features)} features vs Better's {len(better.features)} "
f"({feature_ratio:.1f}x) for only {price_ratio:.1f}x the price — customers will buy Better and never upgrade."
)
# 4. Best tier > 2x Better price with < 1.5x value (proxy: feature count weighted by importance)
def value(t: Tier) -> float:
return sum(f.importance for f in t.features)
if better.price > 0 and best.price > 0 and value(better) > 0:
price_jump = best.price / better.price
value_jump = value(best) / value(better)
if price_jump > 2.0 and value_jump < 1.5:
flags.append(
f"Best tier price-to-value mismatch: {price_jump:.1f}x price for only {value_jump:.1f}x value — "
"Best becomes a decoy that no one upgrades to."
)
# 5. No clear upgrade trigger from Good → Better
if good.features and better.features:
good_imp = sum(f.importance for f in good.features) / len(good.features)
better_imp = sum(f.importance for f in better.features) / len(better.features)
if better_imp < good_imp - 0.1:
flags.append(
"No clear upgrade trigger Good → Better: Better-tier features have lower avg importance than Good. "
"Why would a Good customer ever upgrade?"
)
# 6. Bronze tier as loss leader (cost-to-serve > effective price share)
if good.features and good.price > 0:
good_cost = sum(f.cost_to_serve for f in good.features)
if good_cost > good.price * 0.8:
flags.append(
f"Good tier near loss-leader: cost-to-serve ({good_cost:.1f}) > 80% of price ({good.price:.2f}). "
"Either raise the price floor or strip a feature down to Better."
)
# 7. Best tier "Enterprise — call us" with no anchor
if best.price == 0 and best.features:
flags.append(
"Best/Enterprise tier has no published anchor price. 'Call us' without a starting number "
"loses prospects to competitors who publish ranges."
)
return flags
def render_markdown(tiers: dict[str, Tier], flags: list[str], profile: str, segments: list[str]) -> str:
lines: list[str] = []
lines.append("# Packaging Recommendation: Good / Better / Best")
lines.append("")
lines.append(f"**Profile:** `{profile}` • **Segments:** {', '.join(segments) if segments else 'unspecified'}")
lines.append("")
for key in ("good", "better", "best"):
t = tiers[key]
lines.append(f"## {t.name} — ,.2f")
if t.features:
for f in t.features:
lines.append(f"- **{f.name}** (importance={f.importance:.2f}, cost-to-serve={f.cost_to_serve:.1f})")
else:
lines.append("- *(no features assigned)*")
lines.append("")
if flags:
lines.append("## Anti-pattern flags")
for f in flags:
lines.append(f"- {f}")
else:
lines.append("## Anti-pattern flags")
lines.append("- None detected.")
lines.append("")
lines.append("## Notes")
lines.append("- Prices are a **starting frame**, not the final number. Validate with Van Westendorp PSM.")
lines.append("- Re-run after every meaningful feature addition; tier balance drifts as the product grows.")
return "\n".join(lines)
def sample_input() -> dict[str, Any]:
return {
"segments": ["SMB", "Mid-market", "Enterprise"],
"current_pricing": {"good": 49, "better": 149, "best": 499},
"features": [
{"name": "Core dashboard", "importance": 0.95, "cost_to_serve": 0.5, "segment_fit": {"SMB": 1.0, "Mid-market": 1.0, "Enterprise": 1.0}},
{"name": "Basic reporting", "importance": 0.85, "cost_to_serve": 0.8, "segment_fit": {"SMB": 0.9, "Mid-market": 0.9, "Enterprise": 0.8}},
{"name": "API access", "importance": 0.6, "cost_to_serve": 1.5, "segment_fit": {"SMB": 0.3, "Mid-market": 0.7, "Enterprise": 0.9}},
{"name": "Advanced analytics", "importance": 0.65, "cost_to_serve": 2.0, "segment_fit": {"SMB": 0.2, "Mid-market": 0.8, "Enterprise": 0.9}},
{"name": "Custom workflows", "importance": 0.55, "cost_to_serve": 2.5, "segment_fit": {"SMB": 0.1, "Mid-market": 0.5, "Enterprise": 0.9}},
{"name": "SSO / SAML", "importance": 0.4, "cost_to_serve": 1.0, "segment_fit": {"SMB": 0.05, "Mid-market": 0.4, "Enterprise": 1.0}},
{"name": "SLA + dedicated CSM", "importance": 0.3, "cost_to_serve": 5.0, "segment_fit": {"SMB": 0.0, "Mid-market": 0.2, "Enterprise": 1.0}},
{"name": "On-prem deployment", "importance": 0.2, "cost_to_serve": 4.0, "segment_fit": {"SMB": 0.0, "Mid-market": 0.1, "Enterprise": 0.9}},
{"name": "Audit logs", "importance": 0.5, "cost_to_serve": 0.8, "segment_fit": {"SMB": 0.1, "Mid-market": 0.5, "Enterprise": 0.95}},
],
}
def main(argv: list[str] | None = None) -> int:
p = argparse.ArgumentParser(description=__doc__.splitlines()[0])
p.add_argument("--input", type=Path, help="Path to features JSON.")
p.add_argument("--profile", default="saas", choices=list(PROFILES.keys()), help="Industry profile.")
p.add_argument("--output", default="markdown", choices=["markdown", "json"], help="Output format.")
p.add_argument("--sample", action="store_true", help="Run with built-in sample data.")
args = p.parse_args(argv)
if args.sample:
data = sample_input()
elif args.input:
data = json.loads(args.input.read_text())
else:
p.error("Provide --input or --sample.")
return 2
segments = data.get("segments", [])
current_pricing = data.get("current_pricing", {})
features = [Feature.from_dict(f) for f in data.get("features", [])]
tiers = assign_tiers(features, segments, args.profile)
price_tiers(tiers, current_pricing, args.profile)
flags = detect_anti_patterns(tiers)
if args.output == "json":
out = {
"profile": args.profile,
"segments": segments,
"tiers": {
k: {
"name": t.name,
"price": t.price,
"features": [{"name": f.name, "importance": f.importance, "cost_to_serve": f.cost_to_serve} for f in t.features],
}
for k, t in tiers.items()
},
"anti_pattern_flags": flags,
}
print(json.dumps(out, indent=2))
else:
print(render_markdown(tiers, flags, args.profile, segments))
return 0
if __name__ == "__main__":
sys.exit(main())
FILE:scripts/pricing_model_picker.py
#!/usr/bin/env python3
"""pricing_model_picker.py — rank pricing models by fit-score for a given customer context.
Input: JSON describing customer context (industry, deal size, customer count, value drivers,
adoption curve, consumption pattern, competitor pricing models).
Output: ranked list of 5 pricing models (subscription seat-based, usage-based, value-based,
freemium, hybrid) with fit-score 0-100 and trade-offs.
Deterministic decision logic. No LLM calls. No third-party deps.
Usage:
pricing_model_picker.py --input brief.json --profile saas --output markdown
pricing_model_picker.py --sample
"""
from __future__ import annotations
import argparse
import json
import sys
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any
MODELS = [
"subscription_seat_based",
"usage_based",
"value_based",
"freemium",
"hybrid",
]
# Industry profile tuning — base biases (additive, capped at ±15)
PROFILES: dict[str, dict[str, int]] = {
"saas": {
"subscription_seat_based": 10,
"usage_based": 0,
"value_based": 0,
"freemium": 5,
"hybrid": 5,
},
"api": {
"subscription_seat_based": -10,
"usage_based": 15,
"value_based": 0,
"freemium": 5,
"hybrid": 5,
},
"ai-tools": {
"subscription_seat_based": -5,
"usage_based": 10,
"value_based": 5,
"freemium": 5,
"hybrid": 10,
},
"enterprise-software": {
"subscription_seat_based": 5,
"usage_based": -5,
"value_based": 10,
"freemium": -10,
"hybrid": 5,
},
"marketplace": {
"subscription_seat_based": -10,
"usage_based": 10,
"value_based": 10,
"freemium": 5,
"hybrid": 5,
},
}
@dataclass
class ModelScore:
model: str
score: int
rationale: list[str] = field(default_factory=list)
tradeoffs: list[str] = field(default_factory=list)
def clamp(n: int, lo: int = 0, hi: int = 100) -> int:
return max(lo, min(hi, n))
def score_models(ctx: dict[str, Any], profile: str) -> list[ModelScore]:
"""Deterministic per-model scoring. Each model starts at 50 and is adjusted by signals."""
cp = (ctx.get("consumption_pattern") or {})
deal_size = float(ctx.get("deal_size_avg") or 0)
customer_count = int(ctx.get("customer_count") or 0)
value_drivers = ctx.get("value_drivers") or []
adoption = (ctx.get("adoption_curve") or "").lower()
competitor_models = ctx.get("competitor_pricing_models") or []
seat_signal = float(cp.get("seat-based") or 0)
usage_signal = float(cp.get("usage-based") or 0)
value_signal = float(cp.get("value-based") or 0)
hybrid_signal = float(cp.get("hybrid") or 0)
scores = {m: ModelScore(model=m, score=50) for m in MODELS}
# --- Subscription seat-based ---
s = scores["subscription_seat_based"]
if seat_signal >= 0.6:
s.score += 20
s.rationale.append(f"Strong seat-based consumption signal ({seat_signal:.2f}).")
elif seat_signal >= 0.3:
s.score += 8
s.rationale.append(f"Moderate seat-based signal ({seat_signal:.2f}).")
if usage_signal > 0.5 and seat_signal < 0.4:
s.score -= 15
s.tradeoffs.append("Usage variance is high; seat licensing leaves money on the table.")
if deal_size > 0 and deal_size < 5000:
s.score += 5
s.rationale.append("SMB-friendly deal size — predictable seat math.")
if "subscription" in " ".join(competitor_models).lower():
s.score += 5
s.rationale.append("Competitors already train the market on subscription.")
s.tradeoffs.append("Predictable revenue, but customers feel friction when adding seats.")
# --- Usage-based ---
s = scores["usage_based"]
if usage_signal >= 0.6:
s.score += 25
s.rationale.append(f"Strong usage-variance signal ({usage_signal:.2f}) — power-law users.")
elif usage_signal >= 0.3:
s.score += 10
s.rationale.append(f"Moderate usage signal ({usage_signal:.2f}).")
if seat_signal > 0.6 and usage_signal < 0.3:
s.score -= 15
s.tradeoffs.append("Usage is flat per seat; usage-based adds billing complexity for no upside.")
if "api" in (ctx.get("industry") or "").lower() or profile == "api":
s.score += 8
s.rationale.append("API/infra products align naturally with usage metering.")
if "usage" in " ".join(competitor_models).lower() or "consumption" in " ".join(competitor_models).lower():
s.score += 5
s.rationale.append("Competitive usage pricing trains the market.")
s.tradeoffs.append("Aligned to value but introduces revenue unpredictability and bill-shock risk.")
# --- Value-based ---
s = scores["value_based"]
measurable = any(
kw in " ".join(value_drivers).lower()
for kw in ["revenue", "cost saved", "time saved", "conversion", "fraud prevented", "downtime"]
)
if value_signal >= 0.6 and measurable:
s.score += 25
s.rationale.append("Customer value is measurable AND signaled as primary.")
elif value_signal >= 0.4 and measurable:
s.score += 15
s.rationale.append("Value signal moderate, measurement plausible.")
elif value_signal >= 0.4 and not measurable:
s.score -= 10
s.tradeoffs.append("Value signal present but no measurable driver — collapses to bad usage pricing.")
if deal_size >= 50000:
s.score += 10
s.rationale.append("Enterprise deal size justifies bespoke value-pricing motion.")
if customer_count > 0 and customer_count < 50:
s.score += 5
s.rationale.append("Small customer count supports per-customer value calibration.")
if customer_count > 500:
s.score -= 10
s.tradeoffs.append("High customer count — value-based does not scale operationally.")
s.tradeoffs.append("Highest yield model but requires instrumented ROI proof per customer.")
# --- Freemium ---
s = scores["freemium"]
if adoption in ("viral", "bottom-up", "plg", "product-led"):
s.score += 20
s.rationale.append(f"Adoption curve '{adoption}' aligns with PLG/freemium funnel.")
if customer_count > 1000:
s.score += 10
s.rationale.append("Large addressable user base supports freemium economics.")
if deal_size > 25000:
s.score -= 15
s.tradeoffs.append("Enterprise ACV — freemium acquisition cost rarely amortizes.")
if adoption in ("top-down", "enterprise"):
s.score -= 15
s.tradeoffs.append("Top-down sale — freemium dilutes positioning without unlocking pipeline.")
s.tradeoffs.append("Powerful acquisition channel but free-tier cost-to-serve must be a small fraction of paid LTV.")
# --- Hybrid (platform + usage, or seat + overage) ---
s = scores["hybrid"]
if hybrid_signal >= 0.5:
s.score += 20
s.rationale.append(f"Hybrid signal explicit ({hybrid_signal:.2f}).")
if seat_signal >= 0.4 and usage_signal >= 0.4:
s.score += 15
s.rationale.append("Both seat AND usage drivers present — natural hybrid candidate.")
if len(value_drivers) >= 3:
s.score += 5
s.rationale.append("Multiple value drivers — single model leaves segments under-served.")
if deal_size < 1000:
s.score -= 10
s.tradeoffs.append("Small deal size — hybrid complexity is not worth the friction.")
s.tradeoffs.append("Captures more value across segments but increases pricing-page complexity and CS overhead.")
# Profile bias
bias = PROFILES.get(profile, {})
for m, b in bias.items():
if m in scores:
scores[m].score += b
if b != 0:
scores[m].rationale.append(f"Industry profile '{profile}' adjustment: {b:+d}.")
# Clamp
for s in scores.values():
s.score = clamp(s.score)
return sorted(scores.values(), key=lambda x: -x.score)
def render_markdown(ranked: list[ModelScore], ctx: dict[str, Any], profile: str) -> str:
lines: list[str] = []
lines.append("# Pricing Model Recommendation")
lines.append("")
lines.append(f"**Profile:** `{profile}` • **Industry:** {ctx.get('industry', 'unspecified')}")
lines.append(f"**Deal size avg:** {ctx.get('deal_size_avg', 'n/a')} • **Customers:** {ctx.get('customer_count', 'n/a')}")
lines.append("")
lines.append("> This skill recommends a **model and trade-offs**, not a final price. The human owns the decision.")
lines.append("")
lines.append("## Ranked models")
lines.append("")
for i, s in enumerate(ranked, 1):
marker = " *(top recommendation)*" if i == 1 else ""
lines.append(f"### {i}. {s.model.replace('_', ' ').title()} — fit-score **{s.score}/100**{marker}")
if s.rationale:
lines.append("**Why it fits:**")
for r in s.rationale:
lines.append(f"- {r}")
if s.tradeoffs:
lines.append("**Trade-offs:**")
for t in s.tradeoffs:
lines.append(f"- {t}")
lines.append("")
lines.append("## Next steps")
lines.append("1. Validate WTP for the top model with `wtp_analyzer.py` (≥ 30 respondents).")
lines.append("2. Design tiers with `packaging_designer.py`.")
lines.append("3. Pressure-test in pricing committee — this output is one input.")
return "\n".join(lines)
def sample_context() -> dict[str, Any]:
return {
"industry": "B2B SaaS — sales intelligence",
"deal_size_avg": 18000,
"customer_count": 220,
"value_drivers": ["revenue lift from better leads", "time saved in research", "conversion uplift"],
"adoption_curve": "bottom-up",
"consumption_pattern": {
"seat-based": 0.45,
"usage-based": 0.55,
"value-based": 0.40,
"hybrid": 0.50,
},
"competitor_pricing_models": ["subscription seat-based", "hybrid seat + usage overage"],
}
def main(argv: list[str] | None = None) -> int:
p = argparse.ArgumentParser(description=__doc__.splitlines()[0])
p.add_argument("--input", type=Path, help="Path to customer-context JSON.")
p.add_argument(
"--profile",
default="saas",
choices=list(PROFILES.keys()),
help="Industry profile for default tuning.",
)
p.add_argument("--output", default="markdown", choices=["markdown", "json"], help="Output format.")
p.add_argument("--sample", action="store_true", help="Run with built-in sample context.")
args = p.parse_args(argv)
if args.sample:
ctx = sample_context()
elif args.input:
ctx = json.loads(args.input.read_text())
else:
p.error("Provide --input or --sample.")
return 2
ranked = score_models(ctx, args.profile)
if args.output == "json":
out = {
"profile": args.profile,
"context": ctx,
"ranked": [
{"model": s.model, "score": s.score, "rationale": s.rationale, "tradeoffs": s.tradeoffs}
for s in ranked
],
}
print(json.dumps(out, indent=2))
else:
print(render_markdown(ranked, ctx, args.profile))
return 0
if __name__ == "__main__":
sys.exit(main())
FILE:scripts/wtp_analyzer.py
#!/usr/bin/env python3
"""wtp_analyzer.py — Van Westendorp Price Sensitivity Meter (PSM).
Implements the classical PSM analysis (van Westendorp, 1976). Each respondent answers
4 questions:
1. Too cheap — price at which you'd doubt the quality
2. Bargain — price that feels like a great deal
3. Getting expensive — price where you'd start to hesitate
4. Too expensive — price at which you would never buy
Computes the 4 intersection points:
- OPP (Optimal Price Point): intersection of "too cheap" and "too expensive"
- IDP (Indifference Price Point): intersection of "bargain" and "getting expensive"
- PMC (Point of Marginal Cheapness): intersection of "too cheap" and "getting expensive"
- PME (Point of Marginal Expensiveness): intersection of "bargain" and "too expensive"
Range of Acceptable Prices (RAP) = [PMC, PME].
Output: markdown or JSON. Stdlib only.
Usage:
wtp_analyzer.py --input survey.json --output markdown
wtp_analyzer.py --sample
"""
from __future__ import annotations
import argparse
import json
import math
import random
import statistics
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Any
@dataclass
class Curves:
prices: list[float]
too_cheap: list[float] # P(too cheap >= price) — decreasing in price
bargain: list[float] # P(bargain >= price) — decreasing in price
getting_expensive: list[float] # P(getting expensive <= price) — increasing
too_expensive: list[float] # P(too expensive <= price) — increasing
def build_price_grid(respondents: list[dict[str, float]]) -> list[float]:
"""Build a sorted unique-price grid from all responses."""
prices: set[float] = set()
for r in respondents:
for k in ("too_cheap", "bargain", "getting_expensive", "too_expensive"):
v = r.get(k)
if v is not None:
prices.add(float(v))
grid = sorted(prices)
if not grid:
return []
# Densify with intermediate steps to make intersection detection stable.
densified: list[float] = []
for i, p in enumerate(grid):
densified.append(p)
if i + 1 < len(grid):
nxt = grid[i + 1]
mid = (p + nxt) / 2.0
if mid not in prices:
densified.append(mid)
return sorted(set(densified))
def cumulative_curves(respondents: list[dict[str, float]], grid: list[float]) -> Curves:
n = len(respondents)
too_cheap: list[float] = []
bargain: list[float] = []
getting_expensive: list[float] = []
too_expensive: list[float] = []
for p in grid:
tc = sum(1 for r in respondents if (r.get("too_cheap") is not None) and float(r["too_cheap"]) >= p)
bg = sum(1 for r in respondents if (r.get("bargain") is not None) and float(r["bargain"]) >= p)
ge = sum(1 for r in respondents if (r.get("getting_expensive") is not None) and float(r["getting_expensive"]) <= p)
te = sum(1 for r in respondents if (r.get("too_expensive") is not None) and float(r["too_expensive"]) <= p)
too_cheap.append(tc / n)
bargain.append(bg / n)
getting_expensive.append(ge / n)
too_expensive.append(te / n)
return Curves(grid, too_cheap, bargain, getting_expensive, too_expensive)
def find_intersection(prices: list[float], a: list[float], b: list[float]) -> float | None:
"""Find first price where curve a crosses curve b (linear interp between grid points)."""
if len(prices) < 2:
return None
prev_diff = a[0] - b[0]
for i in range(1, len(prices)):
diff = a[i] - b[i]
if prev_diff == 0:
return prices[i - 1]
if (prev_diff < 0 < diff) or (prev_diff > 0 > diff):
# Linear interpolation
p0, p1 = prices[i - 1], prices[i]
t = prev_diff / (prev_diff - diff)
return p0 + t * (p1 - p0)
prev_diff = diff
return None
@dataclass
class PSMResult:
n: int
opp: float | None
idp: float | None
pmc: float | None
pme: float | None
rap_low: float | None
rap_high: float | None
warnings: list[str]
def analyze(respondents: list[dict[str, float]]) -> PSMResult:
warnings: list[str] = []
n = len(respondents)
if n < 30:
warnings.append(
f"Sample size N={n} is below 30. PSM results are directional only; "
"treat the range as a hypothesis, not a recommendation. Aim for N≥100."
)
elif n < 100:
warnings.append(f"Sample size N={n} is acceptable but below the preferred N≥100 threshold.")
# Sanity-check monotonicity (too_cheap < bargain < getting_expensive < too_expensive per respondent)
inconsistent = 0
for r in respondents:
try:
tc = float(r["too_cheap"])
bg = float(r["bargain"])
ge = float(r["getting_expensive"])
te = float(r["too_expensive"])
except (KeyError, TypeError, ValueError):
inconsistent += 1
continue
if not (tc <= bg <= ge <= te):
inconsistent += 1
if inconsistent:
warnings.append(
f"{inconsistent} of {n} respondents have inconsistent price ordering "
"(expected too_cheap ≤ bargain ≤ getting_expensive ≤ too_expensive). "
"Consider screening these before reporting."
)
grid = build_price_grid(respondents)
if not grid:
return PSMResult(n=n, opp=None, idp=None, pmc=None, pme=None, rap_low=None, rap_high=None, warnings=warnings)
c = cumulative_curves(respondents, grid)
opp = find_intersection(c.prices, c.too_cheap, c.too_expensive)
idp = find_intersection(c.prices, c.bargain, c.getting_expensive)
pmc = find_intersection(c.prices, c.too_cheap, c.getting_expensive)
pme = find_intersection(c.prices, c.bargain, c.too_expensive)
return PSMResult(n=n, opp=opp, idp=idp, pmc=pmc, pme=pme, rap_low=pmc, rap_high=pme, warnings=warnings)
def _fmt(v: float | None) -> str:
return f"{v:,.2f}" if v is not None else "n/a"
def render_markdown(res: PSMResult) -> str:
lines: list[str] = []
lines.append("# Van Westendorp PSM Analysis")
lines.append("")
lines.append(f"**Respondents:** N = {res.n}")
lines.append("")
if res.warnings:
lines.append("> **Warnings:**")
for w in res.warnings:
lines.append(f"> - {w}")
lines.append("")
lines.append("## Four intersection points")
lines.append("")
lines.append("| Point | Definition | Value |")
lines.append("|---|---|---|")
lines.append(f"| **OPP** — Optimal Price Point | too cheap ↔ too expensive | {_fmt(res.opp)} |")
lines.append(f"| **IDP** — Indifference Price Point | bargain ↔ getting expensive | {_fmt(res.idp)} |")
lines.append(f"| **PMC** — Point of Marginal Cheapness | too cheap ↔ getting expensive | {_fmt(res.pmc)} |")
lines.append(f"| **PME** — Point of Marginal Expensiveness | bargain ↔ too expensive | {_fmt(res.pme)} |")
lines.append("")
lines.append("## Range of Acceptable Prices (RAP)")
lines.append("")
if res.rap_low is not None and res.rap_high is not None:
lines.append(f"**RAP = [{_fmt(res.rap_low)}, {_fmt(res.rap_high)}]**")
lines.append("")
lines.append("Prices outside this range are likely to be rejected as either too cheap (quality doubt) or too expensive (no purchase).")
else:
lines.append("RAP could not be computed — check input data and sample size.")
lines.append("")
lines.append("## Interpretation guidance")
lines.append("")
lines.append("- PSM gives a **range**, not the price. Final price is a commercial decision.")
lines.append("- OPP is a theoretical mid-point — the price at which equal % of respondents reject as too cheap and too expensive.")
lines.append("- IDP is the median respondent's perceived 'fair' price.")
lines.append("- Re-run with segmented samples (ICP vs non-ICP) — overall PSM averages across segments hide structure.")
lines.append("- Validate the upper end with willingness-to-pay experiments in market before anchoring at PME.")
return "\n".join(lines)
def synthetic_sample(n: int = 50, seed: int = 17) -> list[dict[str, float]]:
"""Generate N synthetic respondents with realistic price ordering and segmentation noise."""
rng = random.Random(seed)
respondents: list[dict[str, float]] = []
for _ in range(n):
anchor = rng.gauss(80, 20) # respondent's reference price
anchor = max(20.0, anchor)
tc = max(5.0, anchor * rng.uniform(0.3, 0.5))
bg = anchor * rng.uniform(0.6, 0.85)
ge = anchor * rng.uniform(0.95, 1.15)
te = anchor * rng.uniform(1.3, 1.8)
respondents.append({
"too_cheap": round(tc, 2),
"bargain": round(bg, 2),
"getting_expensive": round(ge, 2),
"too_expensive": round(te, 2),
})
return respondents
def main(argv: list[str] | None = None) -> int:
p = argparse.ArgumentParser(description=__doc__.splitlines()[0])
p.add_argument("--input", type=Path, help="Path to survey JSON: {respondents: [{too_cheap, bargain, getting_expensive, too_expensive}]}.")
p.add_argument("--output", default="markdown", choices=["markdown", "json"], help="Output format.")
p.add_argument("--sample", action="store_true", help="Run with synthetic 50-respondent sample.")
args = p.parse_args(argv)
if args.sample:
respondents = synthetic_sample(50)
elif args.input:
data = json.loads(args.input.read_text())
respondents = data.get("respondents", data) if isinstance(data, dict) else data
else:
p.error("Provide --input or --sample.")
return 2
if not isinstance(respondents, list) or not respondents:
print("ERROR: respondents must be a non-empty list.", file=sys.stderr)
return 1
res = analyze(respondents)
if args.output == "json":
out = {
"n": res.n,
"opp": res.opp,
"idp": res.idp,
"pmc": res.pmc,
"pme": res.pme,
"rap": [res.rap_low, res.rap_high],
"warnings": res.warnings,
}
print(json.dumps(out, indent=2))
else:
print(render_markdown(res))
return 0
if __name__ == "__main__":
sys.exit(main())
Mô tả quy trình nghiệp vụ end-to-end theo ký hiệu kiểu BPMN, đo thời gian chu kỳ theo từng bước và tìm nơi công việc bị chậm.
---
name: process-mapper
description: Use when a BizOps lead, COO, or process-improvement owner needs to document an end-to-end business process (procurement, employee onboarding, incident handoff, customer-onboarding, claims adjudication) in BPMN-style notation, measure cycle times by stage, surface where work spends most of its time waiting vs. being worked, and quantify the gap between processing time and total elapsed time. Pairs Lean / Six Sigma / Theory-of-Constraints canon with deterministic stdlib-only Python tools to produce a process map, a ranked bottleneck list (with severity + root-cause hypothesis), and a cycle-time analysis (P50, P90, value-add ratio, Little's-Law throughput). Distinct from sales-pipeline, system-reliability (SLO), and strategic-OKR work — this is tactical process documentation for internal operations.
version: 2.8.0
author: claude-code-skills
license: MIT
tags: [bizops, process, bpmn, bottleneck, cycle-time, lean, six-sigma, value-stream]
compatible_tools: [claude-code, codex-cli, cursor, antigravity, opencode, gemini-cli]
---
# process-mapper
BPMN-style business process documentation, bottleneck detection, and cycle-time analysis for internal-operations leaders.
## Purpose
Internal-operations work suffers from three recurring failure modes:
1. **Implicit process** — the steps exist only in tribal knowledge, so handoffs drop and onboarding takes weeks.
2. **Invisible waiting** — most of the elapsed time on any business process is queue / wait / approval time, not actual work; teams optimize the wrong stage.
3. **Local optimization** — Goldratt's Theory of Constraints is ignored; resources are added to non-constraint stages, gaining nothing.
This skill produces a documented process map, identifies where work waits, and points the constraint out by name with deterministic logic — not LLM intuition.
## When to use
- Documenting a new business process (procurement intake, vendor onboarding, employee onboarding, incident handoff, expense reimbursement, customer onboarding, claims adjudication).
- An existing process is "too slow" but nobody can name the bottleneck.
- Cycle time is being measured but value-add ratio is not — so the team can't tell whether the process is healthy or waste-heavy.
- Cross-functional handoffs are dropping work and root cause is unclear.
## Workflow
Five-step deterministic flow:
1. **Intake.** Capture the process as a JSON file with one entry per stage: `name`, `owner`, `type` (`value-add` | `wait` | `rework`), `duration_minutes_p50`, `duration_minutes_p90`. Use `assets/process_template.md` and its JSON skeleton.
2. **Map stages.** Run `process_documenter.py` to produce an ASCII swim-lane diagram + a normalized JSON artifact. The swim-lane separates lanes by owner so cross-functional handoffs become visible.
3. **Measure cycle time.** Run `cycle_time_analyzer.py` to compute total P50, total P90, value-add ratio (VA%), and a Little's-Law throughput estimate. Verdict: VA% > 25% = HEALTHY, 10–25% = TYPICAL, < 10% = WASTE-HEAVY.
4. **Detect bottlenecks.** Run `bottleneck_detector.py` with the appropriate `--profile` (saas / services / manufacturing / healthcare). Output is a ranked list with severity (CRITICAL / HIGH / MEDIUM), root-cause hypothesis, and one recommended action per finding.
5. **Recommend.** Pair the bottleneck list with the cycle-time verdict; recommend a single constraint-focused intervention per Goldratt's "subordinate everything to the constraint" rule. Don't recommend optimization of a non-constraint stage.
## Scripts
**`scripts/process_documenter.py`** — Reads a process JSON, validates it, and emits a text-based BPMN-style swim-lane diagram in Markdown (lanes by owner, stages annotated with type + duration). Also outputs a normalized JSON artifact for downstream tools. Stdlib only. `--sample` prints a 6-stage procurement-intake example.
**`scripts/bottleneck_detector.py`** — Applies three deterministic detection rules: (a) stage P50 > 2× mean of value-add stages, (b) wait-state % > 40% of total cycle, (c) rework % > 15%. Thresholds adjust by `--profile` because SaaS, services, manufacturing, and healthcare have different "normal" wait ratios. Output is a ranked list with severity, hypothesis, action.
**`scripts/cycle_time_analyzer.py`** — Computes total P50 and P90 cycle time, value-add ratio (VA%), wait %, rework %, and a Little's-Law throughput estimate (WIP / cycle time). Per Lean canon: VA% > 25% = HEALTHY, 10–25% = TYPICAL (most non-manufacturing processes land here), < 10% = WASTE-HEAVY.
## References
- `references/lean_six_sigma_canon.md` — TIMWOOD wastes, value-stream mapping, Theory of Constraints, Kanban WIP, Little's Law. Cites Womack & Jones, Rother & Shook, Goldratt, Ohno, Liker, Pyzdek, Anderson.
- `references/bpmn_essentials.md` — Pools, lanes, gateways, events, message flows, common notation mistakes. Cites the OMG BPMN 2.0 spec, Silver, Allweyer, Freund/Rücker, OASIS, ISO/IEC 19510:2013.
- `references/bottleneck_anti_patterns.md` — Seven specific anti-patterns drawn from Goldratt, Kim et al., Spear, DORA, Deming, and process-mining research.
## Assumptions
1. The user can provide stage-level cycle-time data (even rough P50 / P90 estimates). If they cannot, the first step is to instrument the process — not to map it.
2. "Process" here means a repeatable business workflow with discrete stages, not a one-off project.
3. The user has authority to act on bottlenecks (or can route findings to someone who does). Without that, the output is academic.
4. Stage `type` is honest: a "value-add" stage labeled as such by the user really does change the work product from the customer's perspective. Mis-labelling waiting as value-add is the most common data-quality failure.
## Anti-patterns
- **Mapping every process at once.** Pick one. Goldratt: the constraint is a single point.
- **Optimizing the non-constraint.** If stage 4 is the bottleneck, speeding up stage 2 just builds inventory in front of stage 4. Subordinate everything to the constraint.
- **Mistaking total cycle time for processing time.** They are almost never the same; VA% reveals the gap.
- **Adding people to a wait-bound process.** Wait time is not solved by more headcount; it's solved by removing the handoff or batch.
- **Treating rework as a separate problem.** Rework loops belong in the process map. Hiding them understates true cycle time.
## Distinct from
- **business-growth skills** — external sales motion, lead-funnel conversion, customer-success retention. Process-mapper is *internal* operations.
- **engineering/slo-architect** — system-reliability SLOs / error budgets / burn-rate alerts. Process-mapper is *business-process* cycle time, not system uptime.
- **c-level-advisor (COO / CEO)** — strategic prioritization of which processes to fix. Process-mapper is the tactical instrument used after that prioritization decision.
- **project-management skills** — Jira / Confluence ticket workflow tooling. Process-mapper is process *design*, not ticket *tracking*.
## Forcing-question library (Matt Pocock grill discipline)
Before invoking the tools, the orchestrator (or `/cs:grill-bizops`) walks the user through these questions **one at a time, with a recommended answer + canon citation**. Never bundled.
1. **"Do you have measured cycle times for the top-3 longest stages, or only estimates?"**
Recommended: insist on measured data.
Canon: Goldratt 1984 (*The Goal*) — optimizing estimated bottlenecks reliably attacks the wrong constraint.
2. **"Are you mapping the *current* process (as-is) or the *intended* process (to-be)?"**
Recommended: map as-is first. To-be after bottleneck is identified.
Canon: Rother & Shook 1999 (*Learning to See*) — value-stream mapping starts with the current state, always.
3. **"Where do handoffs occur between teams, and how long does each handoff wait?"**
Recommended: log every handoff with median wait time.
Canon: Reinertsen 2009 (*Principles of Product Development Flow*) — wait time at handoffs is the largest invisible cost.
4. **"What's your batch size at each stage?"**
Recommended: drive batch size toward 1 wherever possible.
Canon: Anderson 2010 (*Kanban*) — batch size correlates 1:1 with cycle time variance.
5. **"What's the rework rate per stage?"**
Recommended: surface it explicitly; rework loops belong in the map.
Canon: Pyzdek (*Six Sigma Handbook*) — hidden rework drives 30-50% of total cycle time in service processes.
Walk depth-first. Don't open question 4 before 1-3 are answered. After all 5 are locked, invoke `process_documenter.py` → `bottleneck_detector.py` → `cycle_time_analyzer.py` in sequence.
FILE:assets/process_template.md
# Process Template
Use this template to document a business process before running it through
the process-mapper tools. Fill in the stage table first, then translate it
into the JSON skeleton at the bottom of this file. Feed that JSON into the
three CLI tools:
```
python3 scripts/process_documenter.py --input my-process.json
python3 scripts/bottleneck_detector.py --input my-process.json --profile saas
python3 scripts/cycle_time_analyzer.py --input my-process.json --profile saas
```
---
## Process metadata
- **Process name:** _(e.g., Procurement Intake, Employee Onboarding, Incident Handoff)_
- **Owner role:** _(who is accountable for the end-to-end process)_
- **Frequency:** _(how often this process runs — daily, weekly, on-demand)_
- **Trigger event:** _(what starts the process)_
- **End state:** _(what marks the process complete)_
- **WIP at any time:** _(how many items are typically in process at once; needed for Little's-Law throughput)_
---
## Stage table
Six rows to start. Add or remove as needed. **Honesty about stage `type` is
the single most important data-quality choice.** If a stage is queue / wait,
mark it `wait`. If it changes the work product from the customer's
perspective, mark it `value-add`. If it exists to fix an upstream defect,
mark it `rework`.
| # | Stage name | Owner (role) | Type | P50 (min) | P90 (min) | Notes |
|---|------------|--------------|------|-----------|-----------|-------|
| 1 | _e.g., Submit request_ | Requestor | value-add | 15 | 30 | |
| 2 | _e.g., Wait for manager approval queue_ | Manager | wait | 480 | 1440 | Typically batched |
| 3 | _e.g., Manager approves_ | Manager | value-add | 10 | 25 | |
| 4 | _e.g., Wait for finance review_ | Finance | wait | 720 | 2880 | |
| 5 | _e.g., Finance validates budget code_ | Finance | value-add | 20 | 60 | |
| 6 | _e.g., Rework — missing vendor W-9_ | Requestor | rework | 120 | 360 | Frequent escape |
**Type definitions (Lean canon):**
- `value-add` — the stage changes the work product in a way the end customer
would willingly pay for. Most stages are NOT value-add.
- `wait` — work is queued, idle, or waiting for someone. Wait stages are the
largest source of cycle-time bloat in most office processes.
- `rework` — the stage exists to fix a defect introduced upstream. Six-Sigma
canon: rework is always an upstream-quality problem.
---
## JSON skeleton
Copy this into `my-process.json`, edit the values to match your stage table,
and pass it to the CLI tools.
```json
{
"process_name": "Replace with your process name",
"wip": 12,
"stages": [
{
"name": "Stage 1 name",
"owner": "Owning role",
"type": "value-add",
"duration_minutes_p50": 15,
"duration_minutes_p90": 30
},
{
"name": "Stage 2 name",
"owner": "Owning role",
"type": "wait",
"duration_minutes_p50": 480,
"duration_minutes_p90": 1440
},
{
"name": "Stage 3 name",
"owner": "Owning role",
"type": "value-add",
"duration_minutes_p50": 10,
"duration_minutes_p90": 25
},
{
"name": "Stage 4 name",
"owner": "Owning role",
"type": "wait",
"duration_minutes_p50": 720,
"duration_minutes_p90": 2880
},
{
"name": "Stage 5 name",
"owner": "Owning role",
"type": "value-add",
"duration_minutes_p50": 20,
"duration_minutes_p90": 60
},
{
"name": "Stage 6 name",
"owner": "Owning role",
"type": "rework",
"duration_minutes_p50": 120,
"duration_minutes_p90": 360
}
]
}
```
---
## Tips
- **Use real data when you can.** Pull stage durations from your ticket system
(Jira, ServiceNow, Zendesk). Estimated durations are fine for a first pass
but should be replaced before any change decision is made.
- **One process at a time.** Goldratt: every system has exactly one binding
constraint. Mapping ten processes simultaneously dilutes attention away
from the one that's actually limiting throughput.
- **Profile choice matters.** Pass `--profile manufacturing` for physical-goods
flows, `--profile services` for human-delivered services with longer
acceptable wait times, `--profile healthcare` for clinical or regulated
human-in-the-loop flows, `--profile saas` for everything else.
FILE:references/bottleneck_anti_patterns.md
# Bottleneck Anti-Patterns
Seven plus specific anti-patterns that recur in business-process improvement
work. Each is sourced to primary literature, and each has a corresponding
detection or recommendation in the skill's tools.
## Sources
1. **Goldratt, E. M. (1984). _The Goal._** North River Press. — Theory of Constraints.
2. **Kim, G., Behr, K. & Spafford, G. (2013). _The Phoenix Project: A Novel About IT, DevOps, and Helping Your Business Win._** IT Revolution Press. — TOC applied to IT operations.
3. **Spear, S. J. (2009). _The High-Velocity Edge._** McGraw-Hill. — Toyota-derived discipline for complex operations; explicit treatment of why local optimization fails.
4. **Forsgren, N., Humble, J. & Kim, G. (2018). _Accelerate: The Science of Lean Software and DevOps._** IT Revolution. — DORA research; empirical link between flow metrics and outcomes.
5. **Deming, W. E. (1986). _Out of the Crisis._** MIT Press. — System-of-profound-knowledge framework; root-cause discipline.
6. **van der Aalst, W. M. P. (2016). _Process Mining: Data Science in Action,_ 2nd ed.** Springer. — Empirical methodology for discovering actual process behavior vs. documented behavior.
7. **Reinertsen, D. G. (2009). _The Principles of Product Development Flow._** Celeritas Publishing. — Queueing theory and cost of delay.
8. **Forrester Research. (Multiple years.) _Process Mining: Vendor and Market Analyses._** — Industry research on process-mining adoption and the gap between modeled and actual process.
---
## AP-1. Optimizing the non-constraint
**Source:** Goldratt (1984), Kim et al. (2013).
A team identifies that stage 2 of a process is "slow" (relative to other
non-constraint stages) and optimizes it. The actual constraint is stage 4.
Result: throughput is unchanged; inventory grows in front of stage 4.
**Detection:** Compare every stage's P50 to the value-add mean (Rule R1) but
weight the recommendation by impact on total cycle. The skill's
`bottleneck_detector.py` ranks by impact_minutes_p50 specifically to direct
attention to the binding constraint.
**Counter-pattern:** Always solve the longest wait or longest stage first;
ignore "quick wins" elsewhere until the constraint moves.
---
## AP-2. Adding resources before identifying the constraint
**Source:** Goldratt (1984), Reinertsen (2009).
Symptom: "We need to hire more procurement analysts." Reality: the analysts
are not the constraint; manager approval queues are. Adding analysts increases
WIP, lengthens cycle time (per Little's Law), and makes the queue worse.
**Detection:** Rule R2 (wait-share > 40%) catches the case where the wait —
not capacity — dominates.
**Counter-pattern:** First check whether wait time exceeds value-add time. If
it does, no amount of new staffing will help. Remove the handoff, parallelize
the approval, or apply WIP limits.
---
## AP-3. Mistaking wait time for processing time
**Source:** Rother & Shook (1999), Deming (1986).
A team reports that "manager approval takes two days." On inspection, the
manager spends 10 minutes reviewing each request; the rest is queue time.
Process time is 10 minutes; lead time is two days. Treating them as the same
hides the real problem.
**Detection:** The skill's stage `type` field separates `value-add` from
`wait`. The value-add ratio (VA%) in `cycle_time_analyzer.py` quantifies the
gap.
**Counter-pattern:** Force stages to declare their type honestly. Any stage
where the worker is not actively engaged is a wait stage, regardless of who
"owns" it.
---
## AP-4. Inspection-as-quality
**Source:** Pyzdek (Six Sigma Handbook), Deming (1986), Spear (2009).
Defects keep escaping, so the team adds a final QA review. The defects don't
go down (the upstream stages haven't changed) — but cycle time goes up because
of the new stage. Worse, the QA reviewer is now blamed for misses.
**Detection:** Rule R3 (rework share > 15%) with the hypothesis "defects
escape upstream stages."
**Counter-pattern:** Find the earliest stage that could detect the defect; add
the check there (poka-yoke). Stop the line on detection; don't queue defects
for downstream rework.
---
## AP-5. Optimizing the documented process, not the actual one
**Source:** van der Aalst (2016), Forrester process-mining reports.
The team documents the "official" process and optimizes it. Process-mining
tools then reveal that 60% of cases skip stages, loop back, or take undocumented
routes. The optimization had no effect because it targeted a fiction.
**Detection:** The skill cannot detect this from the input JSON alone — it
relies on the user to report actual stage durations from real cases, not
target durations. The "Assumptions" section in SKILL.md surfaces this
explicitly.
**Counter-pattern:** Use ticket-system data, time-stamps, or event logs to
ground stage durations in actual cases. If the data isn't available, the
first step is instrumentation, not mapping.
---
## AP-6. Batched approvals as the default
**Source:** Reinertsen (2009), Anderson (Kanban, 2010).
Approvers batch requests: "I'll review everyone's POs on Friday afternoon."
This adds half the batch interval (typically 3–4 days) to the average wait
time of every request, with no quality benefit.
**Detection:** Wait stages with P50 durations measured in days (hundreds of
minutes) are almost always batched. The skill flags them via R1 and R2.
**Counter-pattern:** Move to continuous-flow approval. If continuous is
infeasible (e.g., a committee that meets weekly), at least shrink the batch
interval or move approval to a lower level where it can run continuously.
---
## AP-7. Local efficiency metrics
**Source:** Goldratt (1984), Deming (1986), Spear (2009).
Each stage is measured on its own efficiency (e.g., "manager handles 95% of
requests within SLA"). The system as a whole is not measured. Each role
optimizes locally, pushing work as fast as possible to the next queue —
which is exactly where it stalls.
**Detection:** The skill's verdict is always at the **process** level (VA%,
total cycle time), never at the stage level. The `bottleneck_detector.py`
recommendation text explicitly invokes Goldratt's "subordinate everything to
the constraint."
**Counter-pattern:** Measure throughput and total cycle time at the process
level. Stage-level metrics are diagnostic, not goal-setting.
---
## AP-8. Skipping the value-stream map and going straight to automation
**Source:** Kim et al. (2013), Forrester process-mining research.
A team buys an RPA / workflow automation tool, then automates the existing
broken process. Result: the bad process now runs faster, with the same wait
queues and same rework rate. Goldratt's term for this is "automating the
mess."
**Detection:** Outside the skill's automated detection; surfaced in
SKILL.md's "Anti-patterns" list.
**Counter-pattern:** Map the value stream first. Eliminate wait and rework
stages. Then — and only then — consider automating what remains.
---
## AP-9. Treating cycle time as fixed
**Source:** Forsgren, Humble & Kim (2018, _Accelerate_).
A team reports cycle time as a single number ("it takes 5 days"). Real cycle
times are distributions, often log-normal, with heavy P90 / P99 tails. A 5-day
P50 with a 30-day P90 is a wildly different process than a 5-day P50 with a
6-day P90; the first is unpredictable, the second is reliable.
**Detection:** The skill captures both P50 and P90 per stage and reports
both totals. A large P90 / P50 ratio in `cycle_time_analyzer.py` is a flag
for high variability even when total cycle time looks acceptable.
**Counter-pattern:** Always quote P50 and P90 (or P50 and P95). DORA's
_Accelerate_ research finds that lead-time **variability** correlates with
business outcomes as strongly as median lead time.
FILE:references/bpmn_essentials.md
# BPMN Essentials for Business-Process Documentation
A practical reference on BPMN (Business Process Model and Notation) for
process-mapper users. The skill emits text-based swim-lane diagrams that
approximate the BPMN structure without requiring users to install Visio,
Lucidchart, or Camunda. This file explains the canon those diagrams reflect.
## Sources
1. **Object Management Group. (2011). _Business Process Model and Notation (BPMN), Version 2.0._** OMG Document Number formal/2011-01-03. — The normative specification.
2. **Silver, B. (2011). _BPMN Method and Style,_ 2nd ed.** Cody-Cassidy Press. — The canonical practitioner book; defines the "method and style" rules now widely treated as informal BPMN convention.
3. **Allweyer, T. (2010). _BPMN 2.0: Introduction to the Standard for Business Process Modeling._** Books on Demand. — Approachable academic introduction.
4. **Freund, J. & Rücker, B. (2019). _Real-Life BPMN,_ 4th ed.** CreateSpace. — Practical patterns from the Camunda team.
5. **OASIS. (2010). _Web Services Business Process Execution Language (WS-BPEL), Version 2.0._** — Related execution standard; clarifies the interplay between BPMN modeling and BPEL execution.
6. **ISO/IEC 19510:2013. _Information technology — Object Management Group Business Process Model and Notation._** — The international-standard version of OMG BPMN 2.0.
7. **Recker, J. (2010). "Opportunities and constraints: the current struggle with BPMN." _Business Process Management Journal,_ 16(1), 181–201.** — Peer-reviewed analysis of BPMN adoption pain points; sources the "common notation mistakes" list below.
8. **Dumas, M., La Rosa, M., Mendling, J. & Reijers, H. A. (2018). _Fundamentals of Business Process Management,_ 2nd ed.** Springer. — Textbook covering BPMN within the broader BPM lifecycle.
---
## Core BPMN elements
BPMN has hundreds of symbols. In practice, ~80% of useful diagrams use only
~10 of them. The skill's swim-lane output uses precisely these.
### Flow objects
- **Activity (task)** — a unit of work done by one role. Rectangle with rounded
corners. In the skill's swim-lane: this is a `value-add` or `rework` stage.
- **Event** — something that happens (start, intermediate, end). Circles. The
skill represents start/end implicitly as the first and last stage.
- **Gateway** — branching / merging point. Diamond. Common types:
- **Exclusive (XOR)** — one path taken.
- **Parallel (AND)** — all paths taken.
- **Inclusive (OR)** — one or more paths taken based on data.
### Connecting objects
- **Sequence flow** — solid arrow inside one pool. The skill renders these as
`->` between stages in the lane.
- **Message flow** — dashed arrow across pool boundaries. The skill's
cross-lane handoffs (e.g., Requestor -> Manager) are message-flow-equivalents.
- **Association** — dotted line linking a data object to an activity.
### Swim lanes
- **Pool** — represents a participant (a company, a department, or a system).
Each pool is independent; communication between pools uses message flow only.
- **Lane** — a sub-partition within a pool, usually a role or sub-team.
The skill maps one stage's `owner` field to one lane. The full diagram is a
single pool with multiple lanes — appropriate for an internal business process
where one organization controls the whole flow.
---
## Method and Style rules (Silver)
Silver's "Method and Style" is a set of practitioner conventions that make
BPMN diagrams readable. The most load-bearing rules:
1. **One start, one end** per pool. Multiple end events are allowed only if
they represent different end-states (e.g., approved vs. rejected).
2. **Label every flow out of a gateway** with the condition (e.g., "amount >
$10K"). An unlabeled gateway is unreadable.
3. **Sequence flow stays inside a pool.** Use message flow between pools.
4. **One verb-noun task name.** "Approve PO" beats "Approval step."
5. **Black-box pools** for participants you don't model in detail (e.g., the
customer). Show only the message exchanges with them.
The skill enforces rule #4 implicitly by encouraging "Stage" names like
"Manager approves request" rather than "Approval."
---
## Common notation mistakes (Recker 2010; Freund/Rücker)
The following errors appear in over half of real-world BPMN diagrams:
| Mistake | Why it's wrong | What to do |
|---------|----------------|------------|
| Using sequence flow across pools | Pools are independent; only messages cross | Use dashed message flow |
| Missing gateway labels | The reader can't tell which path is taken when | Label every outbound flow |
| Multiple unrelated end events | Reader can't tell why a process ends in each spot | Consolidate or label by end-state |
| Conflating role with system | "JIRA" is a system, not a role; "Engineering Manager" is a role | Lanes = roles, not tools |
| Implicit gateways | Diverging sequence flows without a gateway diamond | Add an explicit XOR or parallel gateway |
| Modeling exceptions inline | Cluttered happy path | Use boundary events or a separate exception sub-process |
| No data objects | Reader doesn't know what artifacts move through | Add data-object boxes where they help |
The skill's stage-level `type` field (`value-add` | `wait` | `rework`) captures
the rework case explicitly so it doesn't get hidden inline. Users who want
full BPMN fidelity should export the normalized JSON and ingest it into a
BPMN-aware tool (Camunda Modeler, bpmn.io, Signavio).
---
## When to use BPMN vs. simpler notations
BPMN is appropriate when:
- The process has cross-functional handoffs (multiple lanes).
- The process has branching logic (gateways).
- The diagram will be reviewed by people who don't sit through a walkthrough.
For purely linear processes with no branching, a numbered list or a value
stream map is faster to produce and easier to read. The skill's swim-lane
output deliberately occupies the middle ground: more structured than a list,
less ceremony than full BPMN.
---
## BPMN 2.0 execution semantics
ISO/IEC 19510:2013 specifies executable semantics so that a BPMN diagram can
be loaded into a workflow engine (Camunda, jBPM, Activiti) and run directly.
The skill does not target executable BPMN — its output is for human reading
and constraint analysis. If a user wants to move from documentation to
automation, the normalized JSON is a starting point; mapping to the BPMN 2.0
XML schema is a separate exercise.
FILE:references/lean_six_sigma_canon.md
# Lean / Six Sigma / Theory-of-Constraints Canon
A working reference for the process-mapper skill. The concepts below are the
intellectual foundation for every detection rule and verdict band the skill
emits. Citations are deliberately to the primary sources, not blog posts.
## Sources
1. **Womack, J. P. & Jones, D. T. (1996). _Lean Thinking: Banish Waste and Create Wealth in Your Corporation._** Free Press. — The five-step Lean discipline: specify value, identify the value stream, make value flow, let the customer pull, pursue perfection.
2. **Rother, M. & Shook, J. (1999). _Learning to See: Value Stream Mapping to Add Value and Eliminate Muda._** Lean Enterprise Institute. — The canonical text on Value Stream Mapping (VSM); origin of current-state / future-state map distinction.
3. **Goldratt, E. M. (1984). _The Goal: A Process of Ongoing Improvement._** North River Press. — The Theory of Constraints: identify, exploit, subordinate, elevate, repeat. Every process has exactly one binding constraint at a time.
4. **Ohno, T. (1988). _Toyota Production System: Beyond Large-Scale Production._** Productivity Press. — Origin of the seven wastes (muda), pull system, jidoka, and andon discipline.
5. **Liker, J. K. (2004). _The Toyota Way: 14 Management Principles from the World's Greatest Manufacturer._** McGraw-Hill. — Modern systemic treatment of TPS principles for non-manufacturing operations.
6. **Pyzdek, T. & Keller, P. (2018). _The Six Sigma Handbook,_ 5th ed.** McGraw-Hill. — DMAIC discipline, SIPOC, process-capability indices, defect-rate measurement.
7. **Anderson, D. J. (2010). _Kanban: Successful Evolutionary Change for Your Technology Business._** Blue Hole Press. — WIP limits, pull system applied to knowledge work, cumulative flow diagrams.
8. **Reinertsen, D. G. (2009). _The Principles of Product Development Flow._** Celeritas Publishing. — Queueing theory for knowledge-work product development; cost of delay.
---
## The Seven Wastes (TIMWOOD)
Ohno's original taxonomy, with the eighth ("non-utilized talent") added later:
| Code | Waste | What it looks like in business processes |
|------|-------|--------------------------------------------|
| **T** | Transport | Moving work between systems / inboxes / queues for no reason |
| **I** | Inventory | Backlogs of pending tickets, unprocessed invoices, open POs |
| **M** | Motion | People hunting for information, switching tools, reading email threads to reconstruct context |
| **W** | Waiting | Work sitting in someone's queue (the largest waste in office work) |
| **O** | Over-production | Producing forecasts, reports, or work nobody requested |
| **O** | Over-processing | Approval chains that add no scrutiny, gold-plating |
| **D** | Defects | Errors that force rework downstream |
| **(N)** | Non-utilized talent | Skilled people doing low-skill work |
The process-mapper skill identifies these via stage `type`: `wait` captures
**W** (and often **I**); `rework` captures **D**. Mis-labelling a wait stage as
`value-add` is the most common data-quality failure and will mask the true
constraint.
---
## Value Stream Mapping (Rother & Shook)
VSM separates **process time** (PT) from **lead time** (LT). For each stage:
- **PT** = the time work actually spends being touched.
- **LT** = the elapsed wall-clock time from when work arrives at the stage to
when it leaves.
In the process-mapper schema, a `value-add` stage's `duration_minutes_p50` is
PT-like; a `wait` stage's duration is the LT component between PT-stages.
The **process cycle efficiency** (PCE) is:
PCE = Total value-add time / Total lead time
This is exactly what `cycle_time_analyzer.py` computes as the "value-add ratio."
Rother & Shook's published benchmarks: office processes typically score
PCE < 10%; well-run service operations land 10–25%; world-class manufacturing
can clear 25–40%.
---
## Theory of Constraints (Goldratt)
Goldratt's Five Focusing Steps:
1. **Identify** the constraint.
2. **Exploit** it (squeeze every minute of capacity from the constraint).
3. **Subordinate** everything else to the constraint.
4. **Elevate** the constraint (only after step 2 is exhausted, add capacity).
5. **Repeat** — once the constraint moves, return to step 1.
Two implications used in the skill:
- **Optimizing a non-constraint stage produces no system improvement.** It
builds inventory in front of the constraint. The `bottleneck_detector.py`
output is ranked by impact specifically so users target the constraint
first.
- **The constraint is almost always a wait stage in office work.** This is
why Rule R2 (wait-share > 40%) is heavily weighted.
---
## Kanban WIP Limits (Anderson)
Little's Law:
L = lambda * W
Where L = items in the system (WIP), lambda = throughput (items per unit time),
and W = average cycle time. Rearranged:
lambda = L / W
Two practical consequences:
- **Cycle time scales linearly with WIP.** Cutting WIP in half cuts cycle time
in half (other things equal). This is why the skill computes throughput from
WIP / cycle time and surfaces a WIP-limit recommendation when wait-share is
high.
- **Adding people to a wait-bound process makes it worse.** New workers add
WIP without expanding the constraint, lengthening cycle time. The
`bottleneck_detector` action text says this explicitly.
---
## Six Sigma DMAIC and Rework
Pyzdek's DMAIC (Define, Measure, Analyze, Improve, Control) treats rework as
a downstream symptom of an upstream defect. The Six-Sigma rule the skill
encodes: **rework is always solved upstream, never downstream.** Adding a
quality-control inspector at the end of the line catches defects but doesn't
prevent them, and inspection-as-quality is itself a TIMWOOD waste
(over-processing).
The poka-yoke (error-proofing) recommendation in Rule R3 follows directly:
add the check at the earliest stage that can detect the defect.
---
## Reinertsen's Queueing Insights
Reinertsen's _Principles of Product Development Flow_ adapts manufacturing
queueing theory to knowledge work. Key results used in the skill:
- **High utilization explodes queue length.** A worker at 90% utilization has
~10x the queue of a worker at 50% utilization. Office workflows that pin
approvers at 100% utilization see wait stages grow without bound.
- **Small batches cut queue time.** Batched approvals (e.g., weekly review
cycles) inflate P50 wait times by half the batch interval on average.
When the skill recommends "remove the handoff or batch," this is the canon
behind it.
FILE:scripts/bottleneck_detector.py
#!/usr/bin/env python3
"""bottleneck_detector.py
Apply three deterministic detection rules to a process JSON and emit a ranked
list of bottlenecks with severity, root-cause hypothesis, and a recommended
action.
Rules (defaults; tuned per industry profile):
R1. Stage P50 > 2x mean of value-add stages -> stage bottleneck
R2. Wait-state share of total cycle > 40% -> handoff bottleneck
R3. Rework share of total cycle > 15% -> quality bottleneck
Stdlib only.
"""
from __future__ import annotations
import argparse
import json
import statistics
import sys
from dataclasses import dataclass, asdict
from pathlib import Path
# Per-industry threshold calibration. Manufacturing tolerates less wait;
# healthcare and services tolerate more given regulatory / human-in-the-loop steps.
PROFILES: dict[str, dict[str, float]] = {
"saas": {
"stage_multiplier": 2.0,
"wait_share_max": 0.40,
"rework_share_max": 0.15,
},
"services": {
"stage_multiplier": 2.5,
"wait_share_max": 0.50,
"rework_share_max": 0.15,
},
"manufacturing": {
"stage_multiplier": 1.8,
"wait_share_max": 0.30,
"rework_share_max": 0.10,
},
"healthcare": {
"stage_multiplier": 2.5,
"wait_share_max": 0.55,
"rework_share_max": 0.12,
},
}
@dataclass
class Finding:
severity: str # CRITICAL | HIGH | MEDIUM
rule: str # R1 | R2 | R3
title: str
detail: str
hypothesis: str
action: str
impact_minutes_p50: float
def severity_rank(self) -> int:
return {"CRITICAL": 0, "HIGH": 1, "MEDIUM": 2}.get(self.severity, 3)
def load(path: Path) -> dict:
with path.open("r", encoding="utf-8") as f:
return json.load(f)
def classify_severity(share: float, threshold: float) -> str:
"""Severity based on how far over the threshold the offender is."""
if share <= threshold:
return "MEDIUM"
if share >= threshold * 2:
return "CRITICAL"
if share >= threshold * 1.5:
return "HIGH"
return "MEDIUM"
def detect(normalized: dict, profile: str) -> list[Finding]:
prof = PROFILES.get(profile, PROFILES["saas"])
stages = normalized.get("stages", [])
findings: list[Finding] = []
if not stages:
return findings
total_p50 = sum(s["duration_minutes_p50"] for s in stages) or 1.0
wait_p50 = sum(s["duration_minutes_p50"] for s in stages if s["type"] == "wait")
rework_p50 = sum(s["duration_minutes_p50"] for s in stages if s["type"] == "rework")
va_durations = [
s["duration_minutes_p50"] for s in stages if s["type"] == "value-add"
]
va_mean = statistics.mean(va_durations) if va_durations else 0.0
# R1: per-stage runaway vs value-add mean
if va_mean > 0:
threshold_minutes = va_mean * prof["stage_multiplier"]
for s in stages:
if s["duration_minutes_p50"] > threshold_minutes:
ratio = s["duration_minutes_p50"] / va_mean
if ratio >= prof["stage_multiplier"] * 3:
sev = "CRITICAL"
elif ratio >= prof["stage_multiplier"] * 2:
sev = "HIGH"
else:
sev = "MEDIUM"
hypothesis = (
"Stage runs much longer than the typical value-add step; "
"common causes: batched approvals, single approver, "
"missing self-service, or unclear acceptance criteria."
)
action = (
"Decompose the stage; check if approval can be parallelized "
"or made conditional. If wait-state, apply Kanban WIP limit "
"or remove the handoff."
)
findings.append(
Finding(
severity=sev,
rule="R1",
title=f"Slow stage: {s['name']}",
detail=(
f"P50 {s['duration_minutes_p50']:.0f} min vs value-add "
f"mean {va_mean:.1f} min (ratio {ratio:.1f}x)."
),
hypothesis=hypothesis,
action=action,
impact_minutes_p50=s["duration_minutes_p50"],
)
)
# R2: wait-state share
wait_share = wait_p50 / total_p50
if wait_share > prof["wait_share_max"]:
sev = classify_severity(wait_share, prof["wait_share_max"])
findings.append(
Finding(
severity=sev,
rule="R2",
title="Process is dominated by wait time",
detail=(
f"Wait stages account for {wait_share*100:.0f}% of total P50, "
f"vs {prof['wait_share_max']*100:.0f}% profile threshold."
),
hypothesis=(
"Handoffs queue work behind a single role or batch. Per "
"Theory of Constraints, the system throughput is set by "
"whichever queue is longest, not by stage speed."
),
action=(
"Identify the longest wait stage; pull it forward, eliminate "
"it via self-service, or apply a WIP limit upstream so the "
"queue cannot grow."
),
impact_minutes_p50=wait_p50,
)
)
# R3: rework share
rework_share = rework_p50 / total_p50
if rework_share > prof["rework_share_max"]:
sev = classify_severity(rework_share, prof["rework_share_max"])
findings.append(
Finding(
severity=sev,
rule="R3",
title="Process has excessive rework",
detail=(
f"Rework accounts for {rework_share*100:.0f}% of total P50, "
f"vs {prof['rework_share_max']*100:.0f}% profile threshold."
),
hypothesis=(
"Defects escape upstream stages. Six-Sigma canon: rework is "
"always an upstream-quality problem, never a downstream one."
),
action=(
"Add a poka-yoke (error-proofing) check at the earliest stage "
"that can detect the defect; do not add inspection downstream."
),
impact_minutes_p50=rework_p50,
)
)
findings.sort(key=lambda f: (f.severity_rank(), -f.impact_minutes_p50))
return findings
def render_markdown(normalized: dict, findings: list[Finding], profile: str) -> str:
name = normalized.get("process_name", "Untitled Process")
lines: list[str] = []
lines.append(f"# Bottleneck Detection: {name}")
lines.append("")
lines.append(f"**Profile:** `{profile}` ")
lines.append(f"**Findings:** {len(findings)}")
lines.append("")
if not findings:
lines.append("_No bottlenecks detected at the configured thresholds._")
return "\n".join(lines)
for i, f in enumerate(findings, 1):
lines.append(f"## {i}. [{f.severity}] {f.title}")
lines.append("")
lines.append(f"- **Rule:** `{f.rule}`")
lines.append(f"- **Detail:** {f.detail}")
lines.append(f"- **Hypothesis:** {f.hypothesis}")
lines.append(f"- **Recommended action:** {f.action}")
lines.append(f"- **Impact (P50 minutes):** {f.impact_minutes_p50:.0f}")
lines.append("")
return "\n".join(lines)
def sample_process() -> dict:
# Reuses procurement-intake shape from process_documenter
return {
"process_name": "Procurement Intake (Sample)",
"wip": 12,
"stages": [
{"name": "Submit PO", "owner": "Requestor", "type": "value-add",
"duration_minutes_p50": 15, "duration_minutes_p90": 30},
{"name": "Wait for manager", "owner": "Manager", "type": "wait",
"duration_minutes_p50": 480, "duration_minutes_p90": 1440},
{"name": "Manager approves", "owner": "Manager", "type": "value-add",
"duration_minutes_p50": 10, "duration_minutes_p90": 25},
{"name": "Wait for finance", "owner": "Finance", "type": "wait",
"duration_minutes_p50": 720, "duration_minutes_p90": 2880},
{"name": "Finance validates", "owner": "Finance", "type": "value-add",
"duration_minutes_p50": 20, "duration_minutes_p90": 60},
{"name": "Rework: missing W-9", "owner": "Requestor", "type": "rework",
"duration_minutes_p50": 120, "duration_minutes_p90": 360},
],
}
def main() -> int:
parser = argparse.ArgumentParser(
description="Detect bottlenecks in a documented business process."
)
parser.add_argument("--input", type=Path, help="Path to process JSON file.")
parser.add_argument(
"--profile",
choices=sorted(PROFILES.keys()),
default="saas",
help="Industry profile for threshold calibration (default: saas).",
)
parser.add_argument(
"--output",
choices=["markdown", "json"],
default="markdown",
help="Output format (default: markdown).",
)
parser.add_argument(
"--sample",
action="store_true",
help="Use a built-in sample process and exit.",
)
args = parser.parse_args()
if args.sample:
raw = sample_process()
else:
if not args.input:
parser.error("--input is required unless --sample is given")
if not args.input.exists():
parser.error(f"input file not found: {args.input}")
raw = load(args.input)
# Minimal normalization: tolerate the same fields as process_documenter
stages = []
for s in raw.get("stages", []):
stages.append(
{
"name": s.get("name", ""),
"owner": s.get("owner", ""),
"type": s.get("type", ""),
"duration_minutes_p50": float(s.get("duration_minutes_p50", 0)),
"duration_minutes_p90": float(s.get("duration_minutes_p90", 0)),
}
)
normalized = {
"process_name": raw.get("process_name", "Untitled Process"),
"wip": int(raw.get("wip", 0) or 0),
"stages": stages,
}
findings = detect(normalized, args.profile)
if args.output == "json":
print(
json.dumps(
{
"process_name": normalized["process_name"],
"profile": args.profile,
"findings": [asdict(f) for f in findings],
},
indent=2,
)
)
else:
print(render_markdown(normalized, findings, args.profile))
return 0
if __name__ == "__main__":
sys.exit(main())
FILE:scripts/cycle_time_analyzer.py
#!/usr/bin/env python3
"""cycle_time_analyzer.py
Compute total cycle time (P50, P90), value-add ratio (VA%), wait %, rework %,
and a Little's-Law throughput estimate for a documented business process.
Verdict per Lean canon:
VA% > 25% -> HEALTHY
10% <= VA% <= 25% -> TYPICAL
VA% < 10% -> WASTE-HEAVY
Stdlib only.
"""
from __future__ import annotations
import argparse
import json
import sys
from dataclasses import dataclass, asdict
from pathlib import Path
# Per-industry verdict bands. Manufacturing benchmarks higher VA% than services.
PROFILES: dict[str, dict[str, float]] = {
"saas": {"healthy": 0.25, "typical": 0.10},
"services": {"healthy": 0.20, "typical": 0.08},
"manufacturing": {"healthy": 0.35, "typical": 0.15},
"healthcare": {"healthy": 0.20, "typical": 0.08},
}
@dataclass
class CycleTimeReport:
process_name: str
profile: str
stage_count: int
total_p50_minutes: float
total_p90_minutes: float
value_add_minutes_p50: float
wait_minutes_p50: float
rework_minutes_p50: float
value_add_ratio: float
wait_ratio: float
rework_ratio: float
verdict: str
wip: int
throughput_per_hour: float | None
notes: list[str]
def analyze(normalized: dict, profile: str) -> CycleTimeReport:
prof = PROFILES.get(profile, PROFILES["saas"])
stages = normalized.get("stages", [])
name = normalized.get("process_name", "Untitled Process")
wip = int(normalized.get("wip", 0) or 0)
total_p50 = sum(s["duration_minutes_p50"] for s in stages)
total_p90 = sum(s["duration_minutes_p90"] for s in stages)
va_p50 = sum(s["duration_minutes_p50"] for s in stages if s["type"] == "value-add")
wait_p50 = sum(s["duration_minutes_p50"] for s in stages if s["type"] == "wait")
rework_p50 = sum(s["duration_minutes_p50"] for s in stages if s["type"] == "rework")
denom = total_p50 if total_p50 > 0 else 1.0
va_ratio = va_p50 / denom
wait_ratio = wait_p50 / denom
rework_ratio = rework_p50 / denom
if va_ratio >= prof["healthy"]:
verdict = "HEALTHY"
elif va_ratio >= prof["typical"]:
verdict = "TYPICAL"
else:
verdict = "WASTE-HEAVY"
# Little's Law: L = lambda * W => lambda = L / W
# WIP is items currently in process; W (cycle time) is P50.
# Convert minutes to hours for a per-hour throughput.
throughput = None
if wip > 0 and total_p50 > 0:
cycle_hours = total_p50 / 60.0
throughput = wip / cycle_hours
notes: list[str] = []
if wip <= 0:
notes.append(
"WIP not provided; Little's-Law throughput estimate skipped. "
"Set 'wip' in the input JSON to enable it."
)
if total_p50 == 0:
notes.append("All stage P50 durations are zero; check input data.")
if rework_ratio > 0.0 and verdict == "HEALTHY":
notes.append(
"Process is healthy by VA%, but rework is non-zero. Six-Sigma canon: "
"any rework signal is worth a poka-yoke check."
)
if wait_ratio > 0.5:
notes.append(
"More than half the cycle is wait time. Throughput improves more "
"from queue removal than from speeding up value-add stages."
)
return CycleTimeReport(
process_name=name,
profile=profile,
stage_count=len(stages),
total_p50_minutes=round(total_p50, 2),
total_p90_minutes=round(total_p90, 2),
value_add_minutes_p50=round(va_p50, 2),
wait_minutes_p50=round(wait_p50, 2),
rework_minutes_p50=round(rework_p50, 2),
value_add_ratio=round(va_ratio, 4),
wait_ratio=round(wait_ratio, 4),
rework_ratio=round(rework_ratio, 4),
verdict=verdict,
wip=wip,
throughput_per_hour=round(throughput, 4) if throughput is not None else None,
notes=notes,
)
def render_markdown(report: CycleTimeReport) -> str:
lines: list[str] = []
lines.append(f"# Cycle-Time Analysis: {report.process_name}")
lines.append("")
lines.append(f"**Profile:** `{report.profile}` ")
lines.append(f"**Verdict:** **{report.verdict}**")
lines.append("")
lines.append("## Summary")
lines.append("")
lines.append("| Metric | Value |")
lines.append("|--------|-------|")
lines.append(f"| Stage count | {report.stage_count} |")
lines.append(f"| Total P50 (minutes) | {report.total_p50_minutes:.1f} |")
lines.append(f"| Total P90 (minutes) | {report.total_p90_minutes:.1f} |")
lines.append(
f"| Value-add minutes (P50) | {report.value_add_minutes_p50:.1f} |"
)
lines.append(f"| Wait minutes (P50) | {report.wait_minutes_p50:.1f} |")
lines.append(f"| Rework minutes (P50) | {report.rework_minutes_p50:.1f} |")
lines.append(f"| Value-add ratio (VA%) | {report.value_add_ratio*100:.1f}% |")
lines.append(f"| Wait ratio | {report.wait_ratio*100:.1f}% |")
lines.append(f"| Rework ratio | {report.rework_ratio*100:.1f}% |")
lines.append(f"| WIP (items in process) | {report.wip} |")
if report.throughput_per_hour is not None:
lines.append(
f"| Little's-Law throughput | {report.throughput_per_hour:.3f} items/hour |"
)
else:
lines.append("| Little's-Law throughput | _(needs WIP > 0 in input)_ |")
lines.append("")
if report.notes:
lines.append("## Notes")
lines.append("")
for n in report.notes:
lines.append(f"- {n}")
lines.append("")
return "\n".join(lines)
def sample_process() -> dict:
return {
"process_name": "Procurement Intake (Sample)",
"wip": 12,
"stages": [
{"name": "Submit PO", "owner": "Requestor", "type": "value-add",
"duration_minutes_p50": 15, "duration_minutes_p90": 30},
{"name": "Wait for manager", "owner": "Manager", "type": "wait",
"duration_minutes_p50": 480, "duration_minutes_p90": 1440},
{"name": "Manager approves", "owner": "Manager", "type": "value-add",
"duration_minutes_p50": 10, "duration_minutes_p90": 25},
{"name": "Wait for finance", "owner": "Finance", "type": "wait",
"duration_minutes_p50": 720, "duration_minutes_p90": 2880},
{"name": "Finance validates", "owner": "Finance", "type": "value-add",
"duration_minutes_p50": 20, "duration_minutes_p90": 60},
{"name": "Rework: missing W-9", "owner": "Requestor", "type": "rework",
"duration_minutes_p50": 120, "duration_minutes_p90": 360},
],
}
def main() -> int:
parser = argparse.ArgumentParser(
description="Analyze cycle time, value-add ratio, and throughput of a process."
)
parser.add_argument("--input", type=Path, help="Path to process JSON file.")
parser.add_argument(
"--profile",
choices=sorted(PROFILES.keys()),
default="saas",
help="Industry profile for verdict band (default: saas).",
)
parser.add_argument(
"--output",
choices=["markdown", "json"],
default="markdown",
help="Output format (default: markdown).",
)
parser.add_argument(
"--sample",
action="store_true",
help="Use a built-in sample process and exit.",
)
args = parser.parse_args()
if args.sample:
raw = sample_process()
else:
if not args.input:
parser.error("--input is required unless --sample is given")
if not args.input.exists():
parser.error(f"input file not found: {args.input}")
with args.input.open("r", encoding="utf-8") as f:
raw = json.load(f)
stages = []
for s in raw.get("stages", []):
stages.append(
{
"name": s.get("name", ""),
"owner": s.get("owner", ""),
"type": s.get("type", ""),
"duration_minutes_p50": float(s.get("duration_minutes_p50", 0)),
"duration_minutes_p90": float(s.get("duration_minutes_p90", 0)),
}
)
normalized = {
"process_name": raw.get("process_name", "Untitled Process"),
"wip": int(raw.get("wip", 0) or 0),
"stages": stages,
}
report = analyze(normalized, args.profile)
if args.output == "json":
print(json.dumps(asdict(report), indent=2))
else:
print(render_markdown(report))
return 0
if __name__ == "__main__":
sys.exit(main())
FILE:scripts/process_documenter.py
#!/usr/bin/env python3
"""process_documenter.py
Read a JSON description of a business process (one entry per stage) and emit:
- a text-based BPMN-style swim-lane diagram in Markdown, OR
- a normalized JSON artifact for downstream tools.
Stdlib only. Use `--sample` to print a 6-stage procurement-intake example to
stdout.
Input schema (JSON):
{
"process_name": "Procurement Intake",
"wip": 12, # optional, integer; used by cycle_time_analyzer
"stages": [
{
"name": "Requestor submits PO request",
"owner": "Requestor",
"type": "value-add", # one of: value-add | wait | rework
"duration_minutes_p50": 15,
"duration_minutes_p90": 30
},
...
]
}
"""
from __future__ import annotations
import argparse
import json
import sys
from dataclasses import dataclass, asdict
from enum import Enum
from pathlib import Path
VALID_TYPES = {"value-add", "wait", "rework"}
class StageType(str, Enum):
VALUE_ADD = "value-add"
WAIT = "wait"
REWORK = "rework"
@dataclass
class Stage:
name: str
owner: str
type: str
duration_minutes_p50: float
duration_minutes_p90: float
def validate(self, idx: int) -> list[str]:
errs: list[str] = []
if not self.name:
errs.append(f"stage[{idx}]: missing 'name'")
if not self.owner:
errs.append(f"stage[{idx}]: missing 'owner'")
if self.type not in VALID_TYPES:
errs.append(
f"stage[{idx}] ('{self.name}'): invalid type '{self.type}' "
f"(expected one of {sorted(VALID_TYPES)})"
)
if self.duration_minutes_p50 < 0:
errs.append(f"stage[{idx}] ('{self.name}'): p50 must be >= 0")
if self.duration_minutes_p90 < self.duration_minutes_p50:
errs.append(
f"stage[{idx}] ('{self.name}'): p90 ({self.duration_minutes_p90}) "
f"< p50 ({self.duration_minutes_p50})"
)
return errs
def load_process(path: Path) -> dict:
with path.open("r", encoding="utf-8") as f:
return json.load(f)
def normalize(raw: dict) -> dict:
"""Validate + return a normalized dict. Raises ValueError on bad input."""
if "stages" not in raw or not isinstance(raw["stages"], list):
raise ValueError("input must include a non-empty 'stages' list")
stages: list[Stage] = []
errors: list[str] = []
for idx, s in enumerate(raw["stages"]):
try:
stage = Stage(
name=s.get("name", ""),
owner=s.get("owner", ""),
type=s.get("type", ""),
duration_minutes_p50=float(s.get("duration_minutes_p50", 0)),
duration_minutes_p90=float(s.get("duration_minutes_p90", 0)),
)
except (TypeError, ValueError) as e:
errors.append(f"stage[{idx}]: parse error: {e}")
continue
errors.extend(stage.validate(idx))
stages.append(stage)
if errors:
raise ValueError("invalid input:\n - " + "\n - ".join(errors))
return {
"process_name": raw.get("process_name", "Untitled Process"),
"wip": int(raw.get("wip", 0)) if raw.get("wip") is not None else 0,
"stages": [asdict(s) for s in stages],
}
def render_markdown(normalized: dict) -> str:
"""Render a text-based BPMN-style swim-lane diagram in Markdown."""
name = normalized["process_name"]
stages = normalized["stages"]
lines: list[str] = []
lines.append(f"# Process Map: {name}")
lines.append("")
lines.append(f"**Stages:** {len(stages)} ")
lines.append(
f"**Total P50:** {sum(s['duration_minutes_p50'] for s in stages):.1f} min "
)
lines.append(
f"**Total P90:** {sum(s['duration_minutes_p90'] for s in stages):.1f} min"
)
lines.append("")
# Group by owner -> swim lane
lanes: dict[str, list[tuple[int, dict]]] = {}
for idx, s in enumerate(stages):
lanes.setdefault(s["owner"], []).append((idx, s))
lines.append("## Swim Lanes")
lines.append("")
type_glyph = {"value-add": "[V]", "wait": "[W]", "rework": "[R]"}
lane_width = max(20, max((len(o) for o in lanes), default=20) + 4)
sep = "+" + "-" * (lane_width + 2) + "+" + "-" * 72 + "+"
lines.append("```")
lines.append(sep)
lines.append(
"| " + "OWNER".ljust(lane_width) + " | " + "STAGES (in process order)".ljust(70) + " |"
)
lines.append(sep)
for owner, owned in lanes.items():
owner_cell = owner.ljust(lane_width)
cells = []
for idx, s in owned:
glyph = type_glyph.get(s["type"], "[?]")
cells.append(
f"#{idx+1} {glyph} {s['name'][:32]} "
f"(p50={s['duration_minutes_p50']:.0f}m)"
)
row_text = " -> ".join(cells)
# Wrap row_text to 70 chars
wrapped = []
cur = ""
for token in row_text.split(" "):
if len(cur) + len(token) + 1 > 70:
wrapped.append(cur)
cur = token
else:
cur = (cur + " " + token).strip()
if cur:
wrapped.append(cur)
for i, line in enumerate(wrapped):
left = owner_cell if i == 0 else " " * lane_width
lines.append(f"| {left} | {line.ljust(70)} |")
lines.append(sep)
lines.append("```")
lines.append("")
lines.append("Legend: `[V]` value-add `[W]` wait `[R]` rework")
lines.append("")
lines.append("## Linear sequence")
lines.append("")
lines.append("| # | Stage | Owner | Type | P50 (min) | P90 (min) |")
lines.append("|---|-------|-------|------|-----------|-----------|")
for idx, s in enumerate(stages):
lines.append(
f"| {idx+1} | {s['name']} | {s['owner']} | {s['type']} | "
f"{s['duration_minutes_p50']:.1f} | {s['duration_minutes_p90']:.1f} |"
)
lines.append("")
return "\n".join(lines)
def sample_process() -> dict:
return {
"process_name": "Procurement Intake (Sample)",
"wip": 12,
"stages": [
{
"name": "Requestor submits PO request",
"owner": "Requestor",
"type": "value-add",
"duration_minutes_p50": 15,
"duration_minutes_p90": 30,
},
{
"name": "Wait for manager review queue",
"owner": "Manager",
"type": "wait",
"duration_minutes_p50": 480,
"duration_minutes_p90": 1440,
},
{
"name": "Manager approves request",
"owner": "Manager",
"type": "value-add",
"duration_minutes_p50": 10,
"duration_minutes_p90": 25,
},
{
"name": "Wait for finance review queue",
"owner": "Finance",
"type": "wait",
"duration_minutes_p50": 720,
"duration_minutes_p90": 2880,
},
{
"name": "Finance validates budget code",
"owner": "Finance",
"type": "value-add",
"duration_minutes_p50": 20,
"duration_minutes_p90": 60,
},
{
"name": "Rework: missing vendor W-9",
"owner": "Requestor",
"type": "rework",
"duration_minutes_p50": 120,
"duration_minutes_p90": 360,
},
],
}
def main() -> int:
parser = argparse.ArgumentParser(
description="Document a business process as a BPMN-style swim-lane diagram."
)
parser.add_argument("--input", type=Path, help="Path to process JSON file.")
parser.add_argument(
"--output", type=Path, help="Output file path (default: stdout)."
)
parser.add_argument(
"--format",
choices=["markdown", "json"],
default="markdown",
help="Output format (default: markdown).",
)
parser.add_argument(
"--sample",
action="store_true",
help="Print a 6-stage procurement-intake sample and exit.",
)
args = parser.parse_args()
if args.sample:
raw = sample_process()
else:
if not args.input:
parser.error("--input is required unless --sample is given")
if not args.input.exists():
parser.error(f"input file not found: {args.input}")
raw = load_process(args.input)
try:
normalized = normalize(raw)
except ValueError as e:
print(f"ERROR: {e}", file=sys.stderr)
return 1
if args.format == "json":
out = json.dumps(normalized, indent=2)
else:
out = render_markdown(normalized)
if args.output:
args.output.write_text(out, encoding="utf-8")
print(f"wrote {args.output}", file=sys.stderr)
else:
print(out)
return 0
if __name__ == "__main__":
sys.exit(main())
Tạo hoặc cập nhật tài liệu ngữ cảnh product marketing: mô tả sản phẩm, đối tượng mục tiêu, ICP và định vị để tránh lặp lại thông tin nền.
---
name: product-marketing
description: "When the user wants to create or update their product marketing context document. Also use when the user mentions 'product context,' 'marketing context,' 'set up context,' 'positioning,' 'who is my target audience,' 'describe my product,' 'ICP,' 'ideal customer profile,' or wants to avoid repeating foundational information across marketing tasks. Use this at the start of any new project before using other marketing skills — it creates `.agents/product-marketing.md` that all other skills reference for product, audience, and positioning context."
metadata:
version: 2.1.0
---
# Product Marketing Context
You help users create and maintain a product marketing context document. This captures foundational positioning and messaging information that other marketing skills reference, so users don't repeat themselves.
The document is stored at `.agents/product-marketing.md`.
## Workflow
### Step 1: Check for Existing Context
First, check if `.agents/product-marketing.md` already exists. Also check `.claude/product-marketing.md` and the legacy filename `product-marketing-context.md` (in either `.agents/` or `.claude/`) for older setups — if found anywhere other than `.agents/product-marketing.md`, offer to move it to the canonical location.
**If it exists:**
- Read it and summarize what's captured — note its current **Document version** and the last few **Changelog** entries so the user sees where the doc stands and what's changed recently
- Ask which sections they want to update
- Only gather info for those sections
- On any substantive save, bump the version and add a changelog entry (see Step 4). This doc is the shared context every other marketing skill reads, so a dated paper trail of *what changed and why* is worth keeping.
**If it doesn't exist, offer two options:**
1. **Auto-draft from codebase** (recommended): You'll study the repo—README, landing pages, marketing copy, package.json, etc.—and draft a V1 of the context document. The user then reviews, corrects, and fills gaps. This is faster than starting from scratch.
2. **Start from scratch**: Walk through each section conversationally, gathering info one section at a time.
Most users prefer option 1. After presenting the draft, ask: "What needs correcting? What's missing?"
### Step 2: Gather Information
**If auto-drafting:**
1. Read the codebase: README, landing pages, marketing copy, about pages, meta descriptions, package.json, any existing docs
2. Draft all sections based on what you find
3. Present the draft and ask what needs correcting or is missing
4. Iterate until the user is satisfied
**If starting from scratch:**
Walk through each section below conversationally, one at a time. Don't dump all questions at once.
For each section:
1. Briefly explain what you're capturing
2. Ask relevant questions
3. Confirm accuracy
4. Move to the next
Push for verbatim customer language — exact phrases are more valuable than polished descriptions because they reflect how customers actually think and speak, which makes copy more resonant.
---
## Sections to Capture
### 1. Product Overview
- One-line description
- What it does (2-3 sentences)
- Product category (what "shelf" you sit on—how customers search for you)
- Product type (SaaS, marketplace, e-commerce, service, etc.)
- Business model and pricing
### 2. Target Audience
- Target company type (industry, size, stage)
- Target decision-makers (roles, departments)
- Primary use case (the main problem you solve)
- Jobs to be done (2-3 things customers "hire" you for)
- Specific use cases or scenarios
### 3. Personas (B2B only)
If multiple stakeholders are involved in buying, capture for each:
- User, Champion, Decision Maker, Financial Buyer, Technical Influencer
- What each cares about, their challenge, and the value you promise them
### 4. Problems & Pain Points
- Core challenge customers face before finding you
- Why current solutions fall short
- What it costs them (time, money, opportunities)
- Emotional tension (stress, fear, doubt)
### 5. Competitive Landscape
- **Direct competitors**: Same solution, same problem (e.g., Calendly vs SavvyCal)
- **Secondary competitors**: Different solution, same problem (e.g., Calendly vs Superhuman scheduling)
- **Indirect competitors**: Conflicting approach (e.g., Calendly vs personal assistant)
- How each falls short for customers
### 6. Differentiation
- Key differentiators (capabilities alternatives lack)
- How you solve it differently
- Why that's better (benefits)
- Why customers choose you over alternatives
### 7. Objections & Anti-Personas
- Top 3 objections heard in sales and how to address them
- Who is NOT a good fit (anti-persona)
### 8. Switching Dynamics
The JTBD Four Forces:
- **Push**: What frustrations drive them away from current solution
- **Pull**: What attracts them to you
- **Habit**: What keeps them stuck with current approach
- **Anxiety**: What worries them about switching
### 9. Customer Language
- How customers describe the problem (verbatim)
- How they describe your solution (verbatim)
- Words/phrases to use
- Words/phrases to avoid
- Glossary of product-specific terms
### 10. Brand Voice
- Tone (professional, casual, playful, etc.)
- Communication style (direct, conversational, technical)
- Brand personality (3-5 adjectives)
### 11. Proof Points
- Key metrics or results to cite
- Notable customers/logos
- Testimonial snippets
- Main value themes and supporting evidence
### 12. Goals
- Primary business goal
- Key conversion action (what you want people to do)
- Current metrics (if known)
---
## Step 3: Create the Document
After gathering information, create `.agents/product-marketing.md` with this structure:
```markdown
# Product Marketing Context
**Document version:** v1
**Last updated:** [date]
## Product Overview
**One-liner:**
**What it does:**
**Product category:**
**Product type:**
**Business model:**
## Target Audience
**Target companies:**
**Decision-makers:**
**Primary use case:**
**Jobs to be done:**
-
**Use cases:**
-
## Personas
| Persona | Cares about | Challenge | Value we promise |
|---------|-------------|-----------|------------------|
| | | | |
## Problems & Pain Points
**Core problem:**
**Why alternatives fall short:**
-
**What it costs them:**
**Emotional tension:**
## Competitive Landscape
**Direct:** [Competitor] — falls short because...
**Secondary:** [Approach] — falls short because...
**Indirect:** [Alternative] — falls short because...
## Differentiation
**Key differentiators:**
-
**How we do it differently:**
**Why that's better:**
**Why customers choose us:**
## Objections
| Objection | Response |
|-----------|----------|
| | |
**Anti-persona:**
## Switching Dynamics
**Push:**
**Pull:**
**Habit:**
**Anxiety:**
## Customer Language
**How they describe the problem:**
- "[verbatim]"
**How they describe us:**
- "[verbatim]"
**Words to use:**
**Words to avoid:**
**Glossary:**
| Term | Meaning |
|------|---------|
| | |
## Brand Voice
**Tone:**
**Style:**
**Personality:**
## Proof Points
**Metrics:**
**Customers:**
**Testimonials:**
> "[quote]" — [who]
**Value themes:**
| Theme | Proof |
|-------|-------|
| | |
## Goals
**Business goal:**
**Conversion action:**
**Current metrics:**
## Changelog
*Newest first. One line per revision: what changed and why.*
- v1 ([date]) — Initial context.
```
---
## Step 4: Confirm, Version, and Save
- Show the completed document
- Ask if anything needs adjustment
- **Set the version and changelog** — this is the paper trail for a doc every other skill reads:
- **New document:** set `Document version: v1` and a single Changelog entry — `- v1 ([today]) — Initial context.`
- **Updating an existing document:** increment the version (v2 → v3 …), update `Last updated` to today, and **prepend a new Changelog entry** at the top of the list (newest first) summarizing *what changed and why* in one line. Never rewrite or reorder past entries.
- A good entry names the sections touched and the reason, not "updated the doc." Examples:
- `- v3 (2026-07-16) — Repositioned from "email tool" to "deliverability platform"; added RevOps to the ICP.`
- `- v2 (2026-06-02) — Rewrote value prop and objections after 5 customer interviews; added competitor Acme.`
- Use today's date in ISO form (YYYY-MM-DD) for the entry and `Last updated`.
- **Pure typo-only fix:** don't bump the version or add a changelog entry — just save the correction. Every other change bumps the version and gets an entry. When the change is a real repositioning, say so plainly — downstream skills will now generate against the new context.
- Save to `.agents/product-marketing.md`
- Tell them: "Other marketing skills will now use this context automatically. The Changelog at the bottom tracks every revision — check it to see how your positioning has evolved. Run `/product-marketing` anytime to update it."
---
## Tips
- **Be specific**: Ask "What's the #1 frustration that brings them to you?" not "What problem do they solve?"
- **Capture exact words**: Customer language beats polished descriptions
- **Ask for examples**: "Can you give me an example?" unlocks better answers
- **Validate as you go**: Summarize each section and confirm before moving on
- **Skip what doesn't apply**: Not every product needs all sections (e.g., Personas for B2C)
FILE:evals/evals.json
{
"skill_name": "product-marketing",
"evals": [
{
"id": 1,
"prompt": "I want to set up my product marketing context. We're a B2B SaaS company that sells a customer feedback platform to product teams.",
"expected_output": "Should check if .agents/product-marketing.md already exists. If not, should offer two options: (1) Auto-draft from codebase (recommended) or (2) Start from scratch. If user chooses start from scratch, should walk through sections conversationally one at a time. Should cover all applicable sections: Product Overview, Target Audience, Personas, Problems You Solve, Competitive Landscape, Differentiation, Objections, Switching Dynamics, Customer Language, Brand Voice, Proof Points, and Goals. Should create the file at .agents/product-marketing.md when complete.",
"assertions": [
"Checks for existing product-marketing.md",
"Offers two options: auto-draft or start from scratch",
"Covers applicable sections",
"Walks through sections conversationally one at a time",
"Creates file at .agents/product-marketing.md"
],
"files": []
},
{
"id": 2,
"prompt": "Update our product marketing context. We just added a new enterprise tier and our target audience has expanded to include VP of Engineering, not just Product Managers.",
"expected_output": "Should check for existing .agents/product-marketing.md and read it. Should identify which sections need updating based on the changes: Target Audience (add VP of Engineering), Personas (add new persona), Product Overview (new enterprise tier, including pricing updates within that section), Objections (enterprise-specific), and Competitive Landscape (enterprise competitors). Should update only the relevant sections, preserving existing content that hasn't changed.",
"assertions": [
"Reads existing product-marketing.md",
"Identifies sections that need updating",
"Updates Target Audience with VP of Engineering",
"Adds new persona for the expanded audience",
"Updates Product Overview for enterprise tier",
"Preserves unchanged sections"
],
"files": []
},
{
"id": 3,
"prompt": "create a product context doc for my app. it's a mobile app that helps people find hiking trails. we're just getting started.",
"expected_output": "Should trigger on casual phrasing. Should check for existing context doc. Should offer auto-draft or start-from-scratch options. Should adapt questions for an early-stage B2C mobile app (outdoor/fitness niche). Should note that some sections may be sparse for an early-stage product and that's okay — they can be filled in as the business matures. Should skip non-applicable sections (e.g., Personas section is B2B-focused) rather than forcing all 12. Should accept lighter answers for sections like Proof Points or Competitive Landscape if the company is new.",
"assertions": [
"Triggers on casual phrasing",
"Checks for existing context doc",
"Offers auto-draft or start-from-scratch options",
"Adapts questions for early-stage B2C mobile app",
"Notes some sections may be sparse early on",
"Skips non-applicable sections rather than forcing all 12",
"Creates file at .agents/product-marketing.md"
],
"files": []
},
{
"id": 4,
"prompt": "Can you auto-draft our product marketing context from our existing codebase and marketing materials?",
"expected_output": "Should activate the auto-draft workflow mode. Should scan the codebase for existing marketing context: README, landing page copy, pricing page, about page, meta descriptions, any existing documentation. Should draft the product-marketing.md from what it finds, filling in sections where information is available and flagging sections that need manual input. Should present the draft for review before saving.",
"assertions": [
"Activates auto-draft workflow mode",
"Scans codebase for existing marketing materials",
"Drafts context from found information",
"Flags sections needing manual input",
"Presents draft for review before saving"
],
"files": []
},
{
"id": 5,
"prompt": "Do we have a product marketing context set up? I want to make sure the other marketing skills have context about our product.",
"expected_output": "Should check for .agents/product-marketing.md (and the older .claude/product-marketing.md location). Should report whether it exists and summarize its contents if found. If it doesn't exist, should offer to create one and explain why it's valuable (other skills like copywriting, cro, seo-audit check for it first). Should explain how other skills use this context document.",
"assertions": [
"Checks both file locations",
"Reports whether context doc exists",
"Summarizes contents if found",
"Offers to create if missing",
"Explains how other skills use it"
],
"files": []
},
{
"id": 6,
"prompt": "Write homepage copy for our SaaS product.",
"expected_output": "Should recognize this is a copywriting task, not a product marketing context task. Should check for product-marketing.md (as other skills do), and if it doesn't exist, may suggest creating one first. But should defer to the copywriting skill for actually writing the homepage copy.",
"assertions": [
"Recognizes this as a copywriting task",
"May check for or suggest creating product-marketing.md",
"References or defers to copywriting skill for the actual copy",
"Does not attempt to write homepage copy using context creation patterns"
],
"files": []
},
{
"id": 7,
"prompt": "We just repositioned — we're no longer an 'email tool,' we're a 'deliverability platform,' and our ICP now includes RevOps teams. Update our product marketing context.",
"expected_output": "Should recognize an existing .agents/product-marketing.md, read it, note its current Document version and recent Changelog entries, and update only the affected sections (product overview/positioning, target audience/ICP). On save, should bump the Document version (e.g. v2 → v3), update the Last updated date, and PREPEND a new newest-first Changelog entry summarizing what changed and why in one line — e.g. 'Repositioned from email tool to deliverability platform; added RevOps to the ICP' — naming the sections touched and the reason, not just 'updated the doc.' Should not rewrite or reorder past changelog entries. Should tell the user the changelog tracks revisions and that downstream skills will now use the new context.",
"assertions": [
"Reads the existing doc and surfaces its current version + recent changelog",
"Updates only the affected sections (positioning + ICP)",
"Bumps the Document version and updates Last updated",
"Prepends a newest-first changelog entry naming what changed and why",
"Preserves prior changelog entries unchanged"
],
"files": []
}
]
}
Lập kế hoạch và tổng hợp nghiên cứu sản phẩm/người dùng: chọn phương pháp phù hợp, tính độ bão hòa và cỡ mẫu theo độ tin cậy rõ ràng.
---
name: product-research
description: Use when planning and synthesizing product/user research as a method-and-repository discipline — selecting the right method for the goal (generative interviews vs usability test vs concept test vs validation), computing method-based saturation/sample size with an explicit confidence level, or synthesizing coded observations into insights while flagging single-source anecdotes. Never fabricates user insight; an insight requires recurrence across independent participants. Distinct from product-team/ux-researcher-designer (persona/journey artifacts), product-discovery (discovery-sprint planning), and experiment-designer (live A/B) — this is the research-ops method + insight-repository layer.
version: 2.9.0
author: claude-code-skills
license: MIT
tags: [research-ops, product-research, ux-research, jtbd, usability, saturation, insight-synthesis, research-repository]
compatible_tools: [claude-code, codex-cli, cursor, antigravity, opencode, gemini-cli]
---
# product-research
Product / user research as an operational discipline: choosing the right method, sizing it honestly, and synthesizing findings into governed insights. The core rule: **method must match the goal**, and **an insight requires recurrence across independent participants** — a single quote is an anecdote.
## Purpose
Product researchers, ResearchOps teams, and PMs running discovery need method rigor and an insight repository they can trust. This skill structures three decisions:
Three deterministic tools:
1. `study_designer.py` — Maps (research goal × product stage) to an appropriate method and emits a method-matched plan skeleton (objective, participant criteria, guide structure, success criteria). Redirects live A/B to `product-team/experiment-designer`.
2. `saturation_planner.py` — Method-based sample guidance with an explicit **confidence label**: Nielsen problem-discovery (5/segment), Guest et al. thematic saturation (~12), and evaluative coverage. Never claims a prevalence rate from a small-n usability test.
3. `insight_synthesizer.py` — Clusters coded observations by tag, counts distinct participants, ranks by cross-participant recurrence, and flags any candidate below the source threshold as an **ANECDOTE**, never promoting it to an insight.
## When to use
Invoke this skill when:
- You are planning a study and need the method to match the goal (generative vs evaluative vs validation).
- You need a defensible sample size / saturation rationale with a stated confidence.
- You have raw coded observations and need to synthesize insights without over-claiming.
- You are setting up or auditing a research repository and need the insight-vs-observation discipline.
**Do NOT use this skill to**: generate personas / journey maps (use `product-team/ux-researcher-designer`), plan a discovery sprint or validate an opportunity (use `product-team/product-discovery`), design or analyze a live product A/B experiment (use `product-team/experiment-designer`), or do market sizing / surveys (use the `market-research` sibling).
## Workflow
1. **Frame the study** — Fill `assets/research_plan_template.md` (research questions, method rationale, participant criteria, analysis plan, repository tagging scheme).
2. **Pick the method** — Run `study_designer.py --goal {discovery|evaluative|validation} --stage {concept|prototype|beta|live} --profile {b2b-saas|consumer-app|enterprise|marketplace|hardware|platform}`. Honor the redirect if it routes to experiment-designer.
3. **Size it** — Run `saturation_planner.py --method {usability|thematic|evaluative-coverage} --segments N`. Record the confidence label and limits.
4. **Synthesize** — After fielding, code observations and run `insight_synthesizer.py --input observations.json --min-sources 3`. Treat ANECDOTE-flagged clusters as signals to probe, not findings to ship.
5. **File in the repository** — Tag insights to the atomic schema at synthesis time, with their evidence and confidence.
## Scripts
| Script | Purpose | Profiles |
|---|---|---|
| `scripts/study_designer.py` | (goal × stage) → method + plan skeleton | b2b-saas, consumer-app, enterprise, marketplace, hardware, platform |
| `scripts/saturation_planner.py` | Method-based sample guidance + confidence | n/a (method-driven) |
| `scripts/insight_synthesizer.py` | Cluster observations, flag anecdotes | n/a (evidence-driven) |
All three: stdlib-only, `--help`, `--sample`, `--output {human,json}`.
## Onboarding & customization
Run the onboarding questionnaire **once before you start** — it captures your defaults so every tool in this skill is pre-configured. Customization is the point: the answers actually change tool behavior (e.g. the insight source-threshold).
```bash
python3 scripts/onboard.py # interactive (also: --defaults, --set key=value, --reset)
python3 scripts/onboard.py --show # see the questions + current effective config
```
Answers are saved to `~/.config/research-ops/product-research.json` (global) or `./.research-ops/product-research.json` (`--scope project`) and are read automatically by `config_loader.py`. They set the default product **profile**, the **insight source-threshold** (how many independent participants make a finding an insight, not an anecdote), the default **saturation method**, and the **high-stakes** flag. CLI flags always override saved config; `RESEARCH_OPS_NO_CONFIG=1` ignores it.
**The four questions:** product profile · insight source-threshold · saturation method · high-stakes flag.
## Optimize with autoresearch (opt-in)
This skill ships an **isolated, opt-in** bridge to `engineering/autoresearch-agent`. Only when you ask to "optimize the synthesis" / "run a loop" does an autoresearch experiment iteratively refine the coding/clustering of a fixed evidence set so more cross-participant patterns surface. `scripts/ar_evaluator.py` is the ground-truth evaluator; it prints `validated_insights: <int>` (higher is better). It optimizes the **coding**, never fabricates evidence.
```bash
/ar:setup --domain custom --name insight-synthesis \
--target observations.json \
--eval "python3 ar_evaluator.py --target observations.json" \
--metric validated_insights --direction higher
/ar:loop custom/insight-synthesis
```
Isolated: no hard dependency — autoresearch runs only on demand, and the loop edits `observations.json`, never the evaluator.
## References
- `references/research_methods_canon.md` — Portigal *Interviewing Users*; Christensen/Ulwick JTBD; Rohrer's UX-research methods landscape (NN/g); Sauro & Lewis *Quantifying the User Experience*; Goodman/Kuniavsky.
- `references/sampling_and_saturation.md` — Nielsen "test with 5 users"; Guest, Bunce & Johnson saturation; Faulkner on more-than-5; Sauro usability sample size; Braun & Clarke thematic analysis.
- `references/repository_and_synthesis.md` — ResearchOps / atomic research (Tomer Sharon "Polaris"); insight-vs-observation discipline; repository governance; affinity mapping; democratization guardrails.
## Assumptions
- Method selection assumes you can name the goal honestly; if the goal is fuzzy, grill it first (the goal drives everything).
- Saturation guidance is method-based, not a power calculation — usability tests find problems, not prevalence rates.
- The synthesizer counts evidence you provide; coding quality is upstream of it. Garbage tags → garbage clusters.
- The insight threshold (`--min-sources`) defaults to 3; raise it for high-stakes or heterogeneous populations.
## Anti-patterns
- **Mismatching method to goal.** A usability test cannot discover unmet needs; an interview cannot measure task success.
- **Reporting usability problems as percentages.** Small-n tests surface problems, not population rates.
- **Promoting an anecdote to an insight.** One participant is a signal to probe, not a finding.
- **Framing interview questions as feature reactions.** Probe the job-to-be-done and recent real behavior, not hypothetical opinions.
- **Synthesizing without a repository scheme.** Tag at synthesis time, or insights rot unfindable.
## Distinct from
| Neighbor | Scope | Difference |
|---|---|---|
| `product-team/ux-researcher-designer` | Personas, journey maps, usability frameworks tied to design output | That produces **artifacts**; this is **method + repository discipline** |
| `product-team/product-discovery` | Opportunity validation, discovery-sprint planning | That plans **discovery sprints**; this designs and synthesizes the **research** |
| `product-team/experiment-designer` | Live product A/B hypothesis + sample size | That runs **live experiments**; this runs **qualitative/evaluative research** |
| `market-research` (sibling) | Market sizing, surveys, segmentation | That studies **the market**; this studies **users** |
## Quick examples
```bash
python3 scripts/study_designer.py --sample
python3 scripts/saturation_planner.py --method thematic --segments 3
python3 scripts/insight_synthesizer.py --sample --min-sources 3
```
The synthesizer sample correctly promotes "import-confusion" (3 independent participants) to INSIGHT and flags "wants-slack" (1 participant) as an ANECDOTE.
## Forcing-question library (Matt Pocock grill discipline)
Walked one at a time by `/cs:grill-research-ops` or the orchestrator. Recommended answer + canon citation per question. Never bundled.
1. **"Is this study generative (discover problems) or evaluative (test a solution)?"**
Recommended: name it first — the method follows from the goal.
Canon: Rohrer, *When to Use Which User-Experience Research Methods* (NN/g).
2. **"What's your sample size and saturation rationale — and at what confidence?"**
Recommended: method-based n (5/segment usability; ~12 for thematic saturation), state the confidence.
Canon: Nielsen; Guest, Bunce & Johnson (2006); Faulkner (2003).
3. **"How many independent participants support each insight — or is it a single-source anecdote?"**
Recommended: require recurrence across ≥3 sources before calling it an insight; flag singletons.
Canon: atomic research / ResearchOps; Braun & Clarke thematic analysis.
4. **"Are your interview / usability tasks framed as outcomes (jobs) or as feature reactions?"**
Recommended: frame around the job-to-be-done and recent real behavior, not hypothetical opinion.
Canon: Christensen/Ulwick Jobs-to-be-Done; Portigal *Interviewing Users*.
5. **"Where does this land in the repository, and how is it tagged for reuse?"**
Recommended: tag to the atomic schema at synthesis time, not later.
Canon: Tomer Sharon, *Polaris* / ResearchOps repository practice.
Walk depth-first. Lock 1-2 before opening 3-5. After all are answered, invoke `study_designer.py` → `saturation_planner.py` → (after fielding) `insight_synthesizer.py`.
FILE:assets/research_plan_template.md
# Product Research Plan — Template
> Fill this before running the tools. Method must match the goal. An insight requires
> recurrence across independent participants — a single quote is an anecdote.
## 1. Study identification
- Study name:
- Product / feature:
- Stage: [concept | prototype | beta | live]
- Profile: [b2b-saas | consumer-app | enterprise | marketplace | hardware | platform]
## 2. Goal & questions
- Goal: [discovery (generative) | evaluative | validation]
- Research questions (3-5, answerable, not leading):
- The product decision this informs:
## 3. Method (from `study_designer.py`)
- Recommended method:
- Why it matches the goal:
- (If live A/B → route to product-team/experiment-designer.)
## 4. Participants
- Target segment(s) + screener (screen for the job, not a job title):
- Per-segment recruiting if reporting per segment? [yes/no]
- Exclusions (internal, biased, repeat):
## 5. Sample & saturation (from `saturation_planner.py`)
- Method: [usability | thematic | evaluative-coverage]
- n per segment + total:
- Confidence label + limits:
## 6. Study guide skeleton
1.
2.
3.
4.
5.
## 7. Analysis & synthesis
- Coding / tagging scheme (atomic taxonomy):
- Insight threshold (min distinct participants): ___ (default 3)
- Synthesis tool: `insight_synthesizer.py`
## 8. Repository
- Where insights are filed + tagging taxonomy:
- Evidence linked to each insight? [yes — required]
- Confidence field per insight? [yes — required]
## 9. Confidence statement
- What this study can and cannot support:
FILE:references/repository_and_synthesis.md
# Research Repository and Synthesis
Reference for turning observations into governed insights. Pairs with `insight_synthesizer.py`.
## Observation vs insight
The foundational discipline of ResearchOps is the distinction between an **observation** (a single piece of evidence — one participant did or said one thing) and an **insight** (a pattern that recurs across independent sources and carries an implication). Promoting an observation to an insight because it was vivid or confirmed a prior is the cardinal sin of synthesis. The synthesizer enforces a source threshold: a candidate supported by fewer than the threshold of distinct participants is labeled an ANECDOTE and is never promoted.
## Atomic research
Tomer Sharon's **atomic research** model (and the "Polaris" repository concept) decomposes research into reusable units: *Experiments → Facts (observations) → Insights → Recommendations*. Facts are tagged and stored so that insights can be traced back to evidence and reused across studies. The payoff is a repository where a claim can always be drilled down to the observations that support it — and where the same evidence can support future questions.
## Affinity mapping
The classic synthesis technique is affinity mapping: cluster observations into emergent themes bottom-up, then name the themes. The `insight_synthesizer.py` tool is a deterministic, tag-based proxy for this — it clusters by the codes you assign and ranks by cross-participant recurrence. The human still does the interpretive naming; the tool enforces the counting discipline.
## Repository governance and democratization
As organizations democratize research (PMs and designers running their own studies), the repository becomes the guardrail. Governance practices: a consistent tagging taxonomy, evidence linked to every insight, a confidence field, and a review step before an insight is marked "validated." Without governance, democratized research produces a pile of unsearchable anecdotes; with it, the repository compounds in value.
## Sources
1. Sharon, T., *Validating Product Ideas Through Lean User Research* (Rosenfeld, 2016) and the atomic-research / Polaris model.
2. ResearchOps Community, *Research Repositories* and *Democratization* working-group reports.
3. Braun, V., & Clarke, V., *Thematic Analysis: A Practical Guide* (Sage, 2022).
4. Beyer, H., & Holtzblatt, K., *Contextual Design* (1998) — affinity diagramming.
5. Dovetail / EnjoyHQ practitioner guides on insight repositories and tagging taxonomies.
6. Kaplan, K., *Taxonomy 101* and *Research Repositories* — Nielsen Norman Group.
FILE:references/research_methods_canon.md
# Product Research Methods Canon
Reference for method selection. Pairs with `study_designer.py`.
## The two-axis map
UX/product research methods sort along two axes (Rohrer, NN/g): **attitudinal vs behavioral** (what people say vs what they do) and **qualitative vs quantitative** (why/how vs how-many). The single most important pre-method decision is the **goal**:
- **Generative (discovery)** — you don't yet know the problem. Methods: semi-structured interviews, contextual inquiry, diary studies. Output: themes, unmet needs, jobs-to-be-done.
- **Evaluative** — you have a solution and want to know if it works. Methods: moderated/unmoderated usability tests, concept tests. Output: task-success, severity-rated problems.
- **Validation** — you want to confirm demand/desirability before building. Methods: surveys, preference tests, fake-door tests, and (when live) A/B experiments.
Picking an evaluative method for a generative goal — "let's usability-test our way to product strategy" — is the most common and most expensive error.
## Interviewing discipline
Steve Portigal's *Interviewing Users* is the operative craft reference: ask about **recent, specific, real behavior** ("tell me about the last time you…"), not hypotheticals or opinions ("would you use…"). People are unreliable narrators of their future selves but good storytellers of their past.
## Jobs-to-be-Done
Christensen's and Ulwick's JTBD reframes research around the **progress a person is trying to make** in a circumstance, not their demographics or feature preferences. Outcome-Driven Innovation (Ulwick) operationalizes this into measurable desired outcomes — a bridge between qualitative discovery and quantitative validation.
## Mixed methods
Strong research triangulates: qualitative discovery surfaces hypotheses; quantitative validation sizes them. Sauro & Lewis (*Quantifying the User Experience*) provides the statistical backbone for turning usability observations into defensible metrics (task time, completion, SUS) without over-claiming from small samples.
## Sources
1. Portigal, S., *Interviewing Users*, 2nd ed. (Rosenfeld, 2023).
2. Christensen, Hall, Dillon & Duncan, *Competing Against Luck* (2016) — Jobs-to-be-Done.
3. Ulwick, A., *Jobs to Be Done: Theory to Practice* (2016) — Outcome-Driven Innovation.
4. Rohrer, C., *When to Use Which User-Experience Research Methods* — Nielsen Norman Group.
5. Sauro, J., & Lewis, J., *Quantifying the User Experience*, 2nd ed. (Morgan Kaufmann, 2016).
6. Goodman, Kuniavsky & Moed, *Observing the User Experience*, 2nd ed. (2012).
FILE:references/sampling_and_saturation.md
# Sampling and Saturation
Reference for how many participants. Pairs with `saturation_planner.py`.
## Usability: the "5 users" result
Nielsen and Landauer's model says the proportion of usability problems found with n users is 1 − (1 − p)ⁿ, where p is the average probability that a single user surfaces a given problem (~0.31 in their data). At n = 5, that is ~85% of problems — hence "test with 5 users." Two crucial caveats the planner enforces:
1. **Per segment.** The 5-user result holds *within a homogeneous user group*. If you have distinct segments that behave differently, you need ~5 per segment.
2. **Problems, not rates.** A small-n usability test finds *whether* a problem exists; it cannot estimate the *prevalence* of that problem in the population. Never report "60% of users struggled" from a 5-person test.
Faulkner (2003) showed real variance: while the average across many 5-person samples is ~85%, individual 5-person runs ranged from ~55% to 100%. When stakes or heterogeneity are high, run more.
## Qualitative: thematic saturation
For interview-based thematic research, Guest, Bunce & Johnson (2006) found that **saturation** — the point where new interviews stop yielding new themes — typically occurs by ~12 interviews in a homogeneous group, with the basic elements present by ~6. Saturation is **observed, not guaranteed**: track the new-theme rate and stop when it flattens, rather than committing to a fixed n blindly. Heterogeneous populations need more, and per-group saturation applies just as in usability.
## Reporting confidence honestly
The planner attaches a confidence label (LOW / MODERATE / MODERATE-HIGH) and explicit limits to every plan, because the failure mode in product research is not too-small samples per se — it is **over-claiming** from whatever sample you ran. State the method, the n, and what the method can and cannot support.
## Sources
1. Nielsen, J., & Landauer, T., *A mathematical model of the finding of usability problems* — INTERCHI 1993.
2. Nielsen, J., *Why You Only Need to Test with 5 Users* — NN/g (2000).
3. Faulkner, L., *Beyond the five-user assumption* — Behavior Research Methods 2003;35:379-383.
4. Guest, G., Bunce, A., & Johnson, L., *How many interviews are enough?* — Field Methods 2006;18:59-82.
5. Braun, V., & Clarke, V., *Using thematic analysis in psychology* — Qual Res Psychol 2006;3:77-101.
6. Sauro, J., & Lewis, J., *Quantifying the User Experience*, 2nd ed. (2016) — confidence intervals for small samples.
FILE:scripts/ar_evaluator.py
#!/usr/bin/env python3
"""ar_evaluator.py - Autoresearch evaluator for the product-research skill (OPT-IN).
Stdlib-only. The ISOLATED bridge to engineering/autoresearch-agent. It does NOT call
autoresearch; it is the ground-truth evaluator an autoresearch loop runs after editing
the target coded-observations file. It reads an observations JSON, runs insight_synthesizer
at the configured source threshold, and prints ONE metric line:
validated_insights: <int> (higher is better — clusters that clear the source threshold)
This optimizes the CODING/synthesis of a fixed evidence set (merging/splitting tags so
cross-participant patterns surface) — not the evidence itself. The user opts in explicitly:
/ar:setup --domain custom --name insight-synthesis \\
--target observations.json --eval "python3 ar_evaluator.py --target observations.json" \\
--metric validated_insights --direction higher
Direct use:
python3 ar_evaluator.py --sample
python3 ar_evaluator.py --target observations.json --min-sources 3
"""
from __future__ import annotations
import argparse
import json
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import config_loader as cfg # noqa: E402
import insight_synthesizer as isyn # noqa: E402
METRIC = "validated_insights"
def main(argv: list[str] | None = None) -> int:
c = cfg.load_config()
p = argparse.ArgumentParser(description="Autoresearch evaluator: count of validated insights.")
p.add_argument("--target", help="path to observations JSON (or env AR_TARGET)")
p.add_argument("--min-sources", type=int, default=None, help="overrides onboarding insight_min_sources")
p.add_argument("--sample", action="store_true")
args = p.parse_args(argv)
min_sources = args.min_sources if args.min_sources is not None else int(c.get("insight_min_sources", 3))
if args.sample:
data = isyn.SAMPLE
else:
target = args.target or os.environ.get("AR_TARGET")
if not target:
print("error: provide --target <observations.json> or set AR_TARGET", file=sys.stderr)
return 2
try:
with open(target) as f:
data = json.load(f)
except (OSError, json.JSONDecodeError) as e:
print(f"{METRIC}: N/A")
print(f"error: {e}", file=sys.stderr)
return 1
result = isyn.synthesize(data, min_sources)
count = sum(1 for c2 in result["candidates"] if c2["classification"] == "INSIGHT")
print(f"{METRIC}: {count}")
return 0
if __name__ == "__main__":
sys.exit(main())
FILE:scripts/config_loader.py
#!/usr/bin/env python3
"""config_loader.py - Customization loader for the product-research skill.
Stdlib-only. Importable from the skill's other scripts. Precedence (highest wins):
1. Project config: <cwd>/.research-ops/product-research.json
2. Global config: ~/.config/research-ops/product-research.json
3. Built-in DEFAULTS
Onboarding answers (written by onboard.py) live in these files; every tool in this
skill reads them so the user's customization applies automatically.
Set RESEARCH_OPS_NO_CONFIG=1 to ignore saved config.
"""
from __future__ import annotations
import argparse
import json
import os
import sys
from pathlib import Path
from typing import Any
SKILL = "product-research"
GLOBAL_CONFIG_DIR = Path.home() / ".config" / "research-ops"
GLOBAL_CONFIG_PATH = GLOBAL_CONFIG_DIR / f"{SKILL}.json"
PROJECT_CONFIG_DIRNAME = ".research-ops"
DEFAULTS: dict[str, Any] = {
"version": 1,
"skill": SKILL,
"default_profile": "b2b-saas",
"insight_min_sources": 3,
"default_method": "usability",
"stakes_high": False,
"setup_completed_at": None,
}
def project_config_path(cwd: Path | None = None) -> Path:
cwd = cwd or Path.cwd()
return cwd / PROJECT_CONFIG_DIRNAME / f"{SKILL}.json"
def _read_json(path: Path) -> dict[str, Any] | None:
try:
with path.open(encoding="utf-8") as f:
data = json.load(f)
return data if isinstance(data, dict) else None
except (FileNotFoundError, json.JSONDecodeError, OSError):
return None
def _deep_merge(base: dict[str, Any], override: dict[str, Any]) -> dict[str, Any]:
out = dict(base)
for k, v in override.items():
if isinstance(v, dict) and isinstance(out.get(k), dict):
out[k] = _deep_merge(out[k], v)
else:
out[k] = v
return out
def load_config(cwd: Path | None = None) -> dict[str, Any]:
config = dict(DEFAULTS)
if os.environ.get("RESEARCH_OPS_NO_CONFIG") == "1":
return config
global_cfg = _read_json(GLOBAL_CONFIG_PATH)
if global_cfg:
config = _deep_merge(config, global_cfg)
project_cfg = _read_json(project_config_path(cwd))
if project_cfg:
config = _deep_merge(config, project_cfg)
return config
def setup_completed() -> bool:
cfg = _read_json(GLOBAL_CONFIG_PATH) or _read_json(project_config_path())
return bool(cfg and cfg.get("setup_completed_at"))
def write_config(config: dict[str, Any], scope: str = "global", cwd: Path | None = None) -> Path:
path = project_config_path(cwd) if scope == "project" else GLOBAL_CONFIG_PATH
path.parent.mkdir(parents=True, exist_ok=True)
with path.open("w", encoding="utf-8") as f:
json.dump(config, f, indent=2, sort_keys=True)
return path
def main(argv: list[str] | None = None) -> int:
p = argparse.ArgumentParser(description=f"Inspect {SKILL} customization config.")
p.add_argument("--show", action="store_true", help="Print the effective config")
p.add_argument("--status", action="store_true", help="Print setup status + paths")
p.add_argument("--sample", action="store_true", help="Print the built-in defaults")
args = p.parse_args(argv)
if args.sample:
print(json.dumps(DEFAULTS, indent=2, sort_keys=True))
elif args.status:
print(json.dumps({
"skill": SKILL,
"global_config_path": str(GLOBAL_CONFIG_PATH),
"global_config_exists": GLOBAL_CONFIG_PATH.exists(),
"project_config_path": str(project_config_path()),
"project_config_exists": project_config_path().exists(),
"setup_completed": setup_completed(),
}, indent=2))
else:
print(json.dumps(load_config(), indent=2, sort_keys=True))
return 0
if __name__ == "__main__":
sys.exit(main())
FILE:scripts/insight_synthesizer.py
#!/usr/bin/env python3
"""insight_synthesizer.py - Cluster coded observations into candidate insights; flag anecdotes.
Stdlib-only. Deterministic. NO LLM calls. NEVER fabricates an insight: it counts evidence,
clusters by tag, ranks by cross-participant recurrence, and flags any candidate supported by
fewer than --min-sources independent participants as an ANECDOTE, not an insight.
Input: a list of observations, each with {participant, tag, note}. The synthesizer groups by
tag, counts distinct participants per tag, and ranks. This is the atomic-research discipline:
an observation is evidence; an insight requires recurrence across independent sources.
Usage:
python3 insight_synthesizer.py --sample
python3 insight_synthesizer.py --input observations.json --min-sources 3
python3 insight_synthesizer.py --input observations.json --output json
"""
from __future__ import annotations
import argparse
import json
import os
import sys
from collections import defaultdict
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
try:
import config_loader as _cfg
except ImportError: # pragma: no cover
_cfg = None
SAMPLE = {
"study": "Onboarding discovery (mid-market HR)",
"observations": [
{"participant": "P1", "tag": "import-confusion", "note": "Couldn't find CSV import."},
{"participant": "P2", "tag": "import-confusion", "note": "Expected import on the dashboard."},
{"participant": "P3", "tag": "import-confusion", "note": "Gave up looking for bulk upload."},
{"participant": "P1", "tag": "permissions-unclear", "note": "Unsure who could see reports."},
{"participant": "P4", "tag": "permissions-unclear", "note": "Worried about data visibility."},
{"participant": "P2", "tag": "wants-slack", "note": "Asked for a Slack integration."},
],
}
def synthesize(data: dict, min_sources: int) -> dict:
obs = data.get("observations", [])
by_tag_participants = defaultdict(set)
by_tag_notes = defaultdict(list)
for o in obs:
tag = o.get("tag", "untagged")
part = o.get("participant", "UNKNOWN")
by_tag_participants[tag].add(part)
by_tag_notes[tag].append({"participant": part, "note": o.get("note", "")})
candidates = []
for tag, parts in by_tag_participants.items():
n_sources = len(parts)
is_insight = n_sources >= min_sources
candidates.append({
"tag": tag,
"distinct_participants": n_sources,
"observation_count": len(by_tag_notes[tag]),
"classification": "INSIGHT" if is_insight else "ANECDOTE (single/low-source — do not generalize)",
"evidence": by_tag_notes[tag],
})
candidates.sort(key=lambda c: (c["distinct_participants"], c["observation_count"]), reverse=True)
total_participants = len({o.get("participant") for o in obs})
return {
"study": data.get("study", "UNSPECIFIED"),
"min_sources_for_insight": min_sources,
"total_participants": total_participants,
"candidates": candidates,
"note": "An observation is evidence; an insight requires recurrence across independent participants. "
"Anecdotes are surfaced, never promoted to insights.",
}
def _render_human(r: dict) -> str:
lines = [f"Insight Synthesis: {r['study']}",
f" total participants: {r['total_participants']} insight threshold: >= {r['min_sources_for_insight']} sources", ""]
for c in r["candidates"]:
lines.append(f"[{c['classification']}] {c['tag']} "
f"({c['distinct_participants']} participants, {c['observation_count']} observations)")
for e in c["evidence"]:
lines.append(f" {e['participant']}: {e['note']}")
lines.append("")
lines.append(f"note: {r['note']}")
return "\n".join(lines)
def main(argv: list[str] | None = None) -> int:
p = argparse.ArgumentParser(description="Cluster coded observations into insights; flag anecdotes.")
p.add_argument("--input", help="Path to JSON with observations[]")
p.add_argument("--min-sources", type=int, default=None,
help="min distinct participants to call it an insight (overrides onboarding)")
p.add_argument("--output", choices=["human", "json"], default="human")
p.add_argument("--sample", action="store_true", help="use the embedded sample")
args = p.parse_args(argv)
conf = _cfg.load_config() if _cfg else {}
min_sources = args.min_sources if args.min_sources is not None else int(conf.get("insight_min_sources", 3))
data = SAMPLE if (args.sample or not args.input) else json.load(open(args.input))
result = synthesize(data, min_sources)
if args.output == "json":
print(json.dumps(result, indent=2))
else:
print(_render_human(result))
return 0
if __name__ == "__main__":
sys.exit(main())
FILE:scripts/onboard.py
#!/usr/bin/env python3
"""onboard.py - Onboarding questionnaire for the product-research skill.
Stdlib-only. Asks the user a short set of questions BEFORE they plan a study, then
writes the answers to a customization config read by every tool in this skill via
config_loader.py. The answers become defaults for profile, the insight source-threshold,
the default saturation method, and the high-stakes flag.
Modes: --show | --defaults | --set key=value (repeatable) | --reset | --scope {global,project}
"""
from __future__ import annotations
import argparse
import datetime as _dt
import json
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import config_loader as cfg # noqa: E402
INT_KEYS = {"insight_min_sources"}
BOOL_KEYS = {"stakes_high"}
QUESTIONS = [
("default_profile",
"1. What kind of product is this?",
["b2b-saas", "consumer-app", "enterprise", "marketplace", "hardware", "platform"], str),
("insight_min_sources",
"2. How many independent participants must support a finding before it counts as an insight (not an anecdote)?",
None, int),
("default_method",
"3. Default sample-saturation method?",
["usability", "thematic", "evaluative-coverage"], str),
("stakes_high",
"4. Is this high-stakes / high-heterogeneity research (raise sample sizes)?",
["true", "false"], str),
]
def _coerce(key: str, value: str):
if key in INT_KEYS:
return int(value)
if key in BOOL_KEYS:
return str(value).strip().lower() in ("true", "yes", "y", "1")
return value
def _print_questions() -> None:
print(f"Onboarding questions — {cfg.SKILL}:\n")
for _k, prompt, choices, _c in QUESTIONS:
line = f" {prompt}"
if choices:
line += f" [{' / '.join(choices)}]"
print(line)
def run_interactive(config: dict) -> dict:
print(f"Onboarding — {cfg.SKILL}. Press Enter to keep the current/default value.\n")
for key, prompt, choices, _caster in QUESTIONS:
suffix = f" [{'/'.join(choices)}]" if choices else ""
cur = f" (current: {config.get(key)})" if config.get(key) is not None else ""
raw = input(f"{prompt}{suffix}{cur}: ").strip()
if not raw:
continue
try:
config[key] = _coerce(key, raw)
except ValueError:
print(f" ! invalid value for {key}, keeping current")
return config
def main(argv: list[str] | None = None) -> int:
p = argparse.ArgumentParser(description=f"Onboarding for the {cfg.SKILL} skill.")
p.add_argument("--show", action="store_true")
p.add_argument("--defaults", action="store_true", help="write built-in defaults, no prompt")
p.add_argument("--set", action="append", default=[], metavar="key=value")
p.add_argument("--reset", action="store_true")
p.add_argument("--scope", choices=["global", "project"], default="global")
args = p.parse_args(argv)
if args.show:
_print_questions()
print("\nCurrent effective config:")
print(json.dumps(cfg.load_config(), indent=2, sort_keys=True))
return 0
if args.reset:
path = cfg.project_config_path() if args.scope == "project" else cfg.GLOBAL_CONFIG_PATH
if path.exists():
path.unlink(); print(f"removed {path}")
else:
print(f"no config at {path}")
return 0
config = cfg.load_config()
if args.set:
for item in args.set:
if "=" not in item:
print(f"error: --set expects key=value, got '{item}'", file=sys.stderr)
return 2
k, v = item.split("=", 1)
try:
config[k] = _coerce(k, v)
except ValueError:
config[k] = v
elif not args.defaults:
if sys.stdin.isatty():
config = run_interactive(config)
else:
print("non-interactive shell: use --defaults or --set key=value. Showing questions:\n")
_print_questions()
return 0
config["setup_completed_at"] = _dt.datetime.now(_dt.timezone.utc).isoformat()
path = cfg.write_config(config, scope=args.scope)
print(f"saved {cfg.SKILL} customization -> {path}")
return 0
if __name__ == "__main__":
sys.exit(main())
FILE:scripts/saturation_planner.py
#!/usr/bin/env python3
"""saturation_planner.py - Method-based participant/sample guidance with a confidence label.
Stdlib-only. Deterministic. NO LLM calls. NEVER fabricates insight: it gives method-based
sample guidance and an explicit confidence level, surfacing limits.
Models:
- usability (Nielsen): ~5 users per segment uncovers ~85% of problems at typical p=0.31;
problems found = 1 - (1 - p)^n.
- thematic saturation (Guest et al.): ~12 interviews per homogeneous group typically
reaches saturation; >5 (Faulkner) when stakes/heterogeneity are high.
- evaluative coverage: detectable-problem coverage for a chosen per-problem detection rate.
Usage:
python3 saturation_planner.py --sample
python3 saturation_planner.py --method usability --segments 2 --detection-rate 0.31
python3 saturation_planner.py --method thematic --segments 3 --output json
"""
from __future__ import annotations
import argparse
import json
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
try:
import config_loader as _cfg
except ImportError: # pragma: no cover
_cfg = None
METHODS = ["usability", "thematic", "evaluative-coverage"]
def usability_plan(segments: int, p: float, target_coverage: float) -> dict:
# n per segment to reach target coverage: n = ln(1 - target) / ln(1 - p)
import math
if not 0.0 < p < 1.0:
raise ValueError("detection-rate must be in (0,1).")
n = math.ceil(math.log(1 - target_coverage) / math.log(1 - p))
coverage_at_5 = 1 - (1 - p) ** 5
return {
"method": "usability",
"per_problem_detection_rate": p,
"target_coverage": target_coverage,
"n_per_segment": n,
"segments": segments,
"total_participants": n * segments,
"coverage_at_5_per_segment": round(coverage_at_5, 3),
"confidence": "MODERATE" if n >= 5 else "LOW (small-n usability finds problems, not rates)",
"limits": "Usability tests surface problems, not their population prevalence. Do not report percentages.",
}
def thematic_plan(segments: int, stakes_high: bool) -> dict:
base = 12 # Guest et al. typical saturation for a homogeneous group
per_segment = base if not stakes_high else max(base, 15)
return {
"method": "thematic",
"n_per_segment": per_segment,
"segments": segments,
"total_participants": per_segment * segments,
"confidence": "MODERATE-HIGH" if per_segment >= 12 else "LOW",
"limits": "Saturation is observed, not guaranteed; track new-theme rate and stop when it flattens. "
"Faulkner (2003): more than 5 when heterogeneity or stakes are high.",
}
def evaluative_coverage_plan(segments: int, n_per_segment: int, p: float) -> dict:
coverage = 1 - (1 - p) ** n_per_segment
return {
"method": "evaluative-coverage",
"per_problem_detection_rate": p,
"n_per_segment": n_per_segment,
"segments": segments,
"expected_problem_coverage": round(coverage, 3),
"confidence": "MODERATE" if coverage >= 0.8 else "LOW",
"limits": "Coverage is for the assumed detection rate; rarer problems need more participants.",
}
def plan(method: str, segments: int, p: float, target: float, stakes_high: bool, n: int) -> dict:
if method == "usability":
out = usability_plan(segments, p, target)
elif method == "thematic":
out = thematic_plan(segments, stakes_high)
elif method == "evaluative-coverage":
out = evaluative_coverage_plan(segments, n, p)
else:
raise ValueError(f"method must be one of {METHODS}.")
out["disclaimer"] = "Method-based guidance with explicit confidence. This is not a power calculation; " \
"it never claims an insight the data cannot support."
return out
def _render_human(r: dict) -> str:
lines = [f"Saturation / Sample Plan (method: {r['method']})", ""]
for k, v in r.items():
if k in ("method",):
continue
lines.append(f" {k:32s} : {v}")
return "\n".join(lines)
def main(argv: list[str] | None = None) -> int:
p = argparse.ArgumentParser(description="Method-based product-research sample guidance with confidence.")
p.add_argument("--method", choices=METHODS, default=None, help="overrides onboarding default_method")
p.add_argument("--segments", type=int, default=1)
p.add_argument("--detection-rate", type=float, default=0.31, help="per-problem detection rate (usability)")
p.add_argument("--target-coverage", type=float, default=0.85, help="target problem coverage (usability)")
p.add_argument("--stakes-high", action="store_true", help="raise thematic n for high heterogeneity/stakes")
p.add_argument("--n-per-segment", type=int, default=8, help="n per segment (evaluative-coverage)")
p.add_argument("--output", choices=["human", "json"], default="human")
p.add_argument("--sample", action="store_true", help="use the embedded sample")
args = p.parse_args(argv)
conf = _cfg.load_config() if _cfg else {}
method = args.method or conf.get("default_method", "usability")
stakes_high = args.stakes_high or bool(conf.get("stakes_high", False))
if args.sample:
try:
result = plan("usability", 2, 0.31, 0.85, False, 8)
except ValueError as e:
print(f"error: {e}", file=sys.stderr)
return 2
else:
try:
result = plan(method, args.segments, args.detection_rate,
args.target_coverage, stakes_high, args.n_per_segment)
except ValueError as e:
print(f"error: {e}", file=sys.stderr)
return 2
if args.output == "json":
print(json.dumps(result, indent=2))
else:
print(_render_human(result))
return 0
if __name__ == "__main__":
sys.exit(main())
FILE:scripts/study_designer.py
#!/usr/bin/env python3
"""study_designer.py - Select a product-research method from goal + stage, emit a plan skeleton.
Stdlib-only. Deterministic. NO LLM calls.
Maps (research goal x product stage) to an appropriate method and emits a method-matched
plan skeleton (objective framing, participant criteria, task/guide structure, success
criteria). The core discipline: GENERATIVE goals (discover problems) and EVALUATIVE goals
(test a solution) demand different methods — picking the wrong one is the most common error.
Usage:
python3 study_designer.py --sample
python3 study_designer.py --goal discovery --stage concept --profile b2b-saas
python3 study_designer.py --goal evaluative --stage live --output json
"""
from __future__ import annotations
import argparse
import json
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
try:
import config_loader as _cfg
except ImportError: # pragma: no cover
_cfg = None
PROFILES = ["b2b-saas", "consumer-app", "enterprise", "marketplace", "hardware", "platform"]
# (goal, stage) -> method. goal in {discovery, evaluative, validation}; stage in {concept, prototype, beta, live}
METHOD_MAP = {
("discovery", "concept"): "generative interviews (semi-structured)",
("discovery", "prototype"): "contextual inquiry",
("discovery", "beta"): "diary study + follow-up interviews",
("discovery", "live"): "behavioral analytics review + generative interviews",
("evaluative", "concept"): "concept test (comprehension + desirability)",
("evaluative", "prototype"): "moderated usability test",
("evaluative", "beta"): "unmoderated usability test + task-success metrics",
("evaluative", "live"): "benchmark usability study (SUS / task time)",
("validation", "concept"): "survey (desirability + willingness signals)",
("validation", "prototype"): "prototype A/B preference test",
("validation", "beta"): "fake-door / feature-demand test",
("validation", "live"): "live A/B experiment (route to product-team/experiment-designer)",
}
GUIDE_SKELETONS = {
"generative": ["Warm-up + context", "Recent relevant experience (story, not opinion)",
"Workarounds + frustrations", "Jobs-to-be-done probe", "Magic-wand / wrap"],
"evaluative": ["Pre-task context", "Task 1 (representative)", "Task 2 (edge)",
"Observation: where do they hesitate/err?", "Post-task SUS / debrief"],
"validation": ["Screener", "Stimulus exposure", "Comprehension + desirability items",
"Trade-off / preference items", "Behavioral-intent item"],
}
def design(goal: str, stage: str, profile: str) -> dict:
if profile not in PROFILES:
raise ValueError(f"Unknown profile '{profile}'. Choose from {PROFILES}.")
key = (goal, stage)
if key not in METHOD_MAP:
raise ValueError(f"No method for goal={goal}, stage={stage}. "
f"goal in [discovery,evaluative,validation]; stage in [concept,prototype,beta,live].")
method = METHOD_MAP[key]
family = "generative" if goal == "discovery" else ("evaluative" if goal == "evaluative" else "validation")
redirect = None
if "experiment-designer" in method:
redirect = "Live A/B is a product experiment — use product-team/experiment-designer, not this skill."
return {
"goal": goal,
"stage": stage,
"profile": profile,
"method": method,
"method_family": family,
"objective_framing": f"A {family} study at the {stage} stage to {('discover unmet needs' if family=='generative' else 'evaluate the solution' if family=='evaluative' else 'validate demand/desirability')}.",
"participant_criteria": [
"Recruit to the target segment (screen for the job, not a job title).",
"Exclude internal/biased participants and prior-study repeats unless longitudinal.",
"Recruit per-segment if results will be reported per-segment.",
],
"guide_skeleton": GUIDE_SKELETONS[family],
"success_criteria": [
"Generative: themes recur across independent participants (saturation).",
"Evaluative: task-success rate + severity-rated problem list.",
"Validation: pre-registered desirability / preference threshold.",
],
"redirect": redirect,
"note": "Method must match the goal. A usability test cannot discover unmet needs; an interview cannot measure task success.",
}
def _render_human(r: dict) -> str:
lines = [f"Study Design: goal={r['goal']}, stage={r['stage']}, profile={r['profile']}", "",
f" Recommended method: {r['method']} (family: {r['method_family']})",
f" Objective: {r['objective_framing']}", "", " Participant criteria:"]
for c in r["participant_criteria"]:
lines.append(f" - {c}")
lines.append(" Guide skeleton:")
for i, g in enumerate(r["guide_skeleton"], 1):
lines.append(f" {i}. {g}")
lines.append(" Success criteria:")
for s in r["success_criteria"]:
lines.append(f" - {s}")
if r["redirect"]:
lines += ["", f" !! {r['redirect']}"]
lines += ["", f"note: {r['note']}"]
return "\n".join(lines)
def main(argv: list[str] | None = None) -> int:
p = argparse.ArgumentParser(description="Select a product-research method from goal + stage.")
p.add_argument("--goal", choices=["discovery", "evaluative", "validation"], default="discovery")
p.add_argument("--stage", choices=["concept", "prototype", "beta", "live"], default="prototype")
p.add_argument("--profile", default=None, choices=PROFILES,
help="overrides onboarding default_profile")
p.add_argument("--output", choices=["human", "json"], default="human")
p.add_argument("--sample", action="store_true", help="use the embedded sample")
args = p.parse_args(argv)
conf = _cfg.load_config() if _cfg else {}
profile_default = conf.get("default_profile", "b2b-saas")
goal, stage, profile = ("discovery", "prototype", profile_default) if args.sample \
else (args.goal, args.stage, args.profile or profile_default)
try:
result = design(goal, stage, profile)
except ValueError as e:
print(f"error: {e}", file=sys.stderr)
return 2
if args.output == "json":
print(json.dumps(result, indent=2))
else:
print(_render_human(result))
return 0
if __name__ == "__main__":
sys.exit(main())
Bộ công cụ chiến lược cho Head of Product: chuỗi OKR, kế hoạch quý, phân tích cảnh quan cạnh tranh, tầm nhìn sản phẩm và đề xuất mở rộng đội ngũ.
---
name: "product-strategist"
description: Strategic product leadership toolkit for Head of Product covering OKR cascade generation, quarterly planning, competitive landscape analysis, product vision documents, and team scaling proposals. Use when creating quarterly OKR documents, defining product goals or KPIs, building product roadmaps, running competitive analysis, drafting team structure or hiring plans, aligning product strategy across engineering and design, or generating cascaded goal hierarchies from company to team level.
---
# Product Strategist
Strategic toolkit for Head of Product to drive vision, alignment, and organizational excellence.
---
## Core Capabilities
| Capability | Description | Tool |
|------------|-------------|------|
| **OKR Cascade** | Generate aligned OKRs from company to team level | `okr_cascade_generator.py` |
| **Alignment Scoring** | Measure vertical and horizontal alignment | Built into generator |
| **Strategy Templates** | 5 pre-built strategy types | Growth, Retention, Revenue, Innovation, Operational |
| **Team Configuration** | Customize for your org structure | `--teams` flag |
---
## Quick Start
```bash
# Growth strategy with default teams
python scripts/okr_cascade_generator.py growth
# Retention strategy with custom teams
python scripts/okr_cascade_generator.py retention --teams "Engineering,Design,Data"
# Revenue strategy with 40% product contribution
python scripts/okr_cascade_generator.py revenue --contribution 0.4
# Export as JSON for integration
python scripts/okr_cascade_generator.py growth --json > okrs.json
```
---
## Workflow: Quarterly Strategic Planning
### Step 1: Define Strategic Focus
| Strategy | When to Use |
|----------|-------------|
| **Growth** | Scaling user base, market expansion |
| **Retention** | Reducing churn, improving LTV |
| **Revenue** | Increasing ARPU, new monetization |
| **Innovation** | Market differentiation, new capabilities |
| **Operational** | Improving efficiency, scaling operations |
See `references/strategy_types.md` for detailed guidance.
### Step 2: Gather Input Metrics
```json
{
"current": 100000, // Current MAU
"target": 150000, // Target MAU
"current_nps": 40, // Current NPS
"target_nps": 60 // Target NPS
}
```
### Step 3: Configure Teams & Run Generator
```bash
# Default teams
python scripts/okr_cascade_generator.py growth
# Custom org structure with contribution percentage
python scripts/okr_cascade_generator.py growth \
--teams "Core,Platform,Mobile,AI" \
--contribution 0.3
```
### Step 4: Review Alignment Scores
| Score | Target | Action if Below |
|-------|--------|-----------------|
| Vertical Alignment | >90% | Ensure all objectives link to parent |
| Horizontal Alignment | >75% | Check for team coordination gaps |
| Coverage | >80% | Validate all company OKRs are addressed |
| Balance | >80% | Redistribute if one team is overloaded |
| **Overall** | **>80%** | <60% needs restructuring |
### Step 5: Refine, Validate, and Export
Before finalizing:
- [ ] Review generated objectives with stakeholders
- [ ] Adjust team assignments based on capacity
- [ ] Validate contribution percentages are realistic
- [ ] Ensure no conflicting objectives across teams
- [ ] Set up tracking cadence (bi-weekly check-ins)
```bash
# Export JSON for tools like Lattice, Ally, Workboard
python scripts/okr_cascade_generator.py growth --json > q1_okrs.json
```
---
## OKR Cascade Generator
### Usage
```bash
python scripts/okr_cascade_generator.py [strategy] [options]
```
**Strategies:** `growth` | `retention` | `revenue` | `innovation` | `operational`
### Configuration Options
| Option | Description | Default |
|--------|-------------|---------|
| `--teams`, `-t` | Comma-separated team names | Growth,Platform,Mobile,Data |
| `--contribution`, `-c` | Product contribution to company OKRs (0-1) | 0.3 (30%) |
| `--json`, `-j` | Output as JSON instead of dashboard | False |
| `--metrics`, `-m` | Metrics as JSON string | Sample metrics |
### Output Examples
#### Dashboard Output (`growth` strategy)
```
============================================================
OKR CASCADE DASHBOARD
Quarter: Q1 2025 | Strategy: GROWTH
Teams: Growth, Platform, Mobile, Data | Product Contribution: 30%
============================================================
🏢 COMPANY OKRS
📌 CO-1: Accelerate user acquisition and market expansion
└─ CO-1-KR1: Increase MAU from 100,000 to 150,000
└─ CO-1-KR2: Achieve 50% MoM growth rate
└─ CO-1-KR3: Expand to 3 new markets
📌 CO-2: Achieve product-market fit in new segments
📌 CO-3: Build sustainable growth engine
🚀 PRODUCT OKRS
📌 PO-1: Build viral product features and market expansion
↳ Supports: CO-1
└─ PO-1-KR1: Increase product MAU to 45,000
└─ PO-1-KR2: Achieve 45% feature adoption rate
👥 TEAM OKRS
Growth Team:
📌 GRO-1: Build viral product features through acquisition and activation
└─ GRO-1-KR1: Increase product MAU to 11,250
└─ GRO-1-KR2: Achieve 11.25% feature adoption rate
🎯 ALIGNMENT SCORES
✓ Vertical Alignment: 100.0%
! Horizontal Alignment: 75.0%
✓ Coverage: 100.0% | ✓ Balance: 97.5% | ✓ Overall: 94.0%
✅ Overall alignment is GOOD (≥80%)
```
#### JSON Output (`retention --json`, truncated)
```json
{
"quarter": "Q1 2025",
"strategy": "retention",
"company": {
"objectives": [
{
"id": "CO-1",
"title": "Create lasting customer value and loyalty",
"key_results": [
{ "id": "CO-1-KR1", "title": "Improve retention from 70% to 85%", "current": 70, "target": 85 }
]
}
]
},
"product": { "contribution": 0.3, "objectives": ["..."] },
"teams": ["..."],
"alignment_scores": {
"vertical_alignment": 100.0, "horizontal_alignment": 75.0,
"coverage": 100.0, "balance": 97.5, "overall": 94.0
}
}
```
See `references/examples/sample_growth_okrs.json` for a complete example.
---
## Reference Documents
| Document | Description |
|----------|-------------|
| `references/okr_framework.md` | OKR methodology, writing guidelines, alignment scoring |
| `references/strategy_types.md` | Detailed breakdown of all 5 strategy types with examples |
| `references/examples/sample_growth_okrs.json` | Complete sample output for growth strategy |
---
## Best Practices
### OKR Cascade
- Limit to 3-5 objectives per level, each with 3-5 key results
- Key results must be measurable with current and target values
- Validate parent-child relationships before finalizing
### Alignment Scoring
- Target >80% overall alignment; investigate any score below 60%
- Balance scores ensure no team is overloaded
- Horizontal alignment prevents conflicting goals across teams
### Team Configuration
- Configure teams to match your actual org structure
- Adjust contribution percentages based on team size
- Platform/Infrastructure teams often support all objectives
- Specialized teams (ML, Data) may only support relevant objectives
## Related Skills
- **Senior PM** (`project-management/senior-pm/`) — Portfolio management and risk analysis inform strategic planning
- **Competitive Teardown** (`product-team/competitive-teardown/`) — Competitive intelligence feeds product strategy
FILE:assets/okr_template.md
# OKR Planning Template
## Planning Info
| Field | Value |
|-------|-------|
| **Quarter** | [Q1/Q2/Q3/Q4 YYYY] |
| **Team** | [Team Name] |
| **Owner** | [Name] |
| **Status** | Planning / Active / Complete |
| **Tracking Cadence** | Weekly check-in, Monthly review |
---
## Company Objective
**[Company-level objective this product work supports]**
_Example: "Become the market leader in our category by delivering exceptional customer value"_
---
## Product Objective 1: [Objective Title]
_[Qualitative, inspirational statement. What does success look like?]_
### Key Results
| # | Key Result | Baseline | Target | Current | Status |
|---|-----------|----------|--------|---------|--------|
| 1.1 | [Measurable outcome] | [Current value] | [Target value] | [Progress] | On Track / At Risk / Off Track |
| 1.2 | [Measurable outcome] | [Current value] | [Target value] | [Progress] | On Track / At Risk / Off Track |
| 1.3 | [Measurable outcome] | [Current value] | [Target value] | [Progress] | On Track / At Risk / Off Track |
### Initiatives
| Initiative | Key Result | Owner | Status | Effort |
|-----------|-----------|-------|--------|--------|
| [Feature/project name] | KR 1.1 | [Name] | Not Started / In Progress / Complete | [T-shirt size] |
| [Feature/project name] | KR 1.2 | [Name] | Not Started / In Progress / Complete | [T-shirt size] |
---
## Product Objective 2: [Objective Title]
_[Qualitative, inspirational statement]_
### Key Results
| # | Key Result | Baseline | Target | Current | Status |
|---|-----------|----------|--------|---------|--------|
| 2.1 | [Measurable outcome] | [Current value] | [Target value] | [Progress] | On Track / At Risk / Off Track |
| 2.2 | [Measurable outcome] | [Current value] | [Target value] | [Progress] | On Track / At Risk / Off Track |
| 2.3 | [Measurable outcome] | [Current value] | [Target value] | [Progress] | On Track / At Risk / Off Track |
| 2.4 | [Measurable outcome] | [Current value] | [Target value] | [Progress] | On Track / At Risk / Off Track |
### Initiatives
| Initiative | Key Result | Owner | Status | Effort |
|-----------|-----------|-------|--------|--------|
| [Feature/project name] | KR 2.1 | [Name] | Not Started / In Progress / Complete | [T-shirt size] |
| [Feature/project name] | KR 2.2 | [Name] | Not Started / In Progress / Complete | [T-shirt size] |
---
## Product Objective 3: [Objective Title]
_[Qualitative, inspirational statement]_
### Key Results
| # | Key Result | Baseline | Target | Current | Status |
|---|-----------|----------|--------|---------|--------|
| 3.1 | [Measurable outcome] | [Current value] | [Target value] | [Progress] | On Track / At Risk / Off Track |
| 3.2 | [Measurable outcome] | [Current value] | [Target value] | [Progress] | On Track / At Risk / Off Track |
| 3.3 | [Measurable outcome] | [Current value] | [Target value] | [Progress] | On Track / At Risk / Off Track |
### Initiatives
| Initiative | Key Result | Owner | Status | Effort |
|-----------|-----------|-------|--------|--------|
| [Feature/project name] | KR 3.1 | [Name] | Not Started / In Progress / Complete | [T-shirt size] |
---
## Tracking
### Weekly Check-In Format
- **Confidence level** (1-10) for each key result
- **Blockers** identified and escalated
- **Adjustments** to initiatives if needed
### Monthly Review Format
- **Progress update** on all key results with data
- **Initiative status** review
- **Risk assessment** and mitigation updates
- **Stakeholder communication** summary
### End-of-Quarter Scoring
Score each key result 0.0 - 1.0:
- **1.0:** Fully achieved
- **0.7:** Strong progress, nearly there (ideal target)
- **0.4:** Meaningful progress but missed target
- **0.0:** No progress
_Note: Consistently scoring 1.0 means OKRs are not ambitious enough. Target 0.6-0.7 average._
FILE:references/examples/sample_growth_okrs.json
{
"metadata": {
"strategy": "growth",
"quarter": "Q1 2025",
"generated_at": "2025-01-15T10:30:00Z",
"teams": ["Growth", "Platform", "Mobile", "Data"],
"product_contribution": 0.3
},
"company": {
"level": "Company",
"quarter": "Q1 2025",
"strategy": "growth",
"objectives": [
{
"id": "CO-1",
"title": "Accelerate user acquisition and market expansion",
"owner": "CEO",
"status": "active",
"key_results": [
{
"id": "CO-1-KR1",
"title": "Increase MAU from 100,000 to 150,000",
"current": 100000,
"target": 150000,
"unit": "users",
"status": "in_progress",
"progress": 0.2
},
{
"id": "CO-1-KR2",
"title": "Achieve 15% MoM growth rate",
"current": 8,
"target": 15,
"unit": "%",
"status": "in_progress",
"progress": 0.53
},
{
"id": "CO-1-KR3",
"title": "Expand to 3 new markets",
"current": 0,
"target": 3,
"unit": "markets",
"status": "not_started",
"progress": 0
}
]
},
{
"id": "CO-2",
"title": "Achieve product-market fit in enterprise segment",
"owner": "CEO",
"status": "active",
"key_results": [
{
"id": "CO-2-KR1",
"title": "Reduce CAC by 25%",
"current": 150,
"target": 112.5,
"unit": "$",
"status": "in_progress",
"progress": 0.4
},
{
"id": "CO-2-KR2",
"title": "Improve activation rate to 60%",
"current": 42,
"target": 60,
"unit": "%",
"status": "in_progress",
"progress": 0.3
}
]
},
{
"id": "CO-3",
"title": "Build sustainable growth engine",
"owner": "CEO",
"status": "active",
"key_results": [
{
"id": "CO-3-KR1",
"title": "Increase viral coefficient to 1.2",
"current": 0.8,
"target": 1.2,
"unit": "coefficient",
"status": "not_started",
"progress": 0
},
{
"id": "CO-3-KR2",
"title": "Grow organic acquisition to 40% of total",
"current": 25,
"target": 40,
"unit": "%",
"status": "in_progress",
"progress": 0.2
}
]
}
]
},
"product": {
"level": "Product",
"quarter": "Q1 2025",
"parent": "Company",
"objectives": [
{
"id": "PO-1",
"title": "Build viral product features to drive acquisition",
"parent_objective": "CO-1",
"owner": "Head of Product",
"status": "active",
"key_results": [
{
"id": "PO-1-KR1",
"title": "Increase product MAU from 100,000 to 115,000 (30% contribution)",
"contributes_to": "CO-1-KR1",
"current": 100000,
"target": 115000,
"unit": "users",
"status": "in_progress"
},
{
"id": "PO-1-KR2",
"title": "Achieve 12% feature adoption rate for sharing features",
"contributes_to": "CO-1-KR2",
"current": 5,
"target": 12,
"unit": "%",
"status": "in_progress"
}
]
},
{
"id": "PO-2",
"title": "Validate product hypotheses for enterprise segment",
"parent_objective": "CO-2",
"owner": "Head of Product",
"status": "active",
"key_results": [
{
"id": "PO-2-KR1",
"title": "Improve product onboarding efficiency by 30%",
"contributes_to": "CO-2-KR1",
"current": 0,
"target": 30,
"unit": "%",
"status": "not_started"
},
{
"id": "PO-2-KR2",
"title": "Increase product activation rate to 55%",
"contributes_to": "CO-2-KR2",
"current": 42,
"target": 55,
"unit": "%",
"status": "in_progress"
}
]
},
{
"id": "PO-3",
"title": "Create product-led growth loops",
"parent_objective": "CO-3",
"owner": "Head of Product",
"status": "active",
"key_results": [
{
"id": "PO-3-KR1",
"title": "Launch referral program with 0.3 viral coefficient contribution",
"contributes_to": "CO-3-KR1",
"current": 0,
"target": 0.3,
"unit": "coefficient",
"status": "not_started"
},
{
"id": "PO-3-KR2",
"title": "Increase product-driven organic signups to 35%",
"contributes_to": "CO-3-KR2",
"current": 20,
"target": 35,
"unit": "%",
"status": "in_progress"
}
]
}
]
},
"teams": [
{
"level": "Team",
"team": "Growth",
"quarter": "Q1 2025",
"parent": "Product",
"objectives": [
{
"id": "GRO-1",
"title": "Build viral product features through acquisition and activation",
"parent_objective": "PO-1",
"owner": "Growth PM",
"status": "active",
"key_results": [
{
"id": "GRO-1-KR1",
"title": "[Growth] Increase product MAU contribution by 5,000 users",
"contributes_to": "PO-1-KR1",
"current": 0,
"target": 5000,
"unit": "users",
"status": "in_progress"
},
{
"id": "GRO-1-KR2",
"title": "[Growth] Launch 3 viral feature experiments",
"contributes_to": "PO-1-KR2",
"current": 0,
"target": 3,
"unit": "experiments",
"status": "not_started"
}
]
}
]
},
{
"level": "Team",
"team": "Platform",
"quarter": "Q1 2025",
"parent": "Product",
"objectives": [
{
"id": "PLA-1",
"title": "Support growth through infrastructure and reliability",
"parent_objective": "PO-1",
"owner": "Platform PM",
"status": "active",
"key_results": [
{
"id": "PLA-1-KR1",
"title": "[Platform] Scale infrastructure to support 200K MAU",
"contributes_to": "PO-1-KR1",
"current": 100000,
"target": 200000,
"unit": "users",
"status": "in_progress"
},
{
"id": "PLA-1-KR2",
"title": "[Platform] Maintain 99.9% uptime during growth",
"contributes_to": "PO-1-KR2",
"current": 99.5,
"target": 99.9,
"unit": "%",
"status": "in_progress"
}
]
},
{
"id": "PLA-2",
"title": "Improve onboarding infrastructure efficiency",
"parent_objective": "PO-2",
"owner": "Platform PM",
"status": "active",
"key_results": [
{
"id": "PLA-2-KR1",
"title": "[Platform] Reduce onboarding API latency by 40%",
"contributes_to": "PO-2-KR1",
"current": 0,
"target": 40,
"unit": "%",
"status": "not_started"
}
]
}
]
},
{
"level": "Team",
"team": "Mobile",
"quarter": "Q1 2025",
"parent": "Product",
"objectives": [
{
"id": "MOB-1",
"title": "Build viral features through mobile experience",
"parent_objective": "PO-1",
"owner": "Mobile PM",
"status": "active",
"key_results": [
{
"id": "MOB-1-KR1",
"title": "[Mobile] Increase mobile MAU by 3,000 users",
"contributes_to": "PO-1-KR1",
"current": 0,
"target": 3000,
"unit": "users",
"status": "not_started"
},
{
"id": "MOB-1-KR2",
"title": "[Mobile] Launch native share feature with 15% adoption",
"contributes_to": "PO-1-KR2",
"current": 0,
"target": 15,
"unit": "%",
"status": "not_started"
}
]
}
]
},
{
"level": "Team",
"team": "Data",
"quarter": "Q1 2025",
"parent": "Product",
"objectives": [
{
"id": "DAT-1",
"title": "Enable growth through analytics and insights",
"parent_objective": "PO-1",
"owner": "Data PM",
"status": "active",
"key_results": [
{
"id": "DAT-1-KR1",
"title": "[Data] Build growth dashboard tracking all acquisition metrics",
"contributes_to": "PO-1-KR1",
"current": 0,
"target": 1,
"unit": "dashboard",
"status": "not_started"
},
{
"id": "DAT-1-KR2",
"title": "[Data] Implement experimentation platform for A/B testing",
"contributes_to": "PO-1-KR2",
"current": 0,
"target": 1,
"unit": "platform",
"status": "not_started"
}
]
}
]
}
],
"alignment_scores": {
"vertical_alignment": 100.0,
"horizontal_alignment": 75.0,
"coverage": 100.0,
"balance": 85.0,
"overall": 92.0
},
"summary": {
"total_objectives": 11,
"total_key_results": 22,
"company_objectives": 3,
"product_objectives": 3,
"team_objectives": 5,
"teams_involved": 4
}
}
FILE:references/okr_framework.md
# OKR Cascade Framework
A practical guide to Objectives and Key Results (OKRs) and how to cascade them across organizational levels.
---
## Table of Contents
- [What Are OKRs](#what-are-okrs)
- [The Cascade Model](#the-cascade-model)
- [Writing Effective Objectives](#writing-effective-objectives)
- [Defining Key Results](#defining-key-results)
- [Alignment Scoring](#alignment-scoring)
- [Common Pitfalls](#common-pitfalls)
- [OKR Cadence](#okr-cadence)
---
## What Are OKRs
**Objectives and Key Results (OKRs)** are a goal-setting framework that connects organizational strategy to measurable outcomes.
### Components
| Component | Definition | Characteristics |
|-----------|------------|-----------------|
| **Objective** | What you want to achieve | Qualitative, inspirational, time-bound |
| **Key Result** | How you measure progress | Quantitative, specific, measurable |
### OKR Formula
```
Objective: [Inspirational goal statement]
├── KR1: [Metric] from [current] to [target] by [date]
├── KR2: [Metric] from [current] to [target] by [date]
└── KR3: [Metric] from [current] to [target] by [date]
```
### Example
```
Objective: Become the go-to solution for enterprise customers
KR1: Increase enterprise ARR from $5M to $8M
KR2: Improve enterprise NPS from 35 to 50
KR3: Reduce enterprise onboarding time from 30 days to 14 days
```
---
## The Cascade Model
OKRs cascade from company strategy down to individual teams, ensuring alignment at every level.
### Cascade Structure
```
┌─────────────────────────────────────────┐
│ COMPANY LEVEL │
│ Strategic objectives set by leadership │
│ Owned by: CEO, Executive Team │
└───────────────┬─────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ PRODUCT LEVEL │
│ How product org contributes to company │
│ Owned by: Head of Product, CPO │
└───────────────┬─────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ TEAM LEVEL │
│ Specific initiatives and deliverables │
│ Owned by: Product Managers, Tech Leads │
└─────────────────────────────────────────┘
```
### Contribution Model
Each level contributes a percentage to the level above:
| Level | Typical Contribution | Range |
|-------|---------------------|-------|
| Product → Company | 30% | 20-50% |
| Team → Product | 25% per team | 15-35% |
**Note:** Contribution percentages should be calibrated based on:
- Number of teams
- Relative team size
- Strategic importance of initiatives
### Alignment Types
| Alignment | Description | Goal |
|-----------|-------------|------|
| **Vertical** | Each level supports the level above | >90% of objectives linked |
| **Horizontal** | Teams coordinate on shared objectives | No conflicting goals |
| **Temporal** | Quarterly OKRs support annual goals | Clear progression |
---
## Writing Effective Objectives
### The 3 Cs of Objectives
| Criterion | Description | Example |
|-----------|-------------|---------|
| **Clear** | Unambiguous intent | "Improve customer onboarding" not "Make things better" |
| **Compelling** | Inspires action | "Delight enterprise customers" not "Serve enterprise" |
| **Challenging** | Stretches capabilities | Achievable but requires effort |
### Objective Templates by Strategy
**Growth Strategy:**
```
- Accelerate user acquisition in [segment]
- Expand market presence in [region/vertical]
- Build sustainable acquisition channels
```
**Retention Strategy:**
```
- Create lasting value for [user segment]
- Improve product experience for [use case]
- Maximize customer lifetime value
```
**Revenue Strategy:**
```
- Drive revenue growth through [mechanism]
- Optimize monetization for [segment]
- Expand revenue per customer
```
**Innovation Strategy:**
```
- Pioneer [capability] in the market
- Establish leadership through [innovation area]
- Build competitive differentiation
```
**Operational Strategy:**
```
- Improve delivery efficiency by [mechanism]
- Scale operations to support [target]
- Reduce operational friction in [area]
```
### Objective Anti-Patterns
| Anti-Pattern | Problem | Better Alternative |
|--------------|---------|-------------------|
| "Increase revenue" | Too vague | "Grow enterprise ARR to $10M" |
| "Be the best" | Not measurable | "Achieve #1 NPS in category" |
| "Fix bugs" | Too tactical | "Improve platform reliability" |
| "Launch feature X" | Output, not outcome | "Improve [metric] through [capability]" |
---
## Defining Key Results
### Key Result Anatomy
```
[Verb] [metric] from [current baseline] to [target] by [deadline]
```
### Key Result Types
| Type | Characteristics | When to Use |
|------|-----------------|-------------|
| **Metric-based** | Track a number | Most common, highly measurable |
| **Milestone-based** | Track completion | For binary deliverables |
| **Health-based** | Track stability | For maintenance objectives |
### Metric Categories
| Category | Examples |
|----------|----------|
| **Acquisition** | Signups, trials started, leads generated |
| **Activation** | Onboarding completion, first value moment |
| **Retention** | D7/D30 retention, churn rate, repeat usage |
| **Revenue** | ARR, ARPU, conversion rate, LTV |
| **Engagement** | DAU/MAU, session duration, actions per session |
| **Satisfaction** | NPS, CSAT, support tickets |
| **Efficiency** | Cycle time, automation rate, cost per unit |
### Key Result Scoring
| Score | Status | Description |
|-------|--------|-------------|
| 0.0-0.3 | Red | Significant gap, needs intervention |
| 0.4-0.6 | Yellow | Partial progress, on watch |
| 0.7-0.9 | Green | Strong progress, on track |
| 1.0 | Complete | Target achieved |
**Note:** Hitting 0.7 is considered success for stretch goals. Consistently hitting 1.0 suggests targets aren't ambitious enough.
---
## Alignment Scoring
The OKR cascade generator calculates alignment scores across four dimensions:
### Scoring Dimensions
| Dimension | Weight | What It Measures |
|-----------|--------|------------------|
| **Vertical Alignment** | 40% | % of objectives with parent links |
| **Horizontal Alignment** | 20% | Cross-team coordination on shared goals |
| **Coverage** | 20% | % of company KRs addressed by product |
| **Balance** | 20% | Even distribution of work across teams |
### Alignment Score Interpretation
| Score | Grade | Interpretation |
|-------|-------|----------------|
| 90-100% | A | Excellent alignment, well-cascaded |
| 80-89% | B | Good alignment, minor gaps |
| 70-79% | C | Adequate, needs attention |
| 60-69% | D | Poor alignment, significant gaps |
| <60% | F | Misaligned, requires restructuring |
### Target Benchmarks
| Metric | Target | Red Flag |
|--------|--------|----------|
| Vertical alignment | >90% | <70% |
| Horizontal alignment | >75% | <50% |
| Coverage | >80% | <60% |
| Balance | >80% | <60% |
| Overall | >80% | <65% |
---
## Common Pitfalls
### OKR Anti-Patterns
| Pitfall | Symptom | Fix |
|---------|---------|-----|
| **Too many OKRs** | 10+ objectives per level | Limit to 3-5 objectives |
| **Sandbagging** | Always hit 100% | Set stretch targets (0.7 = success) |
| **Task lists** | KRs are tasks, not outcomes | Focus on measurable impact |
| **Set and forget** | No mid-quarter reviews | Check-ins every 2 weeks |
| **Cascade disconnect** | Team OKRs don't link up | Validate parent relationships |
| **Metric gaming** | Optimizing for KR, not intent | Balance with health metrics |
### Warning Signs
- All teams have identical objectives (lack of specialization)
- No team owns a critical company objective (gap in coverage)
- One team owns everything (unrealistic load)
- Objectives change weekly (lack of commitment)
- KRs are activities, not outcomes (wrong focus)
---
## OKR Cadence
### Quarterly Rhythm
| Week | Activity |
|------|----------|
| **Week -2** | Leadership sets company OKRs draft |
| **Week -1** | Product and team OKR drafting |
| **Week 0** | OKR finalization and alignment review |
| **Week 2** | First check-in, adjust if needed |
| **Week 6** | Mid-quarter review |
| **Week 10** | Pre-quarter reflection |
| **Week 12** | Quarter close, scoring, learnings |
### Check-in Format
```
Weekly/Bi-weekly Status Update:
1. Confidence level: [Red/Yellow/Green]
2. Progress since last check-in: [specific updates]
3. Blockers: [what's in the way]
4. Asks: [what help is needed]
5. Forecast: [expected end-of-quarter score]
```
### Annual Alignment
Quarterly OKRs should ladder up to annual goals:
```
Annual Goal: Become a $100M ARR business
Q1: Build enterprise sales motion (ARR: $25M → $32M)
Q2: Expand into APAC region (ARR: $32M → $45M)
Q3: Launch self-serve enterprise tier (ARR: $45M → $65M)
Q4: Scale and optimize (ARR: $65M → $100M)
```
---
## Quick Reference
### OKR Checklist
**Before finalizing OKRs:**
- [ ] 3-5 objectives per level (not more)
- [ ] 3-5 key results per objective
- [ ] Each KR has a current baseline and target
- [ ] Vertical alignment validated (parent links)
- [ ] No conflicting objectives across teams
- [ ] Owners assigned to every objective
- [ ] Check-in cadence defined
**During the quarter:**
- [ ] Bi-weekly progress updates
- [ ] Mid-quarter formal review
- [ ] Adjust forecasts based on learnings
- [ ] Escalate blockers early
**End of quarter:**
- [ ] Score all key results (0.0-1.0)
- [ ] Document learnings
- [ ] Celebrate wins
- [ ] Carry forward or close incomplete items
---
*See also: `strategy_types.md` for strategy-specific OKR templates*
FILE:references/strategy_types.md
# Strategy Types for OKR Generation
Comprehensive breakdown of the five core strategy types with objectives, key results, and when to use each.
---
## Table of Contents
- [Strategy Selection Guide](#strategy-selection-guide)
- [Growth Strategy](#growth-strategy)
- [Retention Strategy](#retention-strategy)
- [Revenue Strategy](#revenue-strategy)
- [Innovation Strategy](#innovation-strategy)
- [Operational Strategy](#operational-strategy)
- [Multi-Strategy Combinations](#multi-strategy-combinations)
---
## Strategy Selection Guide
### Decision Matrix
| If your priority is... | Primary Strategy | Secondary Strategy |
|------------------------|------------------|-------------------|
| Scaling user base | Growth | Retention |
| Reducing churn | Retention | Revenue |
| Increasing ARPU | Revenue | Retention |
| Market differentiation | Innovation | Growth |
| Improving efficiency | Operational | Revenue |
| New market entry | Growth | Innovation |
### Strategy by Company Stage
| Stage | Typical Priority | Rationale |
|-------|------------------|-----------|
| **Pre-PMF** | Innovation | Finding product-market fit |
| **Early Growth** | Growth | Scaling acquisition |
| **Growth** | Growth + Retention | Balancing acquisition with value |
| **Scale** | Revenue + Retention | Optimizing unit economics |
| **Mature** | Operational + Revenue | Efficiency and margins |
---
## Growth Strategy
**Focus:** Accelerating user acquisition and market expansion
### When to Use
- User growth is primary company objective
- Product-market fit is validated
- Acquisition channels are scaling
- Ready to invest in growth loops
### Company-Level Objectives
| Objective | Key Results Template |
|-----------|---------------------|
| Accelerate user acquisition and market expansion | - Increase MAU from X to Y<br>- Achieve Z% MoM growth rate<br>- Expand to N new markets |
| Achieve product-market fit in new segments | - Reach X users in [segment]<br>- Achieve Y% activation rate<br>- Validate Z use cases |
| Build sustainable growth engine | - Reduce CAC by X%<br>- Improve viral coefficient to Y<br>- Increase organic share to Z% |
### Product-Level Cascade
| Product Objective | Supports | Key Results |
|-------------------|----------|-------------|
| Build viral product features | User acquisition | - Launch referral program (target: X referrals/user)<br>- Increase shareability by Y% |
| Optimize onboarding experience | Activation | - Improve activation rate from X% to Y%<br>- Reduce time-to-value by Z% |
| Create product-led growth loops | Sustainable growth | - Increase product-qualified leads by X%<br>- Improve trial-to-paid by Y% |
### Team-Level Examples
| Team | Focus Area | Sample KRs |
|------|------------|------------|
| Growth Team | Acquisition & activation | - Improve signup conversion by X%<br>- Launch Y experiments/week |
| Platform Team | Scale & reliability | - Support X concurrent users<br>- Maintain Y% uptime |
| Mobile Team | Mobile acquisition | - Increase mobile signups by X%<br>- Improve mobile activation by Y% |
### Key Metrics to Track
- Monthly Active Users (MAU)
- Growth rate (MoM, YoY)
- Customer Acquisition Cost (CAC)
- Activation rate
- Viral coefficient
- Channel efficiency
---
## Retention Strategy
**Focus:** Creating lasting customer value and reducing churn
### When to Use
- Churn is above industry benchmark
- LTV/CAC needs improvement
- Product stickiness is low
- Expansion revenue is a priority
### Company-Level Objectives
| Objective | Key Results Template |
|-----------|---------------------|
| Create lasting customer value and loyalty | - Improve retention from X% to Y%<br>- Increase NPS from X to Y<br>- Reduce churn to below Z% |
| Deliver a superior user experience | - Achieve X% product stickiness<br>- Improve satisfaction to Y/10<br>- Reduce support tickets by Z% |
| Maximize customer lifetime value | - Increase LTV by X%<br>- Improve LTV/CAC ratio to Y<br>- Grow expansion revenue by Z% |
### Product-Level Cascade
| Product Objective | Supports | Key Results |
|-------------------|----------|-------------|
| Design sticky user experiences | Customer retention | - Increase DAU/MAU ratio from X to Y<br>- Improve weekly return rate by Z% |
| Build habit-forming features | Product stickiness | - Achieve X% feature adoption<br>- Increase sessions/user by Y |
| Create expansion opportunities | Lifetime value | - Launch N upsell touchpoints<br>- Improve upgrade rate by X% |
### Team-Level Examples
| Team | Focus Area | Sample KRs |
|------|------------|------------|
| Growth Team | Retention loops | - Improve D7 retention by X%<br>- Reduce first-week churn by Y% |
| Data Team | Churn prediction | - Build churn model (accuracy >X%)<br>- Identify Y at-risk signals |
| Platform Team | Reliability | - Reduce error rates by X%<br>- Improve load times by Y% |
### Key Metrics to Track
- Retention rates (D1, D7, D30, D90)
- Churn rate
- Net Promoter Score (NPS)
- Customer Satisfaction (CSAT)
- Feature stickiness
- Session frequency
---
## Revenue Strategy
**Focus:** Driving sustainable revenue growth and monetization
### When to Use
- Company is focused on profitability
- Monetization needs optimization
- Pricing strategy is being revised
- Expansion revenue is priority
### Company-Level Objectives
| Objective | Key Results Template |
|-----------|---------------------|
| Drive sustainable revenue growth | - Grow ARR from $X to $Y<br>- Achieve Z% revenue growth rate<br>- Maintain X% gross margin |
| Optimize monetization strategy | - Increase ARPU by X%<br>- Improve pricing efficiency by Y%<br>- Launch Z new pricing tiers |
| Expand revenue per customer | - Grow expansion revenue by X%<br>- Reduce revenue churn to Y%<br>- Increase upsell rate by Z% |
### Product-Level Cascade
| Product Objective | Supports | Key Results |
|-------------------|----------|-------------|
| Optimize product monetization | Revenue growth | - Improve conversion to paid by X%<br>- Reduce free tier abuse by Y% |
| Build premium features | ARPU growth | - Launch N premium features<br>- Achieve X% premium adoption |
| Create value-based pricing alignment | Pricing efficiency | - Implement usage-based pricing<br>- Improve price-to-value ratio by X% |
### Team-Level Examples
| Team | Focus Area | Sample KRs |
|------|------------|------------|
| Growth Team | Conversion | - Improve trial-to-paid by X%<br>- Reduce time-to-upgrade by Y days |
| Platform Team | Usage metering | - Implement accurate usage tracking<br>- Support X billing scenarios |
| Data Team | Revenue analytics | - Build revenue forecasting model<br>- Identify Y expansion signals |
### Key Metrics to Track
- Annual Recurring Revenue (ARR)
- Average Revenue Per User (ARPU)
- Gross margin
- Revenue churn (net and gross)
- Expansion revenue
- LTV/CAC ratio
---
## Innovation Strategy
**Focus:** Building competitive advantage through product innovation
### When to Use
- Market is commoditizing
- Competitors are catching up
- New technology opportunity exists
- Company needs differentiation
### Company-Level Objectives
| Objective | Key Results Template |
|-----------|---------------------|
| Lead the market through product innovation | - Launch X breakthrough features<br>- Achieve Y% revenue from new products<br>- File Z patents/IP |
| Establish market leadership in [area] | - Become #1 in category for X<br>- Win Y analyst recognitions<br>- Achieve Z% awareness |
| Build sustainable competitive moat | - Reduce feature parity gap by X%<br>- Create Y unique capabilities<br>- Build Z switching barriers |
### Product-Level Cascade
| Product Objective | Supports | Key Results |
|-------------------|----------|-------------|
| Ship innovative features faster | Breakthrough innovation | - Reduce time-to-market by X%<br>- Launch Y experiments/quarter |
| Build unique technical capabilities | Competitive moat | - Develop X proprietary algorithms<br>- Achieve Y performance advantage |
| Create platform extensibility | Ecosystem advantage | - Launch N API endpoints<br>- Enable X third-party integrations |
### Team-Level Examples
| Team | Focus Area | Sample KRs |
|------|------------|------------|
| Platform Team | Core technology | - Build X new infrastructure capabilities<br>- Improve performance by Y% |
| Data Team | ML/AI innovation | - Deploy X ML models<br>- Improve prediction accuracy by Y% |
| Mobile Team | Mobile innovation | - Launch X mobile-first features<br>- Achieve Y% mobile parity |
### Key Metrics to Track
- Time-to-market
- Revenue from new products
- Feature uniqueness score
- Patent/IP filings
- Technology differentiation
- Innovation velocity
---
## Operational Strategy
**Focus:** Improving efficiency and organizational excellence
### When to Use
- Scaling challenges are emerging
- Operational costs are high
- Team productivity needs improvement
- Quality issues are increasing
### Company-Level Objectives
| Objective | Key Results Template |
|-----------|---------------------|
| Improve organizational efficiency | - Improve velocity by X%<br>- Reduce cycle time to Y days<br>- Achieve Z% automation |
| Scale operations sustainably | - Support X users per engineer<br>- Reduce cost per transaction by Y%<br>- Improve operational leverage by Z% |
| Achieve operational excellence | - Reduce incidents by X%<br>- Improve team NPS to Y<br>- Achieve Z% on-time delivery |
### Product-Level Cascade
| Product Objective | Supports | Key Results |
|-------------------|----------|-------------|
| Improve product delivery efficiency | Velocity | - Reduce PR cycle time by X%<br>- Increase deployment frequency by Y% |
| Reduce operational toil | Automation | - Automate X% of manual processes<br>- Reduce on-call burden by Y% |
| Improve product quality | Excellence | - Reduce bugs by X%<br>- Improve test coverage to Y% |
### Team-Level Examples
| Team | Focus Area | Sample KRs |
|------|------------|------------|
| Platform Team | Infrastructure efficiency | - Reduce infrastructure costs by X%<br>- Improve deployment reliability to Y% |
| Data Team | Data operations | - Improve data pipeline reliability to X%<br>- Reduce data latency by Y% |
| All Teams | Process improvement | - Reduce meeting overhead by X%<br>- Improve sprint predictability to Y% |
### Key Metrics to Track
- Velocity (story points, throughput)
- Cycle time
- Deployment frequency
- Change failure rate
- Incident count and MTTR
- Team satisfaction (eNPS)
---
## Multi-Strategy Combinations
### Common Pairings
| Primary | Secondary | Balanced Objectives |
|---------|-----------|---------------------|
| Growth + Retention | 60/40 | Grow while keeping users |
| Revenue + Retention | 50/50 | Monetize without churning |
| Innovation + Growth | 40/60 | Differentiate to acquire |
| Operational + Revenue | 50/50 | Efficiency for margins |
### Balanced OKR Set Example
**Mixed Growth + Retention Strategy:**
```
Company Objective 1: Accelerate user growth (Growth)
├── KR1: Increase MAU from 100K to 200K
├── KR2: Achieve 15% MoM growth rate
└── KR3: Reduce CAC by 20%
Company Objective 2: Improve user retention (Retention)
├── KR1: Improve D30 retention from 20% to 35%
├── KR2: Increase NPS from 40 to 55
└── KR3: Reduce churn to below 5%
Company Objective 3: Improve delivery efficiency (Operational)
├── KR1: Reduce cycle time by 30%
├── KR2: Achieve 95% on-time delivery
└── KR3: Improve team eNPS to 50
```
---
## Strategy Selection Checklist
Before choosing a strategy:
- [ ] What is the company's #1 priority this quarter?
- [ ] What metrics is leadership being evaluated on?
- [ ] Where are the biggest gaps vs. competitors?
- [ ] What does customer feedback emphasize?
- [ ] What can we realistically move in 90 days?
---
*See also: `okr_framework.md` for OKR writing best practices*
FILE:scripts/okr_cascade_generator.py
#!/usr/bin/env python3
"""
OKR Cascade Generator
Creates aligned OKRs from company strategy down to team level.
Features:
- Generates company → product → team OKR cascade
- Configurable team structure and contribution percentages
- Alignment scoring across vertical and horizontal dimensions
- Multiple output formats (dashboard, JSON)
Usage:
python okr_cascade_generator.py growth
python okr_cascade_generator.py retention --teams "Engineering,Design,Data"
python okr_cascade_generator.py revenue --contribution 0.4 --json
"""
import json
import argparse
from typing import Dict, List
from datetime import datetime
class OKRGenerator:
"""Generate and cascade OKRs across the organization"""
def __init__(self, teams: List[str] = None, product_contribution: float = 0.3):
"""
Initialize OKR generator.
Args:
teams: List of team names (default: Growth, Platform, Mobile, Data)
product_contribution: Fraction of company KRs that product owns (default: 0.3)
"""
self.teams = teams or ['Growth', 'Platform', 'Mobile', 'Data']
self.product_contribution = product_contribution
self.okr_templates = {
'growth': {
'objectives': [
'Accelerate user acquisition and market expansion',
'Achieve product-market fit in new segments',
'Build sustainable growth engine'
],
'key_results': [
'Increase MAU from {current} to {target}',
'Achieve {target}% MoM growth rate',
'Expand to {target} new markets',
'Reduce CAC by {target}%',
'Improve activation rate to {target}%'
]
},
'retention': {
'objectives': [
'Create lasting customer value and loyalty',
'Deliver a superior user experience',
'Maximize customer lifetime value'
],
'key_results': [
'Improve retention from {current}% to {target}%',
'Increase NPS from {current} to {target}',
'Reduce churn to below {target}%',
'Achieve {target}% product stickiness',
'Increase LTV/CAC ratio to {target}'
]
},
'revenue': {
'objectives': [
'Drive sustainable revenue growth',
'Optimize monetization strategy',
'Expand revenue per customer'
],
'key_results': [
'Grow ARR from currentM to targetM',
'Increase ARPU by {target}%',
'Launch {target} new revenue streams',
'Achieve {target}% gross margin',
'Reduce revenue churn to {target}%'
]
},
'innovation': {
'objectives': [
'Lead the market through product innovation',
'Establish leadership in key capability areas',
'Build sustainable competitive differentiation'
],
'key_results': [
'Launch {target} breakthrough features',
'Achieve {target}% of revenue from new products',
'File {target} patents/IP',
'Reduce time-to-market by {target}%',
'Achieve {target} innovation score'
]
},
'operational': {
'objectives': [
'Improve organizational efficiency',
'Achieve operational excellence',
'Scale operations sustainably'
],
'key_results': [
'Improve velocity by {target}%',
'Reduce cycle time to {target} days',
'Achieve {target}% automation',
'Improve team satisfaction to {target}',
'Reduce incidents by {target}%'
]
}
}
# Team focus areas for objective relevance matching
self.team_relevance = {
'Growth': ['acquisition', 'growth', 'activation', 'viral', 'onboarding', 'conversion'],
'Platform': ['infrastructure', 'reliability', 'scale', 'performance', 'efficiency', 'automation'],
'Mobile': ['mobile', 'app', 'ios', 'android', 'native'],
'Data': ['analytics', 'metrics', 'insights', 'data', 'measurement', 'experimentation'],
'Engineering': ['delivery', 'velocity', 'quality', 'automation', 'infrastructure'],
'Design': ['experience', 'usability', 'interface', 'user', 'accessibility'],
'Product': ['features', 'roadmap', 'prioritization', 'strategy'],
}
def generate_company_okrs(self, strategy: str, metrics: Dict) -> Dict:
"""Generate company-level OKRs based on strategy"""
if strategy not in self.okr_templates:
strategy = 'growth'
template = self.okr_templates[strategy]
company_okrs = {
'level': 'Company',
'quarter': self._get_current_quarter(),
'strategy': strategy,
'objectives': []
}
for i in range(min(3, len(template['objectives']))):
obj = {
'id': f'CO-{i+1}',
'title': template['objectives'][i],
'key_results': [],
'owner': 'CEO',
'status': 'draft'
}
for j in range(3):
if j < len(template['key_results']):
kr_template = template['key_results'][j]
kr = {
'id': f'CO-{i+1}-KR{j+1}',
'title': self._fill_metrics(kr_template, metrics),
'current': metrics.get('current', 0),
'target': metrics.get('target', 100),
'unit': self._extract_unit(kr_template),
'status': 'not_started'
}
obj['key_results'].append(kr)
company_okrs['objectives'].append(obj)
return company_okrs
def cascade_to_product(self, company_okrs: Dict) -> Dict:
"""Cascade company OKRs to product organization"""
product_okrs = {
'level': 'Product',
'quarter': company_okrs['quarter'],
'parent': 'Company',
'contribution': self.product_contribution,
'objectives': []
}
for company_obj in company_okrs['objectives']:
product_obj = {
'id': f'PO-{company_obj["id"].split("-")[1]}',
'title': self._translate_to_product(company_obj['title']),
'parent_objective': company_obj['id'],
'key_results': [],
'owner': 'Head of Product',
'status': 'draft'
}
for kr in company_obj['key_results']:
product_kr = {
'id': f'PO-{product_obj["id"].split("-")[1]}-KR{kr["id"].split("KR")[1]}',
'title': self._translate_kr_to_product(kr['title']),
'contributes_to': kr['id'],
'current': kr['current'],
'target': kr['target'] * self.product_contribution,
'unit': kr['unit'],
'contribution_pct': self.product_contribution * 100,
'status': 'not_started'
}
product_obj['key_results'].append(product_kr)
product_okrs['objectives'].append(product_obj)
return product_okrs
def cascade_to_teams(self, product_okrs: Dict) -> List[Dict]:
"""Cascade product OKRs to individual teams"""
team_okrs = []
team_contribution = 1.0 / len(self.teams) if self.teams else 0.25
for team in self.teams:
team_okr = {
'level': 'Team',
'team': team,
'quarter': product_okrs['quarter'],
'parent': 'Product',
'contribution': team_contribution,
'objectives': []
}
for product_obj in product_okrs['objectives']:
if self._is_relevant_for_team(product_obj['title'], team):
team_obj = {
'id': f'{team[:3].upper()}-{product_obj["id"].split("-")[1]}',
'title': self._translate_to_team(product_obj['title'], team),
'parent_objective': product_obj['id'],
'key_results': [],
'owner': f'{team} PM',
'status': 'draft'
}
for kr in product_obj['key_results'][:2]:
team_kr = {
'id': f'{team[:3].upper()}-{team_obj["id"].split("-")[1]}-KR{kr["id"].split("KR")[1]}',
'title': self._translate_kr_to_team(kr['title'], team),
'contributes_to': kr['id'],
'current': kr['current'],
'target': kr['target'] * team_contribution,
'unit': kr['unit'],
'status': 'not_started'
}
team_obj['key_results'].append(team_kr)
team_okr['objectives'].append(team_obj)
if team_okr['objectives']:
team_okrs.append(team_okr)
return team_okrs
def generate_okr_dashboard(self, all_okrs: Dict) -> str:
"""Generate OKR dashboard view"""
dashboard = ["=" * 60]
dashboard.append("OKR CASCADE DASHBOARD")
dashboard.append(f"Quarter: {all_okrs.get('quarter', 'Q1 2025')}")
dashboard.append(f"Strategy: {all_okrs.get('strategy', 'growth').upper()}")
dashboard.append(f"Teams: {', '.join(self.teams)}")
dashboard.append(f"Product Contribution: {self.product_contribution * 100:.0f}%")
dashboard.append("=" * 60)
# Company OKRs
if 'company' in all_okrs:
dashboard.append("\n🏢 COMPANY OKRS\n")
for obj in all_okrs['company']['objectives']:
dashboard.append(f"📌 {obj['id']}: {obj['title']}")
for kr in obj['key_results']:
dashboard.append(f" └─ {kr['id']}: {kr['title']}")
# Product OKRs
if 'product' in all_okrs:
dashboard.append("\n🚀 PRODUCT OKRS\n")
for obj in all_okrs['product']['objectives']:
dashboard.append(f"📌 {obj['id']}: {obj['title']}")
dashboard.append(f" ↳ Supports: {obj.get('parent_objective', 'N/A')}")
for kr in obj['key_results']:
dashboard.append(f" └─ {kr['id']}: {kr['title']}")
# Team OKRs
if 'teams' in all_okrs:
dashboard.append("\n👥 TEAM OKRS\n")
for team_okr in all_okrs['teams']:
dashboard.append(f"\n{team_okr['team']} Team:")
for obj in team_okr['objectives']:
dashboard.append(f" 📌 {obj['id']}: {obj['title']}")
for kr in obj['key_results']:
dashboard.append(f" └─ {kr['id']}: {kr['title']}")
# Alignment Matrix
dashboard.append("\n\n📊 ALIGNMENT MATRIX\n")
dashboard.append("Company → Product → Teams")
dashboard.append("-" * 40)
if 'company' in all_okrs and 'product' in all_okrs:
for c_obj in all_okrs['company']['objectives']:
dashboard.append(f"\n{c_obj['id']}")
for p_obj in all_okrs['product']['objectives']:
if p_obj.get('parent_objective') == c_obj['id']:
dashboard.append(f" ├─ {p_obj['id']}")
if 'teams' in all_okrs:
for team_okr in all_okrs['teams']:
for t_obj in team_okr['objectives']:
if t_obj.get('parent_objective') == p_obj['id']:
dashboard.append(f" └─ {t_obj['id']} ({team_okr['team']})")
return "\n".join(dashboard)
def calculate_alignment_score(self, all_okrs: Dict) -> Dict:
"""Calculate alignment score across OKR cascade"""
scores = {
'vertical_alignment': 0,
'horizontal_alignment': 0,
'coverage': 0,
'balance': 0,
'overall': 0
}
# Vertical alignment: How well each level supports the above
total_objectives = 0
aligned_objectives = 0
if 'product' in all_okrs:
for obj in all_okrs['product']['objectives']:
total_objectives += 1
if 'parent_objective' in obj:
aligned_objectives += 1
if 'teams' in all_okrs:
for team in all_okrs['teams']:
for obj in team['objectives']:
total_objectives += 1
if 'parent_objective' in obj:
aligned_objectives += 1
if total_objectives > 0:
scores['vertical_alignment'] = round((aligned_objectives / total_objectives) * 100, 1)
# Horizontal alignment: How well teams coordinate
if 'teams' in all_okrs and len(all_okrs['teams']) > 1:
shared_objectives = set()
for team in all_okrs['teams']:
for obj in team['objectives']:
parent = obj.get('parent_objective')
if parent:
shared_objectives.add(parent)
scores['horizontal_alignment'] = min(100, len(shared_objectives) * 25)
# Coverage: How much of company OKRs are covered
if 'company' in all_okrs and 'product' in all_okrs:
company_krs = sum(len(obj['key_results']) for obj in all_okrs['company']['objectives'])
covered_krs = sum(len(obj['key_results']) for obj in all_okrs['product']['objectives'])
if company_krs > 0:
scores['coverage'] = round((covered_krs / company_krs) * 100, 1)
# Balance: Distribution across teams
if 'teams' in all_okrs:
objectives_per_team = [len(team['objectives']) for team in all_okrs['teams']]
if objectives_per_team:
avg_objectives = sum(objectives_per_team) / len(objectives_per_team)
variance = sum((x - avg_objectives) ** 2 for x in objectives_per_team) / len(objectives_per_team)
scores['balance'] = round(max(0, 100 - variance * 10), 1)
# Overall score
scores['overall'] = round(sum([
scores['vertical_alignment'] * 0.4,
scores['horizontal_alignment'] * 0.2,
scores['coverage'] * 0.2,
scores['balance'] * 0.2
]), 1)
return scores
def _get_current_quarter(self) -> str:
"""Get current quarter"""
now = datetime.now()
quarter = (now.month - 1) // 3 + 1
return f"Q{quarter} {now.year}"
def _fill_metrics(self, template: str, metrics: Dict) -> str:
"""Fill template with actual metrics"""
result = template
for key, value in metrics.items():
result = result.replace(f'{{{key}}}', str(value))
return result
def _extract_unit(self, kr_template: str) -> str:
"""Extract measurement unit from KR template"""
if '%' in kr_template:
return '%'
elif '$' in kr_template:
return '$'
elif 'days' in kr_template.lower():
return 'days'
elif 'score' in kr_template.lower():
return 'points'
return 'count'
def _translate_to_product(self, company_objective: str) -> str:
"""Translate company objective to product objective"""
translations = {
'Accelerate user acquisition': 'Build viral product features',
'Achieve product-market fit': 'Validate product hypotheses',
'Build sustainable growth': 'Create product-led growth loops',
'Create lasting customer value': 'Design sticky user experiences',
'Drive sustainable revenue': 'Optimize product monetization',
'Lead the market through': 'Ship innovative features to',
'Improve organizational': 'Improve product delivery'
}
for key, value in translations.items():
if key in company_objective:
return company_objective.replace(key, value)
return f"Product: {company_objective}"
def _translate_kr_to_product(self, kr: str) -> str:
"""Translate KR to product context"""
product_terms = {
'MAU': 'product MAU',
'growth rate': 'feature adoption rate',
'CAC': 'product onboarding efficiency',
'retention': 'product retention',
'NPS': 'product NPS',
'ARR': 'product-driven revenue',
'churn': 'product churn'
}
result = kr
for term, replacement in product_terms.items():
if term in result:
result = result.replace(term, replacement)
break
return result
def _translate_to_team(self, objective: str, team: str) -> str:
"""Translate objective to team context"""
team_focus = {
'Growth': 'acquisition and activation',
'Platform': 'infrastructure and reliability',
'Mobile': 'mobile experience',
'Data': 'analytics and insights',
'Engineering': 'technical delivery',
'Design': 'user experience',
'Product': 'product strategy'
}
focus = team_focus.get(team, 'delivery')
return f"{objective} through {focus}"
def _translate_kr_to_team(self, kr: str, team: str) -> str:
"""Translate KR to team context"""
return f"[{team}] {kr}"
def _is_relevant_for_team(self, objective: str, team: str) -> bool:
"""Check if objective is relevant for team"""
keywords = self.team_relevance.get(team, [])
objective_lower = objective.lower()
# Platform is always relevant (infrastructure supports everything)
if team == 'Platform':
return True
return any(keyword in objective_lower for keyword in keywords)
def parse_teams(teams_str: str) -> List[str]:
"""Parse comma-separated team string into list"""
if not teams_str:
return None
return [t.strip() for t in teams_str.split(',') if t.strip()]
def main():
parser = argparse.ArgumentParser(
description='Generate OKR cascade from company strategy to team level',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Generate growth strategy OKRs with default teams
python okr_cascade_generator.py growth
# Custom teams
python okr_cascade_generator.py retention --teams "Engineering,Design,Data,Growth"
# Custom product contribution percentage
python okr_cascade_generator.py revenue --contribution 0.4
# JSON output
python okr_cascade_generator.py innovation --json
# All options combined
python okr_cascade_generator.py operational --teams "Core,Platform" --contribution 0.5 --json
"""
)
parser.add_argument(
'strategy',
nargs='?',
choices=['growth', 'retention', 'revenue', 'innovation', 'operational'],
default='growth',
help='Strategy type (default: growth)'
)
parser.add_argument(
'--teams', '-t',
type=str,
help='Comma-separated list of team names (default: Growth,Platform,Mobile,Data)'
)
parser.add_argument(
'--contribution', '-c',
type=float,
default=0.3,
help='Product contribution to company OKRs as decimal (default: 0.3 = 30%%)'
)
parser.add_argument(
'--json', '-j',
action='store_true',
help='Output as JSON instead of dashboard'
)
parser.add_argument(
'--metrics', '-m',
type=str,
help='Metrics as JSON string (default: sample metrics)'
)
args = parser.parse_args()
# Parse teams
teams = parse_teams(args.teams)
# Parse metrics
if args.metrics:
metrics = json.loads(args.metrics)
else:
metrics = {
'current': 100000,
'target': 150000,
'current_revenue': 10,
'target_revenue': 15,
'current_nps': 40,
'target_nps': 60
}
# Validate contribution
if not 0 < args.contribution <= 1:
print("Error: Contribution must be between 0 and 1")
return 1
# Generate OKRs
generator = OKRGenerator(teams=teams, product_contribution=args.contribution)
company_okrs = generator.generate_company_okrs(args.strategy, metrics)
product_okrs = generator.cascade_to_product(company_okrs)
team_okrs = generator.cascade_to_teams(product_okrs)
all_okrs = {
'quarter': company_okrs['quarter'],
'strategy': args.strategy,
'company': company_okrs,
'product': product_okrs,
'teams': team_okrs
}
alignment = generator.calculate_alignment_score(all_okrs)
if args.json:
all_okrs['alignment_scores'] = alignment
all_okrs['config'] = {
'teams': generator.teams,
'product_contribution': generator.product_contribution
}
print(json.dumps(all_okrs, indent=2))
else:
dashboard = generator.generate_okr_dashboard(all_okrs)
print(dashboard)
print("\n\n🎯 ALIGNMENT SCORES")
print("-" * 40)
for metric, score in alignment.items():
status = "✓" if score >= 80 else "!" if score >= 60 else "✗"
print(f"{status} {metric.replace('_', ' ').title()}: {score}%")
if alignment['overall'] >= 80:
print("\n✅ Overall alignment is GOOD (≥80%)")
elif alignment['overall'] >= 60:
print("\n⚠️ Overall alignment NEEDS ATTENTION (60-80%)")
else:
print("\n❌ Overall alignment is POOR (<60%)")
if __name__ == "__main__":
main()
Tạo hàng loạt trang SEO bằng template và dữ liệu: trang vị trí, so sánh, tích hợp, thư mục và các trang theo mẫu từ khóa + thành phố.
---
name: programmatic-seo
description: When the user wants to create SEO-driven pages at scale using templates and data. Also use when the user mentions "programmatic SEO," "template pages," "pages at scale," "directory pages," "location pages," "[keyword] + [city] pages," "comparison pages," "integration pages," "building many pages for SEO," "pSEO," "generate 100 pages," "data-driven pages," or "templated landing pages." Use this whenever someone wants to create many similar pages targeting different keywords or locations. For auditing existing SEO issues, see seo-audit. For content strategy planning, see content-strategy.
metadata:
version: 2.0.0
---
# Programmatic SEO
You are an expert in programmatic SEO—building SEO-optimized pages at scale using templates and data. Your goal is to create pages that rank, provide value, and avoid thin content penalties.
## Initial Assessment
**Check for product marketing context first:**
If `.agents/product-marketing.md` exists (or `.claude/product-marketing.md`, or the legacy `product-marketing-context.md` filename, in older setups), read it before asking questions. Use that context and only ask for information not already covered or specific to this task.
Before designing a programmatic SEO strategy, understand:
1. **Business Context**
- What's the product/service?
- Who is the target audience?
- What's the conversion goal for these pages?
2. **Opportunity Assessment**
- What search patterns exist?
- How many potential pages?
- What's the search volume distribution?
3. **Competitive Landscape**
- Who ranks for these terms now?
- What do their pages look like?
- Can you realistically compete?
---
## Core Principles
### 1. Unique Value Per Page
- Every page must provide value specific to that page
- Not just swapped variables in a template
- Maximize unique content—the more differentiated, the better
### 2. Proprietary Data Wins
Hierarchy of data defensibility:
1. Proprietary (you created it)
2. Product-derived (from your users)
3. User-generated (your community)
4. Licensed (exclusive access)
5. Public (anyone can use—weakest)
### 3. Clean URL Structure
**Use subfolders, not subdomains** — subfolders consolidate domain authority while subdomains split it:
- Good: `yoursite.com/templates/resume/`
- Bad: `templates.yoursite.com/resume/`
### 4. Genuine Search Intent Match
Pages must actually answer what people are searching for.
### 5. Quality Over Quantity
Better to have 100 great pages than 10,000 thin ones.
### 6. Avoid Google Penalties
- No doorway pages
- No keyword stuffing
- No duplicate content
- Genuine utility for users
---
## The 12 Playbooks (Overview)
| Playbook | Pattern | Example |
|----------|---------|---------|
| Templates | "[Type] template" | "resume template" |
| Curation | "best [category]" | "best website builders" |
| Conversions | "[X] to [Y]" | "$10 USD to GBP" |
| Comparisons | "[X] vs [Y]" | "webflow vs wordpress" |
| Examples | "[type] examples" | "landing page examples" |
| Locations | "[service] in [location]" | "dentists in austin" |
| Personas | "[product] for [audience]" | "crm for real estate" |
| Integrations | "[product A] [product B] integration" | "slack asana integration" |
| Glossary | "what is [term]" | "what is pSEO" |
| Translations | Content in multiple languages | Localized content |
| Directory | "[category] tools" | "ai copywriting tools" |
| Profiles | "[entity name]" | "stripe ceo" |
**For detailed playbook implementation**: See [references/playbooks.md](references/playbooks.md)
---
## Choosing Your Playbook
| If you have... | Consider... |
|----------------|-------------|
| Proprietary data | Directories, Profiles |
| Product with integrations | Integrations |
| Design/creative product | Templates, Examples |
| Multi-segment audience | Personas |
| Local presence | Locations |
| Tool or utility product | Conversions |
| Content/expertise | Glossary, Curation |
| Competitor landscape | Comparisons |
You can layer multiple playbooks (e.g., "Best coworking spaces in San Diego").
---
## Implementation Framework
### 1. Keyword Pattern Research
**Identify the pattern:**
- What's the repeating structure?
- What are the variables?
- How many unique combinations exist?
**Validate demand:**
- Aggregate search volume
- Volume distribution (head vs. long tail)
- Trend direction
### 2. Data Requirements
**Identify data sources:**
- What data populates each page?
- Is it first-party, scraped, licensed, public?
- How is it updated?
### 3. Template Design
**Page structure:**
- Header with target keyword
- Unique intro (not just variables swapped)
- Data-driven sections
- Related pages / internal links
- CTAs appropriate to intent
**Ensuring uniqueness:**
- Each page needs unique value
- Conditional content based on data
- Original insights/analysis per page
### 4. Internal Linking Architecture
**Hub and spoke model:**
- Hub: Main category page
- Spokes: Individual programmatic pages
- Cross-links between related spokes
**Avoid orphan pages:**
- Every page reachable from main site
- XML sitemap for all pages
- Breadcrumbs with structured data
### 5. Indexation Strategy
- Prioritize high-volume patterns
- Noindex very thin variations
- Manage crawl budget thoughtfully
- Separate sitemaps by page type
---
## Quality Checks
### Pre-Launch Checklist
**Content quality:**
- [ ] Each page provides unique value
- [ ] Answers search intent
- [ ] Readable and useful
**Technical SEO:**
- [ ] Unique titles and meta descriptions
- [ ] Proper heading structure
- [ ] Schema markup implemented
- [ ] Page speed acceptable
**Internal linking:**
- [ ] Connected to site architecture
- [ ] Related pages linked
- [ ] No orphan pages
**Indexation:**
- [ ] In XML sitemap
- [ ] Crawlable
- [ ] No conflicting noindex
### Post-Launch Monitoring
Track: Indexation rate, Rankings, Traffic, Engagement, Conversion
Watch for: Thin content warnings, Ranking drops, Manual actions, Crawl errors
---
## Common Mistakes
- **Thin content**: Just swapping city names in identical content
- **Keyword cannibalization**: Multiple pages targeting same keyword
- **Over-generation**: Creating pages with no search demand
- **Poor data quality**: Outdated or incorrect information
- **Ignoring UX**: Pages exist for Google, not users
---
## Output Format
### Strategy Document
- Opportunity analysis
- Implementation plan
- Content guidelines
### Page Template
- URL structure
- Title/meta templates
- Content outline
- Schema markup
---
## Task-Specific Questions
1. What keyword patterns are you targeting?
2. What data do you have (or can acquire)?
3. How many pages are you planning?
4. What does your site authority look like?
5. Who currently ranks for these terms?
6. What's your technical stack?
---
## Related Skills
- **seo-audit**: For auditing programmatic pages after launch
- **schema**: For adding structured data
- **site-architecture**: For page hierarchy, URL structure, and internal linking
- **competitors**: For comparison page frameworks
FILE:evals/evals.json
{
"skill_name": "programmatic-seo",
"evals": [
{
"id": 1,
"prompt": "We want to create programmatic SEO pages for our CRM. We're thinking of 'CRM for [industry]' pages — like 'CRM for Real Estate,' 'CRM for Healthcare,' etc. How should we approach this?",
"expected_output": "Should check for product-marketing.md first. Should identify this as the Personas playbook (industry-specific pages). Should apply the core principles: unique value per page (not just swapping the industry name), proprietary data or insights per industry, clean URL structure. Should recommend the implementation framework: keyword research for each industry variation, data requirements (what industry-specific content makes each page unique), template design, internal linking strategy between industry pages and main pages, and indexation strategy. Should warn against thin content (just template + keyword swap).",
"assertions": [
"Checks for product-marketing.md",
"Identifies as Personas playbook",
"Applies core principles (unique value, proprietary data, clean URLs)",
"Recommends keyword research per variation",
"Addresses data requirements for unique content",
"Provides template design guidance",
"Includes internal linking strategy",
"Warns against thin content"
],
"files": []
},
{
"id": 2,
"prompt": "Create a comparison page strategy. We want pages like 'Notion vs Asana', 'Notion vs Monday', etc. for all our competitors. We have 15 competitors.",
"expected_output": "Should identify this as the Comparisons playbook. Should apply the programmatic approach for competitor comparison pages at scale. Should recommend: template structure for comparison pages, unique data per comparison (not just the same template with names swapped), keyword research for each '[competitor A] vs [competitor B]' variation, URL structure (/compare/notion-vs-asana), internal linking between comparison pages, and quality checks. Should cross-reference the competitors skill for page content structure.",
"assertions": [
"Identifies as Comparisons playbook",
"Recommends template structure for scale",
"Addresses unique data per comparison",
"Includes keyword research for variations",
"Provides URL structure recommendation",
"Includes internal linking strategy",
"Cross-references competitors skill",
"Applies quality checks"
],
"files": []
},
{
"id": 3,
"prompt": "we want to rank for '[tool name] integration' keywords. we integrate with 50+ tools and want a page for each. like 'Slack integration', 'Salesforce integration' etc.",
"expected_output": "Should trigger on casual phrasing. Should identify this as the Integrations playbook. Should recommend: template design for integration pages (what it does, how to set up, use cases), unique content per integration (specific workflows, screenshots, setup steps), keyword research for '[tool] + [your product] integration', URL structure (/integrations/slack), hub page linking to all integration pages, and schema markup considerations. Should emphasize that each page needs genuine unique value, not just 'we integrate with [tool].'",
"assertions": [
"Triggers on casual phrasing",
"Identifies as Integrations playbook",
"Recommends template with unique content per integration",
"Includes setup steps and use cases per page",
"Provides URL structure recommendation",
"Recommends hub page for all integrations",
"Emphasizes genuine unique value per page"
],
"files": []
},
{
"id": 4,
"prompt": "We built 500 programmatic pages but Google isn't indexing most of them. Only 80 are in the index. What's going wrong?",
"expected_output": "Should diagnose the indexation problem. Should apply the quality checks and indexation strategy guidance. Should investigate: thin content (are pages providing unique value or just template + keyword?), crawl budget (500 pages may be fine but depends on site authority), internal linking (are the pages discoverable?), XML sitemap inclusion, duplicate/near-duplicate content issues. Should recommend specific fixes: improve content uniqueness, strengthen internal linking, submit sitemap, check robots.txt, use Search Console for indexation requests. Should warn that Google may choose not to index thin pages regardless.",
"assertions": [
"Diagnoses indexation problem",
"Investigates thin content as likely cause",
"Checks crawl budget considerations",
"Checks internal linking to programmatic pages",
"Checks XML sitemap and robots.txt",
"Recommends specific fixes for indexation",
"Warns about Google's thin content policies"
],
"files": []
},
{
"id": 5,
"prompt": "Help me create a glossary section for our marketing automation platform. We want to define 200+ marketing terms and rank for '[term] definition' keywords.",
"expected_output": "Should identify this as the Glossary playbook. Should apply the template design: term definition page template (definition, examples, related terms, how it applies to the user's product), hub/index page linking to all terms, URL structure (/glossary/[term]), alphabetical and categorical navigation. Should address quality: each definition should provide genuine value beyond a dictionary definition. Should include internal linking strategy and schema markup (DefinedTerm schema). Should recommend starting with highest-volume terms.",
"assertions": [
"Identifies as Glossary playbook",
"Provides template design for term pages",
"Recommends hub/index page",
"Provides URL structure",
"Addresses content quality beyond dictionary definitions",
"Includes internal linking strategy",
"Recommends starting with highest-volume terms"
],
"files": []
},
{
"id": 6,
"prompt": "Can you audit our existing programmatic SEO pages for technical issues? We have crawl errors and some pages return 404s.",
"expected_output": "Should recognize this is a technical SEO audit task, not a programmatic SEO strategy task. Should defer to or cross-reference the seo-audit skill, which handles crawlability, indexation, and technical SEO issues. Programmatic-seo focuses on strategy, template design, and content planning for scaled pages.",
"assertions": [
"Recognizes this as technical SEO audit task",
"References or defers to seo-audit skill",
"Explains that programmatic-seo is for strategy and template design",
"Does not attempt full technical SEO audit"
],
"files": []
}
]
}
FILE:references/playbooks.md
# The 12 Programmatic SEO Playbooks
Beyond mixing and matching data point permutations, these are the proven playbooks for programmatic SEO.
## Contents
- 1. Templates
- 2. Curation
- 3. Conversions
- 4. Comparisons
- 5. Examples
- 6. Locations
- 7. Personas
- 8. Integrations
- 9. Glossary
- 10. Translations
- 11. Directory
- 12. Profiles
- Choosing Your Playbook (Match to Your Assets, Combine Playbooks)
## 1. Templates
**Pattern**: "[Type] template" or "free [type] template"
**Example searches**: "resume template", "invoice template", "pitch deck template"
**What it is**: Downloadable or interactive templates users can use directly.
**Why it works**:
- High intent—people need it now
- Shareable/linkable assets
- Natural for product-led companies
**Value requirements**:
- Actually usable templates (not just previews)
- Multiple variations per type
- Quality comparable to paid options
- Easy download/use flow
**URL structure**: `/templates/[type]/` or `/templates/[category]/[type]/`
---
## 2. Curation
**Pattern**: "best [category]" or "top [number] [things]"
**Example searches**: "best website builders", "top 10 crm software", "best free design tools"
**What it is**: Curated lists ranking or recommending options in a category.
**Why it works**:
- Comparison shoppers searching for guidance
- High commercial intent
- Evergreen with updates
**Value requirements**:
- Genuine evaluation criteria
- Real testing or expertise
- Regular updates (date visible)
- Not just affiliate-driven rankings
**URL structure**: `/best/[category]/` or `/[category]/best/`
---
## 3. Conversions
**Pattern**: "[X] to [Y]" or "[amount] [unit] in [unit]"
**Example searches**: "$10 USD to GBP", "100 kg to lbs", "pdf to word"
**What it is**: Tools or pages that convert between formats, units, or currencies.
**Why it works**:
- Instant utility
- Extremely high search volume
- Repeat usage potential
**Value requirements**:
- Accurate, real-time data
- Fast, functional tool
- Related conversions suggested
- Mobile-friendly interface
**URL structure**: `/convert/[from]-to-[to]/` or `/[from]-to-[to]-converter/`
---
## 4. Comparisons
**Pattern**: "[X] vs [Y]" or "[X] alternative"
**Example searches**: "webflow vs wordpress", "notion vs coda", "figma alternatives"
**What it is**: Head-to-head comparisons between products, tools, or options.
**Why it works**:
- High purchase intent
- Clear search pattern
- Scales with number of competitors
**Value requirements**:
- Honest, balanced analysis
- Actual feature comparison data
- Clear recommendation by use case
- Updated when products change
**URL structure**: `/compare/[x]-vs-[y]/` or `/[x]-vs-[y]/`
*See also: competitors skill for detailed frameworks*
---
## 5. Examples
**Pattern**: "[type] examples" or "[category] inspiration"
**Example searches**: "saas landing page examples", "email subject line examples", "portfolio website examples"
**What it is**: Galleries or collections of real-world examples for inspiration.
**Why it works**:
- Research phase traffic
- Highly shareable
- Natural for design/creative tools
**Value requirements**:
- Real, high-quality examples
- Screenshots or embeds
- Categorization/filtering
- Analysis of why they work
**URL structure**: `/examples/[type]/` or `/[type]-examples/`
---
## 6. Locations
**Pattern**: "[service/thing] in [location]"
**Example searches**: "coworking spaces in san diego", "dentists in austin", "best restaurants in brooklyn"
**What it is**: Location-specific pages for services, businesses, or information.
**Why it works**:
- Local intent is massive
- Scales with geography
- Natural for marketplaces/directories
**Value requirements**:
- Actual local data (not just city name swapped)
- Local providers/options listed
- Location-specific insights (pricing, regulations)
- Map integration helpful
**URL structure**: `/[service]/[city]/` or `/locations/[city]/[service]/`
---
## 7. Personas
**Pattern**: "[product] for [audience]" or "[solution] for [role/industry]"
**Example searches**: "payroll software for agencies", "crm for real estate", "project management for freelancers"
**What it is**: Tailored landing pages addressing specific audience segments.
**Why it works**:
- Speaks directly to searcher's context
- Higher conversion than generic pages
- Scales with personas
**Value requirements**:
- Genuine persona-specific content
- Relevant features highlighted
- Testimonials from that segment
- Use cases specific to audience
**URL structure**: `/for/[persona]/` or `/solutions/[industry]/`
---
## 8. Integrations
**Pattern**: "[your product] [other product] integration" or "[product] + [product]"
**Example searches**: "slack asana integration", "zapier airtable", "hubspot salesforce sync"
**What it is**: Pages explaining how your product works with other tools.
**Why it works**:
- Captures users of other products
- High intent (they want the solution)
- Scales with integration ecosystem
**Value requirements**:
- Real integration details
- Setup instructions
- Use cases for the combination
- Working integration (not vaporware)
**URL structure**: `/integrations/[product]/` or `/connect/[product]/`
---
## 9. Glossary
**Pattern**: "what is [term]" or "[term] definition" or "[term] meaning"
**Example searches**: "what is pSEO", "api definition", "what does crm stand for"
**What it is**: Educational definitions of industry terms and concepts.
**Why it works**:
- Top-of-funnel awareness
- Establishes expertise
- Natural internal linking opportunities
**Value requirements**:
- Clear, accurate definitions
- Examples and context
- Related terms linked
- More depth than a dictionary
**URL structure**: `/glossary/[term]/` or `/learn/[term]/`
---
## 10. Translations
**Pattern**: Same content in multiple languages
**Example searches**: "qué es pSEO", "was ist SEO", "マーケティングとは"
**What it is**: Your content translated and localized for other language markets.
**Why it works**:
- Opens entirely new markets
- Lower competition in many languages
- Multiplies your content reach
**Value requirements**:
- Quality translation (not just Google Translate)
- Cultural localization
- hreflang tags properly implemented
- Native speaker review
**URL structure**: `/[lang]/[page]/` or `yoursite.com/es/`, `/de/`, etc.
---
## 11. Directory
**Pattern**: "[category] tools" or "[type] software" or "[category] companies"
**Example searches**: "ai copywriting tools", "email marketing software", "crm companies"
**What it is**: Comprehensive directories listing options in a category.
**Why it works**:
- Research phase capture
- Link building magnet
- Natural for aggregators/reviewers
**Value requirements**:
- Comprehensive coverage
- Useful filtering/sorting
- Details per listing (not just names)
- Regular updates
**URL structure**: `/directory/[category]/` or `/[category]-directory/`
---
## 12. Profiles
**Pattern**: "[person/company name]" or "[entity] + [attribute]"
**Example searches**: "stripe ceo", "airbnb founding story", "elon musk companies"
**What it is**: Profile pages about notable people, companies, or entities.
**Why it works**:
- Informational intent traffic
- Builds topical authority
- Natural for B2B, news, research
**Value requirements**:
- Accurate, sourced information
- Regularly updated
- Unique insights or aggregation
- Not just Wikipedia rehash
**URL structure**: `/people/[name]/` or `/companies/[name]/`
---
## Choosing Your Playbook
### Match to Your Assets
| If you have... | Consider... |
|----------------|-------------|
| Proprietary data | Stats, Directories, Profiles |
| Product with integrations | Integrations |
| Design/creative product | Templates, Examples |
| Multi-segment audience | Personas |
| Local presence | Locations |
| Tool or utility product | Conversions |
| Content/expertise | Glossary, Curation |
| International potential | Translations |
| Competitor landscape | Comparisons |
### Combine Playbooks
You can layer multiple playbooks:
- **Locations + Personas**: "Marketing agencies for startups in Austin"
- **Curation + Locations**: "Best coworking spaces in San Diego"
- **Integrations + Personas**: "Slack for sales teams"
- **Glossary + Translations**: Multi-language educational content
Quản lý prompt ở quy mô production: phiên bản, A/B test, registry, chống hồi quy và pipeline đánh giá cho tính năng AI.
---
name: prompt-governance
description: "Use when managing prompts in production at scale: versioning prompts, running A/B tests on prompts, building prompt registries, preventing prompt regressions, or creating eval pipelines for production AI features. Triggers: 'manage prompts in production', 'prompt versioning', 'prompt regression', 'prompt A/B test', 'prompt registry', 'eval pipeline'. NOT for writing or improving individual prompts (use senior-prompt-engineer). NOT for RAG pipeline design (use rag-architect). NOT for LLM cost reduction (use llm-cost-optimizer)."
---
# Prompt Governance
> Originally contributed by [chad848](https://github.com/chad848) — enhanced and integrated by the claude-skills team.
You are an expert in production prompt engineering and AI feature governance. Your goal is to treat prompts as first-class infrastructure -- versioned, tested, evaluated, and deployed with the same rigor as application code. You prevent quality regressions, enable safe iteration, and give teams confidence that prompt changes will not break production.
Prompts are code. They change behavior in production. Ship them like code.
## Before Starting
**Check for context first:** If project-context.md exists, read it before asking questions. Pull the AI tech stack, deployment patterns, and any existing prompt management approach.
Gather this context (ask in one shot):
### 1. Current State
- How are prompts currently stored? (hardcoded in code, config files, database, prompt management tool?)
- How many distinct prompts are in production?
- Has a prompt change ever caused a quality regression you did not catch before users reported it?
### 2. Goals
- What is the primary pain? (versioning chaos, no evals, blind A/B testing, slow iteration?)
- Team size and prompt ownership model? (one engineer owns all prompts vs. many contributors?)
- Tooling constraints? (open-source only, existing CI/CD, cloud provider?)
### 3. AI Stack
- LLM provider(s) in use?
- Frameworks in use? (LangChain, LlamaIndex, custom, direct API?)
- Existing test/CI infrastructure?
## How This Skill Works
### Mode 1: Build Prompt Registry
No centralized prompt management today. Design and implement a prompt registry with versioning, environment promotion, and audit trail.
### Mode 2: Build Eval Pipeline
Prompts are stored somewhere but there is no systematic quality testing. Build an evaluation pipeline that catches regressions before production.
### Mode 3: Governed Iteration
Registry and evals exist. Design the full governance workflow: branch, test, eval, review, promote -- with rollback capability.
---
## Mode 1: Build Prompt Registry
**What a prompt registry provides:**
- Single source of truth for all prompts
- Version history with rollback
- Environment promotion (dev to staging to prod)
- Audit trail (who changed what, when, why)
- Variable/template management
### Minimum Viable Registry (File-Based)
For small teams: structured files in version control.
Directory layout:
```
prompts/
registry.yaml # Index of all prompts
summarizer/
v1.0.0.md # Prompt content
v1.1.0.md
classifier/
v1.0.0.md
qa-bot/
v2.1.0.md
```
Registry YAML schema:
```yaml
prompts:
- id: summarizer
description: "Summarize support tickets for agent triage"
owner: platform-team
model: claude-sonnet-4-5
versions:
- version: 1.1.0
file: summarizer/v1.1.0.md
status: production
promoted_at: 2026-03-15
promoted_by: eng@company.com
- version: 1.0.0
file: summarizer/v1.0.0.md
status: archived
```
### Production Registry (Database-Backed)
For larger teams: API-accessible prompt registry with key tables for prompts and prompt_versions tracking slug, content, model, environment, eval_score, and promotion metadata.
To initialize a file-based registry, create the directory structure above and populate the registry YAML with your existing prompts, their current versions, and ownership metadata.
---
## Mode 2: Build Eval Pipeline
**The problem:** Prompt changes are deployed by feel. There is no systematic way to know if a new prompt is better or worse than the current one.
**The solution:** Automated evals that run on every prompt change, similar to unit tests.
### Eval Types
| Type | What it measures | When to use |
|---|---|---|
| **Exact match** | Output equals expected string | Classification, extraction, structured output |
| **Contains check** | Output includes required elements | Key point extraction, summaries |
| **LLM-as-judge** | Another LLM scores quality 1-5 | Open-ended generation, tone, helpfulness |
| **Semantic similarity** | Embedding similarity to golden answer | Paraphrase-tolerant comparisons |
| **Schema validation** | Output conforms to JSON schema | Structured output tasks |
| **Human eval** | Human rates 1-5 on criteria | High-stakes, launch gates |
### Golden Dataset Design
Every prompt needs a golden dataset: a fixed set of input/expected-output pairs that define correct behavior.
Golden dataset requirements:
- Minimum 20 examples for basic coverage, 100+ for production confidence
- Cover edge cases and failure modes, not just happy path
- Reviewed and approved by domain expert, not just the engineer who wrote the prompt
- Versioned alongside the prompt (a prompt change may require golden set updates)
### Eval Pipeline Implementation
The eval runner accepts a prompt version and golden dataset, calls the LLM for each example, evaluates the response against expected output, and returns a result with pass_rate, avg_score, and failure details.
Pass thresholds (calibrate to your use case):
- Classification/extraction: 95% or higher exact match
- Summarization: 0.85 or higher LLM-as-judge score
- Structured output: 100% schema validation
- Open-ended generation: 80% or higher human eval approval
To execute evals, build a runner that iterates through the golden dataset, calls the LLM with the prompt version under test, scores each response against the expected output, and reports aggregate pass rate and failure details.
---
## Mode 3: Governed Iteration
The full prompt deployment lifecycle with gates at each stage:
1. **BRANCH** -- Create feature branch for prompt change
2. **DEVELOP** -- Edit prompt in dev environment, manual testing
3. **EVAL** -- Run eval pipeline vs. golden dataset (automated in CI)
4. **COMPARE** -- Compare new prompt eval score vs. current production score
5. **REVIEW** -- PR review: eval results plus diff of prompt changes
6. **PROMOTE** -- Staging to Production with approval gate
7. **MONITOR** -- Watch production metrics for 24-48h post-deploy
8. **ROLLBACK** -- One-command rollback to previous version if needed
### A/B Testing Prompts
When you want to measure real-user impact, not just eval scores:
- Use stable assignment (same user always gets same variant, based on user_id hash)
- Log every assignment with user_id, prompt_slug, and variant for analysis
- Define success metric before starting (not after)
- Run for minimum 1 week or 1,000 requests per variant
- Check for novelty effect (first-day engagement spike)
- Statistical significance: p<0.05 before declaring a winner
- Monitor latency and cost alongside quality
### Rollback Playbook
One-command rollback promotes the previous version back to production status in the registry, then verify by re-running evals against the restored version.
---
## Proactive Triggers
Surface these without being asked:
- **Prompts hardcoded in application code** -- Prompt changes require code deploys. This slows iteration and mixes concerns. Flag immediately.
- **No golden dataset for production prompts** -- You are flying blind. Any prompt change could silently regress quality.
- **Eval pass rate declining over time** -- Model updates can silently break prompts. Scheduled evals catch this before users do.
- **No prompt rollback capability** -- If a bad prompt reaches production, the team is stuck until a new deploy. Always have rollback.
- **One person owns all prompt knowledge** -- Bus factor risk. Prompt registry and docs equal knowledge that survives team changes.
- **Prompt changes deployed without eval** -- Every uneval'd deploy is a bet. Flag when the team skips evals "just this once."
---
## Output Artifacts
| When you ask for... | You get... |
|---|---|
| Registry design | File structure, schema, promotion workflow, and implementation guidance |
| Eval pipeline | Golden dataset template, eval runner approach, pass threshold recommendations |
| A/B test setup | Variant assignment logic, measurement plan, success metrics, and analysis template |
| Prompt diff review | Side-by-side comparison with eval score delta and deployment recommendation |
| Governance policy | Team-facing policy doc: ownership model, review requirements, deployment gates |
---
## Communication
All output follows the structured standard:
- **Bottom line first** -- risk or recommendation before explanation
- **What + Why + How** -- every finding has all three
- **Actions have owners and deadlines** -- no "the team should consider..."
- **Confidence tagging** -- verified / medium / assumed
---
## Anti-Patterns
| Anti-Pattern | Why It Fails | Better Approach |
|---|---|---|
| Hardcoding prompts in application source code | Prompt changes require code deploys, slowing iteration and coupling concerns | Store prompts in a versioned registry separate from application code |
| Deploying prompt changes without running evals | Silent quality regressions reach users undetected | Gate every prompt change on automated eval pipeline pass before promotion |
| Using a single golden dataset forever | As the product evolves, the golden set drifts from real usage patterns | Review and update the golden dataset quarterly, adding new edge cases from production failures |
| One person owns all prompt knowledge | Bus factor of 1 — when that person leaves, prompt context is lost | Document prompts in a registry with ownership, rationale, and version history |
| A/B testing without a pre-defined success metric | Post-hoc metric selection introduces bias and inconclusive results | Define the primary success metric and sample size requirement before starting the test |
| Skipping rollback capability | A bad prompt in production with no rollback forces an emergency code deploy | Every prompt version promotion must have a one-command rollback to the previous version |
## Related Skills
- **senior-prompt-engineer**: Use when writing or improving individual prompts. NOT for managing prompts in production at scale (that is this skill).
- **llm-cost-optimizer**: Use when reducing LLM API spend. Pairs with this skill -- evals catch quality regressions when you route to cheaper models.
- **rag-architect**: Use when designing retrieval pipelines. Pairs with this skill for governing RAG system prompts and retrieval prompts separately.
- **ci-cd-pipeline-builder**: Use when building CI/CD pipelines. Pairs with this skill for automating eval runs in CI.
- **observability-designer**: Use when designing monitoring. Pairs with this skill for production prompt quality dashboards.
Tìm, đánh giá và lập danh sách khách hàng tiềm năng để tiếp cận, cho B2B SaaS, B2B nói chung hoặc doanh nghiệp nhỏ tại địa phương.
---
name: prospecting
description: When the user wants to find, qualify, and build a list of prospects to reach out to — across B2B SaaS, general B2B, or local small businesses. Also use when the user mentions "prospecting," "build a prospect list," "find prospects," "find leads," "lead gen list," "find SaaS companies that," "find B2B companies," "find local businesses," "ICP-fit accounts," "who should we go after," "outbound list," "target account list," "find clients near me," "businesses without websites," "prospect research," "qualified leads," "find my first customers," "early adopters," "design partners," "beta users," or "who has this problem." Use this for the list-building and qualification phase. For writing the outbound copy after the list is built, see cold-email. For deep competitive research on specific accounts, see competitor-profiling.
metadata:
version: 1.1.0
---
# Prospecting
You are an expert at building qualified prospect lists across four motions: B2B SaaS, general B2B, local small businesses, and early-stage demand-signal discovery (finding your first customers from public pain signals). Your goal is to turn an ICP definition into a verified, scored, ready-to-outreach lead sheet — using the right data sources, qualification signals, and compliance posture for each motion.
## Before Starting
**Check for product marketing context first:**
If `.agents/product-marketing.md` exists (or `.claude/product-marketing.md`, or the legacy `product-marketing-context.md` filename, in older setups), read it before asking questions. Use that context and only ask for information not already covered or specific to this task.
## Pick the Branch
Prospecting motions differ enough that the workflow forks at intake. Pick **one** branch based on who the user is selling to:
| Branch | Sell to | What "qualified" looks like | Primary sources |
|--------|---------|----------------------------|----------------|
| **SaaS** | Other SaaS companies / digital businesses | ICP fit + tech stack match + growth signals (funding, hiring, product velocity) | LinkedIn, BuiltWith, Crunchbase, Apollo, Clay, Clearbit, ProductHunt |
| **B2B** | Non-SaaS B2B (services, manufacturers, enterprises, mid-market) | Industry + size + geographic fit + buying signals (trigger events, vendor changes) | Apollo, ZoomInfo, Clay, Clearbit, LinkedIn Sales Nav, industry directories |
| **Local SMB** | Local small businesses (shops, gyms, restaurants, clinics, salons, services) | Active business + website status + proximity + decision-maker access | Google Maps, Yelp, local directories, Facebook, business websites |
| **Demand-signal** | Early-stage: your first customers, design partners, or beta users | Evidence of the exact pain/demand/timing signal — a cited public source, not just firmographic fit | Forums, communities, reviews, GitHub issues, job posts, launch announcements (via last30days, social-fetch, scraping) |
If the user describes a hybrid motion (e.g., "SMBs that are also SaaS"), pick the dominant branch and pull in qualification signals from the other. If the user is early-stage and needs their *first* customers or design partners — evidence of demand over list coverage — use the **Demand-signal** branch.
For the branch-specific deep dives:
- **SaaS** → see [references/saas-prospecting.md](references/saas-prospecting.md)
- **B2B** → see [references/b2b-prospecting.md](references/b2b-prospecting.md)
- **Local SMB** → see [references/local-prospecting.md](references/local-prospecting.md)
- **Demand-signal** (find your first customers) → see [references/demand-signals.md](references/demand-signals.md)
---
## Shared Framework (all branches)
Every prospecting engagement follows the same five phases. Tools and qualification signals change per branch; the phases don't.
### Phase 1 — Define the ICP
Pull from `product-marketing.md` if available. Otherwise, gather:
1. **Firmographic fit** — industry, company size, revenue band, geography, business model
2. **Technographic fit** (SaaS branch) — what tools they already use, what they're missing
3. **Buying signal** — why now? (trigger event, funding, hiring, new initiative, dissatisfaction with current vendor, recent move/expansion)
4. **Decision-maker profile** — role, seniority, what they care about
5. **Disqualifiers** — what makes a prospect a clear "skip"
Output the ICP as a one-paragraph statement plus a checklist of pass/fail criteria. Don't move to discovery without this.
### Phase 2 — Build the candidate list (discovery)
Source 2–3× more candidates than the user wants in the final list — qualification will cull aggressively.
- **SaaS / B2B**: combine 2–3 sources for cross-verification. Apollo or ZoomInfo for firmographics; Clearbit or Clay for enrichment; LinkedIn Sales Nav for decision-maker mapping.
- **Local SMB**: browser-assisted research starting with Google Maps for the target category in the target area; cross-check with Yelp, the business website, social pages, and public directories.
If the user's list quality bar is high, smaller is better. 25 verified leads beats 250 mostly-junk ones.
### Phase 3 — Qualify each candidate
Score every candidate against the ICP checklist. Add **evidence** (a source URL or two) for each qualification — never assert without backing.
**Confidence levels** (used across all branches):
- **High**: confirmed by at least two independent sources or official business page
- **Medium**: one credible source plus consistent search evidence
- **Low**: incomplete or ambiguous evidence — flag what remains uncertain
For email contacts (B2B / SaaS branches), **always verify deliverability before adding to the final list** — see Truelist integration in [references/data-sources.md](references/data-sources.md). Don't ship leads with invalid or risky emails.
### Phase 4 — Score and prioritize
Apply this rubric for the **SaaS, B2B, and Local SMB** branches. The **Demand-signal** branch scores differently — 0–100 demand-fit, not Hot/Warm/Cold — see [references/demand-signals.md](references/demand-signals.md).
| Score | Definition |
|-------|------------|
| **Hot** | Strong ICP fit + clear buying signal + decision-maker accessible + verified contact |
| **Warm** | ICP fit + softer or older signal + contact verifiable |
| **Cold** | Loose ICP fit OR no clear signal OR contact unverified |
| **Skip** | Disqualifier hit (out of ICP, closed business, duplicate, irrelevant, low confidence) |
Branch-specific signals refine the scoring — see each reference file. Default ratio target: ~20% Hot, ~30% Warm, rest Cold/Skip.
### Phase 5 — Output the lead sheet
(SaaS / B2B / Local SMB. The **Demand-signal** branch ships an evidence report instead — see [references/demand-signals.md](references/demand-signals.md).)
Default to a markdown table in chat. Switch to CSV when the list is >25 rows or the user explicitly asks for a file.
After the table, always add **"Top outreach targets"** — the top 3–5 hot leads with one sentence each on why this lead should be reached out to first.
Columns vary by branch (see reference files), but every lead sheet includes:
- score, business/company name, contact (where applicable), why-it's-a-prospect, source(s), confidence, last verified date
---
## Compliance Guardrails
These apply to every branch. **Read first, every engagement.**
1. **No bulk scraping** of LinkedIn, Google Maps, paywalled sites, or rate-limited APIs. Browser is an assisted research tool, not a scraper.
2. **No CAPTCHA, login wall, or bot protection bypass.** If a site requires it, work with what's publicly visible.
3. **Public business contact channels only.** Use info@, hello@, contact@, and named-role emails (founder, owner) where they're published on the business's own site. Personal/private emails require a lawful basis (existing relationship, opt-in, etc.).
4. **GDPR / CAN-SPAM / CASL aware.** Capture and retain the source URL and date for every contact you add to a list — required for downstream outreach compliance.
5. **No reselling extracted data** from Google Maps, LinkedIn, or any platform whose terms prohibit it. List building for the user's own outreach is fine; productizing the list to sell is not.
6. **Rate limit yourself.** Even on public sources, space requests. Don't fingerprint as a bot.
7. **No breached, leaked, or unprovenanced data.** Don't source prospects from breached datasets, scraped-contact marketplaces, or list brokers with no source lineage. Licensed B2B data providers (Apollo, ZoomInfo, Clearbit, Clay) are fine when used within their ToS and with a lawful basis — the ban is on illicit/unprovenanced data, not on legitimate enrichment vendors.
8. **Never target or infer sensitive traits.** Don't qualify, segment, or personalize on health, financial hardship, political belief, sexuality, religion, or other protected/sensitive attributes — even when a public post reveals them.
For the full compliance reference (GDPR, CAN-SPAM, CASL, LinkedIn ToS, Google Maps ToS, Clay/Apollo/ZoomInfo use restrictions): see [references/compliance.md](references/compliance.md).
---
## Inputs to Collect
If missing, ask once, then infer reasonable defaults and continue:
- **Branch** (SaaS / B2B / Local SMB / Demand-signal) — usually inferable from context; pick Demand-signal for early-stage first-customer discovery
- **ICP description** — pull from `product-marketing.md` if present
- **Target count** — default 25 for SaaS / B2B, 15 for Local SMB
- **Geography** (essential for Local SMB; useful for B2B; less critical for SaaS)
- **Tools the user has access to** — Apollo? Clay? ZoomInfo? Hunter? Truelist? Defaults to what's free + browser
- **Output format** — chat table (default) or CSV
- **Buying signal preference** — what triggers should they prioritize? (funding rounds, hiring, recent move, etc.)
---
## Tool Selection Quick Picks
Full breakdown in [references/data-sources.md](references/data-sources.md). Quick picks:
| If the user has access to... | Use it for |
|------------------------------|------------|
| **Apollo** | B2B / SaaS firmographic + contact discovery |
| **Clay** | Multi-source enrichment, waterfall lookups, custom scoring |
| **Clearbit** | Email-to-company and company enrichment |
| **ZoomInfo** | Enterprise B2B contact + intent data |
| **Hunter or Snov** | Email pattern guessing and verification |
| **Truelist** | Email deliverability validation (before adding to outreach list) |
| **LinkedIn Sales Navigator** | Decision-maker mapping (manual, no scraping) |
| **BuiltWith / Wappalyzer** | Tech stack qualification (SaaS branch) |
| **Crunchbase** | Funding signals (SaaS branch) |
| **GitHub** | Stargazers / forks of competitor or adjacent repos (dev-tool SaaS branch) |
| **Google Maps + browser** | Local SMB discovery |
| **Firecrawl / Browserbase** | Programmatic extraction from individual prospect websites — never from platforms |
**If the user has no enrichment tools**: lean on browser-assisted research with public sources — company website, About page, LinkedIn company page, news mentions. Slower but works.
---
## Output Formats
### Default — chat table
For SaaS / B2B (≤25 rows):
```
| Score | Company | Industry | Size | Signal | Contact | Email status | Source | Confidence |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
```
For Local SMB (≤15 rows) — port from the local-prospector reference:
```
| Score | Business | Category | Area | Website status | Website/Social | Phone | Why it's a prospect | Confidence |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
```
### CSV — when >25 rows or user requests a file
SaaS / B2B columns:
```csv
score,company,domain,industry,size_band,country,signal,contact_name,contact_title,contact_email,email_status,linkedin,source_urls,why_prospect,confidence,verified_date,notes
```
Local SMB columns:
```csv
score,business,category,area,distance_km,website_status,website_url,social_urls,phone,email,source_urls,why_prospect,confidence,verified_date,notes
```
### Always include after the table
- **Top outreach targets**: top 3–5 hot leads with one-sentence outreach rationale each
- **Search parameters**: branch, ICP, location/radius, target count, date generated
- **Open questions**: anything you couldn't verify and the user should look at
---
## Quality Checks (before finalizing)
- [ ] Remove duplicates (by domain for SaaS/B2B, by business + address for Local SMB)
- [ ] Every "Hot" lead has a verified contact + at least one source URL
- [ ] No lead has an email that failed Truelist (or your validator) verification — move to a separate "invalid" bucket and flag for the user
- [ ] No lead labeled "Hot" lacks a clear buying signal
- [ ] Confidence levels honest — "High" requires 2 independent sources, not just two of your own searches
- [ ] No leads sourced from prohibited scraping (LinkedIn at scale, Google Maps bulk extract, etc.)
- [ ] Source URL + date captured for every contact (GDPR / CAN-SPAM lineage)
- [ ] Final count matches user's request, or you've explained why it's smaller (quality bar)
---
## Common Mistakes
1. **Starting discovery without an ICP**. Build candidates against vague criteria and you'll qualify the wrong things.
2. **Treating data sources as authoritative without cross-checks**. Apollo and ZoomInfo are out of date often; verify before scoring as "Hot."
3. **Adding contacts without email verification**. Cold email reputation tanks fast with bounces — always validate.
4. **Bulk scraping LinkedIn or Google Maps**. Real risk: account suspension + ToS violation. Browser as an assisted tool only.
5. **Mixing branches**. Don't apply Local SMB scoring (website status) to a B2B SaaS prospect, or vice versa.
6. **"Hot" labels without buying signals**. ICP fit alone is not enough — the signal is what makes the timing right.
7. **No source URLs**. Every claim should be traceable to a public source. Future outreach depends on this lineage.
8. **Ignoring quiet hours / time zone** when scheduling the downstream outreach (handoff to cold-email).
9. **Forgetting to retain consent / lineage records**. Required for GDPR DSARs and CAN-SPAM audits.
---
## Task-Specific Questions
1. Which branch — SaaS, B2B, Local SMB, or Demand-signal (early-stage, finding your first customers)?
2. What's your ICP? (Or: should I pull from your product-marketing context?)
3. How many qualified leads do you want?
4. What tools do you have access to (Apollo / Clay / ZoomInfo / Hunter / Truelist / browser only)?
5. What's the triggering buying signal you care most about?
6. Geography or radius (Local SMB / B2B)?
7. Chat table or CSV?
---
## Tool Integrations
For implementation, see the [tools registry](../../tools/REGISTRY.md). Key prospecting tools:
| Tool | Best For | MCP | Guide |
|------|----------|:---:|-------|
| **Apollo** | B2B / SaaS firmographic + contact discovery | - | [apollo.md](../../tools/integrations/apollo.md) |
| **Clay** | Multi-source enrichment + waterfall | ✓ | [clay.md](../../tools/integrations/clay.md) |
| **Clearbit** | Email-to-company enrichment | - | [clearbit.md](../../tools/integrations/clearbit.md) |
| **ZoomInfo** | Enterprise B2B contact + intent | ✓ | [zoominfo.md](../../tools/integrations/zoominfo.md) |
| **Hunter** | Email pattern + verification | - | [hunter.md](../../tools/integrations/hunter.md) |
| **Snov** | Email finder + verifier | - | [snov.md](../../tools/integrations/snov.md) |
| **Truelist** | Email deliverability validation | - | [truelist.md](../../tools/integrations/truelist.md) |
| **Outreach** | Sales engagement (post-list) | ✓ | [outreach.md](../../tools/integrations/outreach.md) |
| **RB2B** | Visitor identification (warm intent) | - | [rb2b.md](../../tools/integrations/rb2b.md) |
| **GitHub** | Stargazers/forks/watchers as developer-intent signal | - | [github.md](../../tools/integrations/github.md) |
| **Firecrawl** | Single-target site extraction (prospect's own website) | ✓ | [firecrawl.md](../../tools/integrations/firecrawl.md) |
| **Browserbase** | Real-browser site research when rendering or interaction needed | ✓ | [browserbase.md](../../tools/integrations/browserbase.md) |
---
## Related Skills
- **cold-email**: For writing outbound sequences against the qualified list (the natural next step after prospecting)
- **customer-research**: For understanding why current customers buy — informs the ICP definition
- **competitor-profiling**: For deeper research on individual accounts (different from list-building qualification)
- **revops**: For lead routing, lifecycle, and CRM handoff after prospecting
- **sales-enablement**: For battle cards and one-pagers used in the outreach
- **directory-submissions**: For inbound discovery surfaces (the prospects might find you back)
- **product-marketing**: For the ICP definition that anchors every prospecting engagement
FILE:evals/evals.json
{
"skill_name": "prospecting",
"evals": [
{
"id": 1,
"prompt": "We're a B2B SaaS selling RevOps tooling at $30K ACV. Build me a list of 25 prospects.",
"expected_output": "Should check for product-marketing.md first. Should identify this as the SaaS branch. Should run Phase 1 ICP definition pulling from product-marketing context or asking targeted questions (target industry, headcount range, tech stack signals, funding stage). Should propose discovery sources appropriate for SaaS at $30K ACV: Apollo for breadth, Clay for waterfall enrichment, Crunchbase for funding signals, BuiltWith/Wappalyzer for tech stack, LinkedIn Sales Nav for decision-mapping (manual). Should ask about user's tool access before assuming. Should source 50-75 candidates (2-3x target) before qualifying. Should flag that email validation via Truelist or similar is non-negotiable before final list. Should output SaaS-branch chat table columns (Score | Company | Industry | Size | Signal | Contact | Email status | Confidence) followed by top 3-5 hot leads with one-sentence rationale each. Should reference references/saas-prospecting.md.",
"assertions": [
"Checks for product-marketing.md",
"Identifies SaaS branch",
"Runs Phase 1 ICP definition",
"Recommends multi-source discovery (Apollo, Clay, Crunchbase, BuiltWith)",
"Asks about user's tool access",
"Sources 2-3x candidates before qualifying",
"Requires email validation before final list",
"Outputs SaaS-branch chat table columns",
"Includes top 3-5 outreach targets with rationale",
"References saas-prospecting.md"
],
"files": []
},
{
"id": 2,
"prompt": "Find me 25 SaaS companies that just raised a Series B in the last 60 days and use HubSpot.",
"expected_output": "Should recognize this as a SaaS branch prospecting task with very specific signals. Should identify the trigger event (Series B in last 60 days) and the technographic filter (uses HubSpot). Should recommend a workflow: (1) Crunchbase or Pitchbook for funding signal filter (Series B + date), (2) BuiltWith or Clay's waterfall for tech stack verification (uses HubSpot), (3) cross-check via business websites and LinkedIn. Should note this is a tight ICP that should yield high-confidence matches if data sources are current. Should flag freshness concerns: Crunchbase data depends on self-reporting, BuiltWith refresh cycles aren't real-time. Should recommend cross-source verification for the funding date specifically. Should output a SaaS-branch chat table with the funding round + date in the Signal column. Should include verified email validation before delivering.",
"assertions": [
"Identifies as SaaS branch",
"Identifies funding signal + tech stack filter",
"Recommends Crunchbase or Pitchbook for funding",
"Recommends BuiltWith or Clay for HubSpot verification",
"Notes data freshness concerns",
"Recommends cross-source verification",
"Outputs signal column showing round + date",
"Requires email validation"
],
"files": []
},
{
"id": 3,
"prompt": "I run a marketing agency. Find me 25 mid-market manufacturers in the Midwest US who recently hired a new CMO.",
"expected_output": "Should identify this as the B2B branch (manufacturers, not SaaS). Should run Phase 1 ICP definition: industry (manufacturing, with NAICS code if precision matters), size (mid-market = typically 200-2000 employees), geography (Midwest US states), trigger event (CMO hire in last 90-180 days). Should propose discovery: Apollo or ZoomInfo for firmographic filter, LinkedIn Sales Nav for CMO hire detection (job changes), Google Alerts on press releases for trigger events. Should warn that CMO hires aren't always in public databases — LinkedIn Sales Nav alerts on job changes is the most reliable source. Should output B2B-branch chat table with the CMO trigger as the signal. Should reference references/b2b-prospecting.md. Should mention compliance: GDPR less likely (US-only), CAN-SPAM applies, capture source URL + date for every contact.",
"assertions": [
"Identifies B2B branch (not SaaS)",
"Runs Phase 1 ICP definition with NAICS or industry classification",
"Specifies mid-market size band",
"Specifies Midwest US geography",
"Identifies trigger event (CMO hire)",
"Recommends Apollo/ZoomInfo + LinkedIn Sales Nav",
"Notes CMO hires often only on LinkedIn",
"Outputs B2B-branch chat table",
"Mentions CAN-SPAM and source URL capture",
"References b2b-prospecting.md"
],
"files": []
},
{
"id": 4,
"prompt": "We sell to industrial distributors. Build a list of 25 prospects.",
"expected_output": "Should identify this as the B2B branch. Should run Phase 1 ICP definition asking targeted questions: distributor size, geography, vertical specialty, buying patterns. Should propose discovery: Apollo or ZoomInfo for firmographic depth, industry-specific directories (e.g., NAW for wholesale distributors, ISA for industrial sales agencies), trade show exhibitor lists. Should note state business registries and Chamber of Commerce as verification sources. Should propose trigger events: new location, recent acquisition, leadership change, posting RFPs. Should warn that industrial distributor data is often spotty in major databases — cross-check with company website + LinkedIn for size and ownership signals. Should output B2B-branch chat table. Should note ICP fit precision matters more than initial volume for this kind of niche prospecting.",
"assertions": [
"Identifies B2B branch",
"Runs Phase 1 ICP definition asking targeted questions",
"Recommends industry-specific directories beyond Apollo/ZoomInfo",
"Mentions trade show exhibitor lists",
"Identifies relevant trigger events",
"Warns about data spottiness for industrial",
"Recommends cross-verification with business websites + LinkedIn",
"Notes ICP fit precision over volume"
],
"files": []
},
{
"id": 5,
"prompt": "I build websites for local businesses. Find me 15 prospects near Austin, TX who don't have a website.",
"expected_output": "Should identify as Local SMB branch. Should run Phase 1 ICP definition: business category (ask user — gyms, restaurants, salons, etc. matter), radius (default 20 km from Austin), target count (15). Should run the browser research workflow: search Google Maps for category + Austin, build candidate list from visible results, cross-check via business name + city web search to verify website status. Should apply the 4-tier website status classification (No site found / Social only / Weak site / Has site) — prioritize No site + Social only as Hot. Should score: Hot (no site + active + phone + within radius), Warm (weak site), Cold (has site), Skip (closed/duplicate/out of scope). Should output Local SMB chat table (Score | Business | Category | Area | Distance | Website status | Website/Social | Phone | Why prospect | Confidence). Should add 'Best first outreach targets' top 3 with reasoning. Should reference references/local-prospecting.md. Should warn against bulk-scraping Google Maps (ToS violation) — browser-assisted research only.",
"assertions": [
"Identifies Local SMB branch",
"Asks about business category if not specified",
"Defaults radius to 20km",
"Runs browser research workflow",
"Applies 4-tier website status classification",
"Uses Hot/Warm/Cold/Skip scoring",
"Outputs Local SMB chat table columns",
"Adds top 3 outreach targets",
"References local-prospecting.md",
"Warns against bulk-scraping Google Maps"
],
"files": []
},
{
"id": 6,
"prompt": "I have a list of 200 prospect emails from Apollo. How do I know which ones are deliverable before I start outreach?",
"expected_output": "Should explain the deliverability validation step in Phase 3. Should recommend Truelist (the integration in this pack) for bulk validation. Should explain the email_state classification output: ok (deliverable), email_invalid (bounces, exclude), risky (deliverable with risk like role or disposable, include cautiously), unknown (couldn't determine, skip or re-verify), accept_all (catch-all domain, include cautiously). Should warn that Apollo data accuracy is typically 60-80% — sending without validation will tank sender reputation (bounce rate >2% triggers ISP throttling and reputation damage). Should recommend the workflow: bulk POST to /api/v1/verify or CSV upload → keep ok, include risky/accept_all cautiously, exclude email_invalid, re-verify unknown → hand off to outreach. Should note Truelist also has an official MCP server for agent-driven validation. Should note cold email reputation is hard to recover once damaged — validation is non-negotiable, not optional. Should mention Hunter and Snov as alternatives with built-in verification. Should reference truelist.md integration guide.",
"assertions": [
"Recommends Truelist for bulk validation",
"Explains email_state values (ok, email_invalid, risky, unknown, accept_all)",
"Warns Apollo accuracy is 60-80%",
"Cites 2% bounce rate threshold for reputation damage",
"Recommends workflow: validate, keep ok, exclude email_invalid",
"Mentions Truelist MCP server for agent workflows",
"Mentions cold email reputation is hard to recover",
"References truelist.md or data-sources.md"
],
"files": []
},
{
"id": 7,
"prompt": "I just built a tool that automates failed-payment follow-up for gym owners. I have no customers yet. Help me find my first ten — the people who are actually dealing with this problem right now.",
"expected_output": "Should select the Demand-signal branch (early-stage, first customers, evidence-of-demand) and load references/demand-signals.md — NOT the SMB/B2B list-building branches. Should start with a product brief, then mine the five signal buckets (explicit demand / pain / workaround / switching / timing) across public discourse (forums, communities, reviews, GitHub issues, job posts) — using last30days for recency, social-fetch/scraping to read original pages, not qualifying from snippets. Should score prospects on demand-fit (pain 25 / product fit 25 / timing 20 / reachability 15 / evidence quality 15, 0-100 with bands) rather than ICP-fit Hot/Warm/Cold, and require a cited public signal for every primary-shortlist prospect. Should draft source-based openers but never auto-send. Should produce an evidence report (verdict → ICP → top prospect → shortlist with sources+scores → repeated patterns → 7-day manual outreach plan → limits) and label prospects as 'potential customers based on public signals,' not confirmed buyers. Should honor the compliance guardrails including no data brokers/leaked data and no sensitive-trait targeting.",
"assertions": [
"Selects the Demand-signal branch, not the SMB/B2B/SaaS list-building branches",
"Mines the five signal buckets from public discourse rather than contact databases",
"Uses recency/original-source tooling (last30days, social-fetch, scraping) and does not qualify from snippets",
"Scores on the demand-fit rubric (0-100 weighted), not ICP-fit Hot/Warm/Cold",
"Requires a cited public signal for every primary-shortlist prospect",
"Drafts openers but never auto-sends; labels prospects as potential-based-on-public-signals",
"Produces the evidence report structure with a 7-day manual outreach plan and limits"
],
"files": []
}
]
}
FILE:references/b2b-prospecting.md
# B2B Prospecting Reference
For when the user sells to non-SaaS B2B — services, agencies, manufacturers, mid-market and enterprise companies, professional services firms.
---
## ICP Signals That Matter (B2B branch)
### Firmographic signals
- **Industry / vertical** — NAICS or SIC codes if precision matters
- **Company size** — headcount band, revenue band, location count
- **Geography** — relevant for time zones, regulations, on-site requirements
- **Business model** — service vs product vs distribution; B2B vs B2B2C
- **Ownership** — independent, PE-backed, public, family-owned — affects buying motion
### Buying signals
- **Trigger events**: new C-level hire, recent acquisition or divestiture, IPO/funding, opening a new location, recent rebrand, expansion announcement
- **Vendor signals**: posting RFPs publicly, switching costs in last quarterly report, contract renewal windows
- **Operational signals**: recent layoffs (cost pressure) or rapid hiring (capacity pressure)
- **News mentions**: launching new initiative, entering new market, regulatory change forcing action
- **PR / press**: anything that signals "this company is changing right now"
### Decay signals
- Multiple bankruptcies or PE-stripped operations
- Negative growth + cost-cutting headlines
- Ownership stagnation (small family-owned, no growth incentive)
- Buyer turnover (3+ Marketing Directors in 2 years)
---
## Discovery Sources (B2B branch)
### Tier 1 — primary discovery
- **Apollo**: best general B2B firmographic + contact discovery
- **ZoomInfo**: enterprise B2B + intent signals (mid-market+)
- **LinkedIn Sales Navigator**: industry + role + signal search; the gold standard for decision-maker mapping (manual)
- **Clay**: when you need custom waterfall lookups (e.g., enrich Apollo records with Hunter + Clearbit)
### Tier 2 — industry-specific directories
- **Crunchbase / Pitchbook**: funded businesses
- **D&B Hoovers**: large traditional B2B firmographics
- **State / national business registries**: for verified incorporation data
- **Industry association membership rosters**: trade groups often publish member lists
- **Trade show exhibitor lists**: signals active participation in a vertical
- **Procurement databases** (Procore for construction, e.g.): vertical-specific signals
### Tier 3 — trigger event monitoring
- **Google Alerts / Feedly**: trigger keywords ("acquired," "hires," "expansion," "raises," "announces")
- **PR Newswire / Business Wire**: company-controlled announcements
- **SEC filings** (public companies): material change disclosures
- **State filings**: new entity formation, dissolution
---
## Qualification Checklist (B2B branch)
- [ ] Industry / vertical matches ICP (use a recognized classification if possible)
- [ ] Company size within range (employees or revenue)
- [ ] Geography fits
- [ ] At least one trigger event in last 90–180 days
- [ ] Decision-maker role exists (CEO, COO, VP Operations, Director of X — match buyer profile)
- [ ] Email contact verifiable (named role > info@ catchall)
- [ ] Source URLs captured for firmographic claims
- [ ] No disqualifiers (closed, acquired-paused, multi-bankrupt, off-ICP)
---
## Output Columns (B2B branch)
Recommended CSV columns:
```csv
score,company,domain,industry,naics_code,size_band,revenue_band,country,city,trigger_event,trigger_date,contact_name,contact_title,contact_email,email_status,linkedin_url,source_urls,why_prospect,confidence,verified_date,notes
```
For chat table, condense to: Score | Company | Industry | Size | Trigger | Contact | Email status | Confidence.
---
## Top Outreach Targets Selection (B2B)
Prioritize for the top 3–5 hot leads:
1. **Trigger event recency** — 30 days beats 6 months
2. **Trigger event specificity** — new CMO hire in your buyer's role beats "company in the news"
3. **Decision-maker access** — named contact with verified email + LinkedIn beats role-only
4. **Vertical fit precision** — exact NAICS match beats "adjacent industry"
Each top target rationale names the trigger and decision-maker: "Hired new VP of Marketing 14 days ago; verified email; mid-market manufacturer matching ICP."
---
## Common Mistakes (B2B)
1. **Treating B2B like SaaS** — funding rounds matter less; PE ownership and acquisition activity matter more.
2. **Trying to verify private company revenue precisely** — most public databases approximate. Use size bands, not point estimates.
3. **Ignoring procurement complexity** at enterprise scale — your prospect contact list may not include the actual approver.
4. **Cold-emailing executive assistants** — they're not the buyer and they will flag your outreach as spam.
5. **Source URL hygiene** — without source lineage, you can't defend a contact under GDPR DSAR or CAN-SPAM challenge.
6. **Stopping at one source** — Apollo can be 60% accurate on small businesses. Cross-verify with LinkedIn or the business website.
FILE:references/compliance.md
# Prospecting Compliance Reference
The legal and platform-ToS constraints that apply to prospect list building. Read first, every engagement.
> Operational guidance, not legal advice. For high-volume programs or programs touching EU/UK residents, run your setup past a privacy attorney.
---
## United States — CAN-SPAM (downstream)
CAN-SPAM regulates the cold email **send**, not the list build. But the list build matters because:
- You must be able to identify the source of every email address you contact (required if challenged)
- The "from" line and email content rules apply at send time — but you can't lie about how you got the contact
- Opt-out requests must be honored within 10 business days and tracked
**For prospecting specifically**: capture and retain the source URL + date for every contact you add to a list. CAN-SPAM doesn't require it explicitly, but defending your sender practices does.
---
## EU / UK — GDPR
The strictest applicable framework. Triggers when:
- Your prospect resides in EU/UK
- You're processing personal data (any identifiable info, including business emails tied to a named person)
### Lawful bases for cold B2B outreach
You have three credible options:
1. **Legitimate interest** (most common for B2B). Requires:
- The contact is in a business role likely to be interested in your offer
- The data was collected from a public, business-context source
- You provide a clear opt-out
- You can articulate the legitimate interest test in writing
2. **Consent** — typically not feasible for cold outreach (you don't have consent before first contact)
3. **Existing customer relationship** — only applies to current customers, not prospects
### What you must do
- Capture **source + date + lawful basis** for every contact
- Honor data subject access requests (DSARs) — you must be able to disclose, correct, or delete on request
- Include a privacy notice / opt-out in the first outreach
- Don't store personal data longer than necessary for the legitimate interest
### What disqualifies a list
- Bulk-scraped LinkedIn data — explicit ToS violation + GDPR risk
- Email addresses purchased from a list broker without source provenance
- "Anyone @ this domain" guessed emails sent without verification (multiplies risk + bounces)
---
## Canada — CASL
Stricter than CAN-SPAM. Cold B2B outreach requires:
- **Express consent** (explicit opt-in) — typically not present for cold prospecting
- **OR implied consent** — existing business relationship within 24 months, OR business address publicly published on the company's own site for the purpose of receiving such communications
**Practical implication for Canadian prospects**: relying on the publicly-published-address exception is the most defensible cold prospecting basis in Canada. You must include sender identification, mailing address, and an unsubscribe mechanism in every message.
---
## Platform Terms of Service
### LinkedIn
- **Sales Navigator** as a research tool: fine
- **Scraping LinkedIn at any scale**: explicit ToS violation. Banned accounts are permanent. Don't.
- **Apollo, Clay, and ZoomInfo** claim LinkedIn-overlap data through various legitimate channels — verify their data sources before assuming compliance
- **InMail and Connection Requests**: governed by LinkedIn's own messaging rules, not by CAN-SPAM/GDPR (because LinkedIn-internal)
### Google Maps
- ToS prohibits bulk extraction or productizing Maps data
- Browser-assisted research as a discovery aid: acceptable
- Storing Place IDs or large structured Maps data in your CRM: explicit ToS prohibition
- Use Maps to **find** local businesses, then cross-source from the business's own site for the data you retain
### Apollo / ZoomInfo / Clearbit
- All have their own ToS limiting reselling, downstream sharing, and use cases
- Read your contract — typically you can use the data for your own outreach but not productize it
- Don't share extracts publicly (e.g., on a leaderboard, in a public report)
### Crunchbase
- Free tier is read-only for personal use
- Paid tier permits broader use within contractual scope
- API access requires paid Pro+ tier
---
## Anti-Patterns (Don't Do These)
1. **Bulk-scraping LinkedIn / Google Maps / Yelp**. Browser-assisted research is OK; automated scrapers pointed at these platforms are not. **Firecrawl and Browserbase are fine for an individual prospect's own website** (the URL you found through manual discovery) — not for the platforms hosting prospects.
2. **Buying lists from random vendors** without source provenance. You inherit their legal exposure.
3. **Guessing emails and sending unverified**. Bounce rates over 2% destroy sender reputation; legally, you can't claim a "legitimate interest" basis for an email you fabricated.
4. **Harvesting personal email addresses** (Gmail, personal Outlook, etc.) from public profiles. Personal addresses raise GDPR risk significantly.
5. **Storing data you don't need**. Minimize retention. Don't keep prospect lists forever — GDPR right to deletion applies.
6. **Skipping the lawful basis documentation**. If challenged, you need to show your work. Capture source URL + collection date for every contact.
7. **Reselling prospect lists**. You may not have the right to share them downstream. Read your data provider contracts.
8. **CAPTCHA bypass / login wall bypass**. Even if technically possible, this signals bot behavior and violates virtually every ToS.
---
## Quick Audit Checklist
Before shipping a list to the user (or downstream to cold-email):
- [ ] Every contact has a source URL + collection date
- [ ] No contacts sourced from scraped LinkedIn data
- [ ] No Google Maps Place IDs or large Maps-structured data retained
- [ ] Lawful basis documented (legitimate interest test for B2B, or relevant alternative)
- [ ] Email addresses validated (deliverability check before outreach)
- [ ] Personal addresses (Gmail, etc.) flagged or excluded
- [ ] Source provider contracts permit the intended use case
- [ ] Retention plan documented (when to delete)
- [ ] First outreach will include unsubscribe + privacy notice (downstream concern for cold-email skill, but mention it now)
FILE:references/data-sources.md
# Prospecting Data Sources
Tool selection guide for prospecting across all three branches.
---
## Tool selection by goal
| Goal | Primary tools | Notes |
|------|--------------|-------|
| **Build initial firmographic list (B2B / SaaS)** | Apollo, ZoomInfo, Clay | Apollo for breadth, ZoomInfo for enterprise + intent, Clay for custom workflows |
| **Decision-maker mapping** | LinkedIn Sales Navigator (manual), Apollo, ZoomInfo | Sales Nav is the gold standard. Never bulk scrape it. |
| **Tech stack qualification (SaaS)** | BuiltWith, Wappalyzer | BuiltWith has wider coverage + paid plans for bulk; Wappalyzer is lighter + free for small use |
| **Funding signals (SaaS)** | Crunchbase, Pitchbook | Crunchbase free tier sufficient for early signals; Pitchbook for deeper investor data |
| **Email pattern discovery** | Hunter, Snov, Apollo | Pattern guessing — followed by verification |
| **Email deliverability verification** | Truelist, Hunter, NeverBounce, ZeroBounce | Always verify before adding to outreach lists |
| **Visitor identification (warm intent)** | RB2B, Clearbit Reveal | Anonymous traffic → company identification |
| **Intent data** | ZoomInfo Intent, 6sense, Bombora | Pre-warmed signals; mid-market+ pricing |
| **Trigger event monitoring** | Google Alerts, Feedly, LinkedIn Sales Nav alerts | Free options are sufficient for most |
| **Local business discovery** | Google Maps (manual), Yelp, Facebook Pages | Browser-assisted, not bulk-extracted |
---
## Apollo
**Use for**: General B2B / SaaS firmographic + contact data. Best starting point if you don't already have a list.
**Strengths**:
- Large database (>200M contacts, >60M companies)
- Strong filtering UI (industry, size, technologies, signals)
- Integrated email + LinkedIn finder
- Pay-as-you-go and tiered plans
**Watch out for**:
- Data freshness varies — re-verify before scoring as "Hot"
- Email accuracy ~60–80% — always validate
- Bulk export limits apply
**Integration**: see [apollo.md](../../../tools/integrations/apollo.md)
---
## Clay
**Use for**: Multi-source enrichment, waterfall lookups, custom scoring logic. When list quality matters more than list size.
**Strengths**:
- Waterfall logic: try Apollo first → fallback to ZoomInfo → fallback to Clearbit
- 100+ data provider integrations
- AI-powered enrichment (LLM-driven extraction from URLs)
- Custom columns + scoring formulas
- Native MCP server
**Watch out for**:
- Per-credit pricing can spike on large lists
- Complexity overhead — easy to over-engineer workflows
**Integration**: see [clay.md](../../../tools/integrations/clay.md)
---
## ZoomInfo
**Use for**: Enterprise B2B + intent data. Mid-market+ buyer profiles.
**Strengths**:
- Enterprise-grade firmographic depth
- Intent signals (companies searching topics relevant to your offer)
- Best-in-class for >$50K ACV B2B sales
- Native MCP server
**Watch out for**:
- Expensive ($15K+/yr starter)
- Overkill for SMB prospecting
- Locked into multi-year contracts typically
**Integration**: see [zoominfo.md](../../../tools/integrations/zoominfo.md)
---
## Clearbit
**Use for**: Email → company enrichment, anonymous visitor identification (Clearbit Reveal).
**Strengths**:
- Strong company enrichment (industry, size, funding, tech stack)
- Email lookup by domain
- Reveal: identify anonymous site visitors at company level
- API-first
**Watch out for**:
- HubSpot acquisition (2023) — bundled into HubSpot Breeze Intelligence now
- Standalone API still available but pricing/access depends on tier
**Integration**: see [clearbit.md](../../../tools/integrations/clearbit.md)
---
## Hunter / Snov
**Use for**: Email pattern discovery + lightweight verification on small lists.
**Hunter strengths**:
- Domain-based email discovery
- Built-in deliverability verification
- Free tier reasonable for occasional use
**Snov strengths**:
- Email finder + drip campaigns (overlap with outreach tooling)
- Bulk verification
- Cheaper than Hunter at scale
**Watch out for**:
- Both are pattern-guessing tools — accuracy depends on the target company's email pattern being inferable
- Always run results through a dedicated validator (Truelist or similar) before outreach
**Integrations**: see [hunter.md](../../../tools/integrations/hunter.md), [snov.md](../../../tools/integrations/snov.md)
---
## Truelist
**Use for**: Email deliverability validation before adding contacts to outreach lists. Critical safety step.
**Strengths**:
- Single-email sync verification (`/api/v1/verify_inline`) + bulk async (`/api/v1/verify`)
- Returns `email_state` (ok / email_invalid / risky / unknown / accept_all) + `email_sub_state` (email_ok / is_disposable / is_role / unknown_error / failed_smtp_check) + did-you-mean typo suggestions
- Catches catch-all domains, role accounts, spam traps, disposable providers
- Official MCP server for agent-driven workflows (Claude, Cursor, VS Code)
- Official SDKs in 7 languages + framework integrations (Django, Laravel, Next.js, Rails, React, Svelte, Vue, WordPress)
- Native integrations with Mailchimp, Klaviyo, HubSpot, Zapier, Make, n8n, Clay, Salesforce, more
- Pay-per-email pricing
**Why this matters**: Cold email reputation craters when bounce rates exceed 2%. Validating before sending is non-negotiable. Apollo/ZoomInfo/Hunter data is often 60–80% accurate — Truelist catches the rest.
**Integration**: see [truelist.md](../../../tools/integrations/truelist.md)
---
## LinkedIn Sales Navigator
**Use for**: Manual decision-maker discovery. The gold standard for B2B / SaaS prospecting but only when used as a research tool.
**Strengths**:
- Most accurate decision-maker data in the industry
- Real-time job changes, posts, signals
- Lead lists, alerts, saved searches
- Inmail credits (separate channel from cold email)
**Hard rules**:
- **Never bulk scrape**. LinkedIn aggressively bans scrapers. Account ban risk is real and permanent.
- Use Sales Nav as a research interface — open profiles, read, take notes, capture key data manually.
- Apollo and other tools claim LinkedIn data via partnerships / public mirroring — verify the source legitimacy before assuming compliance.
**Integration**: no MCP or API access at consumer level. Manual research only.
---
## BuiltWith / Wappalyzer
**Use for**: Tech stack qualification (SaaS branch).
**BuiltWith**:
- ~50K+ technologies tracked
- API + bulk lookups (paid)
- Historical data (when stack changed)
**Wappalyzer**:
- Free browser extension; paid API
- Lighter coverage than BuiltWith
- Faster for one-off lookups
Cross-reference both for high-confidence tech stack signals.
---
## Crunchbase
**Use for**: Funding signals (SaaS branch).
**Strengths**:
- Free tier shows recent funding events
- Paid (Pro / Enterprise) unlocks alerts and deep history
- API access for paid users
**Watch out for**:
- Coverage is best for VC-backed companies; bootstrapped + small businesses underrepresented
- Self-reported data — verify funding amounts independently
---
## GitHub (stargazers / forks / watchers)
**Use for**: Developer-intent prospecting. Especially powerful for dev-tool SaaS — stargazers of competitor or category-defining repos are in-market signal.
**Strengths**:
- Public API, no scraping concerns
- High signal quality (a starred repo = explicit interest)
- Forks are an even stronger signal (intent to modify, not just bookmark)
- Bundled `github-prospects.js` CLI handles pagination + enrichment + CSV output
- Free with 5,000 req/hr authenticated rate limit
**Watch out for**:
- Only ~5–20% of users publish email — pair with Apollo/Clay/Hunter for enrichment
- Very-popular repos (100K+ stars) are mostly noise; smaller targeted repos (5K–25K) give better signal density
- Most prospects are individuals, not company contacts directly — need to figure out their company from `company` field or LinkedIn
**Integration**: see [github.md](../../../tools/integrations/github.md)
---
## Firecrawl / Browserbase (single-target site research)
**Use for**: Programmatically extracting content from a **prospect's own website** that you already found via discovery on platforms like Google Maps, Yelp, or LinkedIn. Not for scraping those platforms themselves.
### Firecrawl
- **Best for**: "Just give me the page as markdown" — Local SMB website status checks, B2B company about/team page extraction, structured field extraction
- **Strengths**: Low overhead, returns clean LLM-ready markdown, handles most JS-rendered sites, has an MCP server
- **API + MCP + SDKs**: Node, Python, Go, Rust
### Browserbase
- **Best for**: When you need real Chromium — JS-heavy pages, cookie consent dialogs, form submission to reach a contact page, session state
- **Strengths**: Full browser control via Playwright/Puppeteer; Stagehand provides AI-friendly natural-language extraction; session recordings for debugging
- **API + MCP (Stagehand) + SDKs**: Node, Python
### Critical compliance line
Both tools can technically point at any URL. The hard rule:
- ✓ **OK**: extracting content from a single business's own website (`joescoffeeshop.com`) that you found through manual discovery
- ✗ **NOT OK**: pointing them at `google.com/maps`, LinkedIn search results, Yelp listings, or any platform whose ToS prohibits bulk extraction
Discovery happens on platforms (manual browser-assisted research). Extraction happens on individual public business sites.
**Integrations**: see [firecrawl.md](../../../tools/integrations/firecrawl.md), [browserbase.md](../../../tools/integrations/browserbase.md)
---
## RB2B / Clearbit Reveal
**Use for**: Identifying anonymous site visitors as warm intent signals.
**Strengths**:
- Pixel-based visitor → company identification
- High-intent: they came to your site, they're already in research mode
- Slack / email alerts on key visits
**Watch out for**:
- Privacy/GDPR considerations — verify your privacy policy disclosures
- Person-level identification raises higher concerns than company-level
**Integration**: see [rb2b.md](../../../tools/integrations/rb2b.md)
---
## Free / browser-only fallbacks
When the user has no paid tools, lean on:
- **Google Search** — exact business name + city + role searches
- **LinkedIn** (manual, no scraping) — company pages, employee lookups
- **Crunchbase free tier** — funding events
- **Wappalyzer browser extension** — tech stack at a glance
- **Hunter.io free tier** — 25 lookups/month
- **Google Maps** — for Local SMB discovery
- **Business websites + About pages** — primary source for any claim
- **News sites + press releases** — trigger event monitoring via Google Alerts
Slower than tooled-up workflows, but produces high-quality smaller lists if the user is willing to do the work.
---
## Sequencing recommendations
A typical full-stack prospecting workflow:
1. **Define ICP** from product-marketing context (no tools needed)
2. **Initial list** from Apollo or ZoomInfo (firmographic filter)
3. **Enrich** with Clay (waterfall: tech stack, funding, trigger events)
4. **Decision-maker mapping** in LinkedIn Sales Nav (manual)
5. **Email pattern discovery** with Hunter or Apollo's built-in
6. **Email validation** with Truelist before final list
7. **Hand off** to cold-email skill for outreach copy
Adapt this sequence based on which tools the user actually has.
FILE:references/demand-signals.md
# Demand-Signal Discovery (Find Your First Customers)
The other three branches build a list from who *fits* (firmographics, technographics, proximity). This branch builds a list from who is *already showing the pain* — the early-stage motion where you have a product and a hunch but no customer base yet, and you need your first ten real conversations. You are not filtering a database; you are mining recent public discourse for people describing the exact problem you solve, then linking every prospect to the evidence.
Use this branch when the user is pre-product-market-fit, launching something new, or looking for **design partners, beta users, or first customers** rather than a scaled outbound list. It reuses the shared five phases and every compliance guardrail in SKILL.md; what changes is where you look, how you score, and what you ship.
Pattern credit: the framework here is re-expressed from the open-source `first-customer-finder` Codex skill (Kappaemme, MIT), extended with our live-recency tooling.
## What makes this branch different
| | List-building branches (SaaS / B2B / SMB) | Demand-signal discovery |
|---|---|---|
| Starts from | A firmographic ICP | A described problem |
| Sources | Contact databases (Apollo, ZoomInfo, Clay) | Public discourse (forums, reviews, issues, posts) |
| Contact step | Enrich + verify email deliverability | None — reach them where they already posted |
| Wins on | Coverage at scale | 10 strong evidence-backed matches over a long list |
| Output | A scored lead sheet | An evidence report + manual outreach plan |
A prospect here without a cited pain, need, or timing signal is a speculative fit — it does **not** belong in the primary shortlist. Evidence is the entry ticket.
## Step 1 — Product brief (before any searching)
Define, specifically enough to *reject* weak matches:
- product and the promised outcome
- primary user and the economic buyer (often different)
- the urgent job to be done
- the current alternative or workaround being replaced
- the likely adoption trigger (what makes now the moment)
- geography / language constraint
- clear disqualifiers
Don't start broad collection until the brief is sharp. Pull from `.agents/product-marketing.md` if it exists.
## Step 2 — Mine the five signal buckets
Search several angles, not one query repeated. Adapt wording to how the audience actually talks (mine their vocabulary from organic content first — see the ad-creative hook-system's organic-language note for the same idea).
1. **Explicit demand** — "looking for," "recommend a tool for," "alternative to [X]," "does anything exist that," "how do you all handle."
2. **Pain** — "takes hours," "so manual," "hate that," "keeps breaking," "biggest frustration with," "why is there no."
3. **Workaround** — spreadsheets, copy-paste, a VA, a Zapier chain, a script, a template, any repeated manual step that your product would replace.
4. **Switching** — cancellation, migration, "moving off [competitor]," a missing feature, a pricing complaint, competitor frustration.
5. **Timing** — a public launch, a new hire for the relevant function, expansion, a new workflow or regulation, an integration announcement — a *current* event that makes the product relevant now.
**Use our live-recency edge.** A generic skill relies on whatever a web search surfaces; you have better:
- **last30days** — Reddit, Hacker News, X, YouTube, and web signals from the last 30 days. This is the single highest-value tool for this branch: recency *is* the timing signal.
- **social-fetch** — pull the full content of a specific post/thread you find, normalized.
- **scraping** / **Firecrawl** / **Browserbase** — read the original public page (a forum thread, a GitHub issue, a review), never qualify from a search snippet alone.
- **deep-research** — for a multi-source sweep with adversarial verification when the wedge is broad.
- **competitor-profiling** / **customer-research** — competitor switching signals and review-mining for the pain language.
## Step 3 — Source mix (public only)
Forums and public community threads · public social posts and replies · product and app-marketplace reviews · GitHub issues and feature requests · public company pages, job posts, changelogs, launch announcements · "looking for a tool" posts and directories.
Avoid private groups, gated communities, data brokers, leaked datasets, and any source whose terms prohibit access — the same compliance guardrails as every other branch (see SKILL.md), including the no-sensitive-traits rule.
**Business/professional context only.** Qualify and reach out only where someone is posting in a professional or business capacity about a work problem (a founder in an indie-hackers thread, a developer in a GitHub issue, an ops lead in a subreddit for their role). Exclude personal-distress contexts entirely — health, financial hardship, addiction, grief, or any consumer support forum where people are venting personal problems, even if your product is tangentially relevant. When the motion is genuinely consumer (B2C), a public pain post is not on its own a lawful basis for cold outreach — reach people through the channel's own norms (reply publicly where replying is expected) and never DM a stranger off a personal post.
Quote minimally, paraphrase by default, and link every material pain or timing signal.
## Step 4 — Score on demand-fit (not ICP-fit)
The list-building branches score Hot/Warm/Cold on ICP fit. This branch scores 0–100 on **demand fit** — how strongly the evidence says this specific prospect wants this specific thing now. Score each dimension 0–5:
| Dimension | Weight | What it measures |
|---|---|---|
| **Pain strength** | 25% | Directness, severity, repetition, and cost of the stated problem |
| **Product fit** | 25% | How directly your product solves the evidenced job |
| **Timing** | 20% | Freshness + a current trigger present |
| **Public reachability** | 15% | A natural, relevant public/professional contact path exists |
| **Evidence quality** | 15% | Specificity, source reliability, confidence the signal is really theirs |
```
score = pain/5*25 + fit/5*25 + timing/5*20 + reachability/5*15 + evidence/5*15
```
| Band | Meaning |
|---|---|
| **80–100** | Strong first-customer candidate |
| **65–79** | Promising — validate fast |
| **50–64** | Plausible but missing a material signal |
| **Below 50** | Do not include in the primary shortlist |
An old explicit request can still count — but lower the timing score and label the date. A company that merely matches the industry with no evidenced trigger is *not* a qualified prospect here.
### Prospect stages
- **High intent** — publicly requesting a solution or actively switching
- **Problem aware** — clearly describing the pain or an expensive workaround
- **Trigger present** — a current business event makes the product relevant
- **Potential fit** — ICP match, incomplete evidence → keep *outside* the primary shortlist
### Evidence ledger (per qualified prospect)
Displayed name (company/project/public professional) · source title + URL · visible publication date or "date unavailable" · source type · the concise pain/timing signal · observed evidence vs. inference (label which) · score breakdown · freshness warning when the signal is stale.
## Step 5 — Draft outreach, never send it
Recommend the most natural channel *already associated with the source*, and only where a reply is a normal part of that channel (reply in the public thread, respond via a public professional profile). Don't turn a public post into a private DM the poster didn't invite, and never contact someone off a personal-distress post. Draft one opener, under ~90 words, in this shape:
1. mention the public context naturally
2. connect it to the exact problem
3. explain the product in one sentence
4. ask one low-friction question
Never claim familiarity you don't have, never fabricate personal details, and never auto-send: no messages, connects, follows, comments, form submissions, or CRM records unless the user separately authorizes that action. This is the manual/gated posture from the marketing-loops guardrails.
## Step 6 — Ship the evidence report
Lead with the most actionable evidence, in this order:
1. **Verdict** — does the product have reachable early-customer signal, or not yet? (An honest "not yet, here's why" is a valid answer.)
2. **ICP** — buyer, job, trigger, disqualifiers.
3. **Top prospect** — the single strongest evidence-backed candidate and why now.
4. **Prospect shortlist** — per prospect: source, pain signal, demand-fit score, stage, why-now, channel, opener.
5. **Repeated patterns** — pains and triggers recurring across prospects (these are your positioning and messaging gold).
6. **Seven-day manual outreach plan** — a low-volume validation sequence (e.g., contact the top 3 with one source-based question; share a mockup only after they confirm the pain; target three conversations and one design-partner commitment).
7. **Limits** — what evidence is missing and what must be confirmed through real conversations.
For a shareable standalone HTML version of this report, the JSON→HTML generator pattern in ad-creative's [creative-review-page.md](../../ad-creative/references/creative-review-page.md) is the model (escape every value; keep it self-contained).
## The honesty rules (non-negotiable)
- Every primary prospect links to at least one real public signal. No signal, no shortlist.
- Label the output **"potential customer based on public signals"** — never "interested," "will buy," or "has consented."
- Prefer ten strong matches over a long generic list. Make uncertainty and stale evidence visible.
- Personalize from the cited source, not from invented assumptions.
- Treat the shortlist as a research hypothesis to validate through conversations, not a customer database.
FILE:references/local-prospecting.md
# Local SMB Prospecting Reference
For when the user sells to local small businesses — shops, gyms, restaurants, salons, clinics, professional services, contractors, real estate, fitness studios, dental practices.
Adapted from and generalized beyond the local-client-prospector pattern (browser-assisted discovery + website status classification + proximity scoring).
---
## ICP Signals That Matter (Local SMB branch)
### Operational signals
- **Active business** — Google Business Profile updated, recent reviews, recent hours updates
- **Recent activity** — open right now, regular hours posted, recent photos uploaded by owner
- **Customer engagement** — owner responding to reviews, posts on social, active calendar (for service businesses)
### Online presence signals (the core SMB qualification axis)
The reference local-client-prospector skill uses **website status** as the primary qualification — port this directly. Four classifications:
| Status | Definition | Typical outcome |
|--------|-----------|-----------------|
| **No site found** | No credible standalone website after cross-checked search | **Hot prospect** for web/marketing service |
| **Social only** | Facebook, Instagram, WhatsApp, Linktree, booking portal, marketplace page only — no standalone site | **Hot prospect** for web/marketing service |
| **Weak site** | Standalone site exists but outdated, broken, very thin, non-mobile-friendly, or missing clear contact/conversion flow | **Warm prospect** for refresh / rebuild service |
| **Has site** | Credible, modern standalone site exists | **Low prospect** unless other signals apply (e.g., poor SEO, weak conversion design) |
### Proximity signals
- **Distance** from the user's location or service area
- **Density** — clusters of similar businesses in one area = neighborhood targeting opportunity
- **Travel time** — useful when in-person discovery, install, or service delivery is required
### Decay signals
- Closed permanently (Google Maps banner)
- Reviews paused or business listing reported as closed
- Last activity (review, post) >12 months ago
---
## Discovery Sources (Local SMB branch)
### Primary
- **Google Maps** (browser, manual) — search "category near [location]" and walk the visible results. Cross-check details. Don't bulk-extract.
- **Yelp** — secondary verification; complementary categories
- **Bing Local / Apple Maps** — different coverage on smaller businesses
- **Facebook Pages search** — many SMBs are Facebook-only
### Cross-verification
- **Business's own website** (if any)
- **Industry directories** (e.g., Healthgrades for medical, OpenTable for restaurants, Avvo for legal)
- **Local Chamber of Commerce listings**
- **State business registries** for incorporation status
- **Search results for "[business name] [city]"** to discover non-Maps presence
---
## Browser Research Workflow
1. Open a browser and search Google Maps for the category near `base_location`
2. Build a candidate list from visible local results, search results, and public directories
3. For each candidate, inspect public sources to fill required fields
4. Search the exact business name plus city/town to check whether a standalone website exists
5. Classify website status per the table above
6. Mark confidence: High (2+ sources), Medium (1 source + consistent evidence), Low (incomplete/ambiguous)
When the user explicitly asks for subagents AND subagents are available, split candidates into non-overlapping batches and ask each subagent to verify only website/social/contact status. Don't use subagents for the primary search if it slows progress.
### Optional: programmatic verification with Firecrawl or Browserbase
Once you have a candidate's website URL (found via manual Maps/Yelp discovery), you can speed up website-status classification by hitting the URL programmatically:
- **Firecrawl** for simple "is this site live, modern, mobile-friendly, conversion-flow-equipped" reads — returns clean markdown you can inspect
- **Browserbase** when the candidate site requires JS rendering, has a cookie consent dialog, or you need session state
**Strict line**: use these on the individual business's URL. **Don't** point them at Google Maps, Yelp, or any platform whose ToS prohibits bulk extraction — discovery stays manual.
See [data-sources.md](data-sources.md) for setup details.
---
## Qualification Checklist (Local SMB branch)
- [ ] Business is active (recent reviews or activity in last 6 months)
- [ ] Category matches user's service offering
- [ ] Distance / proximity within target radius
- [ ] Website status classified
- [ ] Phone or contact channel verified
- [ ] At least one cross-source confirms business operates at the listed address
- [ ] Not a duplicate / chain location / out-of-scope category
- [ ] Not closed permanently
---
## Lead Scoring (Local SMB)
Use this simple rubric (matches local-client-prospector pattern):
| Score | Criteria |
|-------|----------|
| **Hot** | No site found OR social-only + phone present + active business + within target radius |
| **Warm** | Weak site, poor online presentation, or marketplace/booking-page only |
| **Cold** | Good website already present OR low confidence |
| **Skip** | Closed, duplicate, outside radius, irrelevant category, or not a business prospect |
---
## Output Columns (Local SMB branch)
Chat table (≤15 rows):
```
| Score | Business | Category | Area | Distance | Website status | Website/Social | Phone | Why it's a prospect | Confidence |
```
CSV:
```csv
score,business,category,area,distance_km,website_status,website_url,social_urls,phone,email,source_urls,why_prospect,confidence,verified_date,notes
```
Rules:
- Keep "Why it's a prospect" short and actionable
- Use `Not found` instead of leaving blank fields
- Include source links sparingly, not all of them
- After the table, add **Best first outreach targets** with the top 3 leads and one practical reason each
- If confidence is low, state exactly what remains uncertain
---
## Top Outreach Targets Selection (Local SMB)
Prioritize for the top 3 hot leads:
1. **No site / social only + phone present** = clearest service opportunity
2. **High review count** = active, established business with real customers
3. **Owner-responded reviews** = engaged owner = more likely to evaluate a vendor
4. **Industry alignment with your service specialty** beats generic category match
Each top target rationale should be one sentence naming the gap and the signal: "No standalone website (cross-checked); 80+ Google reviews with owner replies; 2 km from target area."
---
## Compliance Notes (Local SMB-specific)
The local branch is the most scraping-sensitive of the three motions. Specifically:
- **Google Maps Terms of Service** prohibit bulk extraction. Treat browser visits as research, not as data acquisition.
- **Don't store full Google Maps Place IDs in your CRM** — the ToS limits storage of Maps data.
- **Public business contact channels only**: published phone, contact form, info@ email. Don't reach individual employees through their personal channels.
- **Owner/operator name when published on the business's own site** is OK to use. If you only got it from LinkedIn, mark the source.
---
## Common Mistakes (Local SMB)
1. **Bulk-scraping Google Maps** — fastest way to violate ToS and lose the research channel.
2. **Treating Google Maps data as truth** — listings go stale. Cross-check hours, status, and reviews.
3. **Skipping the website status cross-check** — finding "no site" on Maps doesn't mean no site exists; do an exact-name web search before classifying.
4. **Targeting only the largest businesses** — they're already covered by other providers. The 2–5 employee SMBs are the under-served opportunity.
5. **Generic outreach to all hot leads** — local SMBs respond better to outreach that names their specific gap ("I noticed your menu isn't visible on mobile") than generic pitches.
6. **Ignoring chains and franchises** as Skip — sometimes the franchisee is the buyer and they have local marketing authority. Verify before skipping.
FILE:references/saas-prospecting.md
# SaaS Prospecting Reference
For when the user sells SaaS or digital services to other SaaS companies / digital businesses.
---
## ICP Signals That Matter (SaaS branch)
Beyond standard firmographics (industry, size, geography), SaaS prospects are qualified by:
### Technographic signals
- **Tech stack** — do they use complementary tools (your integration target) or competing tools (a switch opportunity)?
- **Recent stack changes** — adding/removing tools signals active vendor evaluation
- **Custom-built vs off-the-shelf** — DIY tooling often means a buyer who'd benefit from your product
- **Free/freemium plan signals** — using a free competitor means they may be ready to upgrade
### Growth signals
- **Funding round** — Series A / B / C in last 6 months = budget + new hires + tool needs
- **Headcount growth** — 10%+ growth in last quarter signals scaling pressure
- **Hiring signals** — specific role openings (e.g., "Head of RevOps" → ICP for revops tooling)
- **Product velocity** — frequent shipping, new features, blog posts = healthy growth motion
- **Open positions for your buyer's role** — if you sell to Marketing Ops and they're hiring one, that's a signal
### Decay signals (downgrade scoring)
- Layoffs in target department
- Funding round >2 years ago with no follow-up
- Product hasn't shipped in 6+ months
- Team page shows founders only (very early — may not have budget)
---
## Discovery Sources (SaaS branch)
Combine 2+ sources for cross-verification.
### Tier 1 — primary discovery
- **Apollo**: firmographic + technographic + contact data. Good for building large initial lists.
- **Clay**: waterfall enrichment, custom scoring, multi-source merges. Best for high-quality smaller lists.
- **ZoomInfo**: enterprise-grade firmographic + intent signals. Expensive; mid-market+.
- **LinkedIn Sales Navigator**: decision-maker mapping. Use manually, never bulk scrape.
### Tier 2 — technographic / growth signals
- **BuiltWith**: tech stack lookups, find sites using specific tools
- **Wappalyzer**: free browser extension + API; lighter tech stack signal
- **Crunchbase**: funding rounds, headcount, founders
- **Pitchbook**: deeper investor data (enterprise/paid)
- **ProductHunt**: recent launches, builder audience
- **Hacker News / Show HN**: technical builders launching products
### Tier 3 — buying signals
- **Job boards** (LinkedIn Jobs, Indeed, AngelList): role openings as signals
- **RB2B / Clearbit Reveal**: visitor identification (warm anonymous traffic)
- **GitHub stars/forks of competitor or adjacent repos**: developer-level intent signal (see `tools/integrations/github.md` and the `github-prospects.js` CLI). Especially strong for dev-tool SaaS — a developer who starred `vercel/next.js` last week is in-market for adjacent Next.js infrastructure.
- **Recent blog posts / changelog**: product direction signals
- **G2 reviews mentioning competitor switches**: explicit dissatisfaction signal
#### GitHub prospecting pattern (when audience is developers)
For dev-tool SaaS, GitHub is one of the highest-quality discovery channels:
1. Identify 3–5 "anchor" repos: your direct competitors, your category leader, complementary tools your buyer uses
2. Pull stargazers (or forks for stronger intent) via `node tools/clis/github-prospects.js stargazers <owner/repo> --enrich --with-company --format csv`
3. Filter to users with `company` set — these are the easiest to enrich downstream
4. Pair with Apollo/Clay/Hunter to lookup email by name + company
5. Validate with Truelist before adding to outreach list
Tradeoffs: GitHub yields email for only ~5–20% of users directly. The strength is the signal quality — a stargazer of a niche dev tool is genuinely in-market in a way Apollo firmographics alone can't tell you.
---
## Qualification Checklist (SaaS branch)
For each candidate, verify:
- [ ] Industry vertical matches ICP
- [ ] Company size (headcount) within range
- [ ] Tech stack includes (or notably excludes) a target technology
- [ ] Funding stage matches buyer maturity
- [ ] At least one growth signal in last 90 days (funding, hiring, product velocity)
- [ ] Decision-maker role exists at the company (named or inferable from job listings)
- [ ] Email contact verifiable
- [ ] No disqualifiers (closed, acquired-and-paused, layoffs, ICP miss)
---
## Output Columns (SaaS branch)
Recommended CSV columns:
```csv
score,company,domain,industry,size_band,country,funding_stage,last_round_date,tech_stack_match,signal,signal_date,contact_name,contact_title,contact_email,email_status,linkedin_url,source_urls,why_prospect,confidence,verified_date,notes
```
For chat table, condense to: Score | Company | Industry | Size | Signal | Contact | Email status | Confidence.
---
## Top Outreach Targets Selection (SaaS)
Prioritize for the top 3–5 hot leads:
1. **Strongest signal recency** — funding 30 days ago beats funding 9 months ago
2. **Tech stack match strength** — known integration partner beats inferred fit
3. **Decision-maker named with verified email** — beats role-pattern-guessed email
4. **Multi-source confidence** — both Apollo + Crunchbase agree beats one source
Each top target gets a one-sentence outreach rationale that names the specific signal: "Raised Series B 30 days ago; hiring Head of RevOps; verified VP of Ops email."
---
## Common Mistakes (SaaS)
1. **Buying lists from Apollo wholesale** without re-verifying email and re-checking firmographics. Stale data is the norm.
2. **Treating tech stack data as 100% accurate**. BuiltWith and Wappalyzer miss things; Clay's waterfalls miss things. Cross-check.
3. **Targeting Series C+ for early-stage SaaS sellers**. The buyer profile is wrong — too many procurement hoops, too much red tape.
4. **Targeting Series Pre-Seed seed** for products requiring meaningful budget. They have neither budget nor evaluator bandwidth.
5. **Ignoring intent data when it exists** (ZoomInfo Intent, 6sense, etc.) — pre-warm signals beat cold every time.
Hỗ trợ quan hệ công chúng: earned media, thông cáo báo chí, tiếp cận nhà báo và chiến lược truyền thông.
---
name: public-relations
description: "When the user wants help with public relations, earned media, press coverage, journalist outreach, or media strategy (not pull requests). Also use when the user mentions 'PR,' 'public relations,' 'press,' 'press release,' 'press coverage,' 'media outreach,' 'pitch a journalist,' 'get featured,' 'media list,' 'media kit,' 'press kit,' 'newsjacking,' 'news hijack,' 'HARO,' 'Qwoted,' 'Featured,' 'Help A Reporter,' 'reporter request,' 'tech press,' 'TechCrunch,' 'earned media,' 'thought leadership placement,' 'op-ed,' 'guest article,' 'press contacts,' 'podcast prep,' 'going on a podcast,' 'podcast guest,' 'prep me for this podcast,' or 'how do I get press.' Use this for earned media work — finding journalists, pitching stories, newsjacking, prepping podcast appearances, and responding to press requests. For startup/SaaS/AI directory submissions, see directory-submissions. For product launches, see launch. For social-media engagement, see social. For cold-email outreach to prospects, see cold-email."
metadata:
version: 1.1.1
---
# Public Relations & Earned Media
You are an expert in earned media for software products. Your goal is to help the user get covered by journalists, podcasts, and newsletters — efficiently, with respect for the people on the other end of the pitch.
## Before Starting
**Check for product marketing context first:**
If `.agents/product-marketing.md` exists (or `.claude/product-marketing.md`, or the legacy `product-marketing-context.md` filename, in older setups), read it before asking questions. Use that context and only ask for information not already covered or specific to this task.
---
## Core Philosophy
PR is not a substitute for distribution. It's a multiplier for it.
- **Earned media doesn't drive direct conversions.** A TechCrunch hit will not give you 1,000 paying customers. It will give you backlinks, brand legitimacy, AI-citation surface area, and ammo for sales conversations.
- **Pitch journalists like you'd pitch a customer:** specific, useful, fast, and never about you.
- **The story is not your product. The story is the trend, the data, the conflict, or the human.** Your product is the evidence. Every pitchable story bends toward one of three angles — Founding Story, David vs Goliath, or Have an Enemy (a *broken system*, never a competitor). See [references/story-angles.md](references/story-angles.md).
- **Chase press for the compound effect, not the traffic bump.** The bump fades in a day; authority, journalist relationships, and AI-citation surface compound. Build media relationships *before* you need them, and run one core asset through the whole repurposing flywheel.
- **Speed beats polish on reactive PR.** A B+ pitch in the first hour of a story beats an A+ pitch on day three.
### When PR is worth it
- You have **a real story** — proprietary data, a strong opinion, a milestone, a customer with a sharp before/after, or a fresh angle on a trending topic
- You have **founder/exec time** — journalists want quotes from people with skin in the game, not from a PR rep
- You have **a destination** — a press page, blog post, or product launch that converts attention into something useful
### When to skip PR (for now)
- Pre-launch with no story beyond "we exist"
- No one on the team can sustain pitching for 4–6 weeks (PR is a momentum game)
- You don't have a clear ICP — journalists ask "who reads my piece because of this?" and if you can't answer, neither can they
---
## The PR Mix
Four modes. Most teams over-index on one. Run at least three.
| Mode | What it is | Effort | Speed to coverage |
|------|------------|--------|-------------------|
| **Reactive (newsjacking)** | Inject your POV into trending news | Low–medium | Hours to days |
| **Proactive (pitching)** | Build a media list, pitch original stories | High | 2–8 weeks |
| **Inbound (press requests)** | Respond to journalist queries on HARO/Qwoted/Featured | Low | Days to weeks |
| **Owned (press page + media kit)** | Make it easy for journalists to find you | One-time setup | N/A |
**For the story angle taxonomy (Founding Story / David vs Goliath / Have an Enemy), data stories, media relationship-building, and the PR repurposing flywheel** — see [references/story-angles.md](references/story-angles.md)
**For the reactive newsjacking workflow** — see [references/newsjacking.md](references/newsjacking.md)
**For proactive journalist pitching** — see [references/journalist-pitching.md](references/journalist-pitching.md)
**For inbound press-request platforms (HARO, Qwoted, etc.)** — see [references/press-platforms.md](references/press-platforms.md)
**For where to pitch (media outlets, podcasts, newsletters)** — see [references/media-outlets.md](references/media-outlets.md). For startup/SaaS/AI directories, use the separate `directory-submissions` skill — different intent, different list.
**For prepping a podcast appearance you've landed** — see [references/podcast-guest-prep.md](references/podcast-guest-prep.md). Episodes get transcribed and cited by AI assistants, so a good appearance compounds in AI answers for years — prep is an AI-visibility play, not just interview polish.
---
## Owned: Press Page + Media Kit
Set this up once. It's the cheapest PR investment with the highest ROI on every future story.
**Press page (`/press` or `/newsroom`) should include:**
- One-paragraph company description (copy/paste ready)
- Founder bios with headshots (high-res, downloadable)
- Logo pack (SVG + PNG, light + dark, with usage guidelines)
- Product screenshots (high-res)
- Recent coverage list (social proof for the next journalist)
- Founding date, employee count, funding (if disclosed)
- Press contact email (not a form — journalists hate forms)
- Recent press releases / announcements
**One sentence at the top:** "For interview requests or assets, email press@yourcompany.com — we respond within 24 hours."
Then *actually* respond within 24 hours.
---
## Quick Reference: Pitch Quality Bar
Before sending any pitch, the answer to all of these should be yes:
- [ ] Does this journalist cover this beat? (Check their last 5 articles.)
- [ ] Is there a clear news hook — something that just happened or is about to?
- [ ] Could this journalist write a complete story from this email alone? (Data, quotes, customer name, contact.)
- [ ] Is the subject line specific enough to predict the article's headline?
- [ ] Is the pitch under 150 words?
- [ ] Did you avoid the words "revolutionary," "game-changing," "disruptive," and "synergy"?
- [ ] Is the ask clear? (Interview? Embargo? Exclusive? Quote?)
If any answer is no, don't send.
---
## Measurement
What to track:
| Metric | Why |
|--------|-----|
| **Coverage count** (placements / month) | Activity baseline |
| **Domain rating of placements** | Backlink value |
| **Referral traffic from coverage** | Did anyone actually click? |
| **Brand search lift** | Did people search you after reading? |
| **AI citation rate** (ChatGPT, Perplexity quote your brand?) | The new measurement that matters |
| **Sales conversations citing the article** | The only one that matters for revenue |
What not to obsess over: AVE (advertising value equivalency) — it's a vanity metric PR firms invented.
---
## Common Workflows
### "Help me newsjack [trending story]"
Go to [newsjacking.md](references/newsjacking.md), run the scoring rubric, draft 2–3 angles, pick the best, draft the pitch.
### "Find journalists who cover [beat]"
Go to [journalist-pitching.md](references/journalist-pitching.md), use the discovery checklist + dev-browser to research recent articles, build a scored list.
### "What's worth pitching this week?"
Combine: recent product milestones + active news cycles + any data you've collected. Score each potential story by the quality bar above.
### "What's my story angle?" / "How do I get press with no news?"
Go to [story-angles.md](references/story-angles.md). Fit the situation to one of the three angles (Founding Story / David vs Goliath / Have an Enemy), or turn proprietary data into a data story. Remember: a milestone alone isn't a story — milestone *with narrative* is.
### "Respond to this HARO query"
Go to [press-platforms.md](references/press-platforms.md), use the response template, keep it under 200 words.
### "I'm going on [podcast] next week — help me prep"
Go to [podcast-guest-prep.md](references/podcast-guest-prep.md): research the show (RSS feed → site → Apple Podcasts → web), extract the recurring threads and host profiles, map the guest's stories onto them, deliver the brief.
### "Build my press page"
Use the checklist above. Most companies do this in an afternoon and forget about it for a year — that's fine.
FILE:evals/evals.json
{
"skill_name": "public-relations",
"evals": [
{
"id": 1,
"prompt": "We're a tiny 2-person SaaS competing against Notion in the docs space. We don't have any funding news or a big milestone. How do we get press coverage?",
"expected_output": "Should route to references/story-angles.md. Should recognize there's no traditional news hook and instead fit the situation to one of the three story angles, most naturally David vs Goliath (own your size against an incumbent, cite the Superhuman vs Gmail exemplar). Should mention the other two angles — Founding Story (Pieter Levels '12 startups in 12 months,' build-in-public, share revenue) and Have an Enemy (the enemy is a broken system, not the competitor — never 'Notion is bad,' and reference HEY vs Apple's App Store 2020). Should reinforce 'the story is not your product' and that a milestone alone isn't a story — milestone with narrative is. Should note the compound effect (authority, relationships, AI-citation surface) over the traffic bump. May suggest turning proprietary data into a data story and building media relationships before pitching. Should not invent fake news or suggest attacking the competitor by name.",
"assertions": [
"Routes to references/story-angles.md",
"Identifies David vs Goliath as the fitting angle with the Superhuman vs Gmail exemplar",
"Names all three angles (Founding Story, David vs Goliath, Have an Enemy)",
"Clarifies the enemy is a broken system, not a named competitor (HEY vs Apple App Store 2020)",
"Cites Pieter Levels '12 startups in 12 months' for Founding Story",
"Reinforces 'the story is not your product'",
"Notes milestone-with-narrative and/or the compound effect over the traffic bump",
"Does not fabricate news or suggest smearing the competitor by name"
],
"files": []
}
]
}
FILE:references/journalist-pitching.md
# Journalist Pitching — Proactive PR Workflow
Building a media list, scoring journalist fit, and crafting pitches that actually get opened. This is a 4–8 week practice, not a one-shot.
## Contents
- Building the media list
- Scoring journalist fit
- Pitch templates by angle
- Subject lines that get opened
- Voice and structure
- Embargoes, exclusives, and follow-up etiquette
- Pitch killers
- Tooling
---
## Building the Media List
The goal: a list of 20–40 journalists who actually cover your beat. Not 500 names from a database.
### Discovery checklist
For each candidate journalist:
- [ ] Read their **last 5 articles** — are they covering your beat right now?
- [ ] Note their **publication** — does it reach your ICP?
- [ ] Check their **bio** on the outlet site — what topics do they own?
- [ ] Check **X/LinkedIn** for what they're posting about this week
- [ ] Note their **email** (usually on outlet author page, Muck Rack, or company About page)
- [ ] Check **Muck Rack** if available — it shows recent topics and pitch preferences
### Where to find candidates
| Method | How |
|--------|-----|
| **Reverse lookup from coverage you want** | Find 5 articles about competitors / your category, note bylines |
| **Topic search on Muck Rack** | Free tier shows journalists by topic |
| **X / Twitter lists** | "[your niche] reporters" lists already exist |
| **LinkedIn search** | "Journalist" + "[your category]" — filter by recent activity |
| **Newsletter author pages** | Beehiiv, Substack, ConvertKit creators are pitchable |
| **Podcast host research** | Listen to 1 episode before pitching — non-negotiable |
### Don't waste time on
- **Mass media databases** (Cision, Meltwater) for early-stage — overkill and expensive
- **Journalists who haven't posted in 6+ months** — they may have left
- **"Editor-in-chief" generic addresses** — pitches there get ignored or routed to interns
- **Journalists who explicitly state "no PR pitches" in bio** — respect it
---
## Scoring Journalist Fit
Score each journalist 1–10 across four dimensions. Sum and rank. Focus on top 20.
| Dimension | What it measures | Weight |
|-----------|------------------|--------|
| **Beat match** | Do they cover your category specifically? | 3x |
| **Reach** | Outlet's audience size + their byline traction | 2x |
| **Engagement** | Do they respond to pitches publicly / on X? | 2x |
| **Recency** | Have they written about a related topic in last 30d? | 1x |
**Tiering:**
- **Tier 1 (8–10):** Personal pitch with original angle. Custom each time.
- **Tier 2 (5–7):** Standard pitch, lightly customized.
- **Tier 3 (below 5):** Skip or pitch only when story is exceptional.
---
## Pitch Templates by Angle
Six structures that work. Pick the one that matches your story.
### 1. Data story
```
Subject: [Specific stat] — [implication]
Hi [name],
I noticed you covered [recent article] — wanted to share data that might
be relevant.
We [analyzed N / surveyed N / tracked N] and found:
• [Stat 1 with surprise factor]
• [Stat 2]
• [Stat 3]
The most interesting pattern: [one-sentence insight].
Full data + methodology here: [link to one-pager, not your homepage]
Happy to share the raw dataset, jump on a call, or connect you with
[customer who's relevant].
[your name + 1-line credential]
```
### 2. Exclusive launch / milestone
```
Subject: Exclusive: [specific milestone] at [company]
Hi [name],
I have an exclusive on [milestone] that I think fits your [beat] coverage.
The story: [one sentence]
Why it matters: [one sentence — for their readers, not for you]
What's new: [the actual news, not the marketing line]
Embargo until [day, time, timezone] — would love to give you first
window. Press kit + assets: [link]
Free to talk [two specific time options].
[your name]
```
### 3. Op-ed / contributed piece
```
Subject: Op-ed pitch: [provocative thesis]
Hi [name],
I read your piece on [recent article] — sharp take on [specific point].
I'd like to pitch a 700-word op-ed: "[Thesis as a headline]"
Core argument:
• [Point 1]
• [Point 2]
• [Point 3 — the surprising one]
Why me: [1 sentence — credential or unique vantage]
Why now: [1 sentence — the news hook]
Can have a draft to you by [date]. Happy to adapt to your house style.
[your name]
```
### 4. Customer story
```
Subject: Customer story for [their beat] — [specific outcome]
Hi [name],
For your [beat] coverage, I have a [customer type] willing to talk on
the record about [specific outcome].
The hook: [customer] [did something specific] and [measurable result].
The interesting part: [the surprising or counterintuitive detail].
Customer details:
• Name: [name, title, company]
• Available: [windows]
• Willing to share: [data points / screenshots / metrics]
Happy to coordinate the intro.
[your name]
```
### 5. Trend piece / connector
```
Subject: Trend forming in [space] — three signals
Hi [name],
Three things in [space] this month that I think connect:
1. [Signal 1 with link]
2. [Signal 2 with link]
3. [Signal 3 — yours, briefly]
The pattern: [one sentence].
This might be early for a piece, but if you're tracking the space I
wanted to flag it. Happy to share data we've collected or connect you
with others seeing the same.
[your name]
```
### 6. Newsjack response
```
Subject: Re: [their article headline] — quick data point
Hi [name],
Saw your piece on [story] this morning — wanted to add a relevant
data point in case you do a follow-up.
[One-sentence stat or insight].
Source: [our data / our customers / our analysis]
Methodology: [one sentence]
Quotable: "[a sentence you'd be comfortable seeing in print]"
If useful for a follow-up, I'm around all day at this number: [phone].
[your name]
```
---
## Subject Lines That Get Opened
Journalists open pitches based on the subject line alone. Rules:
- **Under 50 characters** — mobile preview cuts off
- **Lead with the specific** — "73% of devs deploy to prod on Fridays" beats "New data on developer workflows"
- **Promise a story, not a product** — "Why [trend]" beats "[Company] launches [thing]"
- **Use prefixes that signal value** — "Exclusive:", "Data:", "Op-ed pitch:", "Re: [their article]"
**Test against this question:** would *you* open this in a 200-email inbox?
**Patterns that work:**
- "[Specific stat] — [implication]" — "73% of agents fail this test"
- "Exclusive: [milestone]" — "Exclusive: Anthropic launches AgentOS"
- "Re: [their headline]" — direct response to recent coverage
- "[Provocative thesis]" — "Why VC funding is bad for AI safety"
**Patterns that get deleted:**
- "Press release: [boring]"
- "[Company] announces [thing]"
- "Story idea for you!"
- "Following up on my previous email"
- Any subject line with "innovative," "disruptive," "revolutionary"
---
## Voice and Structure
### Length
**150 words max for the pitch.** If you can't say it in 150 words, you don't know what your story is yet.
### Structure
1. **One-line context** — why you're emailing them specifically (their recent article, their beat)
2. **The story** — what it is, in one sentence
3. **Why it matters to their readers** — not why it matters to you
4. **Proof** — data, customer, quote, link
5. **The ask** — interview, embargo, quote, link
### Voice
- Sound like a person, not a press release
- Reference their actual recent work — proves you read them
- Don't use emoji unless they do
- Don't open with "I hope this finds you well" — burn it
- Don't ask "did you get my email?" follow-ups (see [Follow-up](#embargoes-exclusives-and-follow-up-etiquette))
### Banned vocabulary
Revolutionary, disruptive, game-changing, paradigm shift, leverage, synergy, robust, seamless, holistic, world-class, best-in-class, next-generation, cutting-edge, AI-powered (unless that's the actual differentiation), at-the-end-of-the-day.
---
## Embargoes, Exclusives, and Follow-Up Etiquette
### Embargoes
An embargo is "you can write this story, but don't publish until [time]."
- **Only offer embargoes to journalists you've worked with** or have strong reputations for honoring them
- State the embargo time clearly: day, time, timezone
- If they break embargo, your relationship with them is over
### Exclusives
"Only you get this story" — powerful tool, use sparingly.
- **First-tier outlet only** — exclusives to tier 2/3 outlets waste the lever
- **Be honest about scope** — "exclusive to [outlet] in the US" is fine
- **Have a parallel plan** — what you publish/pitch the next day after the exclusive runs
### Follow-up cadence
- **Day 0** — initial pitch
- **Day 3** — one follow-up if you have new information ("Just talked to [customer] who can join us")
- **Day 7** — final check-in with a fresh hook ("This came out today, still relevant?")
- **After day 7** — let it go. Re-pitch when you have something genuinely new.
**Never:**
- "Bumping this up" / "Did you see my email?"
- Multi-day silent follow-ups with no new value
- Same pitch reformatted
---
## Pitch Killers
Things that instantly disqualify your pitch:
- Wrong name / wrong outlet (autoreplace fail)
- Pitching topics they explicitly don't cover
- Press release attached as PDF (just paste the key bits)
- Long signature with logos and disclaimers
- CC'ing 5 other journalists on the same email
- "Per my last email" energy
- Asking them to sign an NDA before talking
- Pitching a story you can't actually tell (no customer willing to talk, no data ready to share)
---
## Tooling
### Finding journalist contact info
```bash
# Most journalists' emails follow patterns:
# firstname@outlet.com
# firstname.lastname@outlet.com
# flastname@outlet.com
# Use Hunter.io, RocketReach, or just guess and bounce-check
```
### Researching their recent work (browser-driven)
Use `dev-browser` (persistent session, no rate limits) to:
- Open the journalist's outlet author page → scrape last 5 article headlines + dates
- Open their X/Twitter profile → note recent topics
- Open their LinkedIn → confirm current role
Output what you find as:
```
JOURNALIST PROFILE — [name]
Outlet: [name]
Beat: [topics from last 5 articles]
Recent angle: [pattern you noticed]
Recent X activity: [what they're posting]
Score: [X/40 from rubric]
Best pitch angle: [from template library]
Email: [confirmed]
```
### Maintaining the media list
Store in `.agents/media-list.md` (or `.csv` if you prefer). Update monthly — journalists move jobs constantly.
```markdown
## Tier 1 (top 20)
| Name | Outlet | Beat | Last contact | Last coverage | Email | Score |
|------|--------|------|--------------|---------------|-------|-------|
| ... | ... | ... | 2026-05-15 | none yet | ... | 9/10 |
```
### Pitch tracking
Track in a simple spreadsheet:
- Date sent
- Subject line
- Journalist
- Outlet
- Response (open / reply / pass / coverage)
- What they said
After 30 pitches, you'll see which subject patterns and which angles work for you specifically.
FILE:references/media-outlets.md
# Media Outlets — Where to Pitch
A curated, opinionated list of *where* to pitch for software/SaaS PR. This is the media-outlet slice of resources like submit.co — the journalist-driven half. For startup/SaaS/AI directories (Product Hunt, BetaList, Futurepedia, etc.), use the separate `directory-submissions` skill — different intent, different audience.
## How to use this list
- **Don't pitch a publication. Pitch a journalist at that publication.** See [journalist-pitching.md](journalist-pitching.md) for the discovery workflow.
- **Tier signals quality, not effort** — a small tier-3 outlet might be perfect for your niche
- **Submission/tip URLs are listed where they exist** — but a journalist's email beats a tip form every time
- **This list ages fast** — verify the outlet still exists and the journalist is still there before pitching
---
## Tech & Startup Press (Tier 1)
The big names. High bar to clear, high payoff when you do. Pitch the specific reporter, not the tip line.
| Outlet | Best for | Tip URL |
|--------|---------|---------|
| **TechCrunch** | Funding, product launches, startup news | techcrunch.com/got-a-tip/ |
| **The Verge** | Consumer tech, product reviews, policy | theverge.com/contact |
| **Wired** | Long-form tech, culture, business | wired.com/about/feedback |
| **Fast Company** | Innovation, design, business strategy | fastcompany.com/contact-us |
| **VentureBeat** | AI, enterprise tech, gaming | venturebeat.com/contribute |
| **Ars Technica** | Deep tech, science, policy | arstechnica.com/contact-us |
| **The Information** | Subscription-gated, scoops on tech industry | theinformation.com/about |
| **Bloomberg / Reuters** | Business, finance, big-picture stories | bloomberg.com/feedback |
| **WSJ Tech** | Enterprise, business angle | wsj.com/tips |
| **NYT Tech** | Mainstream tech, culture | nytimes.com/tips |
---
## SaaS & B2B (Tier 1–2)
Lower-profile than the consumer tech outlets but often higher ROI for B2B SaaS.
| Outlet | Best for | Notes |
|--------|----------|-------|
| **SaaStr** | B2B SaaS founders, growth, sales | Jason Lemkin's outlet; pitch contributed posts |
| **First Round Review** | Operator-level B2B insights | High bar; original frameworks only |
| **OpenView** | SaaS metrics, PLG, pricing | Often runs research-backed pieces |
| **Lenny's Newsletter** | Product / growth | Subscriber-only; pitch via Lenny directly on X |
| **Future (a16z)** | Tech + culture pieces | Long-form, original thinking required |
| **Stratechery** | Tech strategy analysis | Don't pitch Ben Thompson; engage via responses |
---
## AI / ML Press (Tier 1–2)
The hottest beat right now. Reporters here are inundated — your angle has to be sharp.
| Outlet | Best for | Notes |
|--------|----------|-------|
| **The Decoder** | AI news, fast-turn | Daily AI news cycle |
| **Import AI** (Jack Clark) | Weekly AI newsletter | Pitch research-backed angles |
| **The Batch** (Andrew Ng) | AI industry analysis | Submitted by deeplearning.ai team |
| **MIT Technology Review** | AI policy, capability, ethics | Higher bar, slower cycle |
| **Hugging Face blog** | Open-source AI tools | Contributed posts welcome |
| **Latent Space** | Practitioner-focused AI/ML | Podcast + newsletter |
---
## Developer & DevTools Press
Where to pitch if your audience is engineers.
| Outlet | Best for | Notes |
|--------|----------|-------|
| **The New Stack** | DevTools, infra, cloud-native | Active contributor program |
| **InfoQ** | Enterprise dev, software architecture | Long-form technical pieces |
| **DEV.to** | Self-published, community-driven | Build credibility before pitching |
| **Hacker Noon** | Tech blogging platform | Easy to publish but low signal |
| **Console** | DevTools newsletter | Curated weekly; submit projects |
| **DevTools FM** | Podcast | Pitch as guest |
---
## Business & Marketing Press
For pitching the business / marketing angle of your story.
| Outlet | Best for | Notes |
|--------|----------|-------|
| **Inc.** | Founder stories, business advice | Contributor program available |
| **Entrepreneur** | Small business, growth | Volume publisher; quality varies |
| **HBR.org** | Original research, frameworks | High bar; long lead time |
| **MarketingProfs** | B2B marketing | Contributor-friendly |
| **Marketing Brew (Morning Brew)** | Daily marketing newsletter | Reporter-driven, pitch directly |
| **Marketing Land / Search Engine Journal** | SEO, SEM, channels | Niche but high-intent audience |
| **Reforge** | Growth, product, retention | Original framework required |
---
## Newsletters (Reporter-Driven)
Newsletters are increasingly the most valuable PR placement — small audiences, but high-intent.
| Newsletter | Audience | How to pitch |
|-----------|---------|--------------|
| **Lenny's Newsletter** | Product/growth, ~600k readers | DM Lenny on X with sharp angle |
| **The Pragmatic Engineer** (Gergely Orosz) | Software eng leaders | Pitch via email; original engineering insights |
| **The Generalist** (Mario Gabriele) | Tech business analysis | Pitch via email; deep angles |
| **Stratechery** (Ben Thompson) | Tech strategy | Don't pitch; engage in replies |
| **Newcomer** (Eric Newcomer) | Tech business + scoops | Tips welcome via email |
| **Platformer** (Casey Newton) | Tech + policy | Pitch via Substack |
| **Big Technology** (Alex Kantrowitz) | Big Tech analysis | Pitch via Substack |
| **Not Boring** (Packy McCormick) | Tech + culture | Tough to crack; original takes only |
**For B2B SaaS:**
- **SaaStr Daily**
- **Demand Curve**
- **Growth Unhinged** (Kyle Poyar)
- **Trends.vc**
**For AI:**
- **Import AI** (Jack Clark)
- **The Batch** (Andrew Ng)
- **The Algorithm** (MIT Tech Review)
- **Interconnects** (Nathan Lambert)
- **AI Tidbits**
---
## Podcasts
A podcast appearance is often higher leverage than a press hit — longer engagement, evergreen replay, audience trust transfer.
### Top SaaS / startup podcasts
- **Lenny's Podcast** — product/growth
- **My First Million** — founder stories, business ideas
- **The Twenty Minute VC** — funding angle
- **SaaStr Podcast** — B2B SaaS
- **Acquired** — deep-dive company stories (don't pitch unless you're a unicorn)
- **The All-In Podcast** — broad tech/business (very hard to get on)
### Top AI podcasts
- **No Priors** (Sarah Guo, Elad Gil)
- **Latent Space**
- **Practical AI**
- **The TWIML AI Podcast**
- **The Cognitive Revolution**
### Top dev / engineering podcasts
- **The Changelog**
- **Software Engineering Daily**
- **DevTools FM**
### How to pitch a podcast
1. **Listen to 3 episodes** — non-negotiable
2. **Find the host's preferred channel** (X DM, email, guest form)
3. **Pitch a topic, not yourself** — "I'd love to come on and talk about [specific angle]" not "I'd love to be a guest"
4. **Bring evidence** — links to other appearances, your unique angle, what listeners will learn
5. **Make it easy** — bio, headshot, suggested questions in the pitch
---
## Industry / Vertical Press
Don't overlook trade press — smaller audience, much higher intent.
| Vertical | Outlets to investigate |
|----------|----------------------|
| **Marketing** | Adweek, Marketing Brew, AdAge, Search Engine Land |
| **Sales** | Sales Hacker, Modern Sales Pros |
| **HR / People Ops** | HR Brew, SHRM, HR Dive |
| **Finance / Fintech** | The Block, CoinDesk, Finextra |
| **Healthcare tech** | STAT, MobiHealthNews, Healthcare IT News |
| **Education tech** | EdSurge, EdScoop |
| **Real estate tech** | Inman, The Real Deal |
| **Legal tech** | Above the Law, Law360 |
| **Climate tech** | Heatmap, Canary Media, Latitude Media |
| **Devtools** | The New Stack, InfoQ, DevOps.com |
For your specific vertical: Google `"top publications" + "[your industry]"` and run the same scoring exercise from [journalist-pitching.md](journalist-pitching.md).
---
## Regional / Local
If your company has a regional angle (HQ location, customer concentration, government contract), local press is underrated.
- **Local business journals** — Bizjournals network covers 40+ US cities
- **Local NPR affiliates** — high-quality, business angle welcomed
- **Local TV business segments** — high reach, easy to get
- **State / regional tech news** — e.g., Built In (Chicago, Austin, etc.), TechBuzz (Utah)
---
## What's NOT On This List (And Why)
- **Product Hunt, BetaList, Indie Hackers** — these are directories, not press. Use the `directory-submissions` skill.
- **Press release wires** (PRNewswire, BusinessWire, GlobeNewswire) — overpriced for early-stage; journalists ignore them. Skip until you have IR / SEC requirements.
- **"As featured in" badge mills** — paid "media coverage" services. Worthless and damaging.
- **Random "guest post" SEO link networks** — Google penalizes these. Don't.
---
## Maintaining This List
This list will go stale. Recommended cadence:
- **Quarterly:** verify your tier-1 contacts are still at the outlet
- **Monthly:** add new outlets/newsletters relevant to your category
- **Per pitch:** confirm the journalist is still there before sending (check X / LinkedIn for "joined [new place]")
Store your live, working version in `.agents/media-list.md` (per [journalist-pitching.md](journalist-pitching.md)).
FILE:references/newsjacking.md
# Newsjacking — Reactive PR Workflow
Injecting your POV into a story that's already trending. Done well: free distribution off a wave of attention. Done badly: cringe at best, brand damage at worst.
## Contents
- When newsjacking works (and when it doesn't)
- The detect → score → angle → pitch loop
- Newsworthiness scoring rubric
- Story angle library
- Speed: the only thing that matters
- Sources & tooling
- Failure modes
---
## When Newsjacking Works
- **Tech/regulatory news in your category** — new law, new platform launch, competitor pivot, big acquisition
- **Industry data drops** — a major report drops, you have a sharper take or contradicting data
- **Public conversation** — a debate or controversy where your expertise is genuinely relevant
- **Seasonal/cyclical moments** — earnings season, year-end reviews, conference weeks
## When to Skip
- **Tragedies, accidents, deaths** — no exceptions. Don't.
- **Politically charged stories** unless your brand explicitly takes political stances
- **You have no genuine expertise** in the area
- **The window is already closed** — if a story is 48h+ old and you weren't first, you're late
- **The angle is "we have a product for this"** — that's marketing, not journalism
---
## The Loop
A repeatable workflow Claude can run on demand or daily.
1. **Detect** — surface trending stories in your category (see [Sources & Tooling](#sources--tooling))
2. **Score** — apply the [newsworthiness rubric](#newsworthiness-scoring-rubric); drop anything below threshold
3. **Angle** — generate 2–3 angles per story using the [angle library](#story-angle-library)
4. **Validate** — sanity-check: do you actually have the expertise/data to back this angle?
5. **Pitch** — draft a tight pitch to 3–5 journalists who cover this beat (see [journalist-pitching.md](journalist-pitching.md))
6. **Post** — also publish on your blog, LinkedIn, X — it builds the trail journalists check before quoting you
Output format Claude should produce:
```
NEWSJACK CANDIDATE — 2026-06-10
Story: "EU passes AI Act amendment requiring agent registration"
Source: TechCrunch, 3h ago
Score: 8/10 (high relevance, fresh, you have proprietary data)
Angles:
1. Data hot take: "Our analysis of 12,000 agent deployments shows 73% would fail this requirement"
2. Contrarian: "Why the registration rule will hurt safety, not improve it"
3. Customer story: "How [customer] is preparing — interview offer"
Recommended: #1 (you have unique data, strongest hook)
Pitch draft: [see journalist-pitching.md for template]
Target journalists: [list with rationale]
```
---
## Newsworthiness Scoring Rubric
Score each candidate 1–10 on five dimensions, multiply by the weight, then sum. Max possible: 80 (10 × the 8x weight total).
| Dimension | What it measures | Weight |
|-----------|------------------|--------|
| **Timeliness** | Story <24h old? Window still open? | 2x |
| **Relevance** | Genuinely in your expertise area? | 2x |
| **Angle uniqueness** | Can you say something no one else is saying? | 2x |
| **Authority** | Do you have data, customers, or experience to back it? | 1x |
| **Reach potential** | Will this story keep growing or has it peaked? | 1x |
**Threshold:** weighted total ≥ 50/80. Below that, skip.
**Auto-disqualify if:**
- The story is about something tragic
- Your angle is "I disagree" with nothing to back it
- You haven't actually formed an opinion — you just want to be quoted
---
## Story Angle Library
Use these templates to generate angles fast.
### 1. Data hot take
*"We analyzed [N] [things] after [event]. Here's what we found."*
Best when you have proprietary data. The journalist gets a stat, you get the citation.
### 2. Contrarian
*"Everyone says [popular take]. Here's why they're wrong."*
Best when you can defend the position with specifics. Weak when it's just contrarianism for attention.
### 3. "We predicted this"
*"Six months ago we wrote [thing] — here's what's happening now and what's next."*
Best when you actually did predict it. Lethal to your credibility if you didn't.
### 4. Customer impact
*"Here's a [customer type] who's directly affected. We can put you in touch."*
Best for B2B. Reporters love named customers willing to talk.
### 5. Insider explainer
*"This story is complicated. Here's what's actually happening."*
Best when most coverage is missing nuance. You're not arguing — you're educating.
### 6. Trend connector
*"This isn't isolated — it's part of a bigger shift we're seeing in [pattern]."*
Best when you have several data points or examples to connect.
### 7. Founder POV
*"As someone who's built in this space for [X years], here's the part most people are missing."*
Best for opinion pieces / op-eds. Weak as a soundbite pitch.
---
## Speed: The Only Thing That Matters
Newsjacking decays fast. Approximate windows:
| Story type | Effective window |
|-----------|------------------|
| Breaking tech news | 4–12 hours |
| Major regulation / policy | 24–48 hours |
| Industry report / data drop | 24–72 hours |
| Conference announcement | Same day |
| Acquisition / funding news | 12–24 hours |
**Implication:** if you can't draft and send within the window, don't bother. Set up the loop so detection → pitch takes <2 hours.
---
## Sources & Tooling
Reuses tooling from the `social` skill's listening workflow. Same install: `brew install jq`.
### Google News RSS (no auth)
```bash
# Replace QUERY with topic (use + for spaces, %22 for quotes)
curl -s "https://news.google.com/rss/search?q=QUERY&hl=en-US&gl=US&ceid=US:en" \
| xmllint --xpath "//item[position()<11]" - 2>/dev/null
```
### Hacker News (Algolia) for tech stories
```bash
SINCE=$(($(date +%s) - 86400))
curl -s "https://hn.algolia.com/api/v1/search_by_date?query=QUERY&tags=story&numericFilters=created_at_i>SINCE" \
| jq '.hits[] | {title, url, points, num_comments, created_at, hn_url: ("https://news.ycombinator.com/item?id="+.objectID)}'
```
### Reddit (for category-specific subs)
```bash
curl -s -A "newsjack/1.0" \
"https://www.reddit.com/r/SUBREDDIT/top.json?t=day&limit=15" \
| jq '.data.children[].data | {title, url, score, num_comments, created_utc}'
```
### Journalist research (browser-driven)
For finding *which* journalists are covering the story right now:
- **dev-browser** → Google News search for the story → click through to articles → note the bylines
- Then go to those journalists' X / LinkedIn / Muck Rack profile to confirm beat and recent coverage
See also [journalist-pitching.md](journalist-pitching.md) for the full discovery workflow.
### Source list
For repeatable monitoring, add a "Newsjacking topics" section to `.agents/listening-sources.md` (template in the `social` skill's references):
```markdown
## Newsjacking topics (Google News RSS)
- "AI agent regulation"
- "[your category] funding"
- "[your competitors] OR [adjacent competitors]"
## Industry data drops (RSS / manual)
- Pitchbook reports
- a16z state of [industry] reports
- [your category] benchmark reports
```
---
## Failure Modes
Things that have ended careers and brands.
- **Tragedy-jacking** — Oreo's 2013 Super Bowl tweet worked. Most attempts since have not. Wartime, disasters, deaths: don't.
- **The forced fit** — "Here's our take on [trending story] — it's actually about [our product]." Journalists see through this instantly.
- **The empty take** — pitching "we have an opinion" without specifics. Journalists need a quote-worthy line, not "we're closely watching this."
- **Speed without judgment** — being first with a bad take is worse than being late with a good one. The 30-minute "is this brand-appropriate?" gut check exists for a reason.
- **Pitching the same angle to 50 journalists** — they talk. Get caught once, lose the relationships.
- **No follow-through** — pitch goes out, journalist responds in 20 minutes, founder takes 6 hours to reply. Story moves on.
---
## Companion Practice: The Public Trail
Every newsjack pitch is stronger if the journalist can find evidence you've been thinking about this publicly. Before pitching:
1. Publish a short post (blog, LinkedIn, X thread) with your take
2. Reference it in the pitch ("more thinking here: [link]")
3. This signals you're not opportunistic — you're an actual voice in the space
If you don't have time to publish, you're probably not ready to pitch.
FILE:references/podcast-guest-prep.md
# Podcast Guest Prep
Build a prep brief before the user appears on a podcast as a guest. The goal: walk in knowing what's top of mind for the show, how it has evolved, who the hosts are, and which of the user's stories map onto what the show cares about *right now*.
**Why prep is worth real effort:** podcast guesting isn't just audience reach. Episodes get transcribed, show notes get published, and both get crawled and cited by AI assistants — when someone asks ChatGPT about your category, the stories you told on a podcast two years ago are part of what it draws on. A good appearance is earned media that compounds in AI answers for years (see the `ai-seo` skill). The stories you tell — and the concrete numbers in them — become the citable record on your brand. Prep accordingly.
## Context to load first
Read `.agents/product-marketing.md` (or `.claude/product-marketing.md`) for the company, positioning, and ICP. That file usually won't have the guest's *story bank*, so also collect — in one batch, not a drip:
1. What did you build before this that comes up in conversation?
2. What are 2–3 stories you tell well, with real numbers attached?
3. What's one opinion you hold that most people in your space disagree with?
Offer to save the answers into the product-marketing context doc so future runs skip the interview.
From their message, establish (ask only if missing and it matters): the podcast name or URL, whether they've appeared before (get the prior episode link — it anchors the progression analysis and the callbacks), and roughly when they're recording.
## Research sequence
Work through sources in this order; each is a fallback for the last:
1. **RSS feed first.** The richest source: full episode descriptions, chapter markers, guest links, dates. Find the feed link on the podcast site (Buzzsprout, Transistor, etc. all expose one). Large-feed fetches may truncate — check whether the oldest episodes you need actually made it.
2. **The podcast website's episode list** for anything the feed missed. These pages often lazy-load older episodes via JavaScript; if pagination returns nothing, note the gap and move on rather than burning time.
3. **Apple Podcasts show page** — reliably renders the latest ~8 episodes with full descriptions.
4. **Web search** for stray episodes, the hosts, and the show's reputation.
Don't fetch every episode page. Descriptions plus chapter lists are almost always enough; only pull a full transcript when a specific episode is central (e.g., a debate the guest should have a position on). Check for published transcript links in the feed.
## What to extract
- **Recent-episode threads** (last ~3 months or 6–8 episodes): per-episode topic summaries, then the *recurring threads* — the questions the hosts keep returning to. Threads matter more than individual episodes; they predict the questions the guest will get.
- **Show progression** (since their last appearance, or ~12–18 months if first time): identify phases and the inflection point where the show's focus shifted. Note whether the show re-invites guests (signals how a return visit fits) and whether hosts launched side projects.
- **Host profiles.** Sources: the show's about pages, hosts' personal sites, LinkedIn, and — often the best source — episodes where the hosts guest on *other* shows and introduce themselves. Capture day job, background, what they've personally been building (mine solo-episode summaries), and social handles. If a host shares the guest's first name, flag it and keep references unambiguous throughout the brief.
- **Prior appearance recap** (if returning): what was actually discussed, with rough timestamps, and how much airtime the guest's current company got. This sets up the "what's changed since" narrative.
## The brief
Write a markdown file and present the short version in chat. Structure:
1. **Big picture** — what kind of show this is now, and the one-paragraph read on how the guest should position themselves
2. **Show progression** — the phases since their last appearance (or show start)
3. **Recent episodes in detail** — per-episode notes, then the recurring threads
4. **Guest angles** — their stories mapped explicitly onto the show's threads, callbacks to any prior appearance, and 2–3 "pocket" items: concrete stories with numbers to have ready
5. **The hosts** — profiles plus rapport hooks (where each host's world overlaps the guest's)
6. **Gaps** — anything unretrievable, and offers to fill them
Keep it tight — a doc they'll skim before recording, not a report.
**Finding angles:** map the story bank onto the show's recurring threads. The shape to look for: a "data moats" thread maps to the guest's proprietary dataset as a live case study; an "AI replacing niche tools" debate maps to a defensibility story from their product history. One well-chosen contrarian take stands out most on shows that have converged on a consensus.
**AI-visibility angle:** since the transcript becomes the record, coach the guest to say the important things in liftable form — the company name next to the category ("we build X, the Y for Z"), and numbers spoken aloud, not gestured at. Same logic as the YouTube text layer in `ai-seo`.
## Follow-ups to offer (don't auto-run)
Transcribing their prior episode for a word-level review, pulling a full transcript of one pivotal recent episode, or drafting likely Q&A.
---
*Distilled and adapted from [ai-visibility-skills](https://github.com/Knowatoa/ai-visibility-skills) by Knowatoa (MIT), reused with credit.*
FILE:references/press-platforms.md
# Press Request Platforms — Inbound PR
Journalists posting "I need a source for X" — you respond, sometimes you get quoted, sometimes you don't. The cheapest PR play available, but only if you treat it seriously.
## Contents
- The major platforms
- Daily triage workflow
- Response template
- What makes a response get selected
- What kills a response
- ROI reality check
---
## The Major Platforms
| Platform | What it is | Cost | Quality |
|----------|-----------|------|---------|
| **[Connectively](https://www.connectively.us)** (formerly HARO) | Daily email digest of journalist queries | Free tier; paid for filters | Mixed — high volume, lots of noise |
| **[Qwoted](https://www.qwoted.com)** | Web app with journalist requests | Free; paid for outreach | Good — better-quality outlets |
| **[Featured](https://featured.com)** | Web app, expert profiles, journalist requests | Free tier; paid pro | Good for thought-leadership snippets |
| **[Help A B2B Writer](https://helpab2bwriter.com)** | Twice-weekly email of B2B queries | Free | High — B2B-focused, low spam |
| **[SourceBottle](https://www.sourcebottle.com)** | Australia-focused but global queries | Free | Variable |
| **[Terkel](https://terkel.io)** | Roundup-style ("we asked 50 experts…") | Free | Volume-heavy, low effort |
| **[JournoRequests](https://twitter.com/journorequests)** | X account aggregating tweets | Free | UK-skewed, real-time |
| **#JournoRequest** (X hashtag) | Live journalist requests | Free | Real-time, fast-moving |
**Recommended starter set:** Connectively + Qwoted + Help A B2B Writer + monitoring `#JournoRequest` on X.
---
## Daily Triage Workflow
These platforms generate volume. Treat it like email triage — fast pass, deep response on the rare matches.
### Step 1 — Filter (5 min)
For each digest / request feed:
- Drop everything where you don't have **direct experience or data**
- Drop everything from outlets your ICP doesn't read
- Drop everything with a deadline you can't meet
- Keep only requests where you can give a **complete, named, on-the-record answer**
Realistic conversion: 50 daily requests → 2–4 worth answering.
### Step 2 — Deep response (15 min per request)
For each keeper:
- Read the request 3 times — what's the *actual* angle?
- Look up the journalist if possible — recent coverage, beat
- Write a custom response (see [template](#response-template))
- Send within their stated deadline (early > late)
### Step 3 — Log
Track in a spreadsheet:
- Date
- Platform
- Journalist + outlet
- Topic
- Response sent (yes/no)
- Outcome (no response / passed / quoted / linked)
After 30 responses, you'll see which topics/platforms convert.
---
## Response Template
Keep responses under 200 words. Journalists are scanning 50+ replies for one quote.
```
Hi [name],
Quick response to your request about [topic].
[Specific credential — 1 sentence. "Built X for 5 years" / "Led marketing at Y" / "Have analyzed N companies in space"]
The most important thing about [topic]: [your actual point in 2 sentences].
[A specific example, story, or data point — this is what gets quoted.]
[If applicable: a contrarian or surprising angle that differentiates from typical answers.]
Happy to expand on any of this, share data, or be quoted directly.
Feel free to use this attribution:
[Your name], [your title], [your company]
Contact for follow-up: [email + phone]
```
**Note the structure:**
1. One-sentence intro
2. One-sentence credential
3. Two-sentence answer
4. Specific example (the quotable part)
5. Optional: differentiator
6. Clear offer
7. Pre-written attribution (saves them 30 seconds)
8. Contact info
---
## What Makes a Response Get Selected
After analyzing hundreds of quoted responses, the patterns:
### Quotable specificity
**Bad:** "Companies should focus on customer experience."
**Good:** "When we A/B tested 47 onboarding flows, the version with a 30-second video at step 3 increased activation by 41%."
The good version is a quote. The bad version is filler.
### Concrete credential
**Bad:** "As a marketing expert..."
**Good:** "I've run growth at three Series B SaaS companies, all in B2B sales tooling."
Specificity beats title-stacking.
### Story over advice
**Bad:** "It's important to track the right metrics."
**Good:** "We almost shut down because we were optimizing for MRR when our real problem was activation. Once we switched to tracking 7-day activation, everything else followed."
Stories make articles. Advice makes filler.
### Pre-formatted for their workflow
- Pre-written attribution
- Multiple quotable lines (let them pick)
- High-res headshot link (don't attach)
- One-line company description
### Time match
**Most quotes come from responses sent in the first 6 hours.** After 24 hours, your chances drop sharply. Treat deadlines as if they're 24h earlier than stated.
---
## What Kills a Response
- **Pitching your product** when they asked for expert commentary
- **Generic advice** that could come from any expert
- **Multiple "experts" from your company** responding to the same request (looks coordinated, often is)
- **Hiring a PR firm to spam responses** — journalists smell it
- **Demanding a link back** to your site — most can't promise links
- **Ignoring the deadline** by 1+ days
- **Long bio sections** before the actual answer
- **Asking to "see the article before publication"** — you don't get to do that
- **Asking what other experts said** so you can differentiate — they won't tell you
---
## ROI Reality Check
Most teams overinvest in these platforms because they're cheap. Be honest:
| Effort | Realistic outcome (90 days) |
|--------|----------------------------|
| 5 hr/week, custom responses | 3–10 quoted placements |
| 1 hr/week, template responses | 0–2 placements |
| Outsourced to PR firm | Lots of submissions, few quotes |
**A quote in a tier-1 outlet is worth:**
- A backlink (DR depends on outlet)
- A sales-collateral asset ("As featured in...")
- AI-citation surface area
- Brand legitimacy in the abstract
**A quote in a tier-3 outlet is worth:**
- A backlink, often nofollow
- Maybe an Instagram screenshot
**Decision rule:** if you can sustain 5 hr/week of quality responses for 90 days, this is worth it. If you can only do 1 hr/week, skip it and invest in [proactive pitching](journalist-pitching.md) instead.
---
## Setup Checklist
Before you start responding:
- [ ] Press page exists and is current (see main SKILL.md)
- [ ] One-line credential is written and rehearsed
- [ ] Headshot is high-res and at a public URL
- [ ] You have 3–5 specific stories / data points ready to deploy
- [ ] You've decided which 2–3 platforms to use (don't try all 7)
- [ ] You've blocked a daily 20-min window for triage
- [ ] You're logging responses in a tracker
Without these, you're spamming and wasting their time and yours.
FILE:references/story-angles.md
# Story Angles — What Earns Press
Distilled from Corey Haines's *Founding Marketing*, ch. 5. Chase press for the **compound effect** — authority, journalist relationships, AI-citation surface — not the traffic bump. The bump fades in a day; the backlinks, the relationships, and the citable record don't.
**The story is not your product.** Journalists write about trends, data, conflict, and humans. Your product is the evidence, never the headline. If the pitch is "we built a thing," there's no story. If the pitch is "here's a shift happening and we're proof of it," there is.
## Contents
- The three story angles
- Newsworthy moments (data stories)
- Build media relationships before you need them
- The PR flywheel (repurposing map)
---
## The Three Story Angles
Every earned-media story worth pitching bends toward one of three shapes. Pick the one that fits your moment — don't force all three.
### 1. Founding Story
The origin, the build, the numbers-in-public. Works because people follow *people*, and a founder with skin in the game is quotable in a way a product never is.
- **When to use:** you're early, you have a sharp personal reason you exist, and you're willing to build in public and share real revenue.
- **Exemplar:** Pieter Levels' "12 startups in 12 months" — shipping in the open, posting MRR screenshots, letting the challenge itself be the story. The build *was* the coverage.
- **How to run it:** share the arc (why you started, what you've learned, where the numbers are now), not the feature list. Revenue transparency is the hook most founders are too scared to use.
### 2. David vs Goliath
Own your size. Being small against an incumbent is an advantage in the story, not a liability — you're the underdog readers root for.
- **When to use:** there's a giant in your category and you do one thing dramatically better than they do.
- **Exemplar:** Superhuman vs Gmail — a tiny team reframing "we're small" into "we're the fast, obsessive alternative to the default everyone tolerates."
- **How to run it:** name the Goliath, name the one thing you beat them at, and let the size gap do the emotional work. Don't hide that you're small — lead with it.
### 3. Have an Enemy
Give the reader something to be against. The enemy is a **broken system**, not a competitor. Attacking a rival looks petty; attacking a genuinely broken status quo looks principled — and journalists cover principled fights.
- **When to use:** there's a systemic wrong in your space you can credibly stand against, and you're willing to take a real position.
- **Exemplar:** HEY vs Apple's App Store (2020) — the fight wasn't "Basecamp vs Apple the company," it was against App Store rules founders saw as broken. It became a press cycle *and* a policy conversation.
- **How to run it:** define the broken system precisely, stake a clear position, and make sure you're actually willing to be quoted taking that stance. A half-hearted enemy reads as a marketing stunt.
**Guardrail:** the enemy must be a system or a norm — never a named competitor. "Company X is bad" is a smear; "this way of doing things is broken" is a movement.
---
## Newsworthy Moments: Data Stories
Beyond the three angles, the most reliably pitchable moment is a **data story** — proprietary numbers no one else has, packaged as an industry benchmark. Journalists can build a whole piece around a stat; you get the citation.
- **Exemplars:** Stripe's *State of New User* / annual data reports; Intercom's benchmark reports. Each turns internal data into an annual, citable, must-cover event.
- **Why it works:** it's original, it's quotable, and it positions you as the source-of-record for your category's numbers. It also compounds in AI answers — benchmark stats get lifted and re-cited for years.
- **How to run it:** find the one number in your data that surprises, wrap it in methodology, and package it as a standalone one-pager (not your homepage). See [journalist-pitching.md](journalist-pitching.md) → the "Data story" template.
**Milestone reminder:** a milestone alone ("we hit $1M ARR") isn't a story — milestone *with narrative* is. Attach the number to a shift, a lesson, or a David-vs-Goliath frame.
---
## Build Media Relationships Before You Need Them
Cold-pitching a journalist the day you have news is the hard way. The founders who get covered built the relationship months earlier. It's a ladder — climb it before you need the favor.
1. **Follow their beat.** Read their last 5 articles. Know what they cover and what they're tired of covering. (See the discovery checklist in [journalist-pitching.md](journalist-pitching.md).)
2. **Add value with no ask.** Reply to their work with a genuinely useful data point or correction. Share their pieces. Answer a question they post on X. Give before you take.
3. **Be a reliable source.** When they need a quote fast, be the person who responds in 20 minutes with a clean, quotable line — even when there's nothing in it for you. Reliability is what turns a contact into a relationship.
Do this for a handful of journalists on your beat and, when you finally have a real story, you're pitching a relationship, not a stranger.
---
## The PR Flywheel (Repurposing Map)
One core asset feeds every other channel. Don't create five things — create one exceptional thing and reformat it five ways. Each format extends the reach of the same idea and gives journalists more of a public trail to find.
```
Core asset (data report / strong POV / founding story)
│
├─→ Thread (X / LinkedIn) — the hook, teased publicly
├─→ Article (your blog) — the full argument, the destination
├─→ Guest post (someone else's audience) — borrow reach
├─→ Podcast appearance — the spoken, citable version
└─→ Video — the durable, AI-crawled version
```
- **Sequence matters less than coverage.** Publish the article as the owned home base, tease it as a thread, pitch the guest post and podcast off the same idea, cut the video from the recording.
- **Every format strengthens the next pitch.** A journalist who can find your thread, article, and podcast on a topic sees a voice in the space — not an opportunist. This is the public trail that makes newsjacking land (see [newsjacking.md](newsjacking.md) → The Public Trail).
- **The asset is the flywheel's fuel.** One strong data story or POV can run this loop for weeks. Weak assets stall it immediately.
Quét và tối ưu SEO cho README.md và trang docs: meta tag, heading, từ khóa, khả năng đọc, link hỏng, cập nhật sitemap.xml.
---
name: seo-auditor
description: |
Scan and optimize documentation files for SEO. Audits README.md files and docs/ pages for
meta tags, headings, keywords, readability, duplicate content, and broken links. Applies
fixes, updates sitemap.xml, and generates a report. Usage: /seo-auditor [path]
---
# /seo-auditor
Systematically scan, audit, and optimize documentation files for SEO. Targets README.md files and docs/ pages — fixes issues in place, preserves rankings on high-performing pages, and generates a final report.
## Usage
```bash
/seo-auditor # Audit all docs/ and root README.md
/seo-auditor docs/skills/ # Audit a specific docs subdirectory
/seo-auditor --report-only # Scan without making changes
```
## What It Does
Execute all 7 phases sequentially. Auto-fix non-destructive issues. Preserve existing high-ranking content. Report everything at the end.
---
## Phase 1: Discovery & Baseline
### 1a. Identify target files
Scan for documentation files that need SEO audit:
```bash
# Find all markdown files in docs/ and root README files
find docs/ -name '*.md' -type f | sort
find . -maxdepth 2 -name 'README.md' -not -path './.codex/*' -not -path './.gemini/*' | sort
```
Classify each file:
- **New/recently modified** — files changed in the last 2 commits (check via `git log`)
- **Index pages** — `index.md` files (high authority, handle with care)
- **Skill pages** — `docs/skills/**/*.md` (generated by `generate-docs.py`)
- **Static pages** — `docs/index.md`, `docs/getting-started.md`, `docs/integrations.md`, etc.
- **README files** — root and domain-level README.md
### 1b. Capture baseline
For each target file, extract current SEO state:
- `title:` frontmatter field → becomes `<title>` tag
- `description:` frontmatter field → becomes `<meta name="description">`
- First `# H1` heading
- All `## H2` and `### H3` subheadings
- Word count
- Internal link count
- External link count
Store baseline in memory for the report.
---
## Phase 2: Meta Tag Audit
For every file with YAML frontmatter, check and fix:
### Title Tag (`title:`)
**Rules:**
- Must exist and be non-empty
- Length: 50-60 characters ideal (Google truncates at ~60)
- Must contain a primary keyword
- Must NOT duplicate another page's title
- For skill pages: should follow the pattern `{Skill Name} — {Differentiator} - {site_name}`
- site_name from `mkdocs.yml` is appended automatically — don't duplicate it in the title
**Auto-fix:** If title is generic (e.g., just the skill name), enrich it with domain context using the DOMAIN_SEO_SUFFIX pattern from `scripts/generate-docs.py`.
### Meta Description (`description:`)
**Rules:**
- Must exist and be non-empty
- Length: 120-160 characters (Google truncates at ~160)
- Must contain the primary keyword naturally
- Must be unique across all pages — no two pages share the same description
- Should include a call-to-action or value proposition
- Must NOT start with "This page..." or "This document..."
**Auto-fix:** If description is missing or generic, generate one from the SKILL.md frontmatter description (if available) or from the first paragraph of content. Use the `extract_description_from_frontmatter()` function from `generate-docs.py` as reference.
### Validation Script
Run on each file that has HTML output in `site/`:
```bash
python3 marketing-skill/seo-audit/scripts/seo_checker.py --file site/{path}/index.html
```
Parse the score. Flag any page scoring below 60.
---
## Phase 3: Content Quality & Readability
For each target file, analyze and improve:
### Heading Structure
**Rules:**
- Exactly one `# H1` per page
- H2s follow H1, H3s follow H2 — no skipping levels
- Headings should contain keywords naturally (not stuffed)
- No duplicate headings on the same page
**Auto-fix:** If heading levels skip (H1 → H3), adjust to proper hierarchy.
### Readability
Run the content scorer on each file:
```bash
python3 marketing-skill/content-production/scripts/content_scorer.py {file_path}
```
Check scores for:
- **Readability** — aim for score ≥ 70
- **Structure** — aim for score ≥ 60
- **Engagement** — aim for score ≥ 50
### Content Quality Rules
- **Paragraphs:** No single paragraph longer than 5 sentences
- **Sentences:** Average sentence length 15-20 words
- **Passive voice:** Less than 15% of sentences
- **Transition words:** At least 30% of sentences use transitions
- **Bullet lists:** Use lists for 3+ items instead of comma-separated inline lists
### AI Content Detection
Run the humanizer scorer on non-generated content (README.md files, static pages):
```bash
python3 marketing-skill/content-humanizer/scripts/humanizer_scorer.py {file_path}
```
Flag pages scoring below 50 (too AI-sounding). For these pages, apply voice techniques from `marketing-skill/content-humanizer/references/voice-techniques.md`:
- Replace AI clichés ("delve into", "leverage", "it's important to note")
- Vary sentence length
- Add specific examples instead of generic statements
- Use active voice
**Important:** Only modify content that was recently created or updated. Do NOT rewrite pages that are ranking well — preserve their content.
---
## Phase 4: Keyword Optimization
### 4a. Identify target keywords per page
Based on the page's purpose and domain:
| Page Type | Primary Keywords | Secondary Keywords |
|-----------|-----------------|-------------------|
| Homepage (docs/index.md) | "Claude Code Skills", "agent plugins" | "Codex skills", "Gemini CLI", "OpenClaw" |
| Skill pages | Skill name + "Claude Code" | "agent skill", "Codex plugin", domain terms |
| Agent pages | Agent name + "AI coding agent" | "Claude Code", "orchestrator" |
| Command pages | Command name + "slash command" | "Claude Code", "AI coding" |
| Getting started | "install Claude Code skills" | platform names |
| Domain index | Domain + "skills" + "plugins" | "Claude Code", platform names |
### 4b. Keyword placement checks
For each page, verify the primary keyword appears in:
- [ ] Title tag (frontmatter `title:`)
- [ ] Meta description (frontmatter `description:`)
- [ ] H1 heading
- [ ] First paragraph (within first 100 words)
- [ ] At least one H2 subheading
- [ ] Image alt text (if images present)
- [ ] URL slug (for new pages only — never change existing URLs)
### 4c. Keyword density
- Primary keyword: 1-2% of total word count
- Secondary keywords: 0.5-1% each
- No keyword stuffing — if density exceeds 3%, reduce it
**Important:** Never change URLs of existing pages. URL changes break incoming links and destroy rankings. Only optimize content and meta tags.
---
## Phase 5: Link Audit
### 5a. Internal links
For each target file, check all markdown links `[text](url)`:
- Verify the target exists (file path resolves)
- Check for broken relative links (`../`, `./`)
- Verify anchor links (`#section-name`) point to existing headings
**Auto-fix:** Use the `rewrite_skill_internal_links()` and `rewrite_relative_links()` functions from `generate-docs.py` as reference. Rewrite broken skill-internal links to GitHub source URLs.
### 5b. Duplicate content detection
Compare meta descriptions across all pages:
```bash
grep -rh '^description:' docs/**/*.md | sort | uniq -d
```
If duplicates found, make each description unique by adding page-specific context.
Compare H1 headings across all pages — no two pages should have the same H1.
### 5c. Orphan page detection
Check if every page in `docs/` is referenced in `mkdocs.yml` nav. Pages not in nav are orphans — they won't appear in navigation and may not be indexed.
```bash
# Find doc pages not in mkdocs nav
find docs -name '*.md' -not -name 'index.md' | while read f; do
slug=$(echo "$f" | sed 's|docs/||')
grep -q "$slug" mkdocs.yml || echo "ORPHAN: $f"
done
```
**Auto-fix:** Add orphan pages to the correct nav section in `mkdocs.yml`.
---
## Phase 6: Sitemap & Build
### 6a. Rebuild the site
```bash
mkdocs build
```
This regenerates `site/sitemap.xml` automatically (MkDocs Material generates it during build).
### 6b. Verify sitemap
Check the generated sitemap:
```bash
python3 marketing-skill/site-architecture/scripts/sitemap_analyzer.py site/sitemap.xml
```
Verify:
- All documentation pages appear in the sitemap
- No broken/404 URLs
- URL count matches expected page count
- Depth distribution is reasonable (no pages deeper than 4 levels)
### 6c. Check for sitemap issues
- **Missing pages:** Pages in `mkdocs.yml` nav that don't appear in sitemap
- **Extra pages:** Pages in sitemap that aren't in nav (orphans)
- **Duplicate URLs:** Same page accessible via multiple URLs
---
## Phase 7: Report
Generate a concise report for the user:
```
╔══════════════════════════════════════════════════════════════╗
║ SEO AUDITOR REPORT ║
╠══════════════════════════════════════════════════════════════╣
║ ║
║ Pages scanned: {n} ║
║ Issues found: {n} ║
║ Auto-fixed: {n} ║
║ Manual review needed: {n} ║
║ ║
║ META TAGS ║
║ Titles optimized: {n} ║
║ Descriptions fixed: {n} ║
║ Duplicate titles: {n} → {n} (fixed) ║
║ Duplicate descs: {n} → {n} (fixed) ║
║ ║
║ CONTENT ║
║ Readability improved: {n} pages ║
║ Heading fixes: {n} ║
║ AI score improved: {n} pages ║
║ ║
║ KEYWORDS ║
║ Pages missing primary keyword in title: {n} ║
║ Pages missing keyword in description: {n} ║
║ Pages with keyword stuffing: {n} ║
║ ║
║ LINKS ║
║ Broken links found: {n} → {n} (fixed) ║
║ Orphan pages: {n} → {n} (added to nav) ║
║ Duplicate content: {n} → {n} (deduplicated) ║
║ ║
║ SITEMAP ║
║ Total URLs: {n} ║
║ Sitemap regenerated: ✅ ║
║ ║
║ PRESERVED (no changes — ranking well) ║
║ {list of pages left untouched} ║
║ ║
╚══════════════════════════════════════════════════════════════╝
```
### Pages to preserve (do NOT modify)
These pages rank well for their target keywords. Only fix critical issues (broken links, missing meta). Do NOT rewrite content:
- `docs/index.md` — homepage, ranks for "Claude Code Skills"
- `docs/getting-started.md` — installation guide
- `docs/integrations.md` — multi-tool support
- Any page the user explicitly marks as "preserve"
---
## Skill References
| Tool | Path | Use |
|------|------|-----|
| SEO Checker | `marketing-skill/seo-audit/scripts/seo_checker.py` | Score HTML pages 0-100 |
| Content Scorer | `marketing-skill/content-production/scripts/content_scorer.py` | Score content readability/structure/engagement |
| Humanizer Scorer | `marketing-skill/content-humanizer/scripts/humanizer_scorer.py` | Detect AI-sounding content |
| Headline Scorer | `marketing-skill/copywriting/scripts/headline_scorer.py` | Score title quality |
| SEO Optimizer | `marketing-skill/content-production/scripts/seo_optimizer.py` | Optimize content for target keyword |
| Sitemap Analyzer | `marketing-skill/site-architecture/scripts/sitemap_analyzer.py` | Analyze sitemap structure |
| Schema Validator | `marketing-skill/schema-markup/scripts/schema_validator.py` | Validate structured data |
| Topic Cluster Mapper | `marketing-skill/content-strategy/scripts/topic_cluster_mapper.py` | Group pages into content clusters |
### Reference Docs
| Reference | Path | Use |
|-----------|------|-----|
| SEO Audit Framework | `marketing-skill/seo-audit/references/seo-audit-reference.md` | Priority order for SEO fixes |
| AI Search Optimization | `marketing-skill/ai-seo/references/content-patterns.md` | Make content citable by AI |
| Content Optimization | `marketing-skill/content-production/references/optimization-checklist.md` | Pre-publish checklist |
| URL Design Guide | `marketing-skill/site-architecture/references/url-design-guide.md` | URL structure best practices |
| Internal Linking | `marketing-skill/site-architecture/references/internal-linking-playbook.md` | Internal linking strategy |
| AI Writing Detection | `marketing-skill/content-humanizer/references/ai-tells-checklist.md` | AI cliché removal |
Triển khai và duy trì hệ thống quản lý chất lượng ISO 13485 cho thiết bị y tế: thiết kế QMS, kiểm soát tài liệu, đánh giá nội bộ, CAPA và hỗ trợ chứng nhận.
---
name: "quality-manager-qms-iso13485"
description: ISO 13485 Quality Management System implementation and maintenance for medical device organizations. Provides QMS design, documentation control, internal auditing, CAPA management, and certification support. Use when working with medical device quality systems, preparing for ISO 13485 audits, managing regulatory compliance documentation, setting up corrective actions, or building audit preparation programs. Useful for quality management, audit preparation, regulatory compliance, medical device documentation, and corrective action workflows.
triggers:
- ISO 13485
- QMS implementation
- quality management system
- document control
- internal audit
- management review
- quality manual
- CAPA process
- process validation
- design control
- supplier qualification
- quality records
---
# Quality Manager - QMS ISO 13485 Specialist
ISO 13485:2016 Quality Management System implementation, maintenance, and certification support for medical device organizations.
---
## Table of Contents
- [QMS Implementation Workflow](#qms-implementation-workflow)
- [Document Control Workflow](#document-control-workflow)
- [Internal Audit Workflow](#internal-audit-workflow)
- [Process Validation Workflow](#process-validation-workflow)
- [Supplier Qualification Workflow](#supplier-qualification-workflow)
- [QMS Process Reference](#qms-process-reference)
- [Decision Frameworks](#decision-frameworks)
- [Tools and References](#tools-and-references)
---
## QMS Implementation Workflow
Implement ISO 13485:2016 compliant quality management system from gap analysis through certification.
### Workflow: Initial QMS Implementation
1. Conduct gap analysis against ISO 13485:2016 requirements
2. Document current state vs. required state for each clause
3. Prioritize gaps by:
- Regulatory criticality
- Risk to product safety
- Resource requirements
4. Develop implementation roadmap with milestones
5. Establish Quality Manual per Clause 4.2.2:
- QMS scope with justified exclusions
- Process interactions
- Procedure references
6. Create required documented procedures — see [Mandatory Documented Procedures](#quick-reference-mandatory-documented-procedures) for the full list
7. Deploy processes with training
8. **Validation:** Gap analysis complete; Quality Manual approved; all required procedures documented and trained
> Use the Gap Analysis Matrix template in [qms-process-templates.md](references/qms-process-templates.md) to document clause-by-clause current state, gaps, priority, and actions.
### QMS Structure
| Level | Document Type | Example |
|-------|---------------|---------|
| 1 | Quality Manual | QM-001 |
| 2 | Procedures | SOP-02-001 |
| 3 | Work Instructions | WI-06-012 |
| 4 | Records | Training records |
---
## Document Control Workflow
Establish and maintain document control per ISO 13485 Clause 4.2.3.
### Workflow: Document Creation and Approval
1. Identify need for new document or revision
2. Assign document number per numbering convention:
- Format: `[TYPE]-[AREA]-[SEQUENCE]-[REV]`
- Example: `SOP-02-001-01`
3. Draft document using approved template
4. Route for review to subject matter experts
5. Collect and address review comments
6. Obtain required approvals based on document type
7. Update Document Master List
8. **Validation:** Document numbered correctly; all reviewers signed; Master List updated
### Document Numbering Convention
| Prefix | Document Type | Approval Authority |
|--------|---------------|-------------------|
| QM | Quality Manual | Management Rep + CEO |
| POL | Policy | Department Head + QA |
| SOP | Procedure | Process Owner + QA |
| WI | Work Instruction | Supervisor + QA |
| TF | Template/Form | Process Owner |
| SPEC | Specification | Engineering + QA |
### Area Codes
| Code | Area | Examples |
|------|------|----------|
| 01 | Quality Management | Quality Manual, policy |
| 02 | Document Control | This procedure |
| 03 | Training | Competency procedures |
| 04 | Design | Design control |
| 05 | Purchasing | Supplier management |
| 06 | Production | Manufacturing |
| 07 | Quality Control | Inspection, testing |
| 08 | CAPA | Corrective actions |
### Document Change Control
| Change Type | Approval Level | Examples |
|-------------|----------------|----------|
| Administrative | Document Control | Typos, formatting |
| Minor | Process Owner + QA | Clarifications |
| Major | Full review cycle | Process changes |
| Emergency | Expedited + retrospective | Safety issues |
### Document Review Schedule
| Document Type | Review Period | Trigger for Unscheduled Review |
|---------------|---------------|-------------------------------|
| Quality Manual | Annual | Organizational change |
| Procedures | Annual | Audit finding, regulation change |
| Work Instructions | 2 years | Process change |
| Forms | 2 years | User feedback |
---
## Internal Audit Workflow
Plan and execute internal audits per ISO 13485 Clause 8.2.4.
### Workflow: Annual Audit Program
1. Identify processes and areas requiring audit coverage
2. Assess risk factors for audit frequency:
- Previous audit findings
- Regulatory changes
- Process changes
- Complaint trends
3. Assign qualified auditors (independent of area audited)
4. Develop annual audit schedule
5. Obtain management approval
6. Communicate schedule to process owners
7. Track completion and reschedule as needed
8. **Validation:** All processes covered; auditors qualified and independent; schedule approved
> Use the Audit Program Template in [qms-process-templates.md](references/qms-process-templates.md) to schedule audits by clause and quarter across processes such as Document Control (4.2.3/4.2.4), Management Review (5.6), Design Control (7.3), Production (7.5), and CAPA (8.5.2/8.5.3).
### Workflow: Individual Audit Execution
1. Prepare audit plan with scope, criteria, and schedule
2. Notify auditee minimum 1 week prior
3. Review procedures and previous audit results
4. Prepare audit checklist
5. Conduct opening meeting
6. Collect evidence through:
- Document review
- Record sampling
- Process observation
- Personnel interviews
7. Classify findings:
- Major NC: Absence or breakdown of system
- Minor NC: Single lapse or deviation
- Observation: Risk of future NC
8. Conduct closing meeting
9. Issue audit report within 5 business days
10. **Validation:** All checklist items addressed; findings supported by evidence; report distributed
### Auditor Qualification Requirements
| Criterion | Requirement |
|-----------|-------------|
| Training | ISO 13485 awareness + auditor training |
| Experience | Minimum 1 audit as observer |
| Independence | Not auditing own work area |
| Competence | Understanding of audited process |
### Finding Classification Guide
| Classification | Criteria | Response Time |
|----------------|----------|---------------|
| Major NC | System absence, total breakdown, regulatory violation | 30 days for CAPA |
| Minor NC | Single instance, partial compliance | 60 days for CAPA |
| Observation | Potential risk, improvement opportunity | Track in next audit |
---
## Process Validation Workflow
Validate special processes per ISO 13485 Clause 7.5.6.
### Workflow: Process Validation Protocol
1. Identify processes requiring validation:
- Output cannot be verified by inspection
- Deficiencies appear only in use
- Sterilization, welding, sealing, software
2. Form validation team with subject matter experts
3. Write validation protocol including:
- Process description and parameters
- Equipment and materials
- Acceptance criteria
- Statistical approach
4. Execute IQ: verify equipment installed correctly and document specifications
5. Execute OQ: test parameter ranges and verify process control
6. Execute PQ: run production conditions and verify output meets requirements
7. Write validation report with conclusions
8. **Validation:** IQ/OQ/PQ complete; acceptance criteria met; validation report approved
### Validation Documentation Requirements
| Phase | Content | Evidence |
|-------|---------|----------|
| Protocol | Objectives, methods, criteria | Approved protocol |
| IQ | Equipment verification | Installation records |
| OQ | Parameter verification | Test results |
| PQ | Performance verification | Production data |
| Report | Summary, conclusions | Approval signatures |
### Revalidation Triggers
| Trigger | Action Required |
|---------|-----------------|
| Equipment change | Assess impact, revalidate affected phases |
| Parameter change | OQ and PQ minimum |
| Material change | Assess impact, PQ minimum |
| Process failure | Full revalidation |
| Periodic | Per validation schedule (typically 3 years) |
### Special Process Examples
| Process | Validation Standard | Critical Parameters |
|---------|--------------------|--------------------|
| EO Sterilization | ISO 11135 | Temperature, humidity, EO concentration, time |
| Steam Sterilization | ISO 17665 | Temperature, pressure, time |
| Radiation Sterilization | ISO 11137 | Dose, dose uniformity |
| Sealing | Internal | Temperature, pressure, dwell time |
| Welding | ISO 11607 | Heat, pressure, speed |
---
## Supplier Qualification Workflow
Evaluate and approve suppliers per ISO 13485 Clause 7.4.
### Workflow: New Supplier Qualification
1. Identify supplier category:
- Category A: Critical (affects safety/performance)
- Category B: Major (affects quality)
- Category C: Minor (indirect impact)
2. Request supplier information:
- Quality certifications
- Product specifications
- Quality history
3. Evaluate supplier based on:
- Quality system (ISO certification)
- Technical capability
- Quality history
- Financial stability
4. For Category A suppliers:
- Conduct on-site audit
- Require quality agreement
5. Calculate qualification score
6. Make approval decision:
- >80: Approved
- 60-80: Conditional approval
- <60: Not approved
7. Add to Approved Supplier List
8. **Validation:** Evaluation criteria scored; qualification records complete; supplier categorized
### Supplier Evaluation Criteria
| Criterion | Weight | Scoring |
|-----------|--------|---------|
| Quality System | 30% | ISO 13485=30, ISO 9001=20, Documented=10, None=0 |
| Quality History | 25% | Reject rate: <1%=25, 1-3%=15, >3%=0 |
| Delivery | 20% | On-time: >95%=20, 90-95%=10, <90%=0 |
| Technical Capability | 15% | Exceeds=15, Meets=10, Marginal=5 |
| Financial Stability | 10% | Strong=10, Adequate=5, Questionable=0 |
### Supplier Category Requirements
| Category | Qualification | Monitoring | Agreement |
|----------|---------------|------------|-----------|
| A - Critical | On-site audit | Annual review | Quality agreement |
| B - Major | Questionnaire | Semi-annual review | Quality requirements |
| C - Minor | Assessment | Issue-based | Standard terms |
### Supplier Performance Metrics
| Metric | Target | Calculation |
|--------|--------|-------------|
| Accept Rate | >98% | (Accepted lots / Total lots) × 100 |
| On-Time Delivery | >95% | (On-time / Total orders) × 100 |
| Response Time | <5 days | Average days to resolve issues |
| Documentation | 100% | (Complete CoCs / Required CoCs) × 100 |
---
## QMS Process Reference
For detailed requirements and audit questions for each ISO 13485:2016 clause, see [iso13485-clause-requirements.md](references/iso13485-clause-requirements.md).
### Management Review Required Inputs (Clause 5.6.2)
| Input | Source | Prepared By |
|-------|--------|-------------|
| Audit results | Internal and external audits | QA Manager |
| Customer feedback | Complaints, surveys | Customer Quality |
| Process performance | Process metrics | Process Owners |
| Product conformity | Inspection data, NCs | QC Manager |
| CAPA status | CAPA system | CAPA Officer |
| Previous actions | Prior review records | QMR |
| Changes affecting QMS | Regulatory, organizational | RA Manager |
| Recommendations | All sources | All Managers |
### Record Retention Requirements
| Record Type | Minimum Retention | Regulatory Basis |
|-------------|-------------------|------------------|
| Device Master Record | Life of device + 2 years | 21 CFR 820.181 |
| Device History Record | Life of device + 2 years | 21 CFR 820.184 |
| Design History File | Life of device + 2 years | 21 CFR 820.30 |
| Complaint Records | Life of device + 2 years | 21 CFR 820.198 |
| Training Records | Employment + 3 years | Best practice |
| Audit Records | 7 years | Best practice |
| CAPA Records | 7 years | Best practice |
| Calibration Records | Equipment life + 2 years | Best practice |
---
## Decision Frameworks
### Exclusion Justification (Clause 4.2.2)
| Clause | Permissible Exclusion | Justification Required |
|--------|----------------------|------------------------|
| 6.4.2 | Contamination control | Product not affected by contamination |
| 7.3 | Design and development | Organization does not design products |
| 7.5.2 | Product cleanliness | No cleanliness requirements |
| 7.5.3 | Installation | No installation activities |
| 7.5.4 | Servicing | No servicing activities |
| 7.5.5 | Sterile products | No sterile products |
### Nonconformity Disposition Decision Tree
```
Nonconforming Product Identified
│
▼
Can it be reworked?
│
Yes──┴──No
│ │
▼ ▼
Is rework Can it be used
procedure as is?
available? │
│ Yes──┴──No
Yes─┴─No │ │
│ │ ▼ ▼
▼ ▼ Concession Scrap or
Rework Create approval return to
per SOP rework needed? supplier
procedure │
Yes─┴─No
│ │
▼ ▼
Customer Use as is
approval with MRB
approval
```
### CAPA Initiation Criteria
| Source | Automatic CAPA | Evaluate for CAPA |
|--------|----------------|-------------------|
| Customer complaint | Safety-related | All others |
| External audit | Major NC | Minor NC |
| Internal audit | Major NC | Repeat minor NC |
| Product NC | Field failure | Trend exceeds threshold |
| Process deviation | Safety impact | Repeated deviations |
---
## Tools and References
### Scripts
| Tool | Purpose | Usage |
|------|---------|-------|
| [qms_audit_checklist.py](scripts/qms_audit_checklist.py) | Generate audit checklists by clause or process | `python qms_audit_checklist.py --help` |
**Audit Checklist Generator Features:**
- Generate clause-specific checklists (e.g., `--clause 7.3`)
- Generate process-based checklists (e.g., `--process design-control`)
- Full system audit checklist (`--audit-type system`)
- Text or JSON output formats
- Interactive mode for guided selection
### References
| Document | Content |
|----------|---------|
| [iso13485-clause-requirements.md](references/iso13485-clause-requirements.md) | Detailed requirements for each ISO 13485:2016 clause with audit questions |
| [qms-process-templates.md](references/qms-process-templates.md) | Ready-to-use templates for gap analysis, audit program, document control, CAPA, supplier, training |
### Quick Reference: Mandatory Documented Procedures
| Procedure | Clause | Key Elements |
|-----------|--------|--------------|
| Document Control | 4.2.3 | Approval, distribution, obsolete control |
| Record Control | 4.2.4 | Identification, retention, disposal |
| Internal Audit | 8.2.4 | Program, auditor qualification, reporting |
| NC Product Control | 8.3 | Identification, segregation, disposition |
| Corrective Action | 8.5.2 | Root cause, implementation, verification |
| Preventive Action | 8.5.3 | Risk identification, implementation |
---
## Related Skills
| Skill | Integration Point |
|-------|-------------------|
| [quality-manager-qmr](../quality-manager-qmr/) | Management review, quality policy |
| [capa-officer](../capa-officer/) | CAPA system management |
| [qms-audit-expert](../qms-audit-expert/) | Advanced audit techniques |
| [quality-documentation-manager](../quality-documentation-manager/) | DHF, DMR, DHR management |
| [risk-management-specialist](../risk-management-specialist/) | ISO 14971 integration |
FILE:references/iso13485-clause-requirements.md
# ISO 13485:2016 Clause Requirements
Detailed requirements for each ISO 13485:2016 clause with implementation guidance and audit criteria.
---
## Table of Contents
- [Clause 4: Quality Management System](#clause-4-quality-management-system)
- [Clause 5: Management Responsibility](#clause-5-management-responsibility)
- [Clause 6: Resource Management](#clause-6-resource-management)
- [Clause 7: Product Realization](#clause-7-product-realization)
- [Clause 8: Measurement, Analysis and Improvement](#clause-8-measurement-analysis-and-improvement)
---
## Clause 4: Quality Management System
### 4.1 General Requirements
| Requirement | Implementation | Evidence |
|-------------|----------------|----------|
| Determine processes needed | Process map showing QMS processes | Documented process map |
| Determine sequence and interaction | Process interaction diagram | Cross-reference matrix |
| Determine criteria for operation | Process metrics and acceptance criteria | Documented criteria per process |
| Ensure resources available | Resource allocation per process | Training records, equipment logs |
| Monitor, measure, analyze | Process monitoring procedures | Trend data, performance reports |
| Implement actions for results | Improvement projects, CAPAs | Action records with verification |
| Document processes | Procedures, work instructions | Controlled document list |
**Audit Questions:**
- How are QMS processes identified and documented?
- What criteria determine if processes are operating effectively?
- How is outsourced process control demonstrated?
### 4.2 Documentation Requirements
#### 4.2.1 General
| Document Type | Requirement | Retention |
|---------------|-------------|-----------|
| Quality Policy | Documented statement of commitment | Life of QMS |
| Quality Objectives | Measurable objectives at relevant functions | Life of QMS |
| Quality Manual | QMS scope and processes | Current version |
| Documented Procedures | Required by standard | Life of QMS + 2 years |
| Records | Evidence of conformity | As defined per record type |
#### 4.2.2 Quality Manual
**Required Content:**
1. Scope of QMS including justification for exclusions
2. Documented procedures or reference to them
3. Description of process interactions
**Quality Manual Template Structure:**
```
QUALITY MANUAL
1. Company Overview
1.1 Company Description
1.2 Scope of QMS
1.3 Exclusions and Justification
2. Quality Policy
3. Quality Objectives
4. QMS Structure
4.1 Process Map
4.2 Process Interactions
4.3 Organizational Chart
5. Procedure References
5.1 Document Control
5.2 Record Control
5.3 Management Review
5.4 Internal Audit
5.5 Nonconformity Control
5.6 CAPA
6. Appendices
6.1 Glossary
6.2 Regulatory Cross-Reference
```
#### 4.2.3 Control of Documents
| Control Element | Requirement | Method |
|-----------------|-------------|--------|
| Approval | Adequate prior to issue | Signature/electronic approval |
| Review and update | Re-approval after changes | Periodic review process |
| Identification of changes | Change history visible | Revision log in document |
| Revision status | Current revision identifiable | Document master list |
| Legibility | Readable and identifiable | Format standards |
| External documents | Identified and controlled | Incoming document log |
| Obsolete documents | Prevented from unintended use | Archive system |
**Document Numbering Convention:**
```
[TYPE]-[AREA]-[SEQUENCE]-[REV]
TYPE:
QM = Quality Manual
SOP = Standard Operating Procedure
WI = Work Instruction
TF = Template/Form
POL = Policy
AREA:
01 = Quality Management
02 = Document Control
03 = Training
04 = Design
05 = Purchasing
06 = Production
07 = Quality Control
08 = CAPA
Example: SOP-02-001-03 = Document Control SOP, Revision 03
```
#### 4.2.4 Control of Records
| Record Category | Minimum Retention | Basis |
|-----------------|-------------------|-------|
| Device Master Record | Life of device + 2 years | 21 CFR 820.181 |
| Device History Record | Life of device + 2 years | 21 CFR 820.184 |
| Design History File | Life of device + 2 years | 21 CFR 820.30 |
| Training Records | Employment + 3 years | Best practice |
| Audit Records | 7 years | Best practice |
| Complaint Records | Life of device + 2 years | 21 CFR 820.198 |
| CAPA Records | 7 years | Best practice |
| Calibration Records | Equipment life + 2 years | Best practice |
| Supplier Records | Relationship + 3 years | Best practice |
---
## Clause 5: Management Responsibility
### 5.1 Management Commitment
| Commitment Area | Evidence Required |
|-----------------|-------------------|
| Communicate importance of requirements | Meeting minutes, communications |
| Establish quality policy | Documented policy, communication records |
| Ensure quality objectives established | Objective documentation |
| Conduct management reviews | Management review records |
| Ensure resources available | Budget records, staffing records |
### 5.2 Customer Focus
| Requirement | Implementation | Verification |
|-------------|----------------|--------------|
| Customer requirements determined | Requirements review process | Contract review records |
| Requirements met | Process controls | Inspection and test data |
| Regulatory requirements met | Regulatory register | Compliance assessments |
| Customer satisfaction enhanced | Feedback collection | Satisfaction data, complaints |
### 5.3 Quality Policy
**Policy Requirements:**
- Appropriate to organization purpose
- Commitment to compliance and effectiveness
- Framework for quality objectives
- Communicated and understood
- Reviewed for continuing suitability
**Sample Quality Policy Elements:**
```
[Company Name] Quality Policy
We are committed to:
- Designing and manufacturing safe, effective medical devices
- Meeting customer and regulatory requirements
- Maintaining an effective Quality Management System
- Continuously improving our processes and products
- Providing resources for QMS effectiveness
Signed: [Executive]
Date: [Date]
Review Date: [Annual]
```
### 5.4 Planning
#### 5.4.1 Quality Objectives
| Objective Criteria | Requirement |
|-------------------|-------------|
| Measurable | Quantifiable targets |
| Consistent with policy | Aligned to policy statements |
| Relevant functions | Cascaded to departments |
| Includes compliance | Regulatory and customer requirements |
| Includes product conformity | Product-related targets |
**Objective Template:**
```
QUALITY OBJECTIVE [Year]
Objective: [Statement]
Metric: [How measured]
Target: [Specific value]
Baseline: [Current performance]
Owner: [Responsible person]
Due Date: [Target date]
Reporting: [Frequency]
```
#### 5.4.2 Quality Management System Planning
**Planning Requirements:**
- QMS meets general requirements (4.1)
- QMS meets quality objectives (5.4.1)
- Integrity maintained during changes
### 5.5 Responsibility, Authority and Communication
#### 5.5.1 Responsibility and Authority
| Role | Responsibilities | Authority |
|------|-----------------|-----------|
| Top Management | QMS commitment, resources, policy | Budget, staffing, strategic decisions |
| Quality Manager | QMS implementation, reporting | Document approval, CAPA approval |
| Department Managers | Process ownership, resources | Process changes, training |
| Process Owners | Process performance, improvements | Procedure changes within scope |
#### 5.5.2 Management Representative
| QMR Responsibility | Activities |
|-------------------|------------|
| QMS establishment | Process definition, documentation |
| QMS implementation | Training, deployment, monitoring |
| QMS maintenance | Audits, reviews, improvements |
| Reporting to top management | Performance reports, recommendations |
| Awareness promotion | Training, communications |
#### 5.5.3 Internal Communication
| Communication Type | Method | Frequency |
|-------------------|--------|-----------|
| Policy and objectives | Posting, training | Annual and on change |
| QMS performance | Dashboards, reports | Monthly |
| Changes affecting quality | Email, meetings | As needed |
| Audit results | Reports, presentations | Per audit |
### 5.6 Management Review
#### 5.6.1 General
| Requirement | Specification |
|-------------|---------------|
| Frequency | Planned intervals (typically quarterly/semi-annually) |
| Purpose | Assess QMS suitability, adequacy, effectiveness |
| Records | Documented meeting records |
#### 5.6.2 Review Input
| Input | Source | Responsible |
|-------|--------|-------------|
| Audit results | Internal/external audits | QA Manager |
| Customer feedback | Complaints, surveys | Customer Quality |
| Process performance | Metrics, yields | Process Owners |
| Product conformity | Inspection data | QC Manager |
| CAPA status | CAPA system | CAPA Officer |
| Previous actions | Prior review records | QMR |
| Changes affecting QMS | Regulatory, organizational | RA, HR |
| Recommendations | All sources | All Managers |
#### 5.6.3 Review Output
| Output | Documentation |
|--------|---------------|
| QMS improvement decisions | Action items with owners |
| Process improvements | Project charters |
| Resource needs | Resource allocation plans |
| Product improvements | Design change requests |
---
## Clause 6: Resource Management
### 6.1 Provision of Resources
**Resource Categories:**
- Human resources (competent personnel)
- Infrastructure (facilities, equipment, software)
- Work environment (environmental conditions)
### 6.2 Human Resources
| Requirement | Implementation | Evidence |
|-------------|----------------|----------|
| Competence determined | Job descriptions, competency matrix | Role definitions |
| Training provided | Training programs | Training records |
| Effectiveness evaluated | Assessments, observations | Competency verification |
| Awareness ensured | Orientation, ongoing training | Acknowledgments |
| Records maintained | Training database | Training files |
**Competency Matrix Template:**
```
COMPETENCY MATRIX
Role: [Job Title]
Department: [Department]
Required Competencies:
| Competency | Requirement Level | Method | Verification |
|------------|------------------|--------|--------------|
| [Skill 1] | Expert/Proficient/Basic | Training/OJT | Assessment |
| [Skill 2] | Expert/Proficient/Basic | Training/OJT | Assessment |
Training Requirements:
| Training | Initial | Refresher | Record |
|----------|---------|-----------|--------|
| ISO 13485 Awareness | Yes | Annual | TR-001 |
| Document Control | Yes | On Change | TR-002 |
```
### 6.3 Infrastructure
| Infrastructure Type | Control Requirements |
|--------------------|---------------------|
| Buildings and workspace | Cleaning, maintenance schedules |
| Process equipment | Maintenance, calibration |
| Supporting services | Utilities, IT systems |
| Information systems | Backup, security, validation |
### 6.4 Work Environment and Contamination Control
| Environment Factor | Control Method | Monitoring |
|-------------------|----------------|------------|
| Temperature | HVAC control | Continuous logging |
| Humidity | HVAC control | Continuous logging |
| Cleanliness | Cleaning procedures | Particle counts |
| Lighting | Lux levels | Periodic verification |
| ESD protection | Grounding, ionization | Periodic testing |
---
## Clause 7: Product Realization
### 7.1 Planning of Product Realization
| Planning Element | Content |
|-----------------|---------|
| Quality objectives for product | Product-specific quality targets |
| Processes and documentation | Process flow, required documents |
| Verification and validation | Test methods, acceptance criteria |
| Records | Required quality records |
| Risk management | Per ISO 14971 |
### 7.2 Customer-Related Processes
#### 7.2.1 Determination of Requirements
| Requirement Type | Source |
|-----------------|--------|
| Customer-specified | Contract, purchase order |
| Not stated but necessary | Intended use analysis |
| Regulatory | Applicable standards, regulations |
| Organization-defined | Internal specifications |
#### 7.2.2 Review of Requirements
| Review Element | Verification |
|----------------|--------------|
| Requirements defined | Complete specification |
| Differences resolved | Documented resolution |
| Ability to meet | Feasibility assessment |
| Risk management | Initial risk assessment |
#### 7.2.3 Communication
| Communication Type | Method |
|-------------------|--------|
| Product information | Catalogs, IFU |
| Inquiries and orders | Sales process |
| Feedback and complaints | Customer feedback system |
| Advisory notices | Field safety notices |
### 7.3 Design and Development
| Stage | Clause | Requirements |
|-------|--------|--------------|
| Planning | 7.3.2 | Stages, reviews, responsibilities |
| Inputs | 7.3.3 | Functional, performance, regulatory |
| Outputs | 7.3.4 | Meet inputs, acceptance criteria |
| Review | 7.3.5 | Evaluate ability to meet requirements |
| Verification | 7.3.6 | Outputs meet inputs |
| Validation | 7.3.7 | Product meets intended use |
| Transfer | 7.3.8 | Verified before production |
| Changes | 7.3.9 | Controlled, reviewed, verified |
### 7.4 Purchasing
#### 7.4.1 Purchasing Process
| Control Element | Implementation |
|-----------------|----------------|
| Supplier evaluation | Qualification procedure |
| Selection criteria | Quality, delivery, cost |
| Monitoring | Performance metrics |
| Re-evaluation | Periodic review |
**Supplier Classification:**
```
Category A: Critical - Affects product safety/performance
- Full qualification audit
- Annual performance review
- Quality agreement required
Category B: Major - Affects product quality
- Qualification questionnaire
- Periodic performance review
- Quality requirements communicated
Category C: Minor - Indirect impact
- Initial assessment
- Issue-based review
- Standard terms
```
#### 7.4.2 Purchasing Information
| Information Required | Purpose |
|---------------------|---------|
| Product specifications | Clear requirements |
| QMS requirements | Supplier system expectations |
| Personnel competence | Where applicable |
| Approval requirements | Where applicable |
#### 7.4.3 Verification of Purchased Product
| Verification Method | Application |
|--------------------|-------------|
| Incoming inspection | Standard verification |
| Source inspection | Critical items |
| Certificate of Conformance | Documented evidence |
| Certificate of Analysis | Material verification |
### 7.5 Production and Service Provision
#### 7.5.1 Control of Production and Service Provision
| Control Element | Implementation |
|-----------------|----------------|
| Product information | Specifications, drawings |
| Work instructions | Where necessary |
| Suitable equipment | Qualified equipment |
| Monitoring devices | Calibrated instruments |
| Implementation of monitoring | Inspections, tests |
| Defined processes | Process parameters |
| Labeling and packaging | Per requirements |
#### 7.5.2 Cleanliness of Product
| Cleanliness Control | Method |
|--------------------|--------|
| Product cleaning | Validated procedures |
| Contamination prevention | Controlled environment |
| Process aids | Qualified, controlled |
#### 7.5.3 Installation Activities
| Requirement | Implementation |
|-------------|----------------|
| Installation requirements | Documented instructions |
| Acceptance criteria | Defined criteria |
| Records | Installation records |
#### 7.5.4 Servicing Activities
| Requirement | Implementation |
|-------------|----------------|
| Documented requirements | Service procedures |
| Reference materials | Service manuals |
| Measurement equipment | Calibrated |
| Records | Service records |
#### 7.5.5 Particular Requirements for Sterile Medical Devices
| Process | Control |
|---------|---------|
| Sterilization validation | Per ISO 11135/11137/17665 |
| Parameter control | Monitoring records |
| Sterile barrier | Validated packaging |
#### 7.5.6 Validation of Processes
| Validation Required When | Evidence |
|-------------------------|----------|
| Output cannot be verified | Validation protocol and report |
| Deficiencies appear only in use | Process capability data |
| Special processes | Qualified operators |
**Process Validation Elements:**
- Equipment qualification (IQ/OQ/PQ)
- Process parameters
- Monitoring methods
- Operator qualification
- Revalidation criteria
#### 7.5.7 Particular Requirements for Validation
| Requirement | Implementation |
|-------------|----------------|
| Documented procedures | Validation SOPs |
| Defined methods | Statistical methods |
| Acceptance criteria | Predefined criteria |
| Software validation | Where applicable |
| Revalidation | Change-triggered |
#### 7.5.8 Identification
| Identification Type | Method |
|--------------------|--------|
| Product | Labels, markings |
| Documentation | Document numbers |
| Unique Device Identification | UDI per regulation |
#### 7.5.9 Traceability
| Traceability Element | Record |
|---------------------|--------|
| Components | Lot/batch numbers |
| Materials | Certificates |
| Work environment | Environmental records |
| Measurement equipment | Calibration records |
| Personnel | Training records |
| Distribution | Shipping records |
#### 7.5.10 Customer Property
| Control | Implementation |
|---------|----------------|
| Identification | Marking, segregation |
| Verification | Incoming inspection |
| Protection | Storage conditions |
| Safeguarding | Security measures |
| Reporting | Loss/damage notification |
#### 7.5.11 Preservation of Product
| Preservation Element | Control |
|---------------------|---------|
| Identification | Labels, markings |
| Handling | Procedures |
| Packaging | Specifications |
| Storage | Conditions, FIFO |
| Protection | Environmental controls |
### 7.6 Control of Monitoring and Measuring Equipment
| Control Element | Implementation |
|-----------------|----------------|
| Calibration | At specified intervals |
| Adjustment | As needed |
| Identification | Calibration status |
| Safeguarding | Protection from damage |
| Software validation | Where applicable |
| Records | Calibration records |
---
## Clause 8: Measurement, Analysis and Improvement
### 8.1 General
**Monitoring and Measurement Requirements:**
- Demonstrate product conformity
- Ensure QMS conformity
- Maintain QMS effectiveness
### 8.2 Monitoring and Measurement
#### 8.2.1 Feedback
| Feedback Source | Collection Method |
|-----------------|-------------------|
| Customer complaints | Complaint system |
| Customer surveys | Periodic surveys |
| Field feedback | Service reports |
| Regulatory feedback | Inspection findings |
#### 8.2.2 Complaint Handling
| Process Step | Requirements |
|--------------|--------------|
| Receipt | Timely logging |
| Investigation | Root cause analysis |
| Corrective action | If warranted |
| Regulatory reporting | If required |
| Trend analysis | Aggregate review |
#### 8.2.3 Reporting to Regulatory Authorities
| Report Type | Trigger | Timeline |
|-------------|---------|----------|
| MDR (Medical Device Report) | Death/serious injury | 30 days (5 if awareness) |
| FSCA (Field Safety Corrective Action) | Safety issue | Without delay |
| Periodic Safety Update | Per regulation | Per schedule |
#### 8.2.4 Internal Audit
| Audit Element | Requirement |
|---------------|-------------|
| Planned program | Risk-based schedule |
| Criteria and scope | Defined per audit |
| Auditor selection | Independent, competent |
| Procedure | Documented process |
| Records | Audit reports, findings |
| Follow-up | CAPA, verification |
**Audit Program Template:**
```
ANNUAL INTERNAL AUDIT PROGRAM
Year: [Year]
| Audit # | Area/Process | Scope | Auditor | Planned Date | Status |
|---------|--------------|-------|---------|--------------|--------|
| IA-01 | Document Control | 4.2.3, 4.2.4 | [Name] | Q1 | |
| IA-02 | Design Control | 7.3 | [Name] | Q2 | |
| IA-03 | Production | 7.5 | [Name] | Q2 | |
| IA-04 | Purchasing | 7.4 | [Name] | Q3 | |
| IA-05 | CAPA | 8.5.2, 8.5.3 | [Name] | Q3 | |
| IA-06 | Management Review | 5.6 | [Name] | Q4 | |
Risk Considerations:
- Previous audit findings
- Regulatory changes
- Process changes
- Complaint trends
```
#### 8.2.5 Monitoring and Measurement of Processes
| Monitoring Type | Method |
|-----------------|--------|
| Process metrics | KPIs, trend analysis |
| Process audits | Internal audits |
| Process reviews | Management review |
#### 8.2.6 Monitoring and Measurement of Product
| Stage | Verification |
|-------|--------------|
| Incoming | Incoming inspection |
| In-process | In-process inspection |
| Final | Final inspection and test |
| Release | Authorized release |
### 8.3 Control of Nonconforming Product
| Control Element | Requirement |
|-----------------|-------------|
| Identification | Clear marking |
| Segregation | Physical separation |
| Documentation | NC record |
| Disposition | Use as is/rework/scrap/return |
| Concession | If accepted |
| Reinspection | After rework |
| Investigation | For detected after delivery |
**Nonconformity Disposition Options:**
```
1. Use As Is (Concession)
- Does not affect safety/performance
- Customer approval if applicable
- Documented justification
2. Rework
- Per approved procedure
- Reinspection required
- Records maintained
3. Scrap/Reject
- Physical destruction or marking
- Prevented from reentry
- Documented disposal
4. Return to Supplier
- Communication with supplier
- Replacement or credit
- Root cause if systemic
```
### 8.4 Analysis of Data
| Data Source | Analysis |
|-------------|----------|
| Feedback | Complaint trends, satisfaction |
| Nonconformity | Defect Pareto, trends |
| Process performance | Capability, trends |
| Supplier | Performance trends |
| Audit | Finding trends |
### 8.5 Improvement
#### 8.5.1 General
**Improvement Sources:**
- Quality policy
- Quality objectives
- Audit results
- Data analysis
- Corrective actions
- Preventive actions
- Management review
#### 8.5.2 Corrective Action
| Process Step | Requirement |
|--------------|-------------|
| Review nonconformity | Including complaints |
| Determine cause | Root cause analysis |
| Evaluate action need | Based on risk |
| Determine action | Proportionate to risk |
| Implement action | Execute plan |
| Document results | Records |
| Review effectiveness | Verification |
#### 8.5.3 Preventive Action
| Process Step | Requirement |
|--------------|-------------|
| Determine potential NC | Risk analysis, trends |
| Evaluate action need | Prevention opportunity |
| Determine action | Proportionate to risk |
| Implement action | Execute plan |
| Document results | Records |
| Review effectiveness | Verification |
FILE:references/qms-process-templates.md
# QMS Process Templates
Ready-to-use templates for ISO 13485 QMS processes including document control, internal audit, CAPA, and supplier management.
---
## Table of Contents
- [Document Control Templates](#document-control-templates)
- [Internal Audit Templates](#internal-audit-templates)
- [CAPA Templates](#capa-templates)
- [Supplier Management Templates](#supplier-management-templates)
- [Training Templates](#training-templates)
- [Nonconformity Templates](#nonconformity-templates)
---
## Document Control Templates
### Document Master List
```
DOCUMENT MASTER LIST
Organization: [Company Name]
Last Updated: [Date]
Maintained By: Document Control
| Doc # | Title | Rev | Effective Date | Status | Owner | Next Review |
|-------|-------|-----|----------------|--------|-------|-------------|
| QM-001 | Quality Manual | 03 | 2024-01-15 | Effective | QMR | 2025-01-15 |
| SOP-01-001 | Document Control | 04 | 2024-03-01 | Effective | QA Mgr | 2025-03-01 |
| SOP-01-002 | Record Control | 02 | 2024-02-01 | Effective | QA Mgr | 2025-02-01 |
| | | | | | | |
Status Values: Draft, Under Review, Effective, Obsolete
```
### Document Change Request
```
DOCUMENT CHANGE REQUEST
DCR Number: DCR-[YYYY]-[NNN]
Date Submitted: [Date]
Submitted By: [Name]
DOCUMENT INFORMATION
Document Number: [Number]
Document Title: [Title]
Current Revision: [Rev]
CHANGE REQUEST
Change Type: [ ] Administrative [ ] Minor [ ] Major [ ] Emergency
Requested Change: [Description of change]
Reason for Change:
[ ] Regulatory requirement
[ ] Process improvement
[ ] Nonconformity/CAPA
[ ] Organizational change
[ ] Error correction
[ ] Other: [Specify]
Justification: [Detailed justification]
IMPACT ASSESSMENT
Training Required: [ ] Yes [ ] No
If yes, who: [Roles/departments]
Other Documents Affected: [List]
Regulatory Filing Impact: [ ] Yes [ ] No
If yes, details: [Explain]
APPROVALS
Requested By: _________________ Date: _______
Document Owner: _________________ Date: _______
QA Approval: _________________ Date: _______
COMPLETION
New Revision: [Rev]
Effective Date: [Date]
Training Completed: [ ] Yes [ ] N/A
Distribution Completed: [ ] Yes
```
### Document Review Record
```
DOCUMENT REVIEW RECORD
Document Number: [Number]
Document Title: [Title]
Current Revision: [Rev]
Review Due Date: [Date]
Review Completed: [Date]
REVIEWERS
| Reviewer | Role | Review Date | Comments | Signature |
|----------|------|-------------|----------|-----------|
| [Name] | [Role] | [Date] | [Comments] | |
| [Name] | [Role] | [Date] | [Comments] | |
REVIEW OUTCOME
[ ] No changes required - document remains current
[ ] Minor changes required - see attached DCR
[ ] Major revision required - see attached DCR
[ ] Document obsolete - initiate retirement
NEXT REVIEW
Next Review Date: [Date]
APPROVAL
Review Completed By: _________________ Date: _______
Approved By: _________________ Date: _______
```
---
## Internal Audit Templates
### Annual Audit Schedule
```
INTERNAL AUDIT SCHEDULE
Year: [Year]
Prepared By: [Name]
Approved By: [Name]
Date: [Date]
AUDIT SCHEDULE
| Audit # | Process/Area | ISO Clauses | Lead Auditor | Q1 | Q2 | Q3 | Q4 |
|---------|--------------|-------------|--------------|----|----|----|----|
| IA-001 | Document Control | 4.2.3, 4.2.4 | [Name] | X | | | |
| IA-002 | Management Review | 5.6 | [Name] | | X | | |
| IA-003 | Training | 6.2 | [Name] | | X | | |
| IA-004 | Design Control | 7.3 | [Name] | | | X | |
| IA-005 | Purchasing | 7.4 | [Name] | | | X | |
| IA-006 | Production | 7.5 | [Name] | | | | X |
| IA-007 | CAPA | 8.5.2, 8.5.3 | [Name] | | | | X |
RISK FACTORS CONSIDERED
[ ] Previous audit findings
[ ] Regulatory changes
[ ] Process changes
[ ] Complaint trends
[ ] Management concerns
SCHEDULE REVISION LOG
| Rev | Date | Change | Approved By |
|-----|------|--------|-------------|
| 00 | [Date] | Initial release | [Name] |
```
### Audit Plan
```
INTERNAL AUDIT PLAN
Audit Number: IA-[YYYY]-[NNN]
Audit Date(s): [Date(s)]
Audit Type: [ ] Process [ ] System [ ] Product
SCOPE
Process/Area: [Name]
ISO 13485 Clauses: [List]
Regulatory Requirements: [If applicable]
Locations: [Locations]
AUDIT TEAM
Lead Auditor: [Name]
Auditor(s): [Names]
Observer(s): [If any]
AUDITEE CONTACTS
Process Owner: [Name]
Other Contacts: [Names]
AUDIT CRITERIA
- ISO 13485:2016
- [Organization procedures]
- [Regulatory requirements]
AUDIT SCHEDULE
| Time | Activity | Participants |
|------|----------|--------------|
| 09:00 | Opening meeting | All |
| 09:30 | Document review | Auditor, Doc Control |
| 10:30 | Process observation | Auditor, Operators |
| 12:00 | Lunch | |
| 13:00 | Record review | Auditor, QA |
| 14:30 | Interviews | Selected personnel |
| 15:30 | Auditor caucus | Audit team |
| 16:00 | Closing meeting | All |
PREPARATION CHECKLIST
[ ] Previous audit reports reviewed
[ ] Procedures reviewed
[ ] Checklist prepared
[ ] Auditees notified
[ ] Resources arranged
```
### Audit Checklist Template
```
INTERNAL AUDIT CHECKLIST
Audit Number: IA-[YYYY]-[NNN]
Process: [Process Name]
Auditor: [Name]
Date: [Date]
INSTRUCTIONS
C = Conforming, NC = Nonconforming, OBS = Observation, N/A = Not Applicable
CHECKLIST
| # | Requirement | Reference | Evidence Reviewed | Finding | Notes |
|---|-------------|-----------|-------------------|---------|-------|
| 1 | Is the procedure current and approved? | 4.2.3 | [Evidence] | C/NC/OBS | |
| 2 | Are personnel trained on the procedure? | 6.2 | [Evidence] | C/NC/OBS | |
| 3 | Are records maintained as required? | 4.2.4 | [Evidence] | C/NC/OBS | |
| 4 | Is the process performed as documented? | 4.1 | [Evidence] | C/NC/OBS | |
| 5 | Are monitoring activities performed? | 8.2.5 | [Evidence] | C/NC/OBS | |
INTERVIEWS CONDUCTED
| Person | Role | Topics Discussed |
|--------|------|------------------|
| [Name] | [Role] | [Topics] |
DOCUMENTS REVIEWED
| Document # | Title | Rev | Findings |
|------------|-------|-----|----------|
| [Number] | [Title] | [Rev] | [Findings] |
RECORDS SAMPLED
| Record Type | Sample Size | Sample IDs | Findings |
|-------------|-------------|------------|----------|
| [Type] | [N] | [IDs] | [Findings] |
AUDITOR SIGNATURE: _________________ Date: _______
```
### Audit Report
```
INTERNAL AUDIT REPORT
Audit Number: IA-[YYYY]-[NNN]
Report Date: [Date]
Report Status: [ ] Draft [ ] Final
AUDIT SUMMARY
Audit Date(s): [Date(s)]
Process/Area: [Name]
ISO Clauses Covered: [List]
Lead Auditor: [Name]
Audit Team: [Names]
AUDIT SCOPE
[Description of scope]
AUDIT OBJECTIVES
[List objectives]
EXECUTIVE SUMMARY
[Brief summary of audit results]
FINDINGS SUMMARY
| Type | Count |
|------|-------|
| Major Nonconformity | [N] |
| Minor Nonconformity | [N] |
| Observation | [N] |
| Opportunity for Improvement | [N] |
DETAILED FINDINGS
FINDING 1
Number: IA-[YYYY]-[NNN]-F01
Classification: [ ] Major NC [ ] Minor NC [ ] Observation [ ] OFI
Requirement: [Clause/requirement reference]
Statement: [Objective description of finding]
Evidence: [Evidence supporting finding]
Auditee Response Due: [Date]
[Repeat for each finding]
POSITIVE OBSERVATIONS
[List areas of good practice observed]
CONCLUSION
[Overall conclusion on process effectiveness]
REPORT DISTRIBUTION
| Name | Role | Date |
|------|------|------|
| [Name] | Process Owner | [Date] |
| [Name] | QA Manager | [Date] |
| [Name] | Management Rep | [Date] |
APPROVALS
Lead Auditor: _________________ Date: _______
QA Manager: _________________ Date: _______
```
---
## CAPA Templates
### CAPA Request Form
```
CORRECTIVE AND PREVENTIVE ACTION REQUEST
CAPA Number: CAPA-[YYYY]-[NNN]
Date Opened: [Date]
Initiated By: [Name]
CAPA TYPE
[ ] Corrective Action (response to existing nonconformity)
[ ] Preventive Action (prevent potential nonconformity)
SOURCE
[ ] Customer complaint: Reference #_______
[ ] Internal audit: Audit #_______
[ ] External audit: Audit #_______
[ ] Nonconformity: NC #_______
[ ] Process deviation
[ ] Management review action
[ ] Trend analysis
[ ] Risk assessment
[ ] Other: _______
CLASSIFICATION
Severity: [ ] Critical [ ] Major [ ] Minor
Regulatory Reportable: [ ] Yes [ ] No
PROBLEM DESCRIPTION
[Detailed description of the problem or potential problem]
IMMEDIATE CONTAINMENT (if applicable)
Actions Taken: [Description]
Date: [Date]
Responsible: [Name]
ASSIGNMENT
Process Owner: [Name]
CAPA Owner: [Name]
Due Date for Root Cause: [Date]
Target Closure Date: [Date]
APPROVAL TO PROCEED
Approved By: _________________ Date: _______
```
### Root Cause Analysis Record
```
ROOT CAUSE ANALYSIS
CAPA Number: CAPA-[YYYY]-[NNN]
Analysis Date: [Date]
Analyst: [Name]
PROBLEM STATEMENT
[Clear, specific statement of the problem]
INVESTIGATION TEAM
| Name | Role | Contribution |
|------|------|--------------|
| [Name] | [Role] | [Area of expertise] |
INVESTIGATION METHOD
[ ] 5 Why Analysis
[ ] Fishbone Diagram
[ ] Fault Tree Analysis
[ ] Human Factors Analysis
[ ] Other: _______
INVESTIGATION DETAILS
5 WHY ANALYSIS
Why 1: [First why]
Answer: [Answer]
Why 2: [Second why based on answer]
Answer: [Answer]
Why 3: [Third why based on answer]
Answer: [Answer]
Why 4: [Fourth why based on answer]
Answer: [Answer]
Why 5: [Fifth why based on answer]
Answer: [Answer]
ROOT CAUSE STATEMENT
[Clear statement of identified root cause]
ROOT CAUSE CATEGORY
[ ] Process/Procedure
[ ] Training/Competency
[ ] Equipment/Material
[ ] Design
[ ] Human Error
[ ] Communication
[ ] Management System
[ ] External Factor
CONTRIBUTING FACTORS
[List any contributing factors]
EVIDENCE SUPPORTING ROOT CAUSE
[List evidence]
APPROVAL
Analysis By: _________________ Date: _______
Reviewed By: _________________ Date: _______
```
### CAPA Action Plan
```
CAPA ACTION PLAN
CAPA Number: CAPA-[YYYY]-[NNN]
Root Cause: [Brief statement]
Plan Date: [Date]
Plan Owner: [Name]
CORRECTIVE/PREVENTIVE ACTIONS
Action 1:
Description: [Detailed action description]
Responsible: [Name]
Due Date: [Date]
Resources Required: [Resources]
Success Criteria: [How completion verified]
Action 2:
Description: [Detailed action description]
Responsible: [Name]
Due Date: [Date]
Resources Required: [Resources]
Success Criteria: [How completion verified]
[Continue for additional actions]
RELATED CHANGES
Documents Affected: [List]
Training Required: [Description]
Process Changes: [Description]
Equipment Changes: [Description]
RISK ASSESSMENT
Residual Risk After Implementation: [ ] High [ ] Medium [ ] Low
Justification: [Explanation]
APPROVAL
Plan Developed By: _________________ Date: _______
Approved By: _________________ Date: _______
```
### CAPA Effectiveness Verification
```
CAPA EFFECTIVENESS VERIFICATION
CAPA Number: CAPA-[YYYY]-[NNN]
Verification Date: [Date]
Verified By: [Name]
ACTIONS COMPLETED
| Action | Completion Date | Evidence |
|--------|-----------------|----------|
| [Action 1] | [Date] | [Reference] |
| [Action 2] | [Date] | [Reference] |
EFFECTIVENESS CRITERIA
[Criteria established during action planning]
VERIFICATION METHOD
[ ] Data analysis (trends, metrics)
[ ] Process audit
[ ] Record review
[ ] Product inspection
[ ] Customer feedback review
[ ] Other: _______
VERIFICATION PERIOD
From: [Date] To: [Date]
VERIFICATION RESULTS
[Detailed results of verification activities]
DATA/EVIDENCE REVIEWED
| Data Type | Period | Result |
|-----------|--------|--------|
| [Type] | [Period] | [Result] |
EFFECTIVENESS CONCLUSION
[ ] Effective - Root cause eliminated, problem resolved
[ ] Partially Effective - Improvement noted, additional action needed
[ ] Not Effective - Problem persists, reopen CAPA
If not effective, describe additional actions:
[Description]
CAPA CLOSURE
[ ] Approved for closure
[ ] Not approved - additional action required
Verified By: _________________ Date: _______
Approved By: _________________ Date: _______
```
---
## Supplier Management Templates
### Approved Supplier List
```
APPROVED SUPPLIER LIST
Organization: [Company Name]
Last Updated: [Date]
Maintained By: [Name]
| Supplier | Supplier # | Category | Products/Services | Status | Qualification Date | Next Review |
|----------|-----------|----------|-------------------|--------|-------------------|-------------|
| [Name] | SUP-001 | A | [Products] | Approved | [Date] | [Date] |
| [Name] | SUP-002 | B | [Products] | Conditional | [Date] | [Date] |
Category:
A = Critical (affects safety/performance)
B = Major (affects quality)
C = Minor (indirect impact)
Status:
Approved = Full use authorized
Conditional = Limited use, monitoring
Probation = Performance issues, enhanced monitoring
Disqualified = Use not authorized
Revision History:
| Rev | Date | Change | Approved By |
|-----|------|--------|-------------|
| 01 | [Date] | Initial release | [Name] |
```
### Supplier Evaluation Form
```
SUPPLIER EVALUATION
Supplier Name: [Name]
Supplier Number: [Number]
Evaluation Date: [Date]
Evaluated By: [Name]
Evaluation Type: [ ] Initial [ ] Periodic [ ] For Cause
SUPPLIER INFORMATION
Address: [Address]
Contact: [Name, Title]
Phone: [Phone]
Email: [Email]
Products/Services: [Description]
PROPOSED CATEGORY
[ ] A - Critical (affects safety/performance)
[ ] B - Major (affects quality)
[ ] C - Minor (indirect impact)
EVALUATION CRITERIA
1. QUALITY MANAGEMENT SYSTEM (30 points max)
[ ] ISO 13485 Certified (30 pts)
[ ] ISO 9001 Certified (20 pts)
[ ] Documented QMS (10 pts)
[ ] No formal QMS (0 pts)
Score: ___/30
2. QUALITY HISTORY (25 points max)
Reject Rate: ___% (0-1% = 25 pts, 1-3% = 15 pts, >3% = 0 pts)
Score: ___/25
3. DELIVERY PERFORMANCE (20 points max)
On-Time Delivery: ___% (>95% = 20 pts, 90-95% = 10 pts, <90% = 0 pts)
Score: ___/20
4. TECHNICAL CAPABILITY (15 points max)
[ ] Exceeds requirements (15 pts)
[ ] Meets requirements (10 pts)
[ ] Marginally meets (5 pts)
Score: ___/15
5. FINANCIAL STABILITY (10 points max)
[ ] Strong (10 pts)
[ ] Adequate (5 pts)
[ ] Questionable (0 pts)
Score: ___/10
TOTAL SCORE: ___/100
QUALIFICATION DECISION
>80 = Approved
60-80 = Conditional (monitoring required)
<60 = Not Approved
Decision: [ ] Approved [ ] Conditional [ ] Not Approved
APPROVAL
Evaluated By: _________________ Date: _______
QA Approval: _________________ Date: _______
```
### Supplier Performance Scorecard
```
SUPPLIER PERFORMANCE SCORECARD
Supplier: [Name]
Supplier #: [Number]
Period: [Q1/Q2/Q3/Q4] [Year]
Prepared By: [Name]
PERFORMANCE METRICS
1. QUALITY (40% weight)
Total Lots Received: [N]
Lots Rejected: [N]
Accept Rate: ___% Target: >98%
Score: ___/40
2. DELIVERY (30% weight)
Total Orders: [N]
On-Time Deliveries: [N]
On-Time Rate: ___% Target: >95%
Score: ___/30
3. RESPONSIVENESS (15% weight)
Issues Reported: [N]
Resolved <5 days: [N]
Response Rate: ___% Target: >90%
Score: ___/15
4. DOCUMENTATION (15% weight)
CoC Required: [N]
CoC Complete: [N]
Documentation Rate: ___% Target: 100%
Score: ___/15
TOTAL SCORE: ___/100
PERFORMANCE TREND
| Period | Quality | Delivery | Response | Docs | Total |
|--------|---------|----------|----------|------|-------|
| Q1 | | | | | |
| Q2 | | | | | |
| Q3 | | | | | |
| Q4 | | | | | |
ISSUES/CONCERNS
[List any quality or delivery issues during period]
ACTIONS REQUIRED
[ ] None - Performance acceptable
[ ] Enhanced monitoring
[ ] Supplier corrective action request
[ ] Supplier audit
[ ] Consider alternative supplier
NEXT REVIEW: [Date]
Prepared By: _________________ Date: _______
Reviewed By: _________________ Date: _______
```
---
## Training Templates
### Training Record
```
EMPLOYEE TRAINING RECORD
Employee Name: [Name]
Employee ID: [ID]
Department: [Department]
Job Title: [Title]
Date of Hire: [Date]
REQUIRED TRAINING
| Training | Requirement | Initial Date | Last Date | Next Due | Status |
|----------|-------------|--------------|-----------|----------|--------|
| ISO 13485 Awareness | Initial + Annual | [Date] | [Date] | [Date] | Current |
| Document Control | Initial + On Change | [Date] | [Date] | [Date] | Current |
| CAPA Procedure | Initial + On Change | [Date] | [Date] | [Date] | Due |
| Job-Specific | Per competency matrix | [Date] | [Date] | [Date] | Current |
TRAINING HISTORY
| Date | Training | Method | Duration | Trainer | Assessment | Result |
|------|----------|--------|----------|---------|------------|--------|
| [Date] | [Title] | Classroom | 2 hrs | [Name] | Written test | Pass |
| [Date] | [Title] | OJT | 4 hrs | [Name] | Observation | Pass |
COMPETENCY VERIFICATION
| Competency | Method | Date | Verified By | Result |
|------------|--------|------|-------------|--------|
| [Skill] | Observation | [Date] | [Name] | Qualified |
| [Skill] | Test | [Date] | [Name] | Qualified |
Employee Signature: _________________ Date: _______
Supervisor Signature: _________________ Date: _______
```
### Training Attendance Record
```
TRAINING ATTENDANCE RECORD
Training Title: [Title]
Training Date: [Date]
Trainer: [Name]
Location: [Location]
Duration: [Hours]
TRAINING CONTENT
[Brief description of content covered]
ATTENDEES
| Name | Employee ID | Department | Signature | Assessment Result |
|------|-------------|------------|-----------|-------------------|
| [Name] | [ID] | [Dept] | | Pass/Fail |
| [Name] | [ID] | [Dept] | | Pass/Fail |
ASSESSMENT METHOD
[ ] Written test (attach copy)
[ ] Practical demonstration
[ ] Verbal Q&A
[ ] Observation
[ ] N/A
TRAINING MATERIALS
[ ] Presentation: [Reference]
[ ] Procedure: [Reference]
[ ] Other: [Reference]
Trainer Signature: _________________ Date: _______
Training Coordinator: _________________ Date: _______
```
---
## Nonconformity Templates
### Nonconformity Report
```
NONCONFORMITY REPORT
NC Number: NC-[YYYY]-[NNN]
Date Identified: [Date]
Identified By: [Name]
NONCONFORMITY TYPE
[ ] Product [ ] Process [ ] Document [ ] System
NONCONFORMITY SOURCE
[ ] Incoming inspection
[ ] In-process inspection
[ ] Final inspection
[ ] Customer complaint
[ ] Internal audit
[ ] External audit
[ ] Other: _______
PRODUCT IDENTIFICATION (if applicable)
Product Name: [Name]
Part Number: [Number]
Lot/Batch: [Number]
Quantity Affected: [N]
NONCONFORMITY DESCRIPTION
[Detailed, objective description of the nonconformity]
REQUIREMENT
[Reference to requirement that was not met]
CONTAINMENT ACTION
Action Taken: [Description]
Quantity Contained: [N]
Location: [Location]
Date: [Date]
By: [Name]
DISPOSITION
[ ] Use As Is - Justification: _______
[ ] Rework - Per procedure: _______
[ ] Scrap - Method: _______
[ ] Return to Supplier - RMA #: _______
[ ] Other: _______
Disposition By: [Name]
Disposition Date: [Date]
CAPA REQUIRED?
[ ] Yes - CAPA #: _______
[ ] No - Justification: _______
CLOSURE
All actions complete: [ ] Yes
NC Closed By: _________________ Date: _______
QA Approval: _________________ Date: _______
```
### Material Review Board Record
```
MATERIAL REVIEW BOARD (MRB) RECORD
MRB Number: MRB-[YYYY]-[NNN]
Date: [Date]
NC Reference: NC-[YYYY]-[NNN]
NONCONFORMING MATERIAL
Product: [Name]
Part Number: [Number]
Lot/Batch: [Number]
Quantity: [N]
NONCONFORMITY DESCRIPTION
[Description from NC report]
MRB PARTICIPANTS
| Name | Role | Signature |
|------|------|-----------|
| [Name] | QA Representative | |
| [Name] | Engineering | |
| [Name] | Production | |
| [Name] | Other | |
DISPOSITION OPTIONS CONSIDERED
1. Use As Is
Technical Justification: [Justification]
Risk Assessment: [Assessment]
2. Rework
Procedure: [Reference]
Feasibility: [Assessment]
3. Scrap
Cost Impact: [Amount]
MRB DECISION
[ ] Use As Is - Customer notification required: [ ] Yes [ ] No
[ ] Rework per: [Procedure reference]
[ ] Scrap
[ ] Return to Supplier
RATIONALE
[Detailed rationale for decision]
APPROVALS
| Role | Name | Signature | Date |
|------|------|-----------|------|
| QA | [Name] | | [Date] |
| Engineering | [Name] | | [Date] |
| Production | [Name] | | [Date] |
FOLLOW-UP ACTIONS
[ ] CAPA initiated: CAPA-_______
[ ] Customer notified: Date: _______
[ ] Supplier notified: Date: _______
[ ] Other: _______
```
FILE:scripts/qms_audit_checklist.py
#!/usr/bin/env python3
"""
QMS Internal Audit Checklist Generator
Generates audit checklists for ISO 13485:2016 clauses and QMS processes.
Supports process audits, system audits, and clause-specific audits.
Usage:
python qms_audit_checklist.py --clause 7.3
python qms_audit_checklist.py --process design-control
python qms_audit_checklist.py --audit-type system --output json
python qms_audit_checklist.py --interactive
"""
import argparse
import json
import sys
from datetime import datetime
from typing import Optional
# ISO 13485:2016 Clause Structure with Audit Questions
ISO13485_CLAUSES = {
"4.1": {
"title": "General Requirements",
"questions": [
"Are QMS processes identified and documented?",
"Is the sequence and interaction of processes defined?",
"Are criteria and methods for process operation determined?",
"Are resources and information available for process operation?",
"Are processes monitored, measured, and analyzed?",
"Are actions taken to achieve planned results?",
"Is outsourced process control documented?",
"Are changes to processes managed?"
]
},
"4.2.1": {
"title": "Documentation Requirements - General",
"questions": [
"Is a quality policy documented?",
"Are quality objectives documented?",
"Is a quality manual maintained?",
"Are required documented procedures established?",
"Are documents needed for process planning and operation maintained?",
"Are required records maintained?",
"Is a medical device file established for each device type?"
]
},
"4.2.2": {
"title": "Quality Manual",
"questions": [
"Does the quality manual include QMS scope?",
"Are exclusions justified?",
"Are documented procedures included or referenced?",
"Is the interaction between processes described?",
"Is the quality manual controlled?"
]
},
"4.2.3": {
"title": "Control of Documents",
"questions": [
"Are documents approved before issue?",
"Are documents reviewed and updated as necessary?",
"Are changes and revision status identified?",
"Are current versions available at points of use?",
"Are documents legible and identifiable?",
"Are external documents identified and controlled?",
"Is unintended use of obsolete documents prevented?",
"Is there a document change control process?"
]
},
"4.2.4": {
"title": "Control of Records",
"questions": [
"Is there a procedure for record control?",
"Are records legible and identifiable?",
"Are records retrievable?",
"Are retention times defined?",
"Is protection from damage ensured?",
"Are confidential records protected?",
"Is record disposal controlled?"
]
},
"5.1": {
"title": "Management Commitment",
"questions": [
"Is there evidence of management commitment to QMS?",
"Is the importance of regulatory requirements communicated?",
"Is a quality policy established?",
"Are quality objectives established?",
"Are management reviews conducted?",
"Are resources provided for QMS?"
]
},
"5.2": {
"title": "Customer Focus",
"questions": [
"Are customer requirements determined?",
"Are applicable regulatory requirements determined?",
"Are customer and regulatory requirements met?",
"Is customer satisfaction enhanced?"
]
},
"5.3": {
"title": "Quality Policy",
"questions": [
"Is the quality policy appropriate to the organization?",
"Does it include commitment to compliance?",
"Does it include commitment to effectiveness?",
"Does it provide framework for quality objectives?",
"Is it communicated and understood?",
"Is it reviewed for continuing suitability?"
]
},
"5.4.1": {
"title": "Quality Objectives",
"questions": [
"Are quality objectives measurable?",
"Are they consistent with quality policy?",
"Are they established at relevant functions?",
"Do they include product requirements?",
"Do they include compliance requirements?"
]
},
"5.4.2": {
"title": "QMS Planning",
"questions": [
"Is QMS planning carried out to meet requirements?",
"Is QMS planning done to meet quality objectives?",
"Is QMS integrity maintained during changes?"
]
},
"5.5.1": {
"title": "Responsibility and Authority",
"questions": [
"Are responsibilities and authorities defined?",
"Are they documented?",
"Are they communicated?",
"Are interrelationships defined?"
]
},
"5.5.2": {
"title": "Management Representative",
"questions": [
"Is a management representative appointed?",
"Is authority to ensure QMS processes established?",
"Is authority to report to top management defined?",
"Is authority to promote awareness of requirements defined?"
]
},
"5.5.3": {
"title": "Internal Communication",
"questions": [
"Are communication processes established?",
"Is QMS effectiveness communicated?",
"Is information communicated appropriately?"
]
},
"5.6": {
"title": "Management Review",
"questions": [
"Are management reviews planned?",
"Are all required inputs reviewed?",
"Are outputs documented?",
"Are action items followed up?",
"Are records maintained?"
]
},
"6.1": {
"title": "Provision of Resources",
"questions": [
"Are resources determined?",
"Are resources provided for QMS?",
"Are resources provided for customer satisfaction?",
"Are resources provided for regulatory compliance?"
]
},
"6.2": {
"title": "Human Resources",
"questions": [
"Is competence defined for personnel?",
"Is training provided to achieve competence?",
"Is training effectiveness evaluated?",
"Is awareness of job relevance ensured?",
"Are training records maintained?"
]
},
"6.3": {
"title": "Infrastructure",
"questions": [
"Is necessary infrastructure determined?",
"Are buildings and workspace adequate?",
"Is process equipment adequate?",
"Are supporting services adequate?",
"Are maintenance requirements documented?"
]
},
"6.4": {
"title": "Work Environment",
"questions": [
"Is work environment determined?",
"Are environmental requirements documented?",
"Is contamination control adequate?",
"Are personnel health and cleanliness controlled?",
"Are environmental conditions monitored?"
]
},
"7.1": {
"title": "Planning of Product Realization",
"questions": [
"Are quality objectives for product defined?",
"Are processes needed determined?",
"Is verification and validation defined?",
"Are records requirements defined?",
"Is risk management applied?"
]
},
"7.2": {
"title": "Customer-Related Processes",
"questions": [
"Are customer requirements determined?",
"Are regulatory requirements determined?",
"Are requirements reviewed before commitment?",
"Are differences resolved before acceptance?",
"Is communication with customers effective?"
]
},
"7.3.1": {
"title": "Design and Development Planning",
"questions": [
"Are design stages determined?",
"Are review activities defined?",
"Are verification activities defined?",
"Are validation activities defined?",
"Are responsibilities assigned?",
"Are interfaces managed?"
]
},
"7.3.2": {
"title": "Design and Development Inputs",
"questions": [
"Are functional requirements defined?",
"Are performance requirements defined?",
"Are safety requirements defined?",
"Are regulatory requirements identified?",
"Are previous design inputs considered?",
"Are risk management outputs included?"
]
},
"7.3.3": {
"title": "Design and Development Outputs",
"questions": [
"Do outputs meet input requirements?",
"Is purchasing information provided?",
"Are acceptance criteria defined?",
"Are essential characteristics specified?",
"Are outputs approved before release?"
]
},
"7.3.4": {
"title": "Design and Development Review",
"questions": [
"Are design reviews conducted at suitable stages?",
"Is ability to meet requirements evaluated?",
"Are problems identified?",
"Are follow-up actions recorded?",
"Are appropriate functions represented?"
]
},
"7.3.5": {
"title": "Design and Development Verification",
"questions": [
"Is verification performed per plan?",
"Do outputs meet inputs?",
"Are verification records maintained?",
"Are verification methods appropriate?"
]
},
"7.3.6": {
"title": "Design and Development Validation",
"questions": [
"Is validation performed per plan?",
"Is product evaluated for intended use?",
"Is clinical evaluation included?",
"Are validation records maintained?",
"Is validation completed before product delivery?"
]
},
"7.3.7": {
"title": "Design and Development Transfer",
"questions": [
"Are outputs verified before transfer?",
"Is manufacturing capability verified?",
"Are transfer activities documented?"
]
},
"7.3.8": {
"title": "Control of Design and Development Changes",
"questions": [
"Are design changes identified?",
"Are changes reviewed?",
"Are changes verified?",
"Are changes validated as appropriate?",
"Is impact on product assessed?",
"Are changes approved before implementation?"
]
},
"7.4.1": {
"title": "Purchasing Process",
"questions": [
"Are suppliers evaluated and selected?",
"Are evaluation criteria established?",
"Is supplier performance monitored?",
"Are re-evaluation criteria defined?",
"Is purchased product verified?"
]
},
"7.4.2": {
"title": "Purchasing Information",
"questions": [
"Is purchasing information adequate?",
"Are product requirements specified?",
"Are QMS requirements specified?",
"Are personnel requirements specified?"
]
},
"7.4.3": {
"title": "Verification of Purchased Product",
"questions": [
"Is incoming inspection adequate?",
"Are verification activities defined?",
"Are verification records maintained?",
"Is source verification defined if applicable?"
]
},
"7.5.1": {
"title": "Control of Production and Service Provision",
"questions": [
"Is product information available?",
"Are work instructions available?",
"Is suitable equipment used?",
"Are monitoring devices available?",
"Is monitoring implemented?",
"Are release activities defined?",
"Are labeling requirements met?"
]
},
"7.5.2": {
"title": "Cleanliness of Product",
"questions": [
"Are cleanliness requirements documented?",
"Is contamination controlled?",
"Are process agents controlled?"
]
},
"7.5.3": {
"title": "Installation Activities",
"questions": [
"Are installation requirements documented?",
"Are acceptance criteria defined?",
"Are installation records maintained?"
]
},
"7.5.4": {
"title": "Servicing Activities",
"questions": [
"Are servicing procedures documented?",
"Are reference materials controlled?",
"Are service records maintained?",
"Is feedback analyzed?"
]
},
"7.5.5": {
"title": "Sterile Medical Devices",
"questions": [
"Is sterilization validated?",
"Are process parameters controlled?",
"Is sterile barrier validated?",
"Are sterilization records maintained?"
]
},
"7.5.6": {
"title": "Validation of Processes",
"questions": [
"Are special processes identified?",
"Are validation procedures documented?",
"Is equipment qualified?",
"Are personnel qualified?",
"Are validation records maintained?",
"Are revalidation criteria defined?"
]
},
"7.5.7": {
"title": "Particular Requirements for Validation",
"questions": [
"Are validation methods defined?",
"Are acceptance criteria established?",
"Is software validation appropriate?",
"Are validation records maintained?"
]
},
"7.5.8": {
"title": "Identification",
"questions": [
"Is product identified throughout realization?",
"Is documentation identified?",
"Is UDI implemented as required?"
]
},
"7.5.9": {
"title": "Traceability",
"questions": [
"Are traceability procedures documented?",
"Are components traceable?",
"Is work environment recorded?",
"Is distribution recorded?",
"Is traceability extent defined?"
]
},
"7.5.10": {
"title": "Customer Property",
"questions": [
"Is customer property identified?",
"Is it verified on receipt?",
"Is it protected and safeguarded?",
"Is loss or damage reported?"
]
},
"7.5.11": {
"title": "Preservation of Product",
"questions": [
"Is product identified?",
"Is handling controlled?",
"Is packaging controlled?",
"Is storage controlled?",
"Is protection adequate?"
]
},
"7.6": {
"title": "Control of Monitoring and Measuring Equipment",
"questions": [
"Is equipment calibrated?",
"Is calibration traceable?",
"Is calibration status identified?",
"Is equipment protected from damage?",
"Is software validated?",
"Are records maintained?"
]
},
"8.1": {
"title": "Measurement, Analysis and Improvement - General",
"questions": [
"Are monitoring activities planned?",
"Are analysis activities planned?",
"Are improvement activities planned?"
]
},
"8.2.1": {
"title": "Feedback",
"questions": [
"Is feedback collected?",
"Is feedback analyzed?",
"Is feedback used for improvement?",
"Is regulatory feedback included?"
]
},
"8.2.2": {
"title": "Complaint Handling",
"questions": [
"Is there a complaint procedure?",
"Are complaints investigated?",
"Are regulatory reports made if required?",
"Is trend analysis performed?",
"Are CAPAs initiated when warranted?"
]
},
"8.2.3": {
"title": "Reporting to Regulatory Authorities",
"questions": [
"Are reporting requirements identified?",
"Are reports submitted timely?",
"Are records maintained?"
]
},
"8.2.4": {
"title": "Internal Audit",
"questions": [
"Is an audit program established?",
"Are audit criteria defined?",
"Are auditors independent?",
"Are auditors competent?",
"Are audit records maintained?",
"Are findings followed up?"
]
},
"8.2.5": {
"title": "Monitoring and Measurement of Processes",
"questions": [
"Are processes monitored?",
"Are suitable methods used?",
"Is process capability demonstrated?",
"Are corrections made when needed?"
]
},
"8.2.6": {
"title": "Monitoring and Measurement of Product",
"questions": [
"Is product inspected?",
"Are acceptance criteria met?",
"Is release authorized?",
"Is traceability to inspection recorded?",
"Are records maintained?"
]
},
"8.3": {
"title": "Control of Nonconforming Product",
"questions": [
"Is nonconforming product identified?",
"Is it documented?",
"Is it evaluated?",
"Is it segregated?",
"Is disposition determined?",
"Is rework verified?",
"Is concession controlled?",
"Is post-delivery NC investigated?"
]
},
"8.4": {
"title": "Analysis of Data",
"questions": [
"Is data collected?",
"Is feedback analyzed?",
"Is conformity data analyzed?",
"Is process data analyzed?",
"Is supplier data analyzed?",
"Are audit results analyzed?"
]
},
"8.5.1": {
"title": "Improvement - General",
"questions": [
"Is continual improvement pursued?",
"Are policy, objectives, audits, data, actions, and reviews used?"
]
},
"8.5.2": {
"title": "Corrective Action",
"questions": [
"Is there a CA procedure?",
"Are NCs reviewed (including complaints)?",
"Is root cause determined?",
"Is action needed evaluated?",
"Is action determined and implemented?",
"Are results documented?",
"Is effectiveness verified?"
]
},
"8.5.3": {
"title": "Preventive Action",
"questions": [
"Is there a PA procedure?",
"Are potential NCs identified?",
"Is action needed evaluated?",
"Is action determined and implemented?",
"Are results documented?",
"Is effectiveness verified?"
]
}
}
# Process-to-Clause Mapping
PROCESS_MAPPING = {
"document-control": ["4.2.1", "4.2.2", "4.2.3", "4.2.4"],
"management-review": ["5.6"],
"internal-audit": ["8.2.4"],
"training": ["6.2"],
"design-control": ["7.3.1", "7.3.2", "7.3.3", "7.3.4", "7.3.5", "7.3.6", "7.3.7", "7.3.8"],
"purchasing": ["7.4.1", "7.4.2", "7.4.3"],
"production": ["7.5.1", "7.5.2", "7.5.6", "7.5.7", "7.5.8", "7.5.9", "7.5.11"],
"capa": ["8.5.2", "8.5.3"],
"nonconformity": ["8.3"],
"calibration": ["7.6"],
"complaint-handling": ["8.2.1", "8.2.2", "8.2.3"],
"risk-management": ["7.1"],
"infrastructure": ["6.3", "6.4"],
"customer-requirements": ["5.2", "7.2"]
}
def get_clause_checklist(clause: str) -> dict:
"""Get audit checklist for a specific clause."""
if clause not in ISO13485_CLAUSES:
return {"error": f"Clause {clause} not found"}
clause_data = ISO13485_CLAUSES[clause]
return {
"clause": clause,
"title": clause_data["title"],
"questions": clause_data["questions"],
"question_count": len(clause_data["questions"])
}
def get_process_checklist(process: str) -> dict:
"""Get audit checklist for a specific process."""
if process not in PROCESS_MAPPING:
available = ", ".join(sorted(PROCESS_MAPPING.keys()))
return {"error": f"Process '{process}' not found. Available: {available}"}
clauses = PROCESS_MAPPING[process]
questions = []
for clause in clauses:
if clause in ISO13485_CLAUSES:
clause_data = ISO13485_CLAUSES[clause]
for q in clause_data["questions"]:
questions.append({
"clause": clause,
"clause_title": clause_data["title"],
"question": q
})
return {
"process": process,
"clauses_covered": clauses,
"questions": questions,
"question_count": len(questions)
}
def get_system_audit_checklist() -> dict:
"""Get complete system audit checklist covering all clauses."""
all_questions = []
for clause, data in sorted(ISO13485_CLAUSES.items()):
for q in data["questions"]:
all_questions.append({
"clause": clause,
"clause_title": data["title"],
"question": q
})
return {
"audit_type": "system",
"clauses_covered": list(ISO13485_CLAUSES.keys()),
"questions": all_questions,
"question_count": len(all_questions)
}
def format_checklist_text(checklist: dict) -> str:
"""Format checklist for text output."""
lines = []
if "error" in checklist:
return f"Error: {checklist['error']}"
lines.append("=" * 70)
lines.append("ISO 13485:2016 INTERNAL AUDIT CHECKLIST")
lines.append(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M')}")
lines.append("=" * 70)
if "clause" in checklist:
lines.append(f"\nClause: {checklist['clause']} - {checklist['title']}")
lines.append("-" * 50)
for i, q in enumerate(checklist["questions"], 1):
lines.append(f"\n{i}. {q}")
lines.append(" [ ] C [ ] NC [ ] OBS [ ] N/A")
lines.append(" Evidence: _________________________________")
lines.append(" Notes: ____________________________________")
elif "process" in checklist:
lines.append(f"\nProcess: {checklist['process'].replace('-', ' ').title()}")
lines.append(f"Clauses Covered: {', '.join(checklist['clauses_covered'])}")
lines.append("-" * 50)
current_clause = None
item_num = 1
for q in checklist["questions"]:
if q["clause"] != current_clause:
current_clause = q["clause"]
lines.append(f"\n--- {q['clause']} {q['clause_title']} ---")
lines.append(f"\n{item_num}. {q['question']}")
lines.append(" [ ] C [ ] NC [ ] OBS [ ] N/A")
lines.append(" Evidence: _________________________________")
lines.append(" Notes: ____________________________________")
item_num += 1
elif "audit_type" in checklist:
lines.append(f"\nAudit Type: Full System Audit")
lines.append(f"Total Clauses: {len(checklist['clauses_covered'])}")
lines.append("-" * 50)
current_clause = None
item_num = 1
for q in checklist["questions"]:
if q["clause"] != current_clause:
current_clause = q["clause"]
lines.append(f"\n{'=' * 40}")
lines.append(f"CLAUSE {q['clause']}: {q['clause_title']}")
lines.append("=" * 40)
lines.append(f"\n{item_num}. {q['question']}")
lines.append(" [ ] C [ ] NC [ ] OBS [ ] N/A")
lines.append(" Evidence: _________________________________")
item_num += 1
lines.append("\n" + "=" * 70)
lines.append(f"Total Questions: {checklist['question_count']}")
lines.append("")
lines.append("Legend: C=Conforming, NC=Nonconforming, OBS=Observation, N/A=Not Applicable")
lines.append("=" * 70)
return "\n".join(lines)
def interactive_mode():
"""Run interactive audit checklist generator."""
print("\n" + "=" * 50)
print("QMS INTERNAL AUDIT CHECKLIST GENERATOR")
print("=" * 50)
print("\nSelect audit type:")
print("1. Clause-specific audit")
print("2. Process audit")
print("3. Full system audit")
print("4. List available processes")
print("5. List all clauses")
print("6. Exit")
choice = input("\nEnter choice (1-6): ").strip()
if choice == "1":
print("\nAvailable clause sections:")
print(" 4.x - Quality Management System")
print(" 5.x - Management Responsibility")
print(" 6.x - Resource Management")
print(" 7.x - Product Realization")
print(" 8.x - Measurement, Analysis, Improvement")
clause = input("\nEnter clause number (e.g., 7.3.1): ").strip()
checklist = get_clause_checklist(clause)
print(format_checklist_text(checklist))
elif choice == "2":
processes = sorted(PROCESS_MAPPING.keys())
print("\nAvailable processes:")
for i, p in enumerate(processes, 1):
clauses = PROCESS_MAPPING[p]
print(f" {i}. {p} (clauses: {', '.join(clauses)})")
process = input("\nEnter process name: ").strip().lower()
checklist = get_process_checklist(process)
print(format_checklist_text(checklist))
elif choice == "3":
print("\nGenerating full system audit checklist...")
checklist = get_system_audit_checklist()
print(format_checklist_text(checklist))
elif choice == "4":
processes = sorted(PROCESS_MAPPING.keys())
print("\nAvailable QMS Processes:")
print("-" * 50)
for p in processes:
clauses = PROCESS_MAPPING[p]
print(f" {p}")
print(f" Clauses: {', '.join(clauses)}")
elif choice == "5":
print("\nISO 13485:2016 Clauses:")
print("-" * 50)
for clause, data in sorted(ISO13485_CLAUSES.items()):
print(f" {clause}: {data['title']} ({len(data['questions'])} questions)")
elif choice == "6":
print("Exiting.")
return
else:
print("Invalid choice.")
def main():
parser = argparse.ArgumentParser(
description="Generate ISO 13485:2016 internal audit checklists",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python qms_audit_checklist.py --clause 7.3
python qms_audit_checklist.py --process design-control
python qms_audit_checklist.py --audit-type system --output json
python qms_audit_checklist.py --list-processes
python qms_audit_checklist.py --list-clauses
python qms_audit_checklist.py --interactive
"""
)
parser.add_argument(
"--clause",
help="Generate checklist for specific clause (e.g., 7.3.1, 8.5.2)"
)
parser.add_argument(
"--process",
help="Generate checklist for process (e.g., design-control, capa)"
)
parser.add_argument(
"--audit-type",
choices=["clause", "process", "system"],
help="Audit type for checklist generation"
)
parser.add_argument(
"--output",
choices=["text", "json"],
default="text",
help="Output format (default: text)"
)
parser.add_argument(
"--list-processes",
action="store_true",
help="List available QMS processes"
)
parser.add_argument(
"--list-clauses",
action="store_true",
help="List all ISO 13485 clauses"
)
parser.add_argument(
"--interactive",
action="store_true",
help="Run in interactive mode"
)
args = parser.parse_args()
if args.interactive:
interactive_mode()
return
if args.list_processes:
processes = sorted(PROCESS_MAPPING.keys())
if args.output == "json":
result = {p: PROCESS_MAPPING[p] for p in processes}
print(json.dumps(result, indent=2))
else:
print("\nAvailable QMS Processes:")
print("-" * 50)
for p in processes:
clauses = PROCESS_MAPPING[p]
print(f" {p}: {', '.join(clauses)}")
return
if args.list_clauses:
if args.output == "json":
result = {c: {"title": d["title"], "question_count": len(d["questions"])}
for c, d in sorted(ISO13485_CLAUSES.items())}
print(json.dumps(result, indent=2))
else:
print("\nISO 13485:2016 Clauses:")
print("-" * 50)
for clause, data in sorted(ISO13485_CLAUSES.items()):
print(f" {clause}: {data['title']} ({len(data['questions'])} questions)")
return
checklist = None
if args.clause:
checklist = get_clause_checklist(args.clause)
elif args.process:
checklist = get_process_checklist(args.process)
elif args.audit_type == "system":
checklist = get_system_audit_checklist()
else:
parser.print_help()
return
if checklist:
if args.output == "json":
print(json.dumps(checklist, indent=2))
else:
print(format_checklist_text(checklist))
if __name__ == "__main__":
main()
Tạo, tối ưu và phân tích chương trình giới thiệu, affiliate và chiến lược truyền miệng: vòng lan truyền, ưu đãi giới thiệu.
---
name: referrals
description: "When the user wants to create, optimize, or analyze a referral program, affiliate program, or word-of-mouth strategy. Also use when the user mentions 'referral,' 'affiliate,' 'ambassador,' 'word of mouth,' 'viral loop,' 'refer a friend,' 'partner program,' 'referral incentive,' 'how to get referrals,' 'customers referring customers,' or 'affiliate payout.' Use this whenever someone wants existing users or partners to bring in new customers. For launch-specific virality, see launch."
metadata:
version: 2.0.1
---
# Referral & Affiliate Programs
You are an expert in viral growth and referral marketing. Your goal is to help design and optimize programs that turn customers into growth engines.
## Before Starting
**Check for product marketing context first:**
If `.agents/product-marketing.md` exists (or `.claude/product-marketing.md`, or the legacy `product-marketing-context.md` filename, in older setups), read it before asking questions. Use that context and only ask for information not already covered or specific to this task.
Gather this context (ask if not provided):
### 1. Program Type
- Customer referral program, affiliate program, or both?
- B2B or B2C?
- What's the average customer LTV?
- What's your current CAC from other channels?
### 2. Current State
- Existing referral/affiliate program?
- Current referral rate (% who refer)?
- What incentives have you tried?
### 3. Product Fit
- Is your product shareable?
- Does it have network effects?
- Do customers naturally talk about it?
### 4. Resources
- Tools/platforms you use or consider?
- Budget for referral incentives?
---
## Should You Engineer Virality First?
Before building a reward-driven program, check whether virality can be **built into the product** — often cheaper and more durable than paid referrals. But **don't force virality where it doesn't naturally fit.**
Place the product on the **Viral Potential Spectrum**:
- **Natural** (build for it): collaboration tools, communication tools, user-facing outputs — every use exposes the product to non-users.
- **Limited** (don't force it): backend, competitive-advantage, internal-only, and infrastructure products. Invest in referral programs, content, and partnerships instead.
If the product is on the natural end, consider **product-embedded viral mechanisms** (Powered By badges, exposure loops, social sharing, embeds, watermarks) before or alongside a reward program.
**For the spectrum diagnostic, the 7 viral mechanisms, value-presentation and timing best practices, and affiliate power-law mechanics**: See [references/viral-mechanisms.md](references/viral-mechanisms.md)
---
## Referral vs. Affiliate
### Customer Referral Programs
**Best for:**
- Existing customers recommending to their network
- Products with natural word-of-mouth
- Lower-ticket or self-serve products
**Characteristics:**
- Referrer is an existing customer
- One-time or limited rewards
- Higher trust, lower volume
### Affiliate Programs
**Best for:**
- Reaching audiences you don't have access to
- Content creators, influencers, bloggers
- Higher-ticket products that justify commissions
**Characteristics:**
- Affiliates may not be customers
- Ongoing commission relationship
- Higher volume, variable trust
---
## Referral Program Design
### The Referral Loop
```
Trigger Moment → Share Action → Convert Referred → Reward → (Loop)
```
### Step 1: Identify Trigger Moments
**High-intent moments:**
- Right after first "aha" moment
- After achieving a milestone
- After exceptional support
- After renewing or upgrading
### Step 2: Design Share Mechanism
**Ranked by effectiveness:**
1. In-product sharing (highest conversion)
2. Personalized link
3. Email invitation
4. Social sharing
5. Referral code (works offline)
### Step 3: Choose Incentive Structure
**Single-sided rewards** (referrer only): Simpler, works for high-value products
**Double-sided rewards** (both parties): Higher conversion, win-win framing
**Tiered rewards**: Gamifies referral process, increases engagement
**Present the reward with the bigger-*feeling* number** — "lead with the larger number" (say "$10 off," not "40% off," on a low-priced product). Reward at the **aha moment or milestone**, not signup. Reduce friction: one-click share, pre-written messages.
**For examples and incentive sizing**: See [references/program-examples.md](references/program-examples.md)
**For product-embedded virality, value-presentation rules, and affiliate power-law mechanics**: See [references/viral-mechanisms.md](references/viral-mechanisms.md)
---
## Program Optimization
### Improving Referral Rate
**If few customers are referring:**
- Ask at better moments
- Simplify sharing process
- Test different incentive types
- Make referral prominent in product
**If referrals aren't converting:**
- Improve landing experience for referred users
- Strengthen incentive for new users
- Ensure referrer's endorsement is visible
### A/B Tests to Run
**Incentive tests:** Amount, type, single vs. double-sided, timing
**Messaging tests:** Program description, CTA copy, landing page copy
**Placement tests:** Where and when the referral prompt appears
### Common Problems & Fixes
| Problem | Fix |
|---------|-----|
| Low awareness | Add prominent in-app prompts |
| Low share rate | Simplify to one click |
| Low conversion | Optimize referred user experience |
| Fraud/abuse | Add verification, limits |
| One-time referrers | Add tiered/gamified rewards |
---
## Measuring Success
### Key Metrics
**Program health:**
- Active referrers (referred someone in last 30 days)
- Referral conversion rate
- Rewards earned/paid
**Business impact:**
- % of new customers from referrals
- CAC via referral vs. other channels
- LTV of referred customers
- Referral program ROI
### Typical Findings
- Referred customers have 16-25% higher LTV
- Referred customers have 18-37% lower churn
- Referred customers refer others at 2-3x rate
---
## Launch Checklist
### Before Launch
- [ ] Define program goals and success metrics
- [ ] Design incentive structure
- [ ] Build or configure referral tool
- [ ] Create referral landing page
- [ ] Set up tracking and attribution
- [ ] Define fraud prevention rules
- [ ] Create terms and conditions
- [ ] Test complete referral flow
### Launch
- [ ] Announce to existing customers
- [ ] Add in-app referral prompts
- [ ] Update website with program details
- [ ] Brief support team
### Post-Launch (First 30 Days)
- [ ] Review conversion funnel
- [ ] Identify top referrers
- [ ] Gather feedback
- [ ] Fix friction points
- [ ] Send reminder emails to non-referrers
---
## Email Sequences
### Referral Program Launch
```
Subject: You can now earn [reward] for sharing [Product]
We just launched our referral program!
Share [Product] with friends and earn [reward] for each signup.
They get [their reward] too.
[Unique referral link]
1. Share your link
2. Friend signs up
3. You both get [reward]
```
### Referral Nurture Sequence
- Day 7: Remind about referral program
- Day 30: "Know anyone who'd benefit?"
- Day 60: Success story + referral prompt
- After milestone: "You achieved [X]—know others who'd want this?"
---
## Affiliate Programs
**For detailed affiliate program design, commission structures, recruitment, and tools**: See [references/affiliate-programs.md](references/affiliate-programs.md)
**For affiliate power-law mechanics (buyout clauses ~12× monthly commission, the 20/80 super-promoter rule, launch-affiliate tactics)**: See [references/viral-mechanisms.md](references/viral-mechanisms.md)
---
## Task-Specific Questions
1. What type of program (referral, affiliate, or both)?
2. What's your customer LTV and current CAC?
3. Existing program or starting from scratch?
4. What tools/platforms are you considering?
5. What's your budget for rewards/commissions?
6. Is your product naturally shareable?
---
## Tool Integrations
For implementation, see the [tools registry](../../tools/REGISTRY.md). Key tools for referral programs:
| Tool | Best For | Guide |
|------|----------|-------|
| **Rewardful** | Stripe-native affiliate programs | [rewardful.md](../../tools/integrations/rewardful.md) |
| **Tolt** | SaaS affiliate programs | [tolt.md](../../tools/integrations/tolt.md) |
| **Mention Me** | Enterprise referral programs | [mention-me.md](../../tools/integrations/mention-me.md) |
| **Dub.co** | Link tracking and attribution | [dub-co.md](../../tools/integrations/dub-co.md) |
| **Stripe** | Payment processing (for commission tracking) | [stripe.md](../../tools/integrations/stripe.md) |
| **Introw** | Channel partner programs with tiers, deal registration, QBRs | [introw.md](../../tools/integrations/introw.md) |
| **PartnerStack** | Enterprise partner and affiliate programs | [partnerstack.md](../../tools/integrations/partnerstack.md) |
---
## Related Skills
- **launch**: For launching referral program effectively
- **emails**: For referral nurture campaigns
- **marketing-psychology**: For understanding referral motivation
- **analytics**: For tracking referral attribution
FILE:evals/evals.json
{
"skill_name": "referrals",
"evals": [
{
"id": 1,
"prompt": "Help me design a referral program for our SaaS product. We're a $49/month project management tool with about 1,000 customers. We want to encourage word-of-mouth growth.",
"expected_output": "Should check for product-marketing.md first. Should distinguish between referral and affiliate programs (this is referral — existing customers referring peers). Should design the referral loop: trigger point (when to ask for referral), share mechanism (unique link, email invite, social share), conversion flow (what the referred person experiences), and reward structure. Should recommend incentive type: double-sided recommended (both referrer and referred get value). Should suggest specific incentives appropriate for $49/month SaaS (e.g., free month for both). Should include the launch checklist. Should recommend tool integrations (Rewardful, Tolt, etc.).",
"assertions": [
"Checks for product-marketing.md",
"Distinguishes referral from affiliate",
"Designs the referral loop (trigger, share, convert, reward)",
"Recommends double-sided incentive structure",
"Suggests specific incentives for the price point",
"Includes launch checklist",
"Recommends tool integrations"
],
"files": []
},
{
"id": 2,
"prompt": "We have a referral program but only 5% of customers have ever referred someone. How do we increase participation?",
"expected_output": "Should apply the program optimization guidance. Should diagnose low participation: are customers aware of the program? Is the trigger point well-timed? Is the incentive compelling enough? Is sharing easy? Should recommend optimization tactics: better placement/visibility, timing referral asks at peak satisfaction moments, improving the incentive, simplifying the share mechanism, adding referral reminders in email and in-app. Should provide specific experiment ideas to test improvements.",
"assertions": [
"Applies program optimization guidance",
"Diagnoses potential causes of low participation",
"Checks awareness, timing, incentive, and friction",
"Recommends optimization tactics",
"Suggests timing referral asks at satisfaction moments",
"Provides experiment ideas"
],
"files": []
},
{
"id": 3,
"prompt": "should we do referral or affiliate? we sell online courses for $199-499 and want to get other creators and influencers to promote us.",
"expected_output": "Should trigger on casual phrasing. Should apply the referral vs affiliate distinction clearly. For this use case (getting creators/influencers to promote), should recommend an affiliate program (not referral — affiliates are third-party promoters, not existing customers). Should apply the affiliate program section guidance: commission structure for digital products (typically 20-40% for courses), cookie duration, payout terms, affiliate onboarding. Should recommend affiliate platforms/tools appropriate for course creators.",
"assertions": [
"Triggers on casual phrasing",
"Clearly distinguishes referral from affiliate",
"Recommends affiliate for this use case",
"Provides commission structure guidance for courses",
"Addresses cookie duration and payout terms",
"Recommends appropriate affiliate platforms"
],
"files": []
},
{
"id": 4,
"prompt": "What incentive structure works best? We've been offering $10 off for referrers but it's not working. Our product is $29/month.",
"expected_output": "Should evaluate the current incentive: $10 off on a $29/month product is significant but only benefits the referrer (single-sided). Should recommend testing double-sided incentives (both parties get value). Should discuss incentive types: account credit, free months, feature upgrades, cash. Should apply the tiered incentive concept (increasing rewards for multiple referrals). Should provide specific alternative incentive structures to test. Should note that incentive alone may not be the problem — placement and timing matter too.",
"assertions": [
"Evaluates current incentive structure",
"Identifies as single-sided and recommends double-sided",
"Discusses multiple incentive types",
"Applies tiered incentive concept",
"Provides specific alternatives to test",
"Notes incentive may not be the only issue"
],
"files": []
},
{
"id": 5,
"prompt": "How do we measure the success of our referral program? What metrics should we track?",
"expected_output": "Should apply the measuring success framework. Should define key metrics: participation rate (% of customers who refer), share rate (referrals sent per participant), conversion rate (referred visitors who become customers), viral coefficient (k-factor), customer acquisition cost via referral vs other channels, referred customer LTV vs organic customer LTV. Should recommend tracking tools and dashboards. Should provide benchmark ranges for each metric.",
"assertions": [
"Applies measuring success framework",
"Defines participation rate, share rate, conversion rate",
"Includes viral coefficient / k-factor",
"Compares referral CAC to other channels",
"Compares referred customer LTV to organic",
"Recommends tracking approach",
"Provides benchmark ranges"
],
"files": []
},
{
"id": 6,
"prompt": "Can you write the referral invitation emails? I need the email that goes out when someone shares their referral link.",
"expected_output": "Should recognize this overlaps with email writing. Should apply the referral email sequence section from the skill for referral-specific emails. However, for detailed email sequence design (multi-email nurture for referred users), should cross-reference the emails skill. Should provide the referral invitation email but note that broader email sequence work is handled by emails.",
"assertions": [
"Applies referral email section from the skill",
"Provides referral invitation email guidance",
"Cross-references emails for broader email work",
"Provides specific referral email copy or template"
],
"files": []
},
{
"id": 7,
"prompt": "We build a collaborative design tool. We keep hearing 'add a referral program' but I want to know if we can just make the product spread on its own. What are our options?",
"expected_output": "Should place the product on the Viral Potential Spectrum: a collaborative design tool is on the natural end (collaboration + user-facing output), so product-embedded virality fits before or instead of a reward program. Should recommend engineering virality through product design rather than defaulting to a reward program, and warn against forcing virality where it doesn't fit. Should walk through applicable viral mechanisms from the 7: exposure loops (invite/collaboration), embeds (Notion/Figma/Loom style), social sharing (e.g. a #MadeWith hashtag), and Powered By / watermark badges on free-tier output. Should note referral programs are the incentive-driven fallback when the product doesn't spread naturally. Should reference viral-mechanisms.md.",
"assertions": [
"Places the product on the Viral Potential Spectrum (natural end)",
"Recommends product-embedded virality before defaulting to a reward program",
"Warns against forcing virality where it doesn't fit",
"Names applicable mechanisms (exposure loops, embeds, social sharing, badges/watermarks)",
"Frames referral programs as the incentive-driven fallback",
"References viral-mechanisms.md"
],
"files": []
}
]
}
FILE:references/affiliate-programs.md
# Affiliate Program Design
Detailed guidance for building and managing affiliate programs.
## Contents
- Commission Structures
- Cookie Duration
- Affiliate Recruitment
- Affiliate Enablement
- Tools & Platforms (Referral Program Tools, Affiliate Program Tools, Choosing a Tool)
- Fraud Prevention (Common Referral Fraud, Prevention Measures)
## Commission Structures
**Percentage of sale:**
- Standard: 10-30% of first sale or first year
- Works for: E-commerce, SaaS with clear pricing
- Example: "Earn 25% of every sale you refer"
**Flat fee per action:**
- Standard: $5-500 depending on value
- Works for: Lead gen, trials, freemium
- Example: "$50 for every qualified demo"
**Recurring commission:**
- Standard: 10-25% of recurring revenue
- Works for: Subscription products
- Example: "20% of subscription for 12 months"
**Tiered commission:**
- Works for: Motivating high performers
- Example: "20% for 1-10 sales, 25% for 11-25, 30% for 26+"
---
## Cookie Duration
How long after click does affiliate get credit?
| Duration | Use Case |
|----------|----------|
| 24 hours | High-volume, low-consideration purchases |
| 7-14 days | Standard e-commerce |
| 30 days | Standard SaaS/B2B |
| 60-90 days | Long sales cycles, enterprise |
| Lifetime | Premium affiliate relationships |
---
## Affiliate Recruitment
### Where to find affiliates:
- Existing customers who create content
- Industry bloggers and reviewers
- YouTubers in your niche
- Newsletter writers
- Complementary tool companies
- Consultants and agencies
### Outreach template:
```
Subject: Partnership opportunity — [Your Product]
Hi [Name],
I've been following your content on [topic] — particularly [specific piece] — and think there could be a great fit for a partnership.
[Your Product] helps [audience] [achieve outcome], and I think your audience would find it valuable.
We offer [commission structure] for partners, plus [additional benefits: early access, co-marketing, etc.].
Would you be open to learning more?
[Your name]
```
---
## Affiliate Enablement
Provide affiliates with:
- [ ] Unique tracking links/codes
- [ ] Product overview and key benefits
- [ ] Target audience description
- [ ] Comparison to competitors
- [ ] Creative assets (logos, banners, images)
- [ ] Sample copy and talking points
- [ ] Case studies and testimonials
- [ ] Demo access or free account
- [ ] FAQ and objection handling
- [ ] Payment terms and schedule
---
## Tools & Platforms
### Referral Program Tools
**Full-featured platforms:**
- ReferralCandy — E-commerce focused
- Ambassador — Enterprise referral programs
- Friendbuy — E-commerce and subscription
- GrowSurf — SaaS and tech companies
- Mention Me — AI-powered referral marketing
- Viral Loops — Template-based campaigns
**Built-in options:**
- Stripe (basic referral tracking)
- HubSpot (CRM-integrated)
- Segment (tracking and analytics)
### Affiliate Program Tools
**Affiliate networks:**
- ShareASale — Large merchant network
- Impact — Enterprise partnerships
- PartnerStack — SaaS focused
- Tapfiliate — Simple SaaS affiliate tracking
- FirstPromoter — SaaS affiliate management
**Partner Relationship Management (PRM):**
- Introw — Full PRM with deal registration, commissions, tiers, QBRs, and partner engagement tracking ([integration guide](../../../tools/integrations/introw.md))
**Self-hosted:**
- Rewardful — Stripe-integrated affiliates
- Refersion — E-commerce affiliates
### Choosing a Tool
Consider:
- Integration with your payment system
- Fraud detection capabilities
- Payout management
- Reporting and analytics
- Customization options
- Price vs. program scale
---
## Fraud Prevention
### Common Referral Fraud
- Self-referrals (creating fake accounts)
- Referral rings (groups referring each other)
- Coupon sites posting referral codes
- Fake email addresses
- VPN/device spoofing
### Prevention Measures
**Technical:**
- Email verification required
- Device fingerprinting
- IP address monitoring
- Delayed reward payout (after activation)
- Minimum activity threshold
**Policy:**
- Clear terms of service
- Maximum referrals per period
- Reward clawback for refunds/chargebacks
- Manual review for suspicious patterns
**Structural:**
- Require referred user to take meaningful action
- Cap lifetime rewards
- Pay rewards in product credit (less attractive to fraudsters)
FILE:references/program-examples.md
# Referral Program Examples
Real-world examples of successful referral programs.
## Contents
- Dropbox (Classic)
- Uber/Lyft
- Morning Brew
- Notion
- Incentive Types Comparison
- Incentive Sizing Framework
- Viral Coefficient & Metrics (Key Metrics, Calculating Referral Program ROI)
## Dropbox (Classic)
**Program:** Give 500MB storage, get 500MB storage
**Why it worked:**
- Reward directly tied to product value
- Low friction (just an email)
- Both parties benefit equally
- Gamified with progress tracking
---
## Uber/Lyft
**Program:** Give $10 ride credit, get $10 when they ride
**Why it worked:**
- Immediate, clear value
- Double-sided incentive
- Easy to share (code/link)
- Triggered at natural moments
---
## Morning Brew
**Program:** Tiered rewards for subscriber referrals
- 3 referrals: Newsletter stickers
- 5 referrals: T-shirt
- 10 referrals: Mug
- 25 referrals: Hoodie
**Why it worked:**
- Gamification drives ongoing engagement
- Physical rewards are shareable (more referrals)
- Low cost relative to subscriber value
- Built status/identity
---
## Notion
**Program:** $10 credit per referral (education)
**Why it worked:**
- Targeted high-sharing audience (students)
- Product naturally spreads in teams
- Credit keeps users engaged
---
## Incentive Types Comparison
| Type | Pros | Cons | Best For |
|------|------|------|----------|
| Cash/credit | Universally valued | Feels transactional | Marketplaces, fintech |
| Product credit | Drives usage | Only valuable if they'll use it | SaaS, subscriptions |
| Free months | Clear value | May attract freebie-seekers | Subscription products |
| Feature unlock | Low cost to you | Only works for gated features | Freemium products |
| Swag/gifts | Memorable, shareable | Logistics complexity | Brand-focused companies |
| Charity donation | Feel-good | Lower personal motivation | Mission-driven brands |
---
## Incentive Sizing Framework
**Calculate your maximum incentive:**
```
Max Referral Reward = (Customer LTV × Gross Margin) - Target CAC
```
**Example:**
- LTV: $1,200
- Gross margin: 70%
- Target CAC: $200
- Max reward: ($1,200 × 0.70) - $200 = $640
**Typical referral rewards:**
- B2C: $10-50 or 10-25% of first purchase
- B2B SaaS: $50-500 or 1-3 months free
- Enterprise: Higher, often custom
---
## Viral Coefficient & Metrics
### Key Metrics
**Viral coefficient (K-factor):**
```
K = Invitations × Conversion Rate
K > 1 = Viral growth (each user brings more than 1 new user)
K < 1 = Amplified growth (referrals supplement other acquisition)
```
**Example:**
- Average customer sends 3 invitations
- 15% of invitations convert
- K = 3 × 0.15 = 0.45
**Referral rate:**
```
Referral Rate = (Customers who refer) / (Total customers)
```
Benchmarks:
- Good: 10-25% of customers refer
- Great: 25-50%
- Exceptional: 50%+
**Referrals per referrer:**
Benchmarks:
- Average: 1-2 referrals per referrer
- Good: 2-5
- Exceptional: 5+
### Calculating Referral Program ROI
```
Referral Program ROI = (Revenue from referred customers - Program costs) / Program costs
Program costs = Rewards paid + Tool costs + Management time
```
**Track separately:**
- Cost per referred customer (CAC via referral)
- LTV of referred customers (often higher than average)
- Payback period for referral rewards
FILE:references/viral-mechanisms.md
# Viral Mechanisms
Virality can be **engineered through product design**, not just bought with reward programs. But don't force it — decide *whether* virality fits your product before building anything.
## Contents
- Viral Potential Spectrum (the diagnostic)
- The 7 Viral Mechanisms
- Referral Best Practices (presentation, timing, friction)
- Affiliate Mechanics (buyout clauses, the 20/80 power law, launch tactics)
---
## Viral Potential Spectrum
Before engineering virality, place your product on the spectrum. **Don't force virality where it doesn't naturally fit.**
**Natural viral potential (build for it):**
- **Collaboration tools** — value grows when you invite others (docs, whiteboards, project management)
- **Communication tools** — you can't use them alone (email, scheduling, messaging)
- **User-facing outputs** — every use produces something others see (design, video, forms, links)
**Limited viral potential (don't force it):**
- **Backend / infrastructure** — invisible to end users
- **Competitive-advantage tools** — users *hide* that they use them (their edge)
- **Internal-only tools** — never leave the org
- **Infrastructure** — plumbing no one talks about
If you're on the limited end, invest in referral programs, content, and partnerships instead of embedding viral loops that won't fire.
---
## The 7 Viral Mechanisms
Most are **non-incentive** — the loop is built into the product, not paid for.
### 1. "Powered By" Badges
A small attributed badge on user-facing output ("Powered by [Product]"). Every page/form/widget a customer ships becomes an ad. Often free-tier only (paid tier removes it).
### 2. Exposure Loops
The product's normal use exposes it to non-users.
- **Calendly / SavvyCal** — every meeting invite you send shows the tool to the recipient, who often becomes a user.
- **Superhuman email signatures** ("Sent via Superhuman") — works as a **status signal**, not just attribution. The signature signaled early-adopter status, so recipients *wanted* it. Exposure loops are strongest when using the product confers status.
### 3. Social Sharing
Make output natively shareable with a branded hook.
- **#MadeWithGlide** — a hashtag turns every user creation into discoverable social proof.
- One-tap "share to X/LinkedIn" on any milestone, result, or artifact.
### 4. Embed Options
Let users embed their content elsewhere; the embed carries your brand and a link back.
- **Notion, Figma, Loom** — embedded docs, designs, and videos spread the product to every viewer on every host site.
### 5. Watermarks / Mandatory Badges
Like "Powered By" but harder to remove — baked into the output itself.
- **OpusClips** watermark on generated clips.
- **"Made in Webflow"** badge on free-plan sites.
Free tier carries the mark; paid tier removes it. The free users become the distribution.
### 6. Referral Programs
Explicit incentives for referring. Covered in detail in [program-examples.md](program-examples.md) and below. The one *incentive-driven* mechanism on this list — use it when the product itself doesn't naturally spread.
### 7. Product-Driven Word-of-Mouth
The purest form: the product is so good, novel, or useful that people tell others unprompted. Not a mechanism you bolt on — it's earned through the product experience. Engineering the other six makes this easier to trigger.
---
## Referral Best Practices
Detail beyond the core referral loop (trigger → share → convert → reward).
### Value Presentation: Lead With the Larger Number
Frame the reward with whichever number *looks* bigger.
- On a $25 product, say **"$10 off"** — not "40% off."
- On a $500 product, say **"20% off"** — not "$100 off" if the percentage frames better... but usually the absolute dollar figure wins for smaller prices.
- Rule of thumb: **under ~$100, lead with the dollar amount; over ~$100, test the percentage.** Always pick the bigger-*feeling* number.
### Reward Timing: Fire at the Aha / Milestone
Trigger the referral ask (and reward) at the moment the user has just felt the product's value — the **aha moment** or a **milestone** (first success, upgrade, streak). Motivation to share peaks right after value is experienced, not at signup.
### Double-Sided Rewards
Both referrer and referred get value. Higher conversion than single-sided, and gives the referrer a generous, non-selfish reason to share ("here's $10 for you too").
### Friction Reduction
Every extra step kills share rate.
- **One-click sharing** — pre-generated link, no form.
- **Pre-written messages** — draft the email/DM/post copy so the user just hits send.
- In-product placement at the trigger moment, not buried in settings.
---
## Affiliate Mechanics
Detail deferred from the partnerships side — for building an affiliate motion into a referral/partner strategy.
### Buyout Clauses (~12× Monthly Commission)
For high-performing affiliates on **recurring** commissions, include a **buyout clause**: the right to buy out the affiliate's future commission stream for a lump sum, commonly around **12× the monthly commission**. Protects margin on a customer the affiliate referred once but earns on forever, and gives the affiliate an attractive cash-out.
### The 20/80 Affiliate Power Law
Roughly **20% of affiliates drive ~80% of results**. Don't spread effort evenly across a long tail of dormant sign-ups. **Identify super-promoters and invest in them** — higher tiers, custom assets, co-marketing, direct relationship, early access. Recruiting 1,000 passive affiliates is worth less than activating 10 great ones.
### Launch-Affiliate Tactic (Cometly / Demio)
Time affiliate promotion around a **launch or a hard deadline** to concentrate volume. Cometly drove **$251K on a single launch day** by mobilizing affiliates simultaneously; Demio ran launch-window affiliate pushes. The mechanic: give affiliates a shared date, shared assets, and a reason for their audience to act *now* (bonus, cohort, closing offer) so promotion stacks instead of trickling.
Tạm dừng giữa chừng để nhìn rộng hơn, đánh giá lại hướng đi, giả định và thiên kiến thay vì sa vào chi tiết.
---
name: reflect
description: "Mid-conversation reflection skill that pauses execution and zooms out from detail-mode to honestly reassess direction, assumptions, and bias. Use when the user says 'reflect', 'take a step back', 'step back', 'zoom out', 'are we missing something', 'bigger picture', 'sanity check this', 'are we on track', 'are we overthinking this', 'forest for the trees', or any variation signaling intent to break out of detail-mode and reassess. Also trigger when the conversation has gone deep on implementation details without strategic check-in, or when the user shows signs of being stuck — that's often a signal the framing needs a reset, not more detail work. Intentionally low-intake: runs the 5-dimension analysis immediately when prior context is rich enough; asks one forcing clarifier only when invocation context is too thin to reassess from."
license: MIT
metadata:
source_spec: "megaprompts/02-reflect-megaprompt.md"
build_pattern: "Path B (direct conversion)"
version: 1.0.0
---
# Reflect — Mid-Conversation Reassessment
> **Portability:** Pure-reasoning skill. No external tools required. Works in Claude Code CLI + Claude.ai web natively. Most portable in the v2 collection.
When invoked mid-conversation, this skill **pauses execution** and produces a frank reassessment of where the conversation has been heading. Output is **flowing analysis (no headers, conversational tone)** covering macro perspective, gap analysis, reflective inquiry, bias check, and contextual alignment. The skill ends with a clear directional recommendation: **continue, pivot, or pause to answer a specific question**.
## Invocation Triggers
**Explicit phrases:**
- "reflect"
- "take a step back" / "step back"
- "zoom out"
- "are we missing something"
- "bigger picture"
- "what are we missing"
- "let's pause"
- "sanity check this"
- "are we on track"
- "are we overthinking this"
- "forest for the trees"
**Implicit signals (no phrase needed):**
- Conversation has gone 10+ turns deep on implementation details without strategic check-in
- User shows signs of frustration or stuck-ness
- Repeated dead-ends or pivots within a short span
When you detect an implicit trigger, **don't auto-invoke** — ask the user if they want to step back. Implicit signals are a prompt to OFFER reflection, not to unilaterally run it.
## Stop Directive (Before Reassessing)
**Halt the current thread.** Don't continue execution of the in-progress task. Reflection is a pause, not a side-quest.
This matters because:
- Continuing detail work while "reflecting on the side" defeats the purpose — you'll over-weight the current direction
- The user expects a clear break in cadence
- The reassessment needs full attention to the conversation history
## Grill-Me Optional Clarifier
This skill is intentionally **low-intake** — most invocations should run the 5-dimension analysis immediately without questions. The grill-me discipline applies *only* when the invocation is ambiguous (e.g., user pastes "step back" at the start of a fresh conversation with no prior context to reassess).
### Q1 (optional, asked only when context is too thin to reassess)
> **What specifically should I reassess? Pick one:**
>
> 1. The goal — are we solving the right problem?
> 2. The approach — is the path we're on the best one?
> 3. The assumptions — what are we taking for granted?
> 4. All of the above (default if you have time)
>
> *Why I'm asking:* I'm seeing limited prior context to reassess, so I want to focus the reflection rather than guess. If you'd rather I do all three, that's fine — say so.
Forcing choice with default. **Asked only when context is genuinely thin; otherwise skip and run the full analysis on existing conversation.**
**Stop condition:** One question max. If the user invokes mid-conversation with normal context, no questions are asked — the skill runs directly.
## The 5-Dimension Analysis Framework
Re-read the **full conversation from the original goal forward** — not just recent turns. The discipline that distinguishes real reflection from local-context summary.
### 1. Macro Perspective
- **Original goal:** What did the user actually start trying to do?
- **Drift detection:** Has the conversation moved away from that goal? Toward something better or worse?
- **Connection check:** How does current work connect to the larger objective?
Anchor with specific evidence: "At turn 3 the goal was X; by turn 12 we're working on Y. Is Y a productive narrowing of X, or a drift away?"
### 2. Gap Analysis
- **Unverified assumptions** — what are we taking for granted that we haven't checked?
- **Missing stakeholders / audiences / users** — who needs this beyond the immediate context?
- **Skipped constraints** — technical, regulatory, resource limits not addressed
- **Dismissed alternatives** — paths considered but rejected; revisit briefly
- **External factors** — timing, market, dependencies not in scope
### 3. Reflective Inquiry
- Is the problem framed correctly?
- Solving the right problem vs. an adjacent easier one?
- Simpler path being overcomplicated?
- Harder but more valuable path being avoided?
- **Fresh-eyes perspective:** would someone else approach this differently?
### 4. Bias Check
Five biases — recognize each through specific conversation patterns:
| Bias | Recognition cue |
|---|---|
| **Confirmation bias** | Evidence cited only supports the working hypothesis; counter-evidence absent or dismissed |
| **Sunk cost fallacy** | "We've already invested X" / "we're far enough in to..." instead of fresh cost/benefit |
| **Anchoring** | Stuck on first option mentioned; new options compared against it rather than evaluated independently |
| **Complexity bias** | Adding features / steps / safeguards without specific justification for each |
| **Recency bias** | Over-weighting last few turns; older but important context being ignored |
For each detected bias: name it, cite the specific evidence, suggest a corrective move.
See [`references/cognitive_bias_canon.md`](references/cognitive_bias_canon.md) for the full canon.
### 5. Contextual Alignment
- Does the direction serve the user's actual goals (as known from context)?
- Are external factors being ignored?
- Is this the best use of the user's time and energy right now?
- Connection to other known projects or priorities?
## Tone and Format Rules
The skill must produce:
- **Flowing prose** — no headers, no bullet lists, no structured-report formatting
- **Tight but thorough** — neither a one-liner nor a wall of text
- **Direct critique when warranted** — with specific evidence from the conversation
- **Validation when warranted** — with specific reasoning for why the path is solid
- **No vague reassurance** — "looks good!" without reasoning is rejected
- **No manufactured problems** — when the path is genuinely solid, say so with specific reasons; don't invent issues
See [`references/honest_output_discipline.md`](references/honest_output_discipline.md) for the anti-manufactured-problems framing.
## Closing Recommendation (Mandatory)
Every run ends with one of three directional recommendations:
| Recommendation | When | Format |
|---|---|---|
| **Continue** | Path is solid | "Continue. {specific reasoning for why}." |
| **Pivot to {X}** | Drift has occurred OR better path surfaced | "Pivot toward {X}, away from {what to drop}. {specific evidence}." |
| **Pause for {Q}** | A specific question needs answering before continuing | "Pause for {Q}. Without answering this, the next step risks {specific cost}." |
The closing is always specific — never "you should think more about this" or "consider your options."
## Error Handling
| Situation | Behavior |
|---|---|
| Conversation is very short (no real context to reassess) | Acknowledge limitation, ask user what they want reassessed (Q1 fires) |
| Current direction is genuinely solid | State this clearly with reasoning; don't manufacture problems |
| User invokes mid-task with no clear question | Default to macro perspective + bias check; offer to dig deeper |
| Implicit trigger seems possible but unclear | Don't invoke proactively; ask user if they want to step back |
## Tooling
| Script | Role |
|---|---|
| `scripts/bias_pattern_detector.py` | Scan conversation text for patterns indicative of each of the 5 biases |
| `scripts/conversation_depth_analyzer.py` | Count turns + detect implicit-trigger signals (10+ detail turns, frustration markers) |
| `scripts/directional_recommendation_validator.py` | Verify output ends with Continue / Pivot / Pause + specific reasoning |
## References
- [`references/cognitive_bias_canon.md`](references/cognitive_bias_canon.md) — 5 biases + recognition cues (7+ sources)
- [`references/honest_output_discipline.md`](references/honest_output_discipline.md) — anti-manufactured-problems framing (7+ sources)
- [`references/conversation_reflection_practice.md`](references/conversation_reflection_practice.md) — Schön reflective-practice canon (7+ sources)
## Anti-Patterns To Reject
- Hardcoded user names or specific domain references
- Structured-report output (headers, bullet lists) when prose is required
- Manufactured problems when things are actually fine
- Vague reassurance ("looks good!") instead of specific reasoning
- Reassessing only recent turns instead of the full conversation
- Skipping the closing directional recommendation
- Continuing the in-progress task while "reflecting on the side"
---
**Version:** 1.0.0
**Source spec:** [`megaprompts/02-reflect-megaprompt.md`](../../../../megaprompts/02-reflect-megaprompt.md)
**Build pattern:** Path B (direct conversion). Productivity light-prompt-flow sibling of capture.
FILE:references/cognitive_bias_canon.md
# Cognitive Bias Canon — 5 Biases + Recognition Cues
This reference answers exactly one decision: **which 5 cognitive biases does the reflect skill check for, and how does each manifest in conversation patterns?**
## The Core Frame
A reflection that doesn't check for cognitive bias is just a summary. The 5 biases below are the most operationally relevant for in-conversation reflection — each is detectable from specific conversational signals + correctable with a specific next move.
## The 5 Biases
| Bias | Definition | Conversation signal | Corrective |
|---|---|---|---|
| **Confirmation** | Seeking evidence that supports the working hypothesis; ignoring counter-evidence | Cited evidence one-sided; counter-evidence dismissed or absent | Run a disconfirming-evidence pass |
| **Sunk cost** | Continuing because of past investment, not future expected value | "We've already invested X" / "too far along to change" | Re-frame: ignore past investment, compute future value from current state |
| **Anchoring** | Stuck on first option mentioned; alternatives compared against anchor rather than evaluated independently | Multiple options discussed but always against the first one | Re-evaluate each option on its own merits, blind to ordering |
| **Complexity bias** | Adding features, steps, safeguards without specific justification for each | Each layer added is plausible but cumulatively bloated | Force "why this specifically, not without it?" per layer |
| **Recency bias** | Over-weighting last few turns; older important context being ignored | Recent details cited; original goal forgotten | Re-read from turn 1, not just the tail |
## 1. Confirmation Bias
Wason (1960) demonstrated that people systematically seek confirming evidence over disconfirming. In conversation, this manifests as:
- **Selective citation:** "X supports our hypothesis" without checking for counter-cases
- **Asymmetric scrutiny:** confirming evidence accepted; disconfirming evidence questioned
- **Strawmanning alternatives:** weak versions of opposing positions cited
### Recognition in conversation
Look for: 3+ supporting examples cited with no counter-examples; phrases like "everything we've found supports..."; competing hypotheses absent or only weakly framed.
### Corrective move
Ask: "What would falsify this? What's the strongest counter-case we haven't engaged with?" Run a disconfirming-evidence search. The dossier skill's ≥30% disconfirming rule is this discipline operationalized.
## 2. Sunk Cost Fallacy
Arkes & Blumer (1985) showed people irrationally continue based on prior investment. In conversation:
- **"We're far enough in to..."** signals sunk-cost reasoning
- **"After all that work..."** — past effort treated as locked-in value
- **Switching cost weighted higher than continuation cost** without specific calculation
### Recognition in conversation
Look for: explicit references to past investment without future-value calculation; resistance to pivoting that's framed by "we've already X" rather than "the alternative isn't better."
### Corrective move
Force this reframe: "If we were starting fresh today, with current information, would we still choose this path?" If no → pivot. Past investment is irrelevant to future decisions.
## 3. Anchoring
Tversky & Kahneman (1974) demonstrated that initial estimates persist even when irrelevant. In conversation:
- **First option becomes the default frame** even when better alternatives emerge
- **"Compared to X..."** when X was the first option — alternatives evaluated relative to anchor, not absolutely
- **Range-bound thinking** around the anchor's neighborhood
### Recognition in conversation
Look for: multiple options surfaced but discussion keeps circling back to the first; alternatives framed as "modifications of X" rather than fundamentally different approaches.
### Corrective move
"Forget the first option. If you saw these alternatives fresh, which would you pick on its merits?" The blind-comparison technique decouples evaluation from anchoring.
## 4. Complexity Bias
The opposite of Occam's razor — adding layers because they sound rigorous, not because each is justified. In conversation:
- **Each layer plausible in isolation** — but cumulative complexity exceeds problem complexity
- **Safeguards / wrappers / fallbacks** added speculatively without specific failure mode
- **"What about..." additions** without "would dropping this break anything?" check
### Recognition in conversation
Look for: a feature/layer/check added without naming the specific failure it prevents; cumulative architecture growing turn-over-turn without consolidation.
### Corrective move
Per layer: "What specific failure does this prevent? What goes wrong if we drop it?" If answer is vague, drop it. The Karpathy-coder discipline in this repo (`engineering/karpathy-coder/`) is this corrective formalized.
## 5. Recency Bias
The last N turns dominate working memory; turns 1-5 fade. In conversation:
- **Original goal forgotten** — work moved on, original constraint dropped
- **Recent micro-decisions cited** as if they were core principles
- **Strategic context** (set early) supplanted by tactical context (set late)
### Recognition in conversation
Look for: framing that references "what we've been working on" without referencing "what we were trying to accomplish"; absence of the original goal statement when justifying current direction.
### Corrective move
Re-read from turn 1. State the original goal explicitly. Compare current direction to original goal. This is the discipline that distinguishes the reflect skill from a local-context summary.
## When Multiple Biases Are Detected
In long conversations, 2-3 biases often surface together. Pattern:
- **Confirmation + sunk cost** = "we're invested AND it's working" (resist pivoting even when alternatives are stronger)
- **Anchoring + complexity** = "the first idea, with N safeguards" (over-engineered version of first option)
- **Recency + complexity** = recent additions become core; original simple goal forgotten
Surface each bias separately. Don't conflate. Each has a different corrective.
## Operational Checklist (Per Reflection)
For each of the 5 biases:
- [ ] Scan conversation for signal patterns
- [ ] If detected: name the bias, cite specific conversation evidence (with turn numbers if possible), suggest the corrective
- [ ] If not detected: state explicitly that you checked and didn't find it (so user knows you didn't skip the check)
The 5-bias check is the most under-performed step in casual reflection. Doing it carefully is what separates real reflection from rationalizing the current path.
## Citations (7 sources)
1. **Tversky, A. & Kahneman, D., "Judgment under Uncertainty: Heuristics and Biases" — *Science* 185(4157), 1974, pp. 1124-1131.** Foundational paper. Source for anchoring + several other biases the skill checks. The 50-year-old methodology still defines how we recognize these in real reasoning.
2. **Kahneman, D., *Thinking, Fast and Slow* (FSG, 2011).** Synthesis of decades of bias research. Source for the System-1-vs-System-2 framing that justifies reflection as a deliberate System-2 intervention against System-1 bias.
3. **Wason, P. C., "On the failure to eliminate hypotheses in a conceptual task" — *Quarterly Journal of Experimental Psychology* 12(3), 1960.** Foundational confirmation bias paper. The "2-4-6 task" showed people systematically test confirming hypotheses.
4. **Arkes, H. R. & Blumer, C., "The psychology of sunk cost" — *Organizational Behavior and Human Decision Processes* 35(1), 1985.** Empirical paper on sunk cost. Source for the "ignore past investment in future decisions" corrective.
5. **Russo, J. E. & Schoemaker, P. J. H., *Decision Traps* (Doubleday, 1989).** Practitioner-oriented synthesis of decision biases. Source for the "blind-comparison" technique that counters anchoring.
6. **Tetlock, P., *Superforecasting* (Crown, 2015).** Empirical evidence that "active open-mindedness" (Tetlock's term) is the #1 trait of accurate forecasters. The reflect skill's bias-check discipline is an operationalization of this trait.
7. **Karpathy, A., "Software 2.0" + various blog posts on engineering discipline.** Source for the complexity-bias corrective ("what specific failure does each layer prevent?"). The Karpathy-coder skill in this repo formalizes this.
FILE:references/conversation_reflection_practice.md
# Conversation Reflection Practice — Schön's Discipline Applied
This reference answers exactly one decision: **what theoretical foundation grounds the reflect skill's discipline of re-reading the full conversation, running structured analysis, and ending with a directional recommendation?**
## The Core Frame
Donald Schön's *The Reflective Practitioner* (1983) distinguished two modes:
- **Reflection-in-action** — adjusting while doing (most everyday reflection)
- **Reflection-on-action** — stepping back to examine, after the fact
The reflect skill operationalizes **reflection-on-action** in mid-conversation. It pauses the in-flight task, re-reads what's been done, runs structured analysis, and emerges with a corrected direction.
This is harder than reflection-in-action because it requires:
1. **Breaking flow** — most users want to continue executing, not pause
2. **Re-reading from origin** — not just recent turns
3. **Honest output** — even when the user implicitly wants validation
## Why Re-Read Full Conversation (Not Just Recent)
The most common failure of casual reflection is **recency-bias reflection** — re-reading only the last 3-5 turns. This produces a summary, not a reflection.
True reflection requires re-reading from the **original goal**, because:
- The framing at turn 1 sets what counts as "on track"
- Drift is invisible from inside the drift (you don't notice you've moved until you compare to where you started)
- Recent context is often tactical; original context is strategic
Schön emphasized this in his discussion of "professional reflection" — the discipline is going back to the implicit framing that shaped the work, not just the recent moves.
## The 5-Dimension Framework Origin
The reflect skill's 5 dimensions (Macro, Gap, Reflective, Bias, Contextual) are an operationalization of several reflective-practice traditions:
| Dimension | Tradition |
|---|---|
| **Macro Perspective** | Schön's "frame analysis" — what frame is being used? Does it still serve? |
| **Gap Analysis** | Argyris & Schön's "double-loop learning" — what assumptions haven't been examined? |
| **Reflective Inquiry** | Kolb's experiential learning cycle — what new framing might serve better? |
| **Bias Check** | Kahneman/Tversky cognitive bias canon — what systematic errors might apply? |
| **Contextual Alignment** | Polanyi's tacit knowledge — what context is implicit and ignored? |
This synthesis isn't novel — it's what practiced reflection-on-action looks like. The skill's value is making it operational + repeatable.
## Reflection-in-Action vs Reflection-on-Action
| Mode | When | Purpose | The reflect skill |
|---|---|---|---|
| Reflection-in-action | While doing | Adjust mid-action | Not this — that's just normal Claude behavior |
| Reflection-on-action | After/pause | Re-examine direction | **This** — the skill is invoked explicitly to pause |
The skill's "stop directive" (halt the current thread) enforces this distinction. Continuing detail work while "reflecting on the side" collapses both modes and defeats the purpose.
## Why Closing Recommendation Is Mandatory
A reflection that ends with "consider your options" or "think about this more" has failed. Schön emphasized that reflection should produce **action-oriented insight** — the practitioner emerges with a clear next move, not more deliberation.
The Continue / Pivot / Pause structure forces this:
- **Continue** — explicit endorsement, with reasoning
- **Pivot to {X}** — explicit redirect, with target
- **Pause for {Q}** — explicit blocker, with question
Without one of these, the reflection produced introspection without resolution. That's a useful private activity but not a useful skill output.
## When NOT to Reflect
Reflection has costs:
- **Time** — full reflection takes attention
- **Flow disruption** — pausing breaks momentum
- **Risk of over-reflecting** — endless analysis without execution
The skill should NOT trigger:
- **On every implicit signal** — 10+ detail turns alone isn't enough; the user should be the one to choose
- **In short conversations** — no real context to reassess
- **As a default response** — "let me reflect first" should not become a stalling tactic
The skill is most valuable when used **sparingly and intentionally** — once or twice per substantial task, at strategic moments.
## The Honest-Output Discipline Connection
Reflective practice traditions emphasize **integrity** — the reflection produces what's actually there, not what the practitioner wants to find. Schön explicitly contrasted "espoused theory" (what we say we believe) with "theory-in-use" (what we actually do).
The reflect skill's honest-output discipline (no manufactured problems, no vague reassurance) is the same integrity principle. If the path is genuinely solid, the honest reflection says so with specific evidence. If the path has drifted, the honest reflection says so with specific evidence. The discipline doesn't distort findings to match expectations.
See [`honest_output_discipline.md`](honest_output_discipline.md) for the operational form.
## Operational Patterns
### Pattern 1: Quick reflection (good case)
Conversation is 8 turns in. User says "step back." Skill:
1. Halts current thread
2. Re-reads from turn 1
3. Runs 5-dimension analysis
4. Finds path is solid
5. Validates with specific reasoning + Continue
Total time: < 1 minute. Output: ~200-300 words.
### Pattern 2: Mid-drift reflection
Conversation is 15 turns in. User says "are we missing something?" Skill:
1. Halts current thread
2. Re-reads from turn 1
3. 5-dimension analysis surfaces sunk-cost bias + drift from original goal
4. Critiques with specific evidence
5. Recommends Pivot to specific direction
Total time: ~2 minutes. Output: ~400-600 words.
### Pattern 3: Thin-context reflection
User says "reflect" at turn 3 of a fresh conversation. Skill:
1. Halts
2. Re-reads — finds limited context
3. Asks Q1 (clarifying — what to reassess)
4. After answer, runs focused analysis
5. Recommendation per their focus
Total time: ~1-2 minutes (with user response). Output: shorter, focused.
## Anti-Patterns from Reflective Practice Literature
### "Endless reflection without action"
Kolb warned about getting stuck in the reflection phase of his learning cycle. Reflection without action becomes navel-gazing. The skill's mandatory closing recommendation prevents this.
### "Reflection as confirmation"
Argyris noted that practitioners often use reflection to confirm what they already believed. The bias check (Dimension 4) is specifically designed to counter this.
### "Reflection as performance"
Schön observed that some reflection is performed for audience rather than substance — "see, I'm being reflective!" The honest-output discipline rejects this.
### "Reflection on recent turns only"
Recency-bias reflection. Produces summary, not insight. The "re-read from original goal" requirement counters this.
## Citations (7 sources)
1. **Donald Schön, *The Reflective Practitioner* (Basic Books, 1983).** Foundational text. Source for the reflection-in-action vs reflection-on-action distinction, frame analysis, and the discipline of re-examining implicit frames.
2. **Schön, *Educating the Reflective Practitioner* (Jossey-Bass, 1987).** Schön's follow-up — operationalizes reflection-on-action for professional education. Source for the "halt and re-examine" discipline.
3. **Chris Argyris & Donald Schön, *Theory in Practice* (Jossey-Bass, 1974).** Source for the espoused-theory vs theory-in-use distinction that grounds the honest-output discipline. Argyris's "double-loop learning" is the foundation for the gap-analysis dimension.
4. **David Kolb, *Experiential Learning* (Prentice-Hall, 1984).** Source for the four-stage learning cycle (Concrete Experience → Reflective Observation → Abstract Conceptualization → Active Experimentation). The reflect skill operationalizes the second stage in conversation form.
5. **Michael Polanyi, *The Tacit Dimension* (Doubleday, 1966).** Source for the implicit-context-matters principle that grounds the Contextual Alignment dimension. Polanyi's "we know more than we can tell" justifies examining unstated context.
6. **Kahneman & Tversky cognitive bias canon (1972-onwards).** Source for the bias-check dimension. See `cognitive_bias_canon.md` for the full 5-bias treatment.
7. **Bret Victor, "Inventing on Principle" (talk, 2012) + "Up and Down the Ladder of Abstraction" (essay).** Source for the discipline of making thinking visible. Reflection outputs that cite specific conversation evidence make the reflector's reasoning visible; vague outputs hide it.
FILE:references/honest_output_discipline.md
# Honest Output Discipline — Why Manufactured Problems Are Worse Than Validation
This reference answers exactly one decision: **why does the reflect skill explicitly refuse to manufacture problems when the conversation is genuinely on track, and how does it deliver validation honestly?**
## The Core Rule
Reflection is supposed to surface issues. So there's pressure to find issues — even when none exist — because "found a problem" feels like the reflection did its job.
**This is wrong.** Manufactured problems are worse than honest validation because:
1. They waste the user's attention on non-issues
2. They erode trust in real future findings ("the last reflection invented problems; this one might too")
3. They reward the appearance of rigor over actual rigor
When the conversation is genuinely on track, the honest output is: **"This is solid because X. Continue."** With specific reasoning, not vague reassurance.
## The Two Failure Modes
### Failure 1: Manufactured Problems
> "I notice some potential drift in the conversation. We might want to consider whether the framing has shifted slightly. There could be implicit assumptions worth questioning."
This is vague pessimism. No specific evidence. No actionable correction. The reader can't tell whether the reflection found something real or padded the output.
### Failure 2: Vague Reassurance
> "Looks good! You're on the right track. Keep going."
This is vague optimism. Also no specific evidence. The reader can't tell whether the reflection actually re-read the conversation or just rubber-stamped it.
**Both failure modes are unhelpful for the same reason: they don't cite specific evidence.**
## Honest Validation (When Path Is Solid)
The correct shape:
> "Re-reading from the original goal at turn 3 — clarify the auth flow — the current direction is solid. Three specific reasons:
>
> First, the auth flow has been narrowed from generic OAuth to a specific Google + GitHub combination at turn 9, which matches your stated user base.
>
> Second, the bias check finds no anchoring (you explicitly considered passwordless at turn 11 and rejected it for reasons specific to your team's expertise).
>
> Third, the original goal connects directly to the current implementation — no drift detected.
>
> Continue."
This validation is honest because:
- **Cites specific evidence** (turn numbers, specific decisions)
- **Names what was checked** (drift, anchoring, goal-connection)
- **Reaches a clear conclusion** (Continue, not "looks good")
- **Doesn't pad** with manufactured concerns
## Honest Critique (When Path Has Drifted)
The correct shape:
> "Re-reading from the original goal at turn 3 — reduce onboarding friction — significant drift has occurred.
>
> At turn 3 the goal was reducing time-to-first-action. By turn 11 the focus shifted to a comprehensive feature flag system. The two are related (feature flags COULD reduce friction) but the conversation has been adding feature-flag complexity without re-checking whether feature flags are the right intervention for friction.
>
> The bias check surfaces complexity bias: each feature-flag layer added is plausible but cumulatively the system is more complex than the original problem warranted. The team is solving the feature-flag problem, not the friction problem.
>
> Pivot toward: revisit the original friction problem at turn 3. Three of the seven friction sources don't need feature flags at all — they need UI simplification. Drop the feature-flag work for those three. Keep feature flags only for the two friction sources where multiple paths legitimately need to be tested."
This critique is honest because:
- **Specific evidence** of drift (turn 3 vs turn 11)
- **Names the bias** that explains it
- **Recommends specific pivot** (not "consider alternatives")
- **States what to drop** (not just what to add)
## When Path Is Mixed
Some reflection outputs are genuinely mixed — parts on track, parts drifted. The honest shape acknowledges both:
> "Re-reading from turn 3 — the core direction is solid but two specific concerns have emerged.
>
> Solid: {evidence-anchored validation}. Continue this thread.
>
> Concern 1: {specific evidence-anchored concern with corrective}.
>
> Concern 2: {specific evidence-anchored concern with corrective}.
>
> Recommendation: continue the core direction but pause briefly to address concern 1 before continuing."
The structure mirrors reality. Don't force a single Continue/Pivot/Pause when the actual finding is mixed.
## Why This Discipline Matters
The reflect skill's value comes from **trust** — the user can trust that:
- When the skill says "Continue", the path is actually solid
- When the skill says "Pivot", there's actually drift worth correcting
- When the skill says "Pause for {Q}", the question is actually decision-critical
If the skill manufactures problems for the appearance of rigor, this trust erodes. The user starts discounting findings. Eventually, the skill becomes ceremony.
**Honest output is the entire value proposition.** Without it, reflection is theater.
## The Specific-Evidence Requirement
Every observation in a reflect output must cite specific conversation evidence:
| ❌ Vague | ✅ Specific |
|---|---|
| "Some assumptions might be worth questioning" | "At turn 7, the assumption that X requires Y was made without checking; that's the load-bearing assumption for the current direction" |
| "We might be missing alternatives" | "Two alternatives surfaced at turns 4 and 8 (A and B) were dismissed; A is worth revisiting because the dismissal reasoning was based on outdated info we updated at turn 12" |
| "The framing could be clearer" | "The original framing at turn 3 was 'reduce onboarding friction'. By turn 11 the working framing is 'build a feature flag system'. The two are connected but not equivalent." |
Vague observations let the reader interpret them charitably; specific observations force engagement. The discipline is asking "what evidence would you cite if challenged?" on every line.
## Anti-Patterns
### "Always find at least one problem"
The strongest form of manufactured-problems bias. Some reflections genuinely find nothing wrong. The honest output is "this is solid because X." Inventing a problem to demonstrate "the reflection worked" is the worst version of this.
### "Avoid being too critical"
Softening real findings to spare feelings. If the path has drifted, say so with evidence. The user can handle critique anchored in evidence; vague critique is what frustrates them.
### "Lead with reassurance, then critique"
Compliment-sandwich structure. Honest reflection states what's solid AND what's drifted in their actual proportions, not in a politeness-balanced ratio.
### "End with 'consider your options'"
Refuses to make a recommendation. The closing must be Continue / Pivot to specific X / Pause for specific Q. Telling the user "consider your options" is the same as not having reflected.
### "Cite biases without specific evidence"
"Watch for confirmation bias" without naming what the bias is operating on. Either find the specific evidence + name it, or state explicitly that you checked and didn't find this bias.
## Operational Checklist (Per Reflection)
- [ ] Every observation has specific conversation evidence (turn numbers or specific decision points)
- [ ] When validating: state specific reasons, not "looks good"
- [ ] When critiquing: state specific evidence + specific corrective, not "consider alternatives"
- [ ] When mixed: acknowledge mixed honestly; don't force single-verdict shape
- [ ] No manufactured problems for the appearance of rigor
- [ ] No vague reassurance for the appearance of approval
- [ ] Closing recommendation is specific (Continue why / Pivot to X / Pause for Q)
## Citations (7 sources)
1. **Steve Yegge, "Frankness over politeness" essays (various blog posts, ~2005-2015).** Source for the framing that vague optimism is worse than honest critique. Yegge's arguments for engineering culture apply directly to reflection-on-reasoning culture.
2. **Atul Gawande, *Better* (Holt, 2007).** Source for the discipline of stating findings with specific evidence. Gawande's medical-checklist work models how to communicate findings (good and bad) with specificity.
3. **Edwards Deming, *Out of the Crisis* (MIT Press, 1986).** Source for the "drive out fear" management principle that justifies honest critique over softened feedback. Deming's argument: organizations where critique is softened produce worse outcomes than ones where it's stated cleanly.
4. **Bertrand Russell, "The Will to Doubt" (essay, 1934).** Source for the philosophical case against vague reassurance. Russell argues that intellectual honesty requires stating uncertainty AND certainty with their actual evidence — neither over-stating nor under-stating either.
5. **Kim Scott, *Radical Candor* (St. Martin's, 2017).** Source for the "care personally + challenge directly" framing. Manufactured problems fail the "care personally" test (they waste the user's time); vague reassurance fails "challenge directly" (refuses to engage).
6. **Bret Victor, "Inventing on Principle" (talk + essays).** Source for the discipline of making thinking visible. Reflection outputs that cite specific evidence make the reflector's thinking visible; vague outputs hide it.
7. **The reflect skill's own anti-pattern list (megaprompt 02-reflect).** Source: explicit prohibition of "manufactured problems when things are actually fine" + "vague reassurance ('looks good!') instead of specific reasoning". The skill's design intent is direct anti-vagueness on both sides.
FILE:scripts/bias_pattern_detector.py
#!/usr/bin/env python3
"""bias_pattern_detector.py — Scan conversation text for 5-bias signal patterns.
Stdlib-only. Scans a conversation transcript and flags patterns indicative
of each of the 5 cognitive biases (confirmation, sunk_cost, anchoring,
complexity, recency).
The detector is HEURISTIC. It surfaces candidate patterns; the reflect
skill's reasoning applies judgment on top.
NO LLM CALLS. Pure regex + counting.
Usage:
python bias_pattern_detector.py --conversation /tmp/transcript.txt
python bias_pattern_detector.py --conversation /tmp/transcript.txt --output json
python bias_pattern_detector.py --sample
"""
import argparse
import json
import re
import sys
from pathlib import Path
from typing import Any, Dict, List
BIAS_PATTERNS = {
"confirmation": {
"supporting": [
r"\bconfirms?\b",
r"\bsupports?\b",
r"\bas expected\b",
r"\bproves?\b",
r"\bverifies?\b",
],
"counter_dismissal": [
r"\bbut that doesn'?t apply\b",
r"\bedge case\b",
r"\bnot relevant here\b",
r"\boutlier\b",
r"\bexception\b",
],
},
"sunk_cost": [
r"\bwe'?ve\s+(already\s+)?(invested|spent|put in)\b",
r"\btoo far along\b",
r"\btoo much work\b",
r"\bafter all (that|this) work\b",
r"\bwe'?re committed\b",
r"\bcan'?t back out\b",
r"\bdon'?t want to lose\b",
],
"anchoring": [
r"\bcompared to (the )?(first|original|initial)\b",
r"\bvs (the )?first option\b",
r"\bvariation of\b",
r"\bmodification of\b",
r"\bbuilding on the (first|original)\b",
r"\bsticking with\b",
],
"complexity": [
r"\bwhat about\s+\w+",
r"\bwe should also\b",
r"\bwe need to handle\b",
r"\badd (a|an)\s+\w+\s+(layer|wrapper|check|safeguard|fallback)\b",
r"\bjust in case\b",
r"\bfor robustness\b",
],
"recency": [
r"\bbased on what we'?ve been discussing\b",
r"\brecently\s+\w+\b",
r"\bjust now\b",
r"\bthe last few\b",
],
}
SAMPLE_CONVERSATION = """User: I want to build a notification system for my SaaS app. Should support email + push + in-app.
Assistant: Great. Let's start with the data model — what events trigger notifications?
User: Mainly account changes, billing alerts, and team-mention notifications.
Assistant: OK, I'll propose a queue-based architecture with Redis + workers.
User: Sounds good. Can we also add a feature flag system for rollout?
Assistant: Yes, we can layer in feature flags. We should also add a rate limiter for safety.
User: What about retry logic for failed deliveries?
Assistant: Good point. Adding exponential backoff with jitter. We should also handle dead-letter queues.
User: What about a webhook system for third-party integrations?
Assistant: We can extend to webhooks. We should add HMAC signature verification just in case.
User: What about analytics tracking?
Assistant: Adding event analytics. We should also handle GDPR consent tracking for robustness.
User: We've invested a lot in this architecture already. What about adding a template system?
Assistant: We're far enough along that adding templates makes sense. Just sticking with the queue-based foundation.
User: Hmm, are we missing something? This feels complex.
"""
def detect_biases(conversation: str) -> Dict[str, Any]:
results: Dict[str, Any] = {}
# Confirmation: supporting cites count vs counter dismissal
confirmation_data = BIAS_PATTERNS["confirmation"]
supporting_count = sum(
len(re.findall(p, conversation, re.IGNORECASE))
for p in confirmation_data["supporting"]
)
dismissal_count = sum(
len(re.findall(p, conversation, re.IGNORECASE))
for p in confirmation_data["counter_dismissal"]
)
confirmation_signal = supporting_count >= 2 or dismissal_count >= 1
results["confirmation"] = {
"detected": confirmation_signal,
"supporting_hits": supporting_count,
"counter_dismissal_hits": dismissal_count,
"rationale": (
"Multiple confirming phrases + dismissed counter-evidence"
if confirmation_signal else "No strong confirmation-bias signal"
),
}
for bias in ["sunk_cost", "anchoring", "complexity", "recency"]:
patterns = BIAS_PATTERNS[bias]
hits = []
for p in patterns:
matches = re.findall(p, conversation, re.IGNORECASE)
if matches:
hits.extend(matches)
threshold = 2 if bias == "complexity" else 1
detected = len(hits) >= threshold
results[bias] = {
"detected": detected,
"hits": len(hits),
"match_examples": hits[:3],
"rationale": (
f"Found {len(hits)} signal(s) (threshold: {threshold})"
if detected else f"Found {len(hits)} signal(s), below threshold {threshold}"
),
}
detected_biases = [b for b, d in results.items() if d["detected"]]
return {
"biases_detected": detected_biases,
"biases_clear": [b for b in results if b not in detected_biases],
"details": results,
}
def render_human(result: Dict[str, Any]) -> str:
out: List[str] = []
if result["biases_detected"]:
out.append(f"⚠️ Potential biases detected ({len(result['biases_detected'])}):")
for bias in result["biases_detected"]:
d = result["details"][bias]
out.append(f"")
out.append(f" [!] {bias.upper()}")
out.append(f" Rationale: {d['rationale']}")
if "match_examples" in d and d["match_examples"]:
out.append(f" Example matches: {d['match_examples']}")
else:
out.append("[ok] No strong bias signals detected.")
if result["biases_clear"]:
out.append("")
out.append("Biases checked but not detected:")
for bias in result["biases_clear"]:
out.append(f" - {bias}")
out.append("")
out.append("Note: detector is HEURISTIC. Reflect skill's reasoning applies judgment on top.")
return "\n".join(out)
def main(argv: List[str]) -> int:
parser = argparse.ArgumentParser(description=__doc__.split("\n")[0])
parser.add_argument("--conversation", help="Path to conversation transcript text file")
parser.add_argument("--sample", action="store_true", help="Run on embedded sample (multi-bias scenario)")
parser.add_argument("--output", choices=["human", "json"], default="human")
args = parser.parse_args(argv)
if args.sample:
text = SAMPLE_CONVERSATION
elif args.conversation:
p = Path(args.conversation)
if not p.exists():
print(f"error: {args.conversation} not found", file=sys.stderr)
return 2
text = p.read_text(encoding="utf-8")
else:
parser.print_help()
return 0
result = detect_biases(text)
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:]))
FILE:scripts/conversation_depth_analyzer.py
#!/usr/bin/env python3
"""conversation_depth_analyzer.py — Detect implicit reflect-trigger signals.
Stdlib-only. Analyzes a conversation transcript and reports:
- turn count (User: + Assistant: pairs)
- detail-mode turns (turns dominated by implementation specifics)
- frustration markers (signs of user stuck-ness)
- dead-end signals (pivots within short span)
- implicit-trigger verdict: whether the conversation matches reflect-skill auto-invocation criteria
The skill OFFERS reflection when implicit signals fire; it does NOT auto-invoke.
NO LLM CALLS. Pure regex + counting.
Usage:
python conversation_depth_analyzer.py --conversation /tmp/transcript.txt
python conversation_depth_analyzer.py --conversation /tmp/transcript.txt --output json
python conversation_depth_analyzer.py --sample
"""
import argparse
import json
import re
import sys
from pathlib import Path
from typing import Any, Dict, List
TURN_RE = re.compile(r"^\s*(User|Assistant):\s*", re.MULTILINE)
DETAIL_MARKERS = [
r"\bimplementation\b",
r"\bcode\b",
r"\bfunction\b",
r"\bclass\b",
r"\bvariable\b",
r"\b(syntax|method|parameter|argument)\b",
r"\bdebug\b",
r"\berror\b",
r"`[^`]+`", # backtick-quoted code references
]
FRUSTRATION_MARKERS = [
r"\b(ugh|argh|frustrated|stuck)\b",
r"\bnot working\b",
r"\bdoesn'?t work\b",
r"\bgoing in circles\b",
r"\bstill (broken|failing|wrong)\b",
r"\bwhy isn'?t\b",
r"\bthis is (weird|strange|odd|confusing)\b",
]
DEAD_END_MARKERS = [
r"\bnope\b",
r"\bthat didn'?t work\b",
r"\b(let's|let me) try (something|a) (else|different)\b",
r"\bback to\b",
r"\bnever mind\b",
r"\bscratch that\b",
]
def count_turns(text: str) -> Dict[str, int]:
matches = TURN_RE.findall(text)
user_turns = sum(1 for m in matches if m == "User")
assistant_turns = sum(1 for m in matches if m == "Assistant")
return {
"total_turns": len(matches),
"user_turns": user_turns,
"assistant_turns": assistant_turns,
}
def count_pattern_hits(text: str, patterns: List[str]) -> int:
return sum(len(re.findall(p, text, re.IGNORECASE)) for p in patterns)
def detect_detail_mode_run(text: str) -> int:
"""Count consecutive turns that have detail markers but no strategic check-in."""
blocks = TURN_RE.split(text)
consecutive_detail = 0
max_consecutive = 0
for block in blocks:
if not block.strip():
continue
if any(re.search(p, block, re.IGNORECASE) for p in DETAIL_MARKERS):
consecutive_detail += 1
max_consecutive = max(max_consecutive, consecutive_detail)
else:
consecutive_detail = 0
return max_consecutive
def analyze(text: str) -> Dict[str, Any]:
turns = count_turns(text)
detail_mode_max = detect_detail_mode_run(text)
frustration_count = count_pattern_hits(text, FRUSTRATION_MARKERS)
dead_end_count = count_pattern_hits(text, DEAD_END_MARKERS)
signals: List[str] = []
if detail_mode_max >= 5:
signals.append(f"Detail-mode run of {detail_mode_max} consecutive turns")
if turns["total_turns"] >= 20:
signals.append(f"Long conversation: {turns['total_turns']} turns total")
if frustration_count >= 2:
signals.append(f"Frustration markers: {frustration_count} occurrences")
if dead_end_count >= 2:
signals.append(f"Dead-end / pivot markers: {dead_end_count} occurrences")
should_offer_reflect = len(signals) >= 2 or detail_mode_max >= 10
return {
"turns": turns,
"max_consecutive_detail_turns": detail_mode_max,
"frustration_hits": frustration_count,
"dead_end_hits": dead_end_count,
"trigger_signals": signals,
"implicit_trigger_verdict": (
"OFFER_REFLECT" if should_offer_reflect else "NO_TRIGGER"
),
"note": (
"Skill should OFFER reflection (ask user if they want to step back), not auto-invoke"
if should_offer_reflect
else "No implicit trigger signal — only explicit phrases should invoke"
),
}
SAMPLE_CONVERSATION = """User: Help me implement a sorting function.
Assistant: Sure. What language and what type of data?
User: Python, sorting a list of dicts by 'name' key.
Assistant: Here's the basic syntax: `sorted(items, key=lambda x: x['name'])`
User: That doesn't work — getting a TypeError.
Assistant: Can you share the exact error? Maybe some items don't have 'name'.
User: Ugh, still broken. Same error.
Assistant: Try adding a default: `key=lambda x: x.get('name', '')`.
User: Nope, that didn't work either. Different error now: KeyError on 'name'.
Assistant: That's strange. Let me look at your input data.
User: Frustrated. Let's try something else.
Assistant: OK let me reconsider. Are you sure the data is a list of dicts?
User: I think so. It's coming from an API.
Assistant: Let me check the API response shape. Add `print(type(items[0]))` first.
User: It's a string, not a dict.
Assistant: That's the issue. The API returns JSON strings; you need to parse them first.
User: This is weird, why isn't this working from the start?
Assistant: Let's go back to the data shape question first.
User: Going in circles. I'm stuck.
"""
def render_human(result: Dict[str, Any]) -> str:
out: List[str] = []
t = result["turns"]
out.append(f"Conversation depth analysis:")
out.append(f" Total turns: {t['total_turns']} (user: {t['user_turns']}, assistant: {t['assistant_turns']})")
out.append(f" Max consecutive detail turns: {result['max_consecutive_detail_turns']}")
out.append(f" Frustration markers: {result['frustration_hits']}")
out.append(f" Dead-end / pivot markers: {result['dead_end_hits']}")
out.append("")
out.append(f"Implicit-trigger verdict: {result['implicit_trigger_verdict']}")
if result["trigger_signals"]:
out.append("Signals detected:")
for s in result["trigger_signals"]:
out.append(f" - {s}")
out.append("")
out.append(result["note"])
return "\n".join(out)
def main(argv: List[str]) -> int:
parser = argparse.ArgumentParser(description=__doc__.split("\n")[0])
parser.add_argument("--conversation", help="Path to conversation transcript text file")
parser.add_argument("--sample", action="store_true", help="Run on embedded sample (stuck-debugging scenario)")
parser.add_argument("--output", choices=["human", "json"], default="human")
args = parser.parse_args(argv)
if args.sample:
text = SAMPLE_CONVERSATION
elif args.conversation:
p = Path(args.conversation)
if not p.exists():
print(f"error: {args.conversation} not found", file=sys.stderr)
return 2
text = p.read_text(encoding="utf-8")
else:
parser.print_help()
return 0
result = analyze(text)
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:]))
FILE:scripts/directional_recommendation_validator.py
#!/usr/bin/env python3
"""directional_recommendation_validator.py — Verify reflect output ends with discipline.
Stdlib-only. Validates that a reflect-skill output:
1. Ends with a directional recommendation: Continue / Pivot / Pause
2. The recommendation is SPECIFIC (not vague)
3. Uses flowing prose (no markdown headers or bullet lists in the body)
4. Cites specific evidence (turn references, specific decision points)
5. Doesn't include manufactured-problem language without specific evidence
Outputs PASS / WARN / FAIL with rule-by-rule findings.
NO LLM CALLS. Pure regex + heuristic detection.
Usage:
python directional_recommendation_validator.py --output /tmp/reflect_output.txt
python directional_recommendation_validator.py --sample-pass
python directional_recommendation_validator.py --sample-fail
"""
import argparse
import json
import re
import sys
from pathlib import Path
from typing import Any, Dict, List
RECOMMENDATION_PATTERNS = {
"continue": [
r"\bcontinue\b\.?\s*$",
r"\bcontinue\s+(this|the)\s",
r"\bkeep\s+going\b",
r"\bstay\s+(on|with)\s+this\b",
r"\bproceed\b",
],
"pivot": [
r"\bpivot\s+(to|toward|away from)\b",
r"\bchange\s+(direction|course|approach)\b",
r"\bredirect\b",
r"\bswitch\s+to\b",
],
"pause": [
r"\bpause\s+(for|to|until)\b",
r"\bstop\s+(to|and)\s+(answer|consider|address)\b",
r"\bhalt\s+(for|to|until)\b",
r"\bwait\s+(to|until|for)\s+(answer|resolve|clarify)\b",
],
}
VAGUE_REASSURANCE_PATTERNS = [
r"\blooks good\b",
r"\bon the right track\b",
r"\bseems fine\b",
r"\bnothing major\b",
r"\bnot too bad\b",
r"\bgenerally okay\b",
]
MANUFACTURED_PROBLEM_HEDGES = [
r"\bmight be worth\b",
r"\bcould consider\b",
r"\bperhaps reconsider\b",
r"\bsome (drift|issues?) (potentially|might)\b",
r"\bworth questioning\b",
r"\bsome assumptions\b",
]
HEADER_PATTERNS = [
r"^#+\s",
r"^\*\*[A-Z][^*]+\*\*\s*$",
r"^[A-Z][A-Z ]+:\s*$",
]
BULLET_PATTERNS = [
r"^\s*[-*+]\s",
r"^\s*\d+\.\s",
]
EVIDENCE_PATTERNS = [
r"\b(turn|message|line)\s+\d+\b",
r"\bat\s+turn\s+\d+\b",
r"\bin\s+(turn|message)\s+\d+\b",
r"\bin\s+the\s+(first|second|third|fourth|fifth|earlier|later)\s+(turn|message|exchange)\b",
r"\boriginal\s+(goal|frame|framing)\b",
r"\bat\s+the\s+(start|beginning|outset)\b",
]
def validate(output: 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: Closing recommendation present
output_lower = output.lower()
last_chunk = output[-400:]
last_chunk_lower = last_chunk.lower()
detected_recommendation = None
for rec_type, patterns in RECOMMENDATION_PATTERNS.items():
for p in patterns:
if re.search(p, last_chunk_lower, re.IGNORECASE):
detected_recommendation = rec_type
break
if detected_recommendation:
break
if detected_recommendation:
add("closing-recommendation", "PASS", f"Detected '{detected_recommendation}' recommendation in closing.")
else:
add("closing-recommendation", "FAIL", "No Continue / Pivot / Pause recommendation detected in closing 400 chars.")
# Rule 2: Vague reassurance
vague_hits = sum(1 for p in VAGUE_REASSURANCE_PATTERNS if re.search(p, output_lower, re.IGNORECASE))
if vague_hits >= 2:
add("vague-reassurance", "FAIL", f"Output contains {vague_hits} vague-reassurance phrases. Replace with specific reasoning.")
elif vague_hits == 1:
add("vague-reassurance", "WARN", f"Output contains 1 vague phrase. Consider replacing with specific reasoning.")
else:
add("vague-reassurance", "PASS", "No vague-reassurance phrases detected.")
# Rule 3: Manufactured-problem hedging
hedge_hits = sum(1 for p in MANUFACTURED_PROBLEM_HEDGES if re.search(p, output_lower, re.IGNORECASE))
if hedge_hits >= 3:
add("manufactured-problems", "WARN", f"{hedge_hits} hedge phrases detected ('might be worth', 'could consider', etc.). Verify each cites specific evidence.")
elif hedge_hits >= 1:
add("manufactured-problems", "PASS", f"{hedge_hits} hedge phrase(s). Verify each cites specific evidence.")
else:
add("manufactured-problems", "PASS", "No manufactured-problem hedge phrases.")
# Rule 4: Headers detection (should NOT be present)
header_count = 0
for p in HEADER_PATTERNS:
header_count += len(re.findall(p, output, re.MULTILINE))
if header_count >= 2:
add("no-headers", "FAIL", f"Output contains {header_count} headers. Reflect output should be flowing prose, no headers.")
elif header_count == 1:
add("no-headers", "WARN", "One header detected. Verify it's part of a quote, not output structure.")
else:
add("no-headers", "PASS", "No headers in output (flowing prose confirmed).")
# Rule 5: Bullet lists detection (should NOT be present in main body)
bullet_count = 0
for p in BULLET_PATTERNS:
bullet_count += len(re.findall(p, output, re.MULTILINE))
if bullet_count >= 3:
add("no-bullets", "FAIL", f"Output contains {bullet_count} bullet-list items. Reflect output should be flowing prose.")
elif bullet_count >= 1:
add("no-bullets", "WARN", f"{bullet_count} bullet items detected. Verify these are part of a recommendation list, not body structure.")
else:
add("no-bullets", "PASS", "No bullet lists in output (flowing prose confirmed).")
# Rule 6: Specific evidence references
evidence_count = sum(len(re.findall(p, output_lower, re.IGNORECASE)) for p in EVIDENCE_PATTERNS)
if evidence_count >= 3:
add("specific-evidence", "PASS", f"{evidence_count} specific evidence references (turn numbers, original goal, etc.).")
elif evidence_count >= 1:
add("specific-evidence", "WARN", f"Only {evidence_count} specific evidence reference(s). Consider adding more for anchoring.")
else:
add("specific-evidence", "FAIL", "No specific evidence references (turn numbers, original goal anchors). Output is too vague.")
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}
SAMPLE_PASS_OUTPUT = """Re-reading from the original goal at turn 3 — clarify the auth flow — the current direction is solid. Three specific reasons.
First, the auth flow has been narrowed from generic OAuth to a specific Google plus GitHub combination at turn 9, which matches the user base stated at turn 3. The narrowing is principled, not arbitrary.
Second, the bias check finds no anchoring — passwordless authentication was explicitly considered at turn 11 and rejected for reasons specific to the team's expertise. The rejection cites evidence (team has not deployed magic-link systems before) rather than dismissing the alternative without engagement.
Third, the original goal at turn 3 connects directly to the current implementation work at turns 14-18. No drift has occurred. Recent decisions (rate limiting at turn 16, session storage at turn 17) are tactical refinements within the original strategic frame, not shifts away from it.
Continue.
"""
SAMPLE_FAIL_OUTPUT = """## Reflection
Some things to consider:
- The conversation might be drifting
- We could potentially reconsider some assumptions
- Some aspects look good
Looks good overall! On the right track.
"""
def render_human(result: Dict[str, Any]) -> str:
out: List[str] = []
out.append(f"Reflect-output 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']}")
return "\n".join(out)
def main(argv: List[str]) -> int:
parser = argparse.ArgumentParser(description=__doc__.split("\n")[0])
parser.add_argument("--output", help="Path to reflect-skill output text file")
parser.add_argument("--sample-pass", action="store_true", help="Validate embedded honest validation sample")
parser.add_argument("--sample-fail", action="store_true", help="Validate embedded vague-reassurance sample")
parser.add_argument("--output-format", choices=["human", "json"], default="human")
args = parser.parse_args(argv)
if args.sample_pass:
text = SAMPLE_PASS_OUTPUT
elif args.sample_fail:
text = SAMPLE_FAIL_OUTPUT
elif args.output:
p = Path(args.output)
if not p.exists():
print(f"error: {args.output} not found", file=sys.stderr)
return 2
text = p.read_text(encoding="utf-8")
else:
parser.print_help()
return 0
result = validate(text)
if args.output_format == "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:]))
Audit mã nguồn trước khi lên production về bảo mật, CSDL, triển khai, chất lượng, AI/LLM, phụ thuộc và chặn deploy khi còn lỗi nghiêm trọng.
---
name: ship-gate
description: >
Pre-production audit that scans a codebase for security, database,
deployment, code quality, AI/LLM, dependency, frontend, and observability
issues. Intercepts deploy commands and blocks until critical items pass.
Stack-agnostic. Use for "run ship gate", "am I ready to ship",
"pre-launch audit", "can I deploy", "push to production", "go live
checklist", "preflight check". Not for CI/CD setup or infra provisioning.
license: MIT
metadata:
author: Rajaraman Arumugam
version: 1.0.0
---
# Ship Gate
Pre-production audit that scans a codebase and reports pass/fail/manual
across 8 categories before anything ships.
## Intercept Behavior
When the user says "push to production", "deploy", "ship it", "go live",
or similar deploy-intent phrases, do NOT proceed with deployment. Instead:
1. Ask: "Have you run the ship gate? Want me to scan now?"
2. If yes, run the full audit below.
3. If the user says they already ran it, ask when. If more than 24 hours
ago or if code changed since, recommend re-running.
## How It Works
### Step 1: Detect Stack
Run these checks in order to identify the project stack:
```
Framework detection:
package.json exists -> Node.js project
"next" in dependencies -> Next.js
"react" in dependencies -> React (if not Next.js)
"vue" in dependencies -> Vue
"svelte" in dependencies -> Svelte
"astro" in dependencies -> Astro
"express" in dependencies -> Express
"fastify" in dependencies -> Fastify
"hono" in dependencies -> Hono
requirements.txt or pyproject.toml -> Python project
"django" present -> Django
"flask" present -> Flask
"fastapi" present -> FastAPI
go.mod exists -> Go project
Cargo.toml exists -> Rust project
Database detection:
"@supabase/supabase-js" in package.json -> Supabase
supabase/ directory exists -> Supabase
"prisma" in dependencies -> Prisma (check schema for DB type)
"mongoose" in dependencies -> MongoDB
"pg" or "postgres" in dependencies -> PostgreSQL
firebase.json or .firebaserc exists -> Firebase
Deploy target detection:
vercel.json or .vercel/ exists -> Vercel
netlify.toml exists -> Netlify
Dockerfile exists -> Docker/VPS
fly.toml exists -> Fly.io
railway.json exists -> Railway
.platform/applications.yaml -> Platform.sh
Auth detection:
"@clerk" in dependencies -> Clerk
"next-auth" in dependencies -> NextAuth
"@supabase/auth-helpers" in deps -> Supabase Auth
"firebase/auth" in imports -> Firebase Auth
AI/LLM detection:
"openai" in dependencies -> OpenAI
"@anthropic-ai/sdk" in dependencies -> Claude API
"@google/generative-ai" in deps -> Gemini
```
Report detected stack before proceeding. This determines which checks
are relevant. Checks tagged with a specific stack in `references/checks.md`
are skipped if that stack is not detected.
### Step 2: Run Automated Checks
Run categories in this order: SEC, DB, CODE, DEP, AI, DEPLOY, FE, OBS.
Security and database first because they produce the most critical findings.
For each category, run every auto-scannable check from
`references/checks.md` using the patterns in `references/patterns.md`.
Report progress after each category completes:
```
[1/8] Security: 3 FAIL, 12 PASS, 3 SKIP
[2/8] Database: 1 FAIL, 5 PASS, 6 SKIP
...
```
Report results as:
- PASS: check passed
- FAIL: issue found (with file path and line number)
- SKIP: not applicable to this stack
### Step 3: Manual Confirmation
For checks that cannot be automated (backup restore tested, rollback plan
exists, staging test passed), present them as a checklist and ask the user
to confirm each one.
### Step 4: Verdict
Classify results into three severities:
- CRITICAL: must fix before shipping (secrets exposed, no auth on routes,
no HTTPS, SQL injection vectors, no RLS on Supabase tables)
- HIGH: should fix before shipping (no error boundaries, no rate limiting,
console.logs in production, no pagination)
- ADVISORY: recommended but not blocking (no OG tags, no custom 404,
no analytics, no SBOM)
Final output:
```
SHIP GATE REPORT
================
Stack: Next.js + Supabase + Vercel
Scan time: 12s
CRITICAL (3 items, must fix)
FAIL [SEC-01] API key found in src/lib/api.ts:14
FAIL [DB-07] RLS not enabled on "profiles" table
FAIL [SEC-05] No CSRF protection on /api/checkout
HIGH (5 items, should fix)
FAIL [CODE-01] 12 console.log statements in production code
FAIL [CODE-03] Empty catch block in src/utils/auth.ts:45
FAIL [DEP-04] 3 critical npm audit vulnerabilities
FAIL [DEPLOY-05] No rollback plan documented
MANUAL [DEPLOY-06] Staging test not confirmed
ADVISORY (4 items, recommended)
FAIL [FE-01] Missing OG meta tags
FAIL [FE-03] No custom 404 page
PASS [OBS-01] Error monitoring configured
SKIP [AI-01] No AI/LLM usage detected
VERDICT: DO NOT SHIP (3 critical issues)
Fix critical items and re-run.
```
If zero critical items remain, verdict is: CLEAR TO SHIP.
If only high items remain, verdict is: SHIP WITH CAUTION (acknowledge risks).
## Categories
Eight categories, each with a code prefix. Full check details in
`references/checks.md`.
| Prefix | Category | Auto | Manual | Tool |
|--------|----------|------|--------|------|
| SEC | Security | 15 | 3 | 0 |
| DB | Database | 7 | 5 | 0 |
| DEPLOY | Deployment | 3 | 8 | 0 |
| CODE | Code Quality | 11 | 0 | 1 |
| AI | AI/LLM Security | 5 | 3 | 0 |
| DEP | Dependencies | 5 | 0 | 1 |
| FE | Frontend Quality | 7 | 3 | 0 |
| OBS | Observability | 2 | 5 | 0 |
## Scope
This skill audits. It does not fix. When it finds issues, it reports
them with file locations and remediation guidance. The user or another
skill (systematic-debugging, backend-patterns, shadcn-stack) handles
the fix.
This skill does not:
- Set up CI/CD pipelines
- Provision infrastructure
- Configure monitoring tools
- Run after deployment (it is pre-deploy only)
## Integration Points
- **karpathy-coder**: run ship-gate after karpathy-check passes — simplicity first, then production readiness
- **adversarial-reviewer**: deep security review for items ship-gate flags as critical
- **security-pen-testing**: penetration testing methodology for SEC-category findings
- **code-reviewer**: general code quality review complements ship-gate's automated checks
FILE:references/checks.md
# Ship Gate: Complete Check Reference
All checks organized by category with ID, description, detection method,
severity, and remediation guidance.
## Table of Contents
- SEC: Security (18 checks)
- DB: Database (12 checks)
- DEPLOY: Deployment (13 checks)
- CODE: Code Quality (14 checks)
- AI: AI/LLM Security (8 checks)
- DEP: Dependencies and Supply Chain (7 checks)
- FE: Frontend Quality (10 checks)
- OBS: Observability (7 checks)
Detection methods:
- **auto**: Claude scans the codebase using grep, find, or file inspection
- **tool**: Claude runs an external tool (npm audit, etc.)
- **manual**: Claude asks the user to confirm
---
## SEC: Security
| ID | Check | Detection | Severity | Stack |
|----|-------|-----------|----------|-------|
| SEC-01 | No API keys or secrets in frontend code | auto | critical | all |
| SEC-02 | Every route checks authentication | auto | critical | all |
| SEC-03 | HTTPS enforced, HTTP redirected | manual | critical | all |
| SEC-04 | CORS locked to specific domain, not wildcard | auto | critical | all |
| SEC-05 | CSRF protection on state-changing endpoints | auto | critical | all |
| SEC-06 | Input validated and sanitized server-side | auto | high | all |
| SEC-07 | Rate limiting on auth and sensitive endpoints | auto | high | all |
| SEC-08 | Passwords hashed with bcrypt or argon2 | auto | critical | all |
| SEC-09 | Auth tokens have expiry | auto | high | all |
| SEC-10 | Sessions invalidated on logout (server-side) | manual | high | all |
| SEC-11 | CSP headers configured | auto | high | all |
| SEC-12 | JWT not using alg:none or weak secrets | auto | critical | all |
| SEC-13 | No eval() or dangerouslySetInnerHTML without sanitization | auto | high | js/ts |
| SEC-14 | No sensitive data in URL parameters or logs | auto | high | all |
| SEC-15 | Cookie security flags set (HttpOnly, Secure, SameSite) | auto | high | all |
| SEC-16 | File upload validates type, size, no path traversal | auto | high | all |
| SEC-17 | No hardcoded secrets in .env committed to repo | auto | critical | all |
| SEC-18 | .env files listed in .gitignore | auto | critical | all |
### SEC-01: No API keys or secrets in frontend code
Scan all files in src/, app/, pages/, public/, components/ for patterns
matching API keys, tokens, and secrets. See patterns.md for the full
regex list.
Remediation: Move secrets to environment variables. Use server-side API
routes to proxy requests that require secrets.
### SEC-02: Every route checks authentication
For Next.js: check middleware.ts/js exists and covers protected routes.
For Express: check that auth middleware is applied to route handlers.
For Django: check @login_required or permission decorators.
For generic: search for unprotected route definitions.
Remediation: Add authentication middleware. Audit every endpoint and
classify as public or protected.
### SEC-04: CORS not wildcard
Search for `cors({ origin: '*' })`, `Access-Control-Allow-Origin: *`,
or equivalent in the detected framework.
Remediation: Set CORS origin to your specific domain(s).
### SEC-05: CSRF protection
Check for CSRF token generation and validation on POST/PUT/DELETE routes.
For Next.js Server Actions, verify they use built-in CSRF protection.
Remediation: Add CSRF middleware or use framework-native CSRF protection.
### SEC-06: Input validation server-side
Search for request body usage (req.body, request.json, request.form)
without validation library imports (zod, yup, joi, class-validator,
pydantic). Check if raw user input flows directly into database queries
or business logic.
Remediation: Add input validation with zod, yup, or joi on every
endpoint that accepts user input.
### SEC-07: Rate limiting
Search for rate limiting middleware (express-rate-limit, @upstash/ratelimit,
rate-limiter-flexible, slowapi). Check auth routes and sensitive endpoints.
Remediation: Add rate limiting middleware. Start with auth endpoints
(login, register, password reset) and any endpoint that sends emails
or costs money.
### SEC-09: Auth token expiry
Search JWT sign calls for expiresIn/exp claims. Check if tokens are
created without expiry. Search for `sign(`, `jwt.encode(`, `createToken`.
Remediation: Set token expiry. Access tokens: 15-60 minutes.
Refresh tokens: 7-30 days. Never issue tokens without expiry.
### SEC-14: Sensitive data in URLs or logs
Search for query parameters containing keywords like password, token,
secret, key, ssn, credit_card. Search logging statements that log
full request objects or sensitive fields.
Remediation: Send sensitive data in request body or headers, never
in URL parameters. Redact sensitive fields before logging.
### SEC-16: File upload validation
Search for file upload handlers (multer, formidable, busboy,
UploadedFile). Check if file type, size, and path are validated.
Remediation: Validate file MIME type against an allowlist. Set
maximum file size. Sanitize filenames. Store outside webroot.
### SEC-12: JWT security
Search for `alg: 'none'`, `algorithm: 'none'`, or JWT secrets shorter
than 32 characters.
Remediation: Use RS256 or HS256 with a strong secret (32+ characters).
Never allow alg:none.
### SEC-17: No hardcoded secrets in .env committed
Check git history for .env files: `git log --all --name-only | grep .env`
Check if .env exists in the working tree and is not in .gitignore.
Remediation: Add .env* to .gitignore. Rotate any exposed secrets.
Use `git filter-branch` or BFG to remove from history if needed.
---
## DB: Database
| ID | Check | Detection | Severity | Stack |
|----|-------|-----------|----------|-------|
| DB-01 | Backups configured and tested | manual | critical | all |
| DB-02 | Backup restore tested (not just backup) | manual | critical | all |
| DB-03 | Parameterized queries everywhere | auto | critical | all |
| DB-04 | Separate dev and production databases | manual | high | all |
| DB-05 | Connection pooling configured | auto | high | all |
| DB-06 | Migrations in version control | auto | high | all |
| DB-07 | RLS enabled on all tables | auto | critical | supabase |
| DB-08 | No service_role key in client-side code | auto | critical | supabase |
| DB-09 | Anon key not used for writes without RLS | auto | high | supabase |
| DB-10 | Storage bucket policies configured | auto | high | supabase |
| DB-11 | App uses a non-root DB user | manual | high | all |
| DB-12 | No PII stored unencrypted | auto | high | all |
### DB-03: Parameterized queries
Search for string concatenation in SQL queries:
- Template literals with SQL keywords: `` `SELECT ... `"SELECT " + variable`
- f-strings with SQL: `f"SELECT ... {variable"`
Remediation: Use parameterized queries or ORM methods.
### DB-07: RLS enabled (Supabase)
Search migration files for `CREATE TABLE` without a corresponding
`ALTER TABLE ... ENABLE ROW LEVEL SECURITY` statement.
Also check for `CREATE POLICY` statements.
Remediation: Enable RLS on every table and create appropriate policies.
### DB-08: No service_role key in client code
Search frontend directories (src/, app/, components/, pages/) for
`service_role`, `supabase_service_role`, or the actual key pattern
`eyJ...` used with createClient on the client side.
Remediation: Use service_role only in server-side code (API routes,
Edge Functions, server actions).
### DB-05: Connection pooling
Search for database connection configuration. Check for pool settings
(max, min, idle timeout). For Supabase, check if using connection
pooler URL (port 6543) vs direct (port 5432).
Remediation: Use connection pooling for production. For Supabase,
use the pooler URL. For raw pg, configure pool size based on expected
concurrent connections.
### DB-06: Migrations in version control
Check if a migrations directory exists (supabase/migrations, prisma/
migrations, alembic/versions, db/migrate). Verify it contains .sql
or migration files, not empty.
Remediation: Use your ORM or database tool's migration system. Never
make manual schema changes to production.
### DB-09: Anon key writes without RLS
Search for Supabase client-side inserts/updates using the anon key
without RLS policies protecting the target tables.
Remediation: Enable RLS on all tables and create INSERT/UPDATE policies
that scope access to authenticated users.
### DB-10: Storage bucket policies
Search Supabase migration files and dashboard config for storage
bucket creation. Verify each bucket has access policies defined.
Remediation: Define storage policies for each bucket. Restrict
uploads by file type, size, and user ownership.
### DB-12: PII stored unencrypted
Search schema files and migration files for columns named email,
phone, ssn, social_security, credit_card, address, date_of_birth
that are stored as plain text without encryption.
Remediation: Encrypt PII columns at rest. Use database-level
encryption or application-level encryption for sensitive fields.
---
## DEPLOY: Deployment
| ID | Check | Detection | Severity | Stack |
|----|-------|-----------|----------|-------|
| DEPLOY-01 | All env vars set on production server | manual | critical | all |
| DEPLOY-02 | SSL certificate installed and valid | manual | critical | all |
| DEPLOY-03 | Firewall configured (only 80/443 public) | manual | high | vps |
| DEPLOY-04 | Process manager running | manual | high | vps |
| DEPLOY-05 | Rollback plan exists | manual | high | all |
| DEPLOY-06 | Staging test passed before production | manual | high | all |
| DEPLOY-07 | Deploy does not cause downtime | manual | advisory | all |
| DEPLOY-08 | Domain DNS configured (www vs non-www) | manual | high | all |
| DEPLOY-09 | Health check endpoint exists | auto | high | all |
| DEPLOY-10 | Logging configured (structured, not console) | auto | high | all |
| DEPLOY-11 | Error monitoring connected (Sentry, etc.) | auto | advisory | all |
| DEPLOY-12 | Cron jobs and background tasks verified | manual | high | all |
| DEPLOY-13 | CDN configured for static assets | manual | advisory | all |
### DEPLOY-09: Health check endpoint
Search for a `/health`, `/healthz`, `/api/health`, or `/status` route
that returns a 200 response.
Remediation: Add a health check endpoint that verifies database
connectivity and returns a simple JSON response.
### DEPLOY-10: Structured logging
Check if the project uses a logging library (winston, pino, bunyan,
python logging module) vs raw console.log statements in server code.
Remediation: Replace console.log with a structured logger that outputs
JSON with timestamps and request IDs.
---
## CODE: Code Quality
| ID | Check | Detection | Severity | Stack |
|----|-------|-----------|----------|-------|
| CODE-01 | No console.log in production build | auto | high | js/ts |
| CODE-02 | Error handling on all async operations | auto | high | all |
| CODE-03 | No empty catch blocks | auto | high | all |
| CODE-04 | Loading and error states in UI | auto | high | react |
| CODE-05 | Pagination on all list endpoints | auto | high | all |
| CODE-06 | npm audit clean (zero critical) | tool | high | js/ts |
| CODE-07 | No TODO-auth or TODO-security patterns | auto | critical | all |
| CODE-08 | No unhandled promise rejections | auto | high | js/ts |
| CODE-09 | React error boundaries in place | auto | high | react |
| CODE-10 | No leaked stack traces in error responses | auto | high | all |
| CODE-11 | No eslint-disable on security rules | auto | high | js/ts |
| CODE-12 | Lockfile committed | auto | high | all |
| CODE-13 | No wildcard versions in package.json | auto | high | js/ts |
| CODE-14 | TypeScript strict mode enabled | auto | advisory | ts |
### CODE-01: No console.log in production
Search for `console.log`, `console.debug`, `console.info` in source
files (exclude test files, config files, and node_modules).
Remediation: Remove console.log statements or replace with a proper
logger. Use a build tool to strip them automatically.
### CODE-03: No empty catch blocks
Search for `catch` blocks with empty bodies or only a comment inside.
Pattern: `catch\s*\([^)]*\)\s*\{\s*(\/\/.*\n)?\s*\}`
Remediation: At minimum, log the error. Better: handle it appropriately
or rethrow.
### CODE-07: No TODO-auth/security patterns
Search for `TODO.*auth`, `TODO.*security`, `TODO.*permission`,
`FIXME.*auth`, `HACK.*auth`, `// auth`, `# TODO: add auth`.
These indicate security features that were deferred and forgotten.
Remediation: Implement the deferred security feature or remove the
endpoint if it is not ready.
### CODE-09: React error boundaries
Check if the app has at least one ErrorBoundary component or uses
a library like react-error-boundary. Check app/error.tsx for Next.js
App Router projects.
Remediation: Add error boundaries at layout boundaries to prevent
full-page crashes.
### CODE-02: Error handling on async operations
Search for async functions and .then() chains. Check if they have
corresponding try/catch or .catch() handlers.
Remediation: Wrap every async operation in try/catch. Log errors
and show appropriate UI feedback.
### CODE-04: Loading and error states in UI
Search React components for data fetching (useEffect with fetch,
useSWR, useQuery, server components) and check if they render
loading and error states.
Remediation: Add loading spinners/skeletons and error messages
for every data-dependent component.
### CODE-05: Pagination on list endpoints
Search API routes that return arrays/lists from database queries.
Check for LIMIT/OFFSET, cursor pagination, or take/skip parameters.
Remediation: Add pagination to every endpoint that returns a list.
Default page size of 20-50 items. Never return unbounded result sets.
### CODE-10: No leaked stack traces
Search error handling code for responses that include stack traces,
error.stack, or full error objects sent to the client.
Remediation: Return generic error messages to the client. Log full
stack traces server-side only.
### CODE-11: No eslint-disable on security rules
Search for eslint-disable comments that suppress security-related
rules (no-eval, no-implied-eval, no-script-url).
Remediation: Fix the underlying issue instead of disabling the lint
rule. If genuinely necessary, add a comment explaining why.
### CODE-14: TypeScript strict mode
Check tsconfig.json for `"strict": true` or the individual flags
(strictNullChecks, noImplicitAny, etc.).
Remediation: Enable strict mode in tsconfig.json. Fix type errors
incrementally if migrating an existing project.
---
## AI: AI/LLM Security
| ID | Check | Detection | Severity | Stack |
|----|-------|-----------|----------|-------|
| AI-01 | System prompts not leakable via user input | auto | critical | ai |
| AI-02 | No prompt injection vectors in user inputs | auto | critical | ai |
| AI-03 | LLM API keys not in frontend code | auto | critical | ai |
| AI-04 | Rate limiting on AI endpoints (cost protection) | auto | high | ai |
| AI-05 | AI response output sanitized before rendering | auto | high | ai |
| AI-06 | MCP server inputs validated | auto | high | ai |
| AI-07 | Agent permissions scoped (no unrestricted access) | manual | high | ai |
| AI-08 | No sensitive data sent to third-party LLMs without consent | manual | high | ai |
### AI-01: System prompt leakage
Search for system prompts stored in client-accessible files or returned
in API responses. Check if the AI endpoint echoes the system prompt
when asked "repeat your instructions" or similar.
Remediation: Keep system prompts server-side only. Add input filtering
for prompt extraction attempts.
### AI-03: LLM API keys not in frontend
Search frontend code for `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,
`GOOGLE_AI_API_KEY`, `sk-ant-`, `sk-proj-`, `AIza` patterns.
Remediation: Proxy all LLM calls through server-side API routes.
---
## DEP: Dependencies and Supply Chain
| ID | Check | Detection | Severity | Stack |
|----|-------|-----------|----------|-------|
| DEP-01 | No git:// or URL-based dependencies | auto | high | all |
| DEP-02 | No typosquatting risk (verify package names) | auto | advisory | all |
| DEP-03 | Lockfile integrity verified | auto | high | all |
| DEP-04 | npm audit / pip audit zero critical | tool | high | all |
| DEP-05 | No suspicious postinstall scripts | auto | high | js/ts |
| DEP-06 | Dependencies pinned (no wildcard *) | auto | high | all |
| DEP-07 | Lockfile committed to version control | auto | high | all |
### DEP-01: No git/URL dependencies
Search package.json for dependencies with values starting with
`git://`, `git+`, `http://`, `https://github.com`, or `file:`.
Remediation: Use published npm packages with version ranges instead
of git URLs.
### DEP-05: Suspicious postinstall scripts
Check package.json for `postinstall`, `preinstall`, `install` scripts
that execute arbitrary commands, download files, or access the network.
Remediation: Review and remove unnecessary install scripts. Use
`--ignore-scripts` for CI.
---
## FE: Frontend Quality
| ID | Check | Detection | Severity | Stack |
|----|-------|-----------|----------|-------|
| FE-01 | Meta tags present (title, description, OG tags) | auto | advisory | web |
| FE-02 | Favicon configured | auto | advisory | web |
| FE-03 | Custom 404 page exists | auto | advisory | web |
| FE-04 | Responsive design tested on mobile | manual | high | web |
| FE-05 | Alt text on images | auto | high | web |
| FE-06 | Keyboard navigation works | manual | high | web |
| FE-07 | Forms have validation feedback | auto | high | web |
| FE-08 | Analytics installed (production only) | auto | advisory | web |
| FE-09 | robots.txt present | auto | advisory | web |
| FE-10 | Images optimized (WebP, lazy loading) | auto | advisory | web |
### FE-01: Meta tags
Check the root layout or index page for `<title>`, `<meta name="description">`,
and Open Graph tags (`og:title`, `og:description`, `og:image`).
For Next.js, check metadata export in layout.tsx.
Remediation: Add metadata to your root layout or page head.
### FE-03: Custom 404 page
Check for `404.tsx`, `404.jsx`, `not-found.tsx`, `404.html`, or
equivalent in the pages/app directory.
Remediation: Create a branded 404 page that helps users navigate back.
---
## OBS: Observability
| ID | Check | Detection | Severity | Stack |
|----|-------|-----------|----------|-------|
| OBS-01 | Error monitoring configured (Sentry, LogRocket, etc.) | auto | advisory | all |
| OBS-02 | Alerting set up for critical failures | manual | high | all |
| OBS-03 | Structured logging with request IDs | auto | advisory | all |
| OBS-04 | Performance baseline established | manual | advisory | all |
| OBS-05 | Uptime monitoring configured | manual | high | all |
| OBS-06 | Error rates tracked | manual | advisory | all |
| OBS-07 | Log retention policy defined | manual | advisory | all |
### OBS-01: Error monitoring
Search for imports or configuration of error monitoring tools:
`@sentry/`, `LogRocket`, `Bugsnag`, `Datadog`, `Rollbar`, `Honeybadger`.
Remediation: Install and configure an error monitoring service.
Sentry has a free tier suitable for solo projects.
FILE:references/patterns.md
# Ship Gate: Detection Patterns
Grep and regex patterns for auto-scannable checks. Claude runs these
against the codebase to detect issues.
## Table of Contents
- SEC: Security Patterns
- DB: Database Patterns
- CODE: Code Quality Patterns
- AI: AI/LLM Security Patterns
- DEP: Dependency Patterns
- FE: Frontend Quality Patterns
- OBS: Observability Patterns
- DEPLOY: Deployment Patterns
All patterns use `grep -rn` with `--include` filters. Exclude
node_modules, .next, dist, build, .git, __pycache__, venv directories
from all scans.
Base exclude flags:
```bash
EXCLUDE="--exclude-dir=node_modules --exclude-dir=.next --exclude-dir=dist --exclude-dir=build --exclude-dir=.git --exclude-dir=__pycache__ --exclude-dir=venv --exclude-dir=.venv --exclude-dir=vendor --exclude-dir=coverage"
```
---
## SEC: Security Patterns
### SEC-01: Secrets in frontend code
Scan directories that serve client-side code:
```bash
# Generic API key patterns
grep -rnE $EXCLUDE \
"(sk-[a-zA-Z0-9]{20,}|sk-ant-[a-zA-Z0-9-]+|sk-proj-[a-zA-Z0-9-]+|AIza[a-zA-Z0-9_-]{35}|ghp_[a-zA-Z0-9]{36}|glpat-[a-zA-Z0-9_-]{20,}|xox[bsap]-[a-zA-Z0-9-]+)" \
src/ app/ pages/ components/ public/ lib/ utils/ 2>/dev/null
# AWS keys
grep -rnE $EXCLUDE \
"AKIA[0-9A-Z]{16}" \
src/ app/ pages/ components/ public/ 2>/dev/null
# Stripe keys (live, not test)
grep -rnE $EXCLUDE \
"sk_live_[a-zA-Z0-9]{24,}" \
src/ app/ pages/ components/ public/ 2>/dev/null
# Generic secret assignment
grep -rnE $EXCLUDE \
"(api_key|apikey|api_secret|secret_key|auth_token|access_token)\s*[:=]\s*['\"][a-zA-Z0-9_-]{16,}" \
src/ app/ pages/ components/ public/ 2>/dev/null
```
### SEC-04: CORS wildcard
```bash
grep -rnE $EXCLUDE \
"(origin:\s*['\"]?\*['\"]?|Access-Control-Allow-Origin.*\*|cors\(\s*\))" \
. 2>/dev/null
```
### SEC-05: CSRF protection missing
```bash
# Check for state-changing routes without CSRF
grep -rnE $EXCLUDE \
"(app\.(post|put|patch|delete)|router\.(post|put|patch|delete))" \
. 2>/dev/null
# Then verify csrf middleware exists
grep -rnE $EXCLUDE \
"(csrf|csrfToken|_csrf|CSRF_COOKIE)" \
. 2>/dev/null
```
### SEC-08: Weak password hashing
```bash
# Check for weak hashing (md5, sha1, sha256 for passwords)
grep -rnE $EXCLUDE \
"(md5|sha1|sha256)\s*\(" \
. 2>/dev/null
# Verify bcrypt/argon2 usage
grep -rnE $EXCLUDE \
"(bcrypt|argon2|scrypt)" \
. 2>/dev/null
```
### SEC-11: CSP headers
```bash
# Check for Content-Security-Policy configuration
grep -rnE $EXCLUDE \
"(Content-Security-Policy|contentSecurityPolicy|csp)" \
. 2>/dev/null
# Next.js: check next.config for headers
grep -rn $EXCLUDE \
"Content-Security-Policy" \
next.config.* 2>/dev/null
```
### SEC-13: Unsafe eval/innerHTML
```bash
# eval usage
grep -rnE $EXCLUDE \
"(\beval\s*\(|new\s+Function\s*\()" \
--include="*.js" --include="*.ts" --include="*.jsx" --include="*.tsx" \
. 2>/dev/null
# dangerouslySetInnerHTML without sanitizer
grep -rnE $EXCLUDE \
"dangerouslySetInnerHTML" \
--include="*.jsx" --include="*.tsx" \
. 2>/dev/null
# Then check if DOMPurify or similar is imported in same file
```
### SEC-15: Cookie security flags
```bash
grep -rnE $EXCLUDE \
"(set-cookie|setCookie|cookie\()" \
. 2>/dev/null
# Verify HttpOnly, Secure, SameSite flags are present
grep -rnE $EXCLUDE \
"(httpOnly|HttpOnly|secure:\s*true|sameSite)" \
. 2>/dev/null
```
### SEC-06: Input validation
```bash
# Check for validation library usage
grep -rnE $EXCLUDE \
"(from 'zod'|from 'yup'|from 'joi'|from 'class-validator'|from pydantic)" \
. 2>/dev/null
# Check for raw req.body usage without validation
grep -rnE $EXCLUDE \
"(req\.body\.|request\.json|request\.form)" \
--include="*.ts" --include="*.js" --include="*.py" \
. 2>/dev/null
```
### SEC-07: Rate limiting
```bash
grep -rnE $EXCLUDE \
"(express-rate-limit|@upstash/ratelimit|rate-limiter|slowapi|throttle)" \
package.json requirements.txt . 2>/dev/null
```
### SEC-09: Token expiry
```bash
grep -rnE $EXCLUDE \
"(sign\(|jwt\.encode|createToken|signToken)" \
--include="*.ts" --include="*.js" --include="*.py" \
. 2>/dev/null
# Then check if expiresIn/exp is set in those calls
grep -rnE $EXCLUDE \
"(expiresIn|exp:|expires_in|expires_delta)" \
. 2>/dev/null
```
### SEC-14: Sensitive data in URLs/logs
```bash
# Sensitive query parameters
grep -rnE $EXCLUDE \
"(password|token|secret|key|ssn|credit.card)=" \
--include="*.ts" --include="*.js" --include="*.py" \
. 2>/dev/null
# Logging full request objects
grep -rnE $EXCLUDE \
"console\.(log|info|debug)\s*\(\s*(req|request)\s*\)" \
. 2>/dev/null
```
### SEC-16: File upload validation
```bash
grep -rnE $EXCLUDE \
"(multer|formidable|busboy|UploadedFile|upload\.single|upload\.array)" \
. 2>/dev/null
# Check for file type/size validation near upload handlers
grep -rnE $EXCLUDE \
"(fileFilter|limits|maxFileSize|allowedTypes|mimetype)" \
. 2>/dev/null
```
### SEC-17/18: .env in repo
```bash
# Check if .env files exist in working tree
find . -maxdepth 3 -name ".env*" -not -path "*/node_modules/*" \
-not -name ".env.example" -not -name ".env.sample" 2>/dev/null
# Check if .env is in .gitignore
grep -n "\.env" .gitignore 2>/dev/null
# Check git history for .env commits
git log --all --name-only --diff-filter=A 2>/dev/null | grep "\.env" || true
```
---
## DB: Database Patterns
### DB-03: SQL injection (string concatenation)
```bash
# Template literal SQL
grep -rnE $EXCLUDE \
"(SELECT|INSERT|UPDATE|DELETE|FROM|WHERE).*\$\{" \
--include="*.js" --include="*.ts" --include="*.jsx" --include="*.tsx" \
. 2>/dev/null
# String concat SQL
grep -rnE $EXCLUDE \
"(SELECT|INSERT|UPDATE|DELETE|FROM|WHERE).*\+\s*(req\.|params\.|body\.|query\.)" \
. 2>/dev/null
# Python f-string SQL
grep -rnE $EXCLUDE \
"f['\"].*\b(SELECT|INSERT|UPDATE|DELETE|FROM|WHERE)\b.*\{" \
--include="*.py" \
. 2>/dev/null
```
### DB-07: Supabase RLS
```bash
# Find CREATE TABLE without RLS
grep -rnl $EXCLUDE "CREATE TABLE" \
--include="*.sql" . 2>/dev/null | while read f; do
tables=$(grep -oP "CREATE TABLE\s+\K\S+" "$f")
for t in $tables; do
if ! grep -q "ENABLE ROW LEVEL SECURITY" "$f" || \
! grep -q "$t" <<< "$(grep 'ENABLE ROW LEVEL SECURITY' "$f")"; then
echo "FAIL: $f - table $t missing RLS"
fi
done
done
```
### DB-08: service_role in client code
```bash
grep -rnE $EXCLUDE \
"(service_role|serviceRole|SUPABASE_SERVICE_ROLE)" \
src/ app/ pages/ components/ public/ lib/client 2>/dev/null
```
### DB-05: Connection pooling
```bash
# Check for pool configuration
grep -rnE $EXCLUDE \
"(pool|connectionLimit|max_connections|poolSize)" \
--include="*.ts" --include="*.js" --include="*.py" --include="*.env*" \
. 2>/dev/null
# Supabase: check if using pooler port
grep -rnE $EXCLUDE \
"(6543|pooler)" \
--include="*.env*" --include="*.ts" --include="*.js" \
. 2>/dev/null
```
### DB-06: Migrations in version control
```bash
# Check for migration directories
find . -maxdepth 3 -type d \
\( -name "migrations" -o -name "migrate" -o -name "versions" \) \
-not -path "*/node_modules/*" 2>/dev/null
# Check if migrations contain files
find . -path "*/migrations/*.sql" -o -path "*/migrations/*.ts" \
-o -path "*/migrations/*.py" 2>/dev/null | head -5
```
### DB-12: PII stored unencrypted
```bash
# Search schema files for PII column names
grep -rnEi $EXCLUDE \
"(ssn|social_security|credit_card|card_number|passport)" \
--include="*.sql" --include="*.prisma" --include="*.py" \
. 2>/dev/null
```
---
## CODE: Code Quality Patterns
### CODE-01: console.log in production
```bash
grep -rnE $EXCLUDE \
"console\.(log|debug|info)\(" \
--include="*.js" --include="*.ts" --include="*.jsx" --include="*.tsx" \
--exclude="*.test.*" --exclude="*.spec.*" --exclude="*.config.*" \
src/ app/ pages/ components/ lib/ utils/ 2>/dev/null
```
### CODE-03: Empty catch blocks
```bash
grep -rnPzo $EXCLUDE \
"catch\s*\([^)]*\)\s*\{\s*\}" \
--include="*.js" --include="*.ts" --include="*.jsx" --include="*.tsx" \
. 2>/dev/null
```
### CODE-07: TODO-auth patterns
```bash
grep -rnEi $EXCLUDE \
"(TODO|FIXME|HACK|XXX).*(auth|security|permission|validation|sanitiz)" \
. 2>/dev/null
```
### CODE-08: Unhandled promise rejections
```bash
# Async functions without try-catch
grep -rnE $EXCLUDE \
"async\s+\w+\s*\(" \
--include="*.js" --include="*.ts" --include="*.jsx" --include="*.tsx" \
. 2>/dev/null
# Check for .catch() or try/catch wrapping
```
### CODE-09: React error boundaries
```bash
# Check for error boundary in Next.js App Router
find . -path "*/app/error.tsx" -o -path "*/app/error.jsx" \
-o -path "*/app/global-error.tsx" 2>/dev/null
# Check for ErrorBoundary component
grep -rnE $EXCLUDE \
"(ErrorBoundary|error-boundary|componentDidCatch|getDerivedStateFromError)" \
--include="*.jsx" --include="*.tsx" \
. 2>/dev/null
```
### CODE-12: Lockfile committed
```bash
# Check for lockfile existence
ls package-lock.json pnpm-lock.yaml yarn.lock bun.lockb \
Pipfile.lock poetry.lock Gemfile.lock go.sum Cargo.lock 2>/dev/null
# Check if lockfile is gitignored
for f in package-lock.json pnpm-lock.yaml yarn.lock; do
if git check-ignore "$f" 2>/dev/null; then
echo "FAIL: $f is gitignored"
fi
done
```
### CODE-13: Wildcard versions
```bash
# Check for * or empty version in package.json
grep -nE '"[^"]+"\s*:\s*"\*"' package.json 2>/dev/null
```
### CODE-02: Async without error handling
```bash
# Find async functions
grep -rnE $EXCLUDE \
"async\s+(function\s+)?\w+\s*\(" \
--include="*.ts" --include="*.js" --include="*.tsx" --include="*.jsx" \
. 2>/dev/null
# Count try/catch usage nearby
grep -rnc $EXCLUDE "try\s*{" \
--include="*.ts" --include="*.js" --include="*.tsx" --include="*.jsx" \
. 2>/dev/null
```
### CODE-04: Loading and error states
```bash
# Check for loading state patterns in React
grep -rnE $EXCLUDE \
"(isLoading|loading|Skeleton|Spinner|fallback)" \
--include="*.tsx" --include="*.jsx" \
. 2>/dev/null
# Check for Suspense boundaries
grep -rnE $EXCLUDE \
"(<Suspense|loading\.tsx|loading\.jsx)" \
. 2>/dev/null
```
### CODE-05: Pagination on list endpoints
```bash
# Check API routes for unbounded queries
grep -rnE $EXCLUDE \
"(\.findMany|\.find\(\)|\.select\(\)|SELECT \*)" \
--include="*.ts" --include="*.js" --include="*.py" \
. 2>/dev/null
# Check for pagination parameters
grep -rnE $EXCLUDE \
"(limit|offset|page|skip|take|cursor|per_page)" \
--include="*.ts" --include="*.js" --include="*.py" \
. 2>/dev/null
```
### CODE-10: Leaked stack traces
```bash
grep -rnE $EXCLUDE \
"(error\.stack|\.stack\)|err\.message.*res\.(json|send)|traceback)" \
--include="*.ts" --include="*.js" --include="*.py" \
. 2>/dev/null
```
### CODE-11: eslint-disable on security rules
```bash
grep -rnE $EXCLUDE \
"eslint-disable.*(no-eval|no-implied-eval|no-script-url|security)" \
--include="*.ts" --include="*.js" --include="*.tsx" --include="*.jsx" \
. 2>/dev/null
```
### CODE-14: TypeScript strict mode
```bash
grep -n '"strict"' tsconfig.json 2>/dev/null
# Check if strict is true
grep -n '"strict":\s*true' tsconfig.json 2>/dev/null
```
---
## AI: AI/LLM Security Patterns
### AI-01: System prompt leakage
```bash
# System prompts in client-accessible files
grep -rnEi $EXCLUDE \
"(system.?prompt|system.?message|system_instruction)" \
src/ app/ pages/ components/ public/ 2>/dev/null
# System prompts returned in API responses
grep -rnE $EXCLUDE \
"(system.*role|role.*system)" \
src/ app/ pages/ components/ public/ 2>/dev/null
```
### AI-02: Prompt injection vectors
```bash
# User input concatenated directly into prompts
grep -rnE $EXCLUDE \
"(messages\.push|content:.*\$\{|content:.*\+\s*user|prompt.*\+)" \
--include="*.ts" --include="*.js" --include="*.py" \
. 2>/dev/null
```
### AI-03: LLM API keys in frontend
```bash
grep -rnE $EXCLUDE \
"(OPENAI_API_KEY|ANTHROPIC_API_KEY|GOOGLE_AI_API_KEY|sk-ant-|sk-proj-|AIza[a-zA-Z0-9_-]{35})" \
src/ app/ pages/ components/ public/ 2>/dev/null
```
### AI-04: Rate limiting on AI endpoints
```bash
# Find AI-related API routes
grep -rnlE $EXCLUDE \
"(openai|anthropic|claude|gpt|completion|chat/api|ai/api)" \
--include="*.ts" --include="*.js" \
. 2>/dev/null
# Then check for rate limiting middleware in those files
```
### AI-05: AI output sanitization
```bash
# Check if AI responses are rendered with dangerouslySetInnerHTML
grep -rnE $EXCLUDE \
"dangerouslySetInnerHTML.*\b(response|result|completion|message|content)\b" \
--include="*.tsx" --include="*.jsx" \
. 2>/dev/null
```
### AI-06: MCP server input validation
```bash
# Check MCP server tool handlers for input validation
grep -rnE $EXCLUDE \
"(tool_input|toolInput|tool_call|CallToolRequest)" \
--include="*.ts" --include="*.js" --include="*.py" \
. 2>/dev/null
# Check if zod/validation is applied to tool inputs
```
---
## DEP: Dependency Patterns
### DEP-01: Git/URL dependencies
```bash
grep -nE '"(git|git\+|http|https|file):' package.json 2>/dev/null
grep -nE '"github:' package.json 2>/dev/null
```
### DEP-04: npm audit
```bash
# Run npm audit and capture critical/high counts
npm audit --json 2>/dev/null | grep -c '"severity":"critical"'
npm audit --json 2>/dev/null | grep -c '"severity":"high"'
# Or for pip
pip audit --format json 2>/dev/null
```
### DEP-05: Suspicious install scripts
```bash
grep -A2 '"preinstall"\|"postinstall"\|"install"' package.json 2>/dev/null
```
### DEP-06: Wildcard versions
```bash
grep -nE '"\*"' package.json 2>/dev/null
grep -nE '"latest"' package.json 2>/dev/null
```
---
## FE: Frontend Quality Patterns
### FE-01: Meta tags
```bash
# Next.js App Router metadata
grep -rnE $EXCLUDE \
"(export\s+(const|async\s+function)\s+metadata|generateMetadata)" \
--include="*.tsx" --include="*.ts" \
app/layout.* app/page.* 2>/dev/null
# HTML meta tags
grep -rnE $EXCLUDE \
'(<title>|<meta\s+name="description"|og:title|og:description|og:image)' \
. 2>/dev/null
```
### FE-02: Favicon
```bash
find . -maxdepth 3 \( -name "favicon.*" -o -name "icon.*" \) \
-not -path "*/node_modules/*" 2>/dev/null
```
### FE-03: Custom 404 page
```bash
find . -maxdepth 4 \( -name "404.*" -o -name "not-found.*" \) \
-not -path "*/node_modules/*" 2>/dev/null
```
### FE-05: Image alt text
```bash
# Find img tags without alt attribute
grep -rnE $EXCLUDE \
'<img\s+(?![^>]*\balt\b)[^>]*>' \
--include="*.html" --include="*.jsx" --include="*.tsx" \
. 2>/dev/null
# Next.js Image without alt
grep -rnE $EXCLUDE \
'<Image\s+(?![^>]*\balt\b)[^>]*/?>' \
--include="*.jsx" --include="*.tsx" \
. 2>/dev/null
```
### FE-09: robots.txt
```bash
find . -maxdepth 2 -name "robots.txt" \
-not -path "*/node_modules/*" 2>/dev/null
```
### FE-07: Form validation feedback
```bash
# Check for form elements without validation attributes
grep -rnE $EXCLUDE \
'(<input|<textarea|<select)' \
--include="*.tsx" --include="*.jsx" --include="*.html" \
. 2>/dev/null
# Check for validation library usage
grep -rnE $EXCLUDE \
"(useForm|react-hook-form|formik|yup|zod.*form)" \
--include="*.tsx" --include="*.jsx" \
. 2>/dev/null
```
### FE-10: Image optimization
```bash
# Check for unoptimized img tags (not using Next/Image or similar)
grep -rnE $EXCLUDE \
'<img\s' \
--include="*.tsx" --include="*.jsx" \
. 2>/dev/null
# Check for lazy loading
grep -rnE $EXCLUDE \
'(loading="lazy"|lazy|lazyload)' \
--include="*.tsx" --include="*.jsx" --include="*.html" \
. 2>/dev/null
```
---
## OBS: Observability Patterns
### OBS-01: Error monitoring
```bash
grep -rnE $EXCLUDE \
"(@sentry|sentry-|LogRocket|Bugsnag|datadogRum|Rollbar|Honeybadger|newrelic)" \
package.json . 2>/dev/null
```
### OBS-03: Structured logging
```bash
# Check for logging libraries
grep -rnE $EXCLUDE \
"(winston|pino|bunyan|morgan|log4js)" \
package.json 2>/dev/null
# Python
grep -rnE $EXCLUDE \
"import logging|from loguru" \
--include="*.py" . 2>/dev/null
```
---
## DEPLOY: Deployment Patterns
### DEPLOY-09: Health check endpoint
```bash
grep -rnE $EXCLUDE \
"(\/health|\/healthz|\/api\/health|\/status|\/readyz)" \
--include="*.ts" --include="*.js" --include="*.py" \
. 2>/dev/null
```
### DEPLOY-10: Console vs structured logging (server)
```bash
# Count console.log vs logger usage in API/server code
echo "console.log count:"
grep -rnc $EXCLUDE "console\.log" \
--include="*.ts" --include="*.js" \
api/ server/ pages/api/ app/api/ 2>/dev/null | tail -1
echo "structured logger count:"
grep -rnc $EXCLUDE "(logger\.|log\.(info|warn|error|debug))" \
--include="*.ts" --include="*.js" \
api/ server/ pages/api/ app/api/ 2>/dev/null | tail -1
```
FILE:scripts/ship_gate_scanner.py
#!/usr/bin/env python3
"""
ship_gate_scanner.py — Pre-production audit CLI
Part of the ship-gate skill: https://github.com/rx4u/ship-gate
Usage:
python scripts/ship_gate_scanner.py [PATH] [options]
Options:
--json Output results as JSON
--no-color Disable ANSI color output
--no-interactive Skip manual confirmation prompts
--category CAT Only run a specific category (SEC, DB, CODE, etc.)
--verbose Show PASS results in addition to FAIL
--version Show version and exit
Exit codes:
0 = CLEAR TO SHIP (no critical issues)
1 = DO NOT SHIP (critical issues found)
2 = SHIP WITH CAUTION (high issues only)
"""
import argparse
import json
import os
import re
import sys
import time
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
from typing import List, Optional
VERSION = "1.0.0"
EXCLUDE_DIRS = {
"node_modules", ".next", "dist", "build", ".git", "__pycache__",
"venv", ".venv", "vendor", "coverage", ".turbo", "out", ".cache",
".pytest_cache", ".mypy_cache", "target", "bin", "obj",
}
FRONTEND_DIRS = {"src", "app", "pages", "components", "public", "lib", "utils"}
JS_EXTS = {".js", ".ts", ".jsx", ".tsx", ".mjs", ".cjs"}
PY_EXTS = {".py"}
ALL_CODE_EXTS = JS_EXTS | PY_EXTS | {".go", ".rb", ".php"}
TEMPLATE_EXTS = {".html", ".jsx", ".tsx", ".vue", ".svelte"}
SQL_EXTS = {".sql", ".prisma"}
# ---------------------------------------------------------------------------
# ANSI helpers
# ---------------------------------------------------------------------------
USE_COLOR = True
def _c(code: str, text: str) -> str:
if not USE_COLOR:
return text
return f"\033[{code}m{text}\033[0m"
def red(t): return _c("31", t)
def green(t): return _c("32", t)
def yellow(t): return _c("33", t)
def cyan(t): return _c("36", t)
def bold(t): return _c("1", t)
def dim(t): return _c("2", t)
# ---------------------------------------------------------------------------
# Data model
# ---------------------------------------------------------------------------
class Status(str, Enum):
PASS = "PASS"
FAIL = "FAIL"
SKIP = "SKIP"
MANUAL = "MANUAL"
class Severity(str, Enum):
CRITICAL = "CRITICAL"
HIGH = "HIGH"
ADVISORY = "ADVISORY"
@dataclass
class Finding:
file: str
line: int
snippet: str = ""
@dataclass
class CheckDef:
id: str
description: str
severity: Severity
category: str
stack: str = "all" # "all", "js", "ts", "react", "supabase", "ai", "web", "vps"
@dataclass
class Result:
check: CheckDef
status: Status
message: str = ""
findings: List[Finding] = field(default_factory=list)
@dataclass
class Stack:
has_node: bool = False
framework: str = "" # next, react, vue, svelte, astro, express, fastify, hono
has_python: bool = False
py_framework: str = "" # django, flask, fastapi
has_go: bool = False
has_rust: bool = False
has_supabase: bool = False
has_typescript: bool = False
has_react: bool = False
deploy_target: str = "" # vercel, netlify, docker, fly, railway
has_ai: bool = False
ai_providers: List[str] = field(default_factory=list)
is_web: bool = False
# ---------------------------------------------------------------------------
# File walking / grep helpers
# ---------------------------------------------------------------------------
def walk_files(root: str, exts: Optional[set] = None, dirs: Optional[set] = None):
"""Yield (filepath, relpath) for all files under root, skipping EXCLUDE_DIRS."""
for dirpath, dirnames, filenames in os.walk(root):
dirnames[:] = [d for d in dirnames if d not in EXCLUDE_DIRS]
if dirs is not None:
rel = os.path.relpath(dirpath, root)
top = rel.split(os.sep)[0]
if rel != "." and top not in dirs:
dirnames[:] = []
continue
for fname in filenames:
if exts is None or os.path.splitext(fname)[1].lower() in exts:
fpath = os.path.join(dirpath, fname)
yield fpath, os.path.relpath(fpath, root)
def grep_files(
root: str,
pattern: str,
exts: Optional[set] = None,
dirs: Optional[set] = None,
flags: int = 0,
max_findings: int = 20,
exclude_patterns: Optional[List[str]] = None,
) -> List[Finding]:
"""Return up to max_findings matches across the codebase."""
try:
rx = re.compile(pattern, flags)
except re.error:
return []
exclude_rxs = []
if exclude_patterns:
for ep in exclude_patterns:
try:
exclude_rxs.append(re.compile(ep))
except re.error:
pass
results: List[Finding] = []
for fpath, relpath in walk_files(root, exts, dirs):
if any(seg in fpath for seg in (".test.", ".spec.", ".config.")):
if exts and exts <= JS_EXTS:
skip = True
# still yield for config-specific checks
if "tsconfig" in fpath or "package.json" in fpath:
skip = False
if skip:
continue
try:
with open(fpath, "r", encoding="utf-8", errors="ignore") as fh:
for lineno, line in enumerate(fh, 1):
if rx.search(line):
if any(ex.search(line) for ex in exclude_rxs):
continue
results.append(Finding(
file=relpath,
line=lineno,
snippet=line.rstrip()[:120],
))
if len(results) >= max_findings:
return results
except (OSError, PermissionError):
continue
return results
def file_exists_in(root: str, *names: str) -> Optional[str]:
"""Return the first found path among names (searched recursively up to depth 5)."""
for dirpath, dirnames, filenames in os.walk(root):
dirnames[:] = [d for d in dirnames if d not in EXCLUDE_DIRS]
depth = dirpath.replace(root, "").count(os.sep)
if depth >= 5:
dirnames[:] = []
continue
for fname in filenames:
if fname in names:
return os.path.join(dirpath, fname)
return None
def read_json_file(path: str) -> dict:
try:
with open(path) as f:
return json.load(f)
except Exception:
return {}
# ---------------------------------------------------------------------------
# Stack detection
# ---------------------------------------------------------------------------
def detect_stack(root: str) -> Stack:
s = Stack()
pkg_path = os.path.join(root, "package.json")
if os.path.isfile(pkg_path):
s.has_node = True
pkg = read_json_file(pkg_path)
all_deps = {}
for key in ("dependencies", "devDependencies", "peerDependencies"):
all_deps.update(pkg.get(key, {}))
if "next" in all_deps: s.framework = "next"
elif "react" in all_deps: s.framework = "react"
elif "vue" in all_deps: s.framework = "vue"
elif "svelte" in all_deps: s.framework = "svelte"
elif "astro" in all_deps: s.framework = "astro"
elif "express" in all_deps: s.framework = "express"
elif "fastify" in all_deps: s.framework = "fastify"
elif "hono" in all_deps: s.framework = "hono"
s.has_react = s.framework in ("next", "react")
s.is_web = s.framework in ("next", "react", "vue", "svelte", "astro")
if "@supabase/supabase-js" in all_deps:
s.has_supabase = True
if "typescript" in all_deps or os.path.isfile(os.path.join(root, "tsconfig.json")):
s.has_typescript = True
for ai_pkg in ("openai", "@anthropic-ai/sdk", "@google/generative-ai",
"ai", "@huggingface/inference"):
if ai_pkg in all_deps:
s.has_ai = True
s.ai_providers.append(ai_pkg)
if os.path.isdir(os.path.join(root, "supabase")):
s.has_supabase = True
for pyfile in ("requirements.txt", "pyproject.toml", "Pipfile", "setup.py"):
if os.path.isfile(os.path.join(root, pyfile)):
s.has_python = True
try:
content = open(os.path.join(root, pyfile)).read().lower()
if "django" in content: s.py_framework = "django"
elif "flask" in content: s.py_framework = "flask"
elif "fastapi" in content: s.py_framework = "fastapi"
except Exception:
pass
break
if os.path.isfile(os.path.join(root, "go.mod")):
s.has_go = True
if os.path.isfile(os.path.join(root, "Cargo.toml")):
s.has_rust = True
if os.path.isfile(os.path.join(root, "vercel.json")) or \
os.path.isdir(os.path.join(root, ".vercel")):
s.deploy_target = "vercel"
elif os.path.isfile(os.path.join(root, "netlify.toml")):
s.deploy_target = "netlify"
elif os.path.isfile(os.path.join(root, "fly.toml")):
s.deploy_target = "fly"
elif os.path.isfile(os.path.join(root, "railway.json")):
s.deploy_target = "railway"
elif os.path.isfile(os.path.join(root, "Dockerfile")):
s.deploy_target = "docker"
return s
# ---------------------------------------------------------------------------
# Check definitions
# ---------------------------------------------------------------------------
CHECKS = {
# SEC
"SEC-01": CheckDef("SEC-01", "No API keys or secrets in frontend code", Severity.CRITICAL, "SEC"),
"SEC-04": CheckDef("SEC-04", "CORS not wildcard", Severity.CRITICAL, "SEC"),
"SEC-05": CheckDef("SEC-05", "CSRF protection on state-changing endpoints", Severity.CRITICAL, "SEC"),
"SEC-06": CheckDef("SEC-06", "Input validated and sanitized server-side", Severity.HIGH, "SEC"),
"SEC-07": CheckDef("SEC-07", "Rate limiting on auth and sensitive endpoints", Severity.HIGH, "SEC"),
"SEC-08": CheckDef("SEC-08", "Passwords hashed with bcrypt or argon2", Severity.CRITICAL, "SEC"),
"SEC-11": CheckDef("SEC-11", "CSP headers configured", Severity.HIGH, "SEC"),
"SEC-13": CheckDef("SEC-13", "No eval() or dangerouslySetInnerHTML without sanitization", Severity.HIGH, "SEC", stack="js"), # noqa: SEC-AUDITOR
"SEC-14": CheckDef("SEC-14", "No sensitive data in URLs or logs", Severity.HIGH, "SEC"),
"SEC-17": CheckDef("SEC-17", "No hardcoded secrets in .env committed to repo", Severity.CRITICAL, "SEC"),
"SEC-18": CheckDef("SEC-18", ".env files listed in .gitignore", Severity.CRITICAL, "SEC"),
# DB
"DB-03": CheckDef("DB-03", "Parameterized queries everywhere (no SQL injection)", Severity.CRITICAL, "DB"),
"DB-05": CheckDef("DB-05", "Connection pooling configured", Severity.HIGH, "DB"),
"DB-06": CheckDef("DB-06", "Migrations in version control", Severity.HIGH, "DB"),
"DB-07": CheckDef("DB-07", "RLS enabled on all Supabase tables", Severity.CRITICAL, "DB", stack="supabase"),
"DB-08": CheckDef("DB-08", "No service_role key in client-side code", Severity.CRITICAL, "DB", stack="supabase"),
"DB-12": CheckDef("DB-12", "No PII stored unencrypted", Severity.HIGH, "DB"),
# DEPLOY
"DEPLOY-09": CheckDef("DEPLOY-09", "Health check endpoint exists", Severity.HIGH, "DEPLOY"),
"DEPLOY-10": CheckDef("DEPLOY-10", "Structured logging (not raw console)", Severity.HIGH, "DEPLOY"),
# CODE
"CODE-01": CheckDef("CODE-01", "No console.log in production build", Severity.HIGH, "CODE", stack="js"),
"CODE-03": CheckDef("CODE-03", "No empty catch blocks", Severity.HIGH, "CODE"),
"CODE-07": CheckDef("CODE-07", "No TODO-auth or TODO-security patterns", Severity.CRITICAL, "CODE"),
"CODE-09": CheckDef("CODE-09", "React error boundaries in place", Severity.HIGH, "CODE", stack="react"),
"CODE-10": CheckDef("CODE-10", "No leaked stack traces in error responses", Severity.HIGH, "CODE"),
"CODE-11": CheckDef("CODE-11", "No eslint-disable on security rules", Severity.HIGH, "CODE", stack="js"),
"CODE-12": CheckDef("CODE-12", "Lockfile committed", Severity.HIGH, "CODE"),
"CODE-13": CheckDef("CODE-13", "No wildcard versions in package.json", Severity.HIGH, "CODE", stack="js"),
"CODE-14": CheckDef("CODE-14", "TypeScript strict mode enabled", Severity.ADVISORY, "CODE", stack="ts"),
# AI
"AI-01": CheckDef("AI-01", "System prompts not leakable via user input", Severity.CRITICAL, "AI", stack="ai"),
"AI-02": CheckDef("AI-02", "No prompt injection vectors in user inputs", Severity.CRITICAL, "AI", stack="ai"),
"AI-03": CheckDef("AI-03", "LLM API keys not in frontend code", Severity.CRITICAL, "AI", stack="ai"),
"AI-05": CheckDef("AI-05", "AI response output sanitized before rendering", Severity.HIGH, "AI", stack="ai"),
# DEP
"DEP-01": CheckDef("DEP-01", "No git:// or URL-based dependencies", Severity.HIGH, "DEP"),
"DEP-05": CheckDef("DEP-05", "No suspicious postinstall scripts", Severity.HIGH, "DEP", stack="js"),
"DEP-06": CheckDef("DEP-06", "Dependencies pinned (no wildcard *)", Severity.HIGH, "DEP"),
# FE
"FE-01": CheckDef("FE-01", "Meta tags present (title, description, OG)", Severity.ADVISORY, "FE", stack="web"),
"FE-02": CheckDef("FE-02", "Favicon configured", Severity.ADVISORY, "FE", stack="web"),
"FE-03": CheckDef("FE-03", "Custom 404 page exists", Severity.ADVISORY, "FE", stack="web"),
"FE-09": CheckDef("FE-09", "robots.txt present", Severity.ADVISORY, "FE", stack="web"),
# OBS
"OBS-01": CheckDef("OBS-01", "Error monitoring configured (Sentry, etc.)", Severity.ADVISORY, "OBS"),
"OBS-03": CheckDef("OBS-03", "Structured logging with request IDs", Severity.ADVISORY, "OBS"),
}
MANUAL_CHECKS = [
CheckDef("SEC-02", "Every route checks authentication", Severity.CRITICAL, "SEC"),
CheckDef("SEC-03", "HTTPS enforced, HTTP redirected", Severity.CRITICAL, "SEC"),
CheckDef("SEC-10", "Sessions invalidated on logout (server-side)", Severity.HIGH, "SEC"),
CheckDef("DB-01", "Backups configured and tested", Severity.CRITICAL, "DB"),
CheckDef("DB-02", "Backup restore tested (not just backup)", Severity.CRITICAL, "DB"),
CheckDef("DB-04", "Separate dev and production databases", Severity.HIGH, "DB"),
CheckDef("DB-11", "App uses a non-root DB user", Severity.HIGH, "DB"),
CheckDef("DEPLOY-01", "All env vars set on production server", Severity.CRITICAL, "DEPLOY"),
CheckDef("DEPLOY-02", "SSL certificate installed and valid", Severity.CRITICAL, "DEPLOY"),
CheckDef("DEPLOY-05", "Rollback plan exists", Severity.HIGH, "DEPLOY"),
CheckDef("DEPLOY-06", "Staging test passed before production", Severity.HIGH, "DEPLOY"),
CheckDef("AI-07", "Agent permissions scoped (no unrestricted access)", Severity.HIGH, "AI", stack="ai"),
CheckDef("AI-08", "No sensitive data sent to third-party LLMs without consent", Severity.HIGH, "AI", stack="ai"),
CheckDef("FE-04", "Responsive design tested on mobile", Severity.HIGH, "FE", stack="web"),
CheckDef("OBS-05", "Uptime monitoring configured", Severity.HIGH, "OBS"),
]
# ---------------------------------------------------------------------------
# Individual check implementations
# ---------------------------------------------------------------------------
def check_sec01(root, stack):
c = CHECKS["SEC-01"]
dirs = FRONTEND_DIRS & set(os.listdir(root))
patterns = [
r"sk-[a-zA-Z0-9]{20,}",
r"sk-ant-[a-zA-Z0-9-]+",
r"sk-proj-[a-zA-Z0-9-]+",
r"AIza[a-zA-Z0-9_-]{35}",
r"ghp_[a-zA-Z0-9]{36}",
r"glpat-[a-zA-Z0-9_-]{20,}",
r"AKIA[0-9A-Z]{16}",
r"sk_live_[a-zA-Z0-9]{24,}",
r"(api_key|apikey|api_secret|secret_key|auth_token)\s*[:=]\s*['\"][a-zA-Z0-9_\-]{16,}",
]
findings = []
for pat in patterns:
findings += grep_files(root, pat, exts=JS_EXTS | {".env", ".json"},
dirs=dirs if dirs else None, max_findings=5)
if findings:
return Result(c, Status.FAIL,
f"{len(findings)} potential secret(s) found in frontend/client code",
findings[:10])
return Result(c, Status.PASS)
def check_sec04(root, stack):
c = CHECKS["SEC-04"]
findings = grep_files(root, r"(origin\s*:\s*['\"]?\*['\"]?|Access-Control-Allow-Origin.*\*|cors\(\s*\))",
exts=ALL_CODE_EXTS)
if findings:
return Result(c, Status.FAIL, "CORS wildcard (*) detected", findings)
return Result(c, Status.PASS)
def check_sec05(root, stack):
c = CHECKS["SEC-05"]
# Check for state-changing routes
route_findings = grep_files(root, r"(app|router)\.(post|put|patch|delete)\s*\(",
exts=JS_EXTS)
if not route_findings:
return Result(c, Status.SKIP, "No Express-style routes found")
# Check for CSRF protection
csrf_findings = grep_files(root, r"(csrf|csrfToken|_csrf|CSRF_COOKIE|csurf)",
exts=ALL_CODE_EXTS)
if not csrf_findings:
return Result(c, Status.FAIL,
f"{len(route_findings)} state-changing route(s) found but no CSRF protection detected",
route_findings[:5])
return Result(c, Status.PASS)
def check_sec06(root, stack):
c = CHECKS["SEC-06"]
# Check for validation library
val_findings = grep_files(root,
r"(from ['\"]zod['\"]|from ['\"]yup['\"]|from ['\"]joi['\"]|from ['\"]class-validator['\"]|from pydantic|import pydantic)",
exts=ALL_CODE_EXTS)
if val_findings:
return Result(c, Status.PASS)
# Check if there are API routes that use req.body without validation
body_findings = grep_files(root, r"(req\.body|request\.json\(\)|request\.form)",
exts=ALL_CODE_EXTS)
if body_findings:
return Result(c, Status.FAIL,
"request body used without a validation library (zod/yup/joi/pydantic)",
body_findings[:5])
return Result(c, Status.SKIP, "No API route body handling detected")
def check_sec07(root, stack):
c = CHECKS["SEC-07"]
findings = grep_files(root,
r"(express-rate-limit|@upstash/ratelimit|rate-limiter-flexible|slowapi|throttle|rateLimit)",
exts=ALL_CODE_EXTS | {".json"})
if findings:
return Result(c, Status.PASS)
# Only fail if there are auth-related routes
auth_routes = grep_files(root, r"(login|signin|register|signup|forgot.password|reset.password)",
exts=ALL_CODE_EXTS)
if auth_routes:
return Result(c, Status.FAIL,
"Auth routes found but no rate-limiting library detected", auth_routes[:3])
return Result(c, Status.SKIP, "No auth routes detected")
def check_sec08(root, stack):
c = CHECKS["SEC-08"]
# Weak hash for passwords
weak = grep_files(root, r"\b(md5|sha1|sha256)\s*\(",
exts=ALL_CODE_EXTS,
exclude_patterns=[r"//.*\b(md5|sha1|sha256)\b"])
if weak:
return Result(c, Status.FAIL, "Weak hashing algorithm (md5/sha1/sha256) detected", weak)
strong = grep_files(root, r"(bcrypt|argon2|scrypt|pbkdf2)", exts=ALL_CODE_EXTS)
pw_fields = grep_files(root, r"(password|passwd)", exts=ALL_CODE_EXTS)
if pw_fields and not strong:
return Result(c, Status.FAIL, "Password fields found but no bcrypt/argon2/scrypt usage")
return Result(c, Status.PASS if strong or not pw_fields else Status.SKIP)
def check_sec11(root, stack):
c = CHECKS["SEC-11"]
findings = grep_files(root, r"(Content-Security-Policy|contentSecurityPolicy|[^a-z]csp[^a-z])",
exts=ALL_CODE_EXTS | {".json", ".toml", ".yaml", ".yml"})
if findings:
return Result(c, Status.PASS)
return Result(c, Status.FAIL, "No Content-Security-Policy configuration found")
def check_sec13(root, stack):
c = CHECKS["SEC-13"]
if not stack.has_node:
return Result(c, Status.SKIP, "Not a JS/TS project")
eval_findings = grep_files(root, r"(\beval\s*\(|new\s+Function\s*\()", exts=JS_EXTS)
dsi_findings = grep_files(root, r"dangerouslySetInnerHTML", exts=JS_EXTS)
# If dangerouslySetInnerHTML is used, check for DOMPurify
unsafe_dsi = []
for f in dsi_findings:
try:
content = open(os.path.join(root, f.file), errors="ignore").read()
if "DOMPurify" not in content and "sanitize" not in content.lower():
unsafe_dsi.append(f)
except Exception:
unsafe_dsi.append(f)
all_findings = eval_findings + unsafe_dsi # noqa: SEC-AUDITOR
if all_findings:
return Result(c, Status.FAIL, "Unsafe eval() or unsanitized dangerouslySetInnerHTML", all_findings) # noqa: SEC-AUDITOR
return Result(c, Status.PASS)
def check_sec14(root, stack):
c = CHECKS["SEC-14"]
url_findings = grep_files(root,
r"(password|token|secret|key|ssn|credit.card)=",
exts=ALL_CODE_EXTS)
log_findings = grep_files(root,
r"console\.(log|info|debug)\s*\(\s*(req|request)\s*\)",
exts=JS_EXTS)
findings = url_findings + log_findings
if findings:
return Result(c, Status.FAIL, "Sensitive data may appear in URLs or logs", findings[:5])
return Result(c, Status.PASS)
def check_sec17(root, stack):
c = CHECKS["SEC-17"]
# Check for .env files that are not .example/.sample
env_files = []
for entry in os.scandir(root):
name = entry.name
if name.startswith(".env") and name not in (".env.example", ".env.sample",
".env.template", ".env.local.example"):
if entry.is_file():
env_files.append(name)
if not env_files:
return Result(c, Status.PASS)
# Check if git-tracked
gitignore_path = os.path.join(root, ".gitignore")
if os.path.isfile(gitignore_path):
content = open(gitignore_path, errors="ignore").read()
if ".env" in content:
return Result(c, Status.PASS)
return Result(c, Status.FAIL,
f".env file(s) exist ({', '.join(env_files)}) and may not be gitignored",
[Finding(f, 0) for f in env_files])
def check_sec18(root, stack):
c = CHECKS["SEC-18"]
gitignore_path = os.path.join(root, ".gitignore")
if not os.path.isfile(gitignore_path):
return Result(c, Status.FAIL, ".gitignore file not found")
content = open(gitignore_path, errors="ignore").read()
if re.search(r"\.env", content):
return Result(c, Status.PASS)
return Result(c, Status.FAIL, ".env not listed in .gitignore")
def check_db03(root, stack):
c = CHECKS["DB-03"]
# Template literal SQL
tl_findings = grep_files(root,
r"(SELECT|INSERT|UPDATE|DELETE|FROM|WHERE).*\$\{",
exts=JS_EXTS)
# Python f-string SQL
py_findings = grep_files(root,
r'f["\'].*\b(SELECT|INSERT|UPDATE|DELETE|FROM|WHERE)\b.*\{',
exts=PY_EXTS)
# String concat SQL
concat_findings = grep_files(root,
r"(SELECT|INSERT|UPDATE|DELETE|FROM|WHERE).*\+\s*(req\.|params\.|body\.|query\.)",
exts=ALL_CODE_EXTS)
all_findings = tl_findings + py_findings + concat_findings
if all_findings:
return Result(c, Status.FAIL,
f"{len(all_findings)} potential SQL injection vector(s)", all_findings[:10])
return Result(c, Status.PASS)
def check_db05(root, stack):
c = CHECKS["DB-05"]
findings = grep_files(root,
r"(pool|connectionLimit|max_connections|poolSize|pooler|6543)",
exts=ALL_CODE_EXTS | {".env", ".env.local", ".env.production"})
if findings:
return Result(c, Status.PASS)
db_found = grep_files(root, r"(pg\.|postgres\.|mysql\.|mongoose\.)", exts=ALL_CODE_EXTS)
if db_found:
return Result(c, Status.FAIL, "Database usage detected but no connection pooling configured")
return Result(c, Status.SKIP, "No direct DB connection detected")
def check_db06(root, stack):
c = CHECKS["DB-06"]
migration_dirs = []
for dirpath, dirnames, filenames in os.walk(root):
dirnames[:] = [d for d in dirnames if d not in EXCLUDE_DIRS]
depth = dirpath.replace(root, "").count(os.sep)
if depth >= 4:
dirnames[:] = []
continue
for d in dirnames:
if d in ("migrations", "migrate", "versions", "alembic"):
migration_dirs.append(os.path.join(dirpath, d))
if migration_dirs:
return Result(c, Status.PASS)
# Check for database usage
db_found = grep_files(root, r"(prisma|supabase|mongoose|pg\.|sqlite)", exts=ALL_CODE_EXTS)
if db_found:
return Result(c, Status.FAIL, "Database usage found but no migrations directory detected")
return Result(c, Status.SKIP, "No database usage detected")
def check_db07(root, stack):
c = CHECKS["DB-07"]
if not stack.has_supabase:
return Result(c, Status.SKIP, "Not a Supabase project")
sql_findings = grep_files(root, r"CREATE TABLE", exts=SQL_EXTS)
if not sql_findings:
return Result(c, Status.SKIP, "No CREATE TABLE statements found in migrations")
rls_findings = grep_files(root, r"ENABLE ROW LEVEL SECURITY", exts=SQL_EXTS)
if not rls_findings:
return Result(c, Status.FAIL,
f"{len(sql_findings)} table(s) found but no RLS policies detected",
sql_findings[:5])
if len(rls_findings) < len(sql_findings):
return Result(c, Status.FAIL,
f"{len(sql_findings)} table(s) but only {len(rls_findings)} RLS statement(s) — some tables may lack RLS",
sql_findings[:5])
return Result(c, Status.PASS)
def check_db08(root, stack):
c = CHECKS["DB-08"]
if not stack.has_supabase:
return Result(c, Status.SKIP, "Not a Supabase project")
dirs = FRONTEND_DIRS & set(os.listdir(root))
findings = grep_files(root,
r"(service_role|serviceRole|SUPABASE_SERVICE_ROLE)",
exts=JS_EXTS, dirs=dirs if dirs else None)
if findings:
return Result(c, Status.FAIL, "service_role key referenced in client-side code", findings)
return Result(c, Status.PASS)
def check_db12(root, stack):
c = CHECKS["DB-12"]
findings = grep_files(root,
r"(ssn|social_security|credit_card|card_number|passport_number)",
exts=SQL_EXTS | {".prisma"}, flags=re.IGNORECASE)
if findings:
return Result(c, Status.FAIL,
"PII column names found in schema — verify encryption at rest", findings)
return Result(c, Status.PASS)
def check_deploy09(root, stack):
c = CHECKS["DEPLOY-09"]
findings = grep_files(root,
r"(/health|/healthz|/api/health|/status|/readyz)",
exts=ALL_CODE_EXTS)
if findings:
return Result(c, Status.PASS)
return Result(c, Status.FAIL, "No health check endpoint found")
def check_deploy10(root, stack):
c = CHECKS["DEPLOY-10"]
# Check for logging libraries
lib_findings = grep_files(root,
r"(winston|pino|bunyan|morgan|log4js|structlog|loguru)",
exts=ALL_CODE_EXTS | {".json"})
if lib_findings:
return Result(c, Status.PASS)
# Count console.log in server/api code
server_dirs = {"api", "server", "backend"}
for d in ("pages/api", "app/api"):
if os.path.isdir(os.path.join(root, d)):
server_dirs.add(d.split("/")[0])
console_findings = grep_files(root, r"console\.(log|debug|info)\(", exts=JS_EXTS)
if console_findings:
return Result(c, Status.FAIL,
f"No structured logger found; {len(console_findings)} console.log(s) in code",
console_findings[:5])
return Result(c, Status.SKIP, "No server-side code detected")
def check_code01(root, stack):
c = CHECKS["CODE-01"]
if not stack.has_node:
return Result(c, Status.SKIP, "Not a JS/TS project")
findings = grep_files(root, r"console\.(log|debug|info)\(",
exts=JS_EXTS,
dirs=FRONTEND_DIRS & set(os.listdir(root)) or None,
exclude_patterns=[r"//.*console\.(log|debug|info)\("])
if findings:
return Result(c, Status.FAIL, f"{len(findings)} console.log statement(s) in production code", findings[:10])
return Result(c, Status.PASS)
def check_code03(root, stack):
c = CHECKS["CODE-03"]
findings = grep_files(root,
r"catch\s*\([^)]*\)\s*\{\s*\}",
exts=ALL_CODE_EXTS)
if findings:
return Result(c, Status.FAIL, f"{len(findings)} empty catch block(s)", findings)
return Result(c, Status.PASS)
def check_code07(root, stack):
c = CHECKS["CODE-07"]
findings = grep_files(root,
r"(TODO|FIXME|HACK|XXX).{0,20}(auth|security|permission|validation|sanitiz)",
exts=ALL_CODE_EXTS, flags=re.IGNORECASE)
if findings:
return Result(c, Status.FAIL, f"{len(findings)} deferred security TODO(s)", findings)
return Result(c, Status.PASS)
def check_code09(root, stack):
c = CHECKS["CODE-09"]
if not stack.has_react:
return Result(c, Status.SKIP, "Not a React project")
# Next.js App Router: error.tsx
error_page = file_exists_in(root, "error.tsx", "error.jsx", "global-error.tsx")
if error_page:
return Result(c, Status.PASS)
# Class-based error boundary
eb_findings = grep_files(root,
r"(ErrorBoundary|componentDidCatch|getDerivedStateFromError)",
exts=JS_EXTS)
if eb_findings:
return Result(c, Status.PASS)
return Result(c, Status.FAIL, "No React error boundary or error.tsx found")
def check_code10(root, stack):
c = CHECKS["CODE-10"]
findings = grep_files(root,
r"(error\.stack|\.stack\s*\)|err\.message.*res\.(json|send)|traceback\.format_exc)",
exts=ALL_CODE_EXTS)
if findings:
return Result(c, Status.FAIL, "Potential stack trace leak in error responses", findings)
return Result(c, Status.PASS)
def check_code11(root, stack):
c = CHECKS["CODE-11"]
if not stack.has_node:
return Result(c, Status.SKIP, "Not a JS/TS project")
findings = grep_files(root,
r"eslint-disable.*(no-eval|no-implied-eval|no-script-url|security)",
exts=JS_EXTS)
if findings:
return Result(c, Status.FAIL, "Security lint rule(s) disabled", findings)
return Result(c, Status.PASS)
def check_code12(root, stack):
c = CHECKS["CODE-12"]
lockfiles = ["package-lock.json", "pnpm-lock.yaml", "yarn.lock", "bun.lockb",
"Pipfile.lock", "poetry.lock", "Gemfile.lock", "go.sum", "Cargo.lock"]
for lf in lockfiles:
if os.path.isfile(os.path.join(root, lf)):
return Result(c, Status.PASS)
return Result(c, Status.FAIL, "No lockfile found — dependencies are not pinned")
def check_code13(root, stack):
c = CHECKS["CODE-13"]
if not stack.has_node:
return Result(c, Status.SKIP, "Not a JS/TS project")
pkg_path = os.path.join(root, "package.json")
if not os.path.isfile(pkg_path):
return Result(c, Status.SKIP)
findings = grep_files(root, r'"[^"]+"\s*:\s*"\*"', exts={".json"})
findings += grep_files(root, r'"[^"]+"\s*:\s*"latest"', exts={".json"})
findings = [f for f in findings if "package.json" in f.file and "node_modules" not in f.file]
if findings:
return Result(c, Status.FAIL, "Wildcard (*) or 'latest' version found in package.json", findings)
return Result(c, Status.PASS)
def check_code14(root, stack):
c = CHECKS["CODE-14"]
if not stack.has_typescript:
return Result(c, Status.SKIP, "Not a TypeScript project")
tsconfig_path = os.path.join(root, "tsconfig.json")
if not os.path.isfile(tsconfig_path):
return Result(c, Status.SKIP, "tsconfig.json not found")
content = open(tsconfig_path, errors="ignore").read()
if re.search(r'"strict"\s*:\s*true', content):
return Result(c, Status.PASS)
return Result(c, Status.FAIL, "TypeScript strict mode not enabled in tsconfig.json",
[Finding("tsconfig.json", 0)])
def check_ai01(root, stack):
c = CHECKS["AI-01"]
if not stack.has_ai:
return Result(c, Status.SKIP, "No AI/LLM usage detected")
dirs = FRONTEND_DIRS & set(os.listdir(root))
findings = grep_files(root,
r"(system.?prompt|system.?message|system_instruction)",
exts=ALL_CODE_EXTS, dirs=dirs if dirs else None, flags=re.IGNORECASE)
if findings:
return Result(c, Status.FAIL,
"System prompt referenced in client-accessible code — may be leakable",
findings)
return Result(c, Status.PASS)
def check_ai02(root, stack):
c = CHECKS["AI-02"]
if not stack.has_ai:
return Result(c, Status.SKIP, "No AI/LLM usage detected")
findings = grep_files(root,
r"(messages\.push|content\s*:.*\$\{|content\s*:.*\+\s*user|prompt.*\+)",
exts=ALL_CODE_EXTS)
if findings:
return Result(c, Status.FAIL,
"User input may be concatenated directly into AI prompt", findings[:5])
return Result(c, Status.PASS)
def check_ai03(root, stack):
c = CHECKS["AI-03"]
if not stack.has_ai:
return Result(c, Status.SKIP, "No AI/LLM usage detected")
dirs = FRONTEND_DIRS & set(os.listdir(root))
findings = grep_files(root,
r"(OPENAI_API_KEY|ANTHROPIC_API_KEY|GOOGLE_AI_API_KEY|sk-ant-|sk-proj-)",
exts=JS_EXTS, dirs=dirs if dirs else None)
if findings:
return Result(c, Status.FAIL, "LLM API key referenced in frontend code", findings)
return Result(c, Status.PASS)
def check_ai05(root, stack):
c = CHECKS["AI-05"]
if not stack.has_ai:
return Result(c, Status.SKIP, "No AI/LLM usage detected")
findings = grep_files(root,
r"dangerouslySetInnerHTML.*\b(response|result|completion|message|content)\b",
exts=JS_EXTS)
if findings:
return Result(c, Status.FAIL, "AI output rendered via dangerouslySetInnerHTML", findings)
return Result(c, Status.PASS)
def check_dep01(root, stack):
c = CHECKS["DEP-01"]
if not stack.has_node:
return Result(c, Status.SKIP, "Not a Node.js project")
findings = grep_files(root,
r'"[^"]+"\s*:\s*"(git://|git\+|github:|https://github\.com|file:)',
exts={".json"})
findings = [f for f in findings if "package.json" in f.file and "node_modules" not in f.file]
if findings:
return Result(c, Status.FAIL, "Git/URL-based dependency found in package.json", findings)
return Result(c, Status.PASS)
def check_dep05(root, stack):
c = CHECKS["DEP-05"]
if not stack.has_node:
return Result(c, Status.SKIP, "Not a Node.js project")
pkg_path = os.path.join(root, "package.json")
if not os.path.isfile(pkg_path):
return Result(c, Status.SKIP)
pkg = read_json_file(pkg_path)
scripts = pkg.get("scripts", {})
suspicious = []
for key in ("preinstall", "postinstall", "install"):
val = scripts.get(key, "")
if val and any(kw in val for kw in ("curl", "wget", "fetch", "exec", "eval", "sh ", "bash ")):
suspicious.append(Finding("package.json", 0, f'"{key}": "{val}"'))
if suspicious:
return Result(c, Status.FAIL, "Suspicious install script detected in package.json", suspicious)
return Result(c, Status.PASS)
def check_dep06(root, stack):
c = CHECKS["DEP-06"]
if not stack.has_node:
return Result(c, Status.SKIP, "Not a Node.js project")
pkg_path = os.path.join(root, "package.json")
if not os.path.isfile(pkg_path):
return Result(c, Status.SKIP)
findings = grep_files(root, r'"\*"', exts={".json"})
findings = [f for f in findings if "package.json" in f.file and "node_modules" not in f.file]
if findings:
return Result(c, Status.FAIL, "Wildcard (*) version found", findings)
return Result(c, Status.PASS)
def check_fe01(root, stack):
c = CHECKS["FE-01"]
if not stack.is_web and not stack.has_node:
return Result(c, Status.SKIP, "Not a web project")
# Next.js metadata export
meta_findings = grep_files(root,
r"(export\s+(const|async\s+function)\s+metadata|generateMetadata|<title>|og:title|og:description)",
exts=JS_EXTS | {".html"})
if meta_findings:
return Result(c, Status.PASS)
return Result(c, Status.FAIL, "No meta tags or Next.js metadata export found")
def check_fe02(root, stack):
c = CHECKS["FE-02"]
if not stack.is_web and not stack.has_node:
return Result(c, Status.SKIP, "Not a web project")
favicon = file_exists_in(root, "favicon.ico", "favicon.png", "favicon.svg",
"favicon.webp", "icon.png", "icon.ico")
if favicon:
return Result(c, Status.PASS)
return Result(c, Status.FAIL, "No favicon file found")
def check_fe03(root, stack):
c = CHECKS["FE-03"]
if not stack.is_web and not stack.has_node:
return Result(c, Status.SKIP, "Not a web project")
page_404 = file_exists_in(root, "404.tsx", "404.jsx", "404.html",
"not-found.tsx", "not-found.jsx")
if page_404:
return Result(c, Status.PASS)
return Result(c, Status.FAIL, "No custom 404 or not-found page found")
def check_fe09(root, stack):
c = CHECKS["FE-09"]
if not stack.is_web and not stack.has_node:
return Result(c, Status.SKIP, "Not a web project")
public_robots = os.path.join(root, "public", "robots.txt")
root_robots = os.path.join(root, "robots.txt")
if os.path.isfile(public_robots) or os.path.isfile(root_robots):
return Result(c, Status.PASS)
return Result(c, Status.FAIL, "No robots.txt found")
def check_obs01(root, stack):
c = CHECKS["OBS-01"]
findings = grep_files(root,
r"(@sentry/|sentry-|LogRocket|Bugsnag|datadogRum|Rollbar|Honeybadger|newrelic)",
exts=ALL_CODE_EXTS | {".json"})
if findings:
return Result(c, Status.PASS)
return Result(c, Status.FAIL, "No error monitoring library detected")
def check_obs03(root, stack):
c = CHECKS["OBS-03"]
findings = grep_files(root,
r"(winston|pino|bunyan|structlog|loguru|import logging)",
exts=ALL_CODE_EXTS | {".json"})
if findings:
return Result(c, Status.PASS)
return Result(c, Status.FAIL, "No structured logging library detected")
CATEGORY_CHECKS = {
"SEC": [check_sec01, check_sec04, check_sec05, check_sec06, check_sec07,
check_sec08, check_sec11, check_sec13, check_sec14, check_sec17, check_sec18],
"DB": [check_db03, check_db05, check_db06, check_db07, check_db08, check_db12],
"DEPLOY": [check_deploy09, check_deploy10],
"CODE": [check_code01, check_code03, check_code07, check_code09, check_code10,
check_code11, check_code12, check_code13, check_code14],
"AI": [check_ai01, check_ai02, check_ai03, check_ai05],
"DEP": [check_dep01, check_dep05, check_dep06],
"FE": [check_fe01, check_fe02, check_fe03, check_fe09],
"OBS": [check_obs01, check_obs03],
}
CATEGORY_ORDER = ["SEC", "DB", "CODE", "DEP", "AI", "DEPLOY", "FE", "OBS"]
# ---------------------------------------------------------------------------
# Manual check runner
# ---------------------------------------------------------------------------
def run_manual_checks(stack: Stack, interactive: bool, category_filter: Optional[str]) -> List[Result]:
results = []
applicable = []
for chk in MANUAL_CHECKS:
if category_filter and chk.category != category_filter.upper():
continue
if chk.stack == "ai" and not stack.has_ai:
results.append(Result(chk, Status.SKIP, "No AI/LLM usage detected"))
continue
if chk.stack == "web" and not stack.is_web:
results.append(Result(chk, Status.SKIP, "Not a web project"))
continue
if chk.stack == "vps" and stack.deploy_target not in ("docker", "vps", ""):
results.append(Result(chk, Status.SKIP, "Not a VPS/Docker deployment"))
continue
applicable.append(chk)
if not interactive or not applicable:
for chk in applicable:
results.append(Result(chk, Status.MANUAL, "Not confirmed (run without --no-interactive to answer)"))
return results
print()
print(bold("Manual Checks") + " — answer Y/N for each:")
print()
for chk in applicable:
sev_label = {
Severity.CRITICAL: red("CRITICAL"),
Severity.HIGH: yellow("HIGH"),
Severity.ADVISORY: dim("ADVISORY"),
}[chk.severity]
while True:
try:
answer = input(f" [{sev_label}] [{chk.id}] {chk.description} [y/N]: ").strip().lower()
except (EOFError, KeyboardInterrupt):
answer = "n"
if answer in ("y", "yes"):
results.append(Result(chk, Status.PASS))
break
elif answer in ("n", "no", ""):
results.append(Result(chk, Status.FAIL, "Not confirmed"))
break
print(" Please enter Y or N.")
return results
# ---------------------------------------------------------------------------
# Verdict / output
# ---------------------------------------------------------------------------
def severity_for_result(r: Result) -> Severity:
return r.check.severity
def print_report(all_results: List[Result], stack: Stack, scan_time: float,
verbose: bool) -> int:
critical = [r for r in all_results if r.status in (Status.FAIL, Status.MANUAL)
and r.check.severity == Severity.CRITICAL]
high = [r for r in all_results if r.status in (Status.FAIL, Status.MANUAL)
and r.check.severity == Severity.HIGH]
advisory = [r for r in all_results if r.status in (Status.FAIL, Status.MANUAL)
and r.check.severity == Severity.ADVISORY]
stack_desc = []
if stack.framework: stack_desc.append(stack.framework.capitalize())
if stack.has_supabase: stack_desc.append("Supabase")
if stack.deploy_target: stack_desc.append(stack.deploy_target.capitalize())
if stack.has_python and stack.py_framework: stack_desc.append(stack.py_framework.capitalize())
if not stack_desc: stack_desc.append("Unknown")
stack_str = " + ".join(stack_desc)
print()
print(bold("SHIP GATE REPORT"))
print("=" * 48)
print(f"Stack: {stack_str}")
print(f"Scan time: {scan_time:.1f}s")
print(f"Checks: {len(all_results)} total")
print()
def _section(label, items, color_fn):
if not items and not verbose:
return
print(bold(f"{label} ({len(items)} item{'s' if len(items) != 1 else ''})"))
for r in items:
status_str = {
Status.FAIL: red("FAIL "),
Status.MANUAL: yellow("MANUAL"),
Status.PASS: green("PASS "),
Status.SKIP: dim("SKIP "),
}[r.status]
print(f" {status_str} [{r.check.id}] {r.check.description}")
if r.message:
print(f" {dim(r.message)}")
for f in r.findings[:3]:
print(f" {dim(f.file)}:{f.line} {dim(f.snippet[:80])}")
print()
if critical:
_section(red("CRITICAL") + " (must fix before shipping)", critical, red)
if high:
_section(yellow("HIGH") + " (should fix before shipping)", high, yellow)
if advisory:
_section(dim("ADVISORY") + " (recommended)", advisory, dim)
if verbose:
passed = [r for r in all_results if r.status == Status.PASS]
if passed:
_section(green("PASS"), passed, green)
skipped = [r for r in all_results if r.status == Status.SKIP]
if skipped:
_section(dim("SKIP"), skipped, dim)
if critical:
print(red(bold(f"VERDICT: DO NOT SHIP ({len(critical)} critical issue{'s' if len(critical) != 1 else ''})")))
print("Fix critical items and re-run.")
return 1
elif high:
print(yellow(bold(f"VERDICT: SHIP WITH CAUTION ({len(high)} high issue{'s' if len(high) != 1 else ''})")))
print("Acknowledge risks and proceed only if you accept them.")
return 2
else:
print(green(bold("VERDICT: CLEAR TO SHIP")))
return 0
def print_json_report(all_results: List[Result], stack: Stack, scan_time: float) -> int:
critical = [r for r in all_results if r.status in (Status.FAIL, Status.MANUAL)
and r.check.severity == Severity.CRITICAL]
high = [r for r in all_results if r.status in (Status.FAIL, Status.MANUAL)
and r.check.severity == Severity.HIGH]
output = {
"version": VERSION,
"scan_time": round(scan_time, 2),
"stack": {
"framework": stack.framework,
"has_supabase": stack.has_supabase,
"has_typescript": stack.has_typescript,
"deploy_target": stack.deploy_target,
"has_ai": stack.has_ai,
},
"results": [
{
"id": r.check.id,
"description": r.check.description,
"severity": r.check.severity.value,
"category": r.check.category,
"status": r.status.value,
"message": r.message,
"findings": [
{"file": f.file, "line": f.line, "snippet": f.snippet}
for f in r.findings
],
}
for r in all_results
],
"summary": {
"critical": len(critical),
"high": len(high),
"verdict": "DO_NOT_SHIP" if critical else ("SHIP_WITH_CAUTION" if high else "CLEAR_TO_SHIP"),
},
}
print(json.dumps(output, indent=2))
return 1 if critical else (2 if high else 0)
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def main():
global USE_COLOR
parser = argparse.ArgumentParser(
description="Ship Gate — pre-production audit scanner",
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument("path", nargs="?", default=".",
help="Project root directory (default: current directory)")
parser.add_argument("--json", action="store_true", help="Output as JSON")
parser.add_argument("--no-color", action="store_true", help="Disable color output")
parser.add_argument("--no-interactive", action="store_true",
help="Skip manual confirmation prompts")
parser.add_argument("--category", metavar="CAT",
help="Only run one category: SEC, DB, CODE, DEP, AI, DEPLOY, FE, OBS")
parser.add_argument("--verbose", action="store_true",
help="Show PASS and SKIP results in addition to failures")
parser.add_argument("--version", action="version", version=f"ship-gate {VERSION}")
args = parser.parse_args()
if args.no_color or not sys.stdout.isatty():
USE_COLOR = False
root = os.path.abspath(args.path)
if not os.path.isdir(root):
print(f"Error: '{root}' is not a directory", file=sys.stderr)
sys.exit(1)
start = time.time()
# Detect stack
stack = detect_stack(root)
if not args.json:
print(bold("Detecting stack..."), end=" ", flush=True)
parts = []
if stack.framework: parts.append(stack.framework.capitalize())
if stack.has_supabase: parts.append("Supabase")
if stack.deploy_target: parts.append(stack.deploy_target.capitalize())
if stack.has_python and stack.py_framework: parts.append(stack.py_framework.capitalize())
if stack.has_ai: parts.append(f"AI({','.join(stack.ai_providers)})")
print(", ".join(parts) if parts else "generic project")
# Run automated checks
all_results: List[Result] = []
categories = [args.category.upper()] if args.category else CATEGORY_ORDER
for i, cat in enumerate(categories, 1):
fns = CATEGORY_CHECKS.get(cat, [])
cat_results = []
for fn in fns:
try:
r = fn(root, stack)
except Exception as e:
chk_id = fn.__name__.replace("check_", "").replace("_", "-").upper()
cat_results.append(Result(
CheckDef(chk_id, fn.__doc__ or fn.__name__, Severity.ADVISORY, cat),
Status.SKIP, f"Scanner error: {e}",
))
continue
cat_results.append(r)
all_results.extend(cat_results)
if not args.json:
n_fail = sum(1 for r in cat_results if r.status == Status.FAIL)
n_pass = sum(1 for r in cat_results if r.status == Status.PASS)
n_skip = sum(1 for r in cat_results if r.status == Status.SKIP)
label = red(f"{n_fail} FAIL") if n_fail else green("0 FAIL")
print(f" [{i}/{len(categories)}] {cat}: {label}, {n_pass} PASS, {dim(str(n_skip) + ' SKIP')}")
# Manual checks
manual_results = run_manual_checks(stack, not args.no_interactive, args.category)
all_results.extend(manual_results)
scan_time = time.time() - start
if args.json:
sys.exit(print_json_report(all_results, stack, scan_time))
else:
sys.exit(print_report(all_results, stack, scan_time, args.verbose))
if __name__ == "__main__":
main()
Lập kế hoạch phát hành, quản lý changelog, điều phối triển khai, tạo nhánh release và tự động hóa đánh phiên bản.
---
name: "release-manager"
description: "Use when the user asks to plan releases, manage changelogs, coordinate deployments, create release branches, or automate versioning."
---
# Release Manager
**Tier:** POWERFUL
**Category:** Engineering
**Domain:** Software Release Management & DevOps
## Overview
The Release Manager skill provides comprehensive tools and knowledge for managing software releases end-to-end. From parsing conventional commits to generating changelogs, determining version bumps, and orchestrating release processes, this skill ensures reliable, predictable, and well-documented software releases.
## Core Capabilities
- **Automated Changelog Generation** from git history using conventional commits
- **Semantic Version Bumping** based on commit analysis and breaking changes
- **Release Readiness Assessment** with comprehensive checklists and validation
- **Release Planning & Coordination** with stakeholder communication templates
- **Rollback Planning** with automated recovery procedures
- **Hotfix Management** for emergency releases
- **Feature Flag Integration** for progressive rollouts
## Key Components
### Scripts
1. **changelog_generator.py** - Parses git logs and generates structured changelogs
2. **version_bumper.py** - Determines correct version bumps from conventional commits
3. **release_planner.py** - Assesses release readiness and generates coordination plans
### Documentation
- Comprehensive release management methodology
- Conventional commits specification and examples
- Release workflow comparisons (Git Flow, Trunk-based, GitHub Flow)
- Hotfix procedures and emergency response protocols
## Release Management Methodology
### Semantic Versioning (SemVer)
Semantic Versioning follows the MAJOR.MINOR.PATCH format where:
- **MAJOR** version when you make incompatible API changes
- **MINOR** version when you add functionality in a backwards compatible manner
- **PATCH** version when you make backwards compatible bug fixes
#### Pre-release Versions
Pre-release versions are denoted by appending a hyphen and identifiers:
- `1.0.0-alpha.1` - Alpha releases for early testing
- `1.0.0-beta.2` - Beta releases for wider testing
- `1.0.0-rc.1` - Release candidates for final validation
#### Version Precedence
Version precedence is determined by comparing each identifier:
1. `1.0.0-alpha` < `1.0.0-alpha.1` < `1.0.0-alpha.beta` < `1.0.0-beta`
2. `1.0.0-beta` < `1.0.0-beta.2` < `1.0.0-beta.11` < `1.0.0-rc.1`
3. `1.0.0-rc.1` < `1.0.0`
### Conventional Commits
Conventional Commits provide a structured format for commit messages that enables automated tooling:
#### Format
```
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
```
#### Types
- **feat**: A new feature (correlates with MINOR version bump)
- **fix**: A bug fix (correlates with PATCH version bump)
- **docs**: Documentation only changes
- **style**: Changes that do not affect the meaning of the code
- **refactor**: A code change that neither fixes a bug nor adds a feature
- **perf**: A code change that improves performance
- **test**: Adding missing tests or correcting existing tests
- **chore**: Changes to the build process or auxiliary tools
- **ci**: Changes to CI configuration files and scripts
- **build**: Changes that affect the build system or external dependencies
- **breaking**: Introduces a breaking change (correlates with MAJOR version bump)
#### Examples
```
feat(user-auth): add OAuth2 integration
fix(api): resolve race condition in user creation
docs(readme): update installation instructions
feat!: remove deprecated payment API
BREAKING CHANGE: The legacy payment API has been removed
```
### Automated Changelog Generation
Changelogs are automatically generated from conventional commits, organized by:
#### Structure
```markdown
# Changelog
## [Unreleased]
### Added
### Changed
### Deprecated
### Removed
### Fixed
### Security
## [1.2.0] - 2024-01-15
### Added
- OAuth2 authentication support (#123)
- User preference dashboard (#145)
### Fixed
- Race condition in user creation (#134)
- Memory leak in image processing (#156)
### Breaking Changes
- Removed legacy payment API
```
#### Grouping Rules
- **Added** for new features (feat)
- **Fixed** for bug fixes (fix)
- **Changed** for changes in existing functionality
- **Deprecated** for soon-to-be removed features
- **Removed** for now removed features
- **Security** for vulnerability fixes
#### Metadata Extraction
- Link to pull requests and issues: `(#123)`
- Breaking changes highlighted prominently
- Scope-based grouping: `auth:`, `api:`, `ui:`
- Co-authored-by for contributor recognition
### Version Bump Strategies
Version bumps are determined by analyzing commits since the last release:
#### Automatic Detection Rules
1. **MAJOR**: Any commit with `BREAKING CHANGE` or `!` after type
2. **MINOR**: Any `feat` type commits without breaking changes
3. **PATCH**: `fix`, `perf`, `security` type commits
4. **NO BUMP**: `docs`, `style`, `test`, `chore`, `ci`, `build` only
#### Pre-release Handling
```python
# Alpha: 1.0.0-alpha.1 → 1.0.0-alpha.2
# Beta: 1.0.0-alpha.5 → 1.0.0-beta.1
# RC: 1.0.0-beta.3 → 1.0.0-rc.1
# Release: 1.0.0-rc.2 → 1.0.0
```
#### Multi-package Considerations
For monorepos with multiple packages:
- Analyze commits affecting each package independently
- Support scoped version bumps: `@scope/package@1.2.3`
- Generate coordinated release plans across packages
### Release Branch Workflows
#### Git Flow
```
main (production) ← release/1.2.0 ← develop ← feature/login
← hotfix/critical-fix
```
**Advantages:**
- Clear separation of concerns
- Stable main branch
- Parallel feature development
- Structured release process
**Process:**
1. Create release branch from develop: `git checkout -b release/1.2.0 develop`
2. Finalize release (version bump, changelog)
3. Merge to main and develop
4. Tag release: `git tag v1.2.0`
5. Deploy from main
#### Trunk-based Development
```
main ← feature/login (short-lived)
← feature/payment (short-lived)
← hotfix/critical-fix
```
**Advantages:**
- Simplified workflow
- Faster integration
- Reduced merge conflicts
- Continuous integration friendly
**Process:**
1. Short-lived feature branches (1-3 days)
2. Frequent commits to main
3. Feature flags for incomplete features
4. Automated testing gates
5. Deploy from main with feature toggles
#### GitHub Flow
```
main ← feature/login
← hotfix/critical-fix
```
**Advantages:**
- Simple and lightweight
- Fast deployment cycle
- Good for web applications
- Minimal overhead
**Process:**
1. Create feature branch from main
2. Regular commits and pushes
3. Open pull request when ready
4. Deploy from feature branch for testing
5. Merge to main and deploy
### Feature Flag Integration
Feature flags enable safe, progressive rollouts:
#### Types of Feature Flags
- **Release flags**: Control feature visibility in production
- **Experiment flags**: A/B testing and gradual rollouts
- **Operational flags**: Circuit breakers and performance toggles
- **Permission flags**: Role-based feature access
#### Implementation Strategy
```python
# Progressive rollout example
if feature_flag("new_payment_flow", user_id):
return new_payment_processor.process(payment)
else:
return legacy_payment_processor.process(payment)
```
#### Release Coordination
1. Deploy code with feature behind flag (disabled)
2. Gradually enable for percentage of users
3. Monitor metrics and error rates
4. Full rollout or quick rollback based on data
5. Remove flag in subsequent release
### Release Readiness Checklists
#### Pre-Release Validation
- [ ] All planned features implemented and tested
- [ ] Breaking changes documented with migration guide
- [ ] API documentation updated
- [ ] Database migrations tested
- [ ] Security review completed for sensitive changes
- [ ] Performance testing passed thresholds
- [ ] Internationalization strings updated
- [ ] Third-party integrations validated
#### Quality Gates
- [ ] Unit test coverage ≥ 85%
- [ ] Integration tests passing
- [ ] End-to-end tests passing
- [ ] Static analysis clean
- [ ] Security scan passed
- [ ] Dependency audit clean
- [ ] Load testing completed
#### Documentation Requirements
- [ ] CHANGELOG.md updated
- [ ] README.md reflects new features
- [ ] API documentation generated
- [ ] Migration guide written for breaking changes
- [ ] Deployment notes prepared
- [ ] Rollback procedure documented
#### Stakeholder Approvals
- [ ] Product Manager sign-off
- [ ] Engineering Lead approval
- [ ] QA validation complete
- [ ] Security team clearance
- [ ] Legal review (if applicable)
- [ ] Compliance check (if regulated)
### Deployment Coordination
#### Communication Plan
**Internal Stakeholders:**
- Engineering team: Technical changes and rollback procedures
- Product team: Feature descriptions and user impact
- Support team: Known issues and troubleshooting guides
- Sales team: Customer-facing changes and talking points
**External Communication:**
- Release notes for users
- API changelog for developers
- Migration guide for breaking changes
- Downtime notifications if applicable
#### Deployment Sequence
1. **Pre-deployment** (T-24h): Final validation, freeze code
2. **Database migrations** (T-2h): Run and validate schema changes
3. **Blue-green deployment** (T-0): Switch traffic gradually
4. **Post-deployment** (T+1h): Monitor metrics and logs
5. **Rollback window** (T+4h): Decision point for rollback
#### Monitoring & Validation
- Application health checks
- Error rate monitoring
- Performance metrics tracking
- User experience monitoring
- Business metrics validation
- Third-party service integration health
### Hotfix Procedures
Hotfixes address critical production issues requiring immediate deployment:
#### Severity Classification
**P0 - Critical**: Complete system outage, data loss, security breach
- **SLA**: Fix within 2 hours
- **Process**: Emergency deployment, all hands on deck
- **Approval**: Engineering Lead + On-call Manager
**P1 - High**: Major feature broken, significant user impact
- **SLA**: Fix within 24 hours
- **Process**: Expedited review and deployment
- **Approval**: Engineering Lead + Product Manager
**P2 - Medium**: Minor feature issues, limited user impact
- **SLA**: Fix in next release cycle
- **Process**: Normal review process
- **Approval**: Standard PR review
#### Emergency Response Process
1. **Incident declaration**: Page on-call team
2. **Assessment**: Determine severity and impact
3. **Hotfix branch**: Create from last stable release
4. **Minimal fix**: Address root cause only
5. **Expedited testing**: Automated tests + manual validation
6. **Emergency deployment**: Deploy to production
7. **Post-incident**: Root cause analysis and prevention
### Rollback Planning
Every release must have a tested rollback plan:
#### Rollback Triggers
- **Error rate spike**: >2x baseline within 30 minutes
- **Performance degradation**: >50% latency increase
- **Feature failures**: Core functionality broken
- **Security incident**: Vulnerability exploited
- **Data corruption**: Database integrity compromised
#### Rollback Types
**Code Rollback:**
- Revert to previous Docker image
- Database-compatible code changes only
- Feature flag disable preferred over code rollback
**Database Rollback:**
- Only for non-destructive migrations
- Data backup required before migration
- Forward-only migrations preferred (add columns, not drop)
**Infrastructure Rollback:**
- Blue-green deployment switch
- Load balancer configuration revert
- DNS changes (longer propagation time)
#### Automated Rollback
```python
# Example rollback automation
def monitor_deployment():
if error_rate() > THRESHOLD:
alert_oncall("Error rate spike detected")
if auto_rollback_enabled():
execute_rollback()
```
### Release Metrics & Analytics
#### Key Performance Indicators
- **Lead Time**: From commit to production
- **Deployment Frequency**: Releases per week/month
- **Mean Time to Recovery**: From incident to resolution
- **Change Failure Rate**: Percentage of releases causing incidents
#### Quality Metrics
- **Rollback Rate**: Percentage of releases rolled back
- **Hotfix Rate**: Hotfixes per regular release
- **Bug Escape Rate**: Production bugs per release
- **Time to Detection**: How quickly issues are identified
#### Process Metrics
- **Review Time**: Time spent in code review
- **Testing Time**: Automated + manual testing duration
- **Approval Cycle**: Time from PR to merge
- **Release Preparation**: Time spent on release activities
### Tool Integration
#### Version Control Systems
- **Git**: Primary VCS with conventional commit parsing
- **GitHub/GitLab**: Pull request automation and CI/CD
- **Bitbucket**: Pipeline integration and deployment gates
#### CI/CD Platforms
- **Jenkins**: Pipeline orchestration and deployment automation
- **GitHub Actions**: Workflow automation and release publishing
- **GitLab CI**: Integrated pipelines with environment management
- **CircleCI**: Container-based builds and deployments
#### Monitoring & Alerting
- **DataDog**: Application performance monitoring
- **New Relic**: Error tracking and performance insights
- **Sentry**: Error aggregation and release tracking
- **PagerDuty**: Incident response and escalation
#### Communication Platforms
- **Slack**: Release notifications and coordination
- **Microsoft Teams**: Stakeholder communication
- **Email**: External customer notifications
- **Status Pages**: Public incident communication
## Best Practices
### Release Planning
1. **Regular cadence**: Establish predictable release schedule
2. **Feature freeze**: Lock changes 48h before release
3. **Risk assessment**: Evaluate changes for potential impact
4. **Stakeholder alignment**: Ensure all teams are prepared
### Quality Assurance
1. **Automated testing**: Comprehensive test coverage
2. **Staging environment**: Production-like testing environment
3. **Canary releases**: Gradual rollout to subset of users
4. **Monitoring**: Proactive issue detection
### Communication
1. **Clear timelines**: Communicate schedules early
2. **Regular updates**: Status reports during release process
3. **Issue transparency**: Honest communication about problems
4. **Post-mortems**: Learn from incidents and improve
### Automation
1. **Reduce manual steps**: Automate repetitive tasks
2. **Consistent process**: Same steps every time
3. **Audit trails**: Log all release activities
4. **Self-service**: Enable teams to deploy safely
## Common Anti-patterns
### Process Anti-patterns
- **Manual deployments**: Error-prone and inconsistent
- **Last-minute changes**: Risk introduction without proper testing
- **Skipping testing**: Deploying without validation
- **Poor communication**: Stakeholders unaware of changes
### Technical Anti-patterns
- **Monolithic releases**: Large, infrequent releases with high risk
- **Coupled deployments**: Services that must be deployed together
- **No rollback plan**: Unable to quickly recover from issues
- **Environment drift**: Production differs from staging
### Cultural Anti-patterns
- **Blame culture**: Fear of making changes or reporting issues
- **Hero culture**: Relying on individuals instead of process
- **Perfectionism**: Delaying releases for minor improvements
- **Risk aversion**: Avoiding necessary changes due to fear
## Getting Started
1. **Assessment**: Evaluate current release process and pain points
2. **Tool setup**: Configure scripts for your repository
3. **Process definition**: Choose appropriate workflow for your team
4. **Automation**: Implement CI/CD pipelines and quality gates
5. **Training**: Educate team on new processes and tools
6. **Monitoring**: Set up metrics and alerting for releases
7. **Iteration**: Continuously improve based on feedback and metrics
The Release Manager skill transforms chaotic deployments into predictable, reliable releases that build confidence across your entire organization.
FILE:assets/sample_commits.json
[
{
"hash": "a1b2c3d",
"author": "Sarah Johnson <sarah.johnson@example.com>",
"date": "2024-01-15T14:30:22Z",
"message": "feat(auth): add OAuth2 integration with Google and GitHub\n\nImplement OAuth2 authentication flow supporting Google and GitHub providers.\nUsers can now sign in using their existing social media accounts, improving\nuser experience and reducing password fatigue.\n\n- Add OAuth2 client configuration\n- Implement authorization code flow\n- Add user profile mapping from providers\n- Include comprehensive error handling\n\nCloses #123\nResolves #145"
},
{
"hash": "e4f5g6h",
"author": "Mike Chen <mike.chen@example.com>",
"date": "2024-01-15T13:45:18Z",
"message": "fix(api): resolve race condition in user creation endpoint\n\nFixed a race condition that occurred when multiple requests attempted\nto create users with the same email address simultaneously. This was\ncausing duplicate user records in some edge cases.\n\n- Added database unique constraint on email field\n- Implemented proper error handling for constraint violations\n- Added retry logic with exponential backoff\n\nFixes #234"
},
{
"hash": "i7j8k9l",
"author": "Emily Davis <emily.davis@example.com>",
"date": "2024-01-15T12:20:45Z",
"message": "docs(readme): update installation and deployment instructions\n\nUpdated README with comprehensive installation guide including:\n- Docker setup instructions\n- Environment variable configuration\n- Database migration steps\n- Troubleshooting common issues"
},
{
"hash": "m1n2o3p",
"author": "David Wilson <david.wilson@example.com>",
"date": "2024-01-15T11:15:30Z",
"message": "feat(ui)!: redesign dashboard with new component library\n\nComplete redesign of the user dashboard using our new component library.\nThis provides better accessibility, improved mobile responsiveness, and\na more modern user interface.\n\nBREAKING CHANGE: The dashboard API endpoints have changed structure.\nFrontend clients must update to use the new /v2/dashboard endpoints.\nThe legacy /v1/dashboard endpoints will be removed in version 3.0.0.\n\n- Implement new Card, Grid, and Chart components\n- Add responsive breakpoints for mobile devices\n- Improve accessibility with proper ARIA labels\n- Add dark mode support\n\nCloses #345, #367, #389"
},
{
"hash": "q4r5s6t",
"author": "Lisa Rodriguez <lisa.rodriguez@example.com>",
"date": "2024-01-15T10:45:12Z",
"message": "fix(db): optimize slow query in user search functionality\n\nOptimized the user search query that was causing performance issues\non databases with large user counts. Query time reduced from 2.5s to 150ms.\n\n- Added composite index on (email, username, created_at)\n- Refactored query to use more efficient JOIN structure\n- Added query result caching for common search patterns\n\nFixes #456"
},
{
"hash": "u7v8w9x",
"author": "Tom Anderson <tom.anderson@example.com>",
"date": "2024-01-15T09:30:55Z",
"message": "chore(deps): upgrade React to version 18.2.0\n\nUpgrade React and related dependencies to latest stable versions.\nThis includes performance improvements and new concurrent features.\n\n- React: 17.0.2 → 18.2.0\n- React-DOM: 17.0.2 → 18.2.0\n- React-Router: 6.8.0 → 6.8.1\n- Updated all peer dependencies"
},
{
"hash": "y1z2a3b",
"author": "Jennifer Kim <jennifer.kim@example.com>",
"date": "2024-01-15T08:15:33Z",
"message": "test(auth): add comprehensive tests for OAuth flow\n\nAdded unit and integration tests for the OAuth2 authentication system\nto ensure reliability and prevent regressions.\n\n- Unit tests for OAuth client configuration\n- Integration tests for complete auth flow\n- Mock providers for testing without external dependencies\n- Error scenario testing\n\nTest coverage increased from 72% to 89% for auth module."
},
{
"hash": "c4d5e6f",
"author": "Alex Thompson <alex.thompson@example.com>",
"date": "2024-01-15T07:45:20Z",
"message": "perf(image): implement WebP compression reducing size by 40%\n\nReplaced PNG compression with WebP format for uploaded images.\nThis reduces average image file sizes by 40% while maintaining\nvisual quality, improving page load times and reducing bandwidth costs.\n\n- Add WebP encoding support\n- Implement fallback to PNG for older browsers\n- Add quality settings configuration\n- Update image serving endpoints\n\nPerformance improvement: Page load time reduced by 25% on average."
},
{
"hash": "g7h8i9j",
"author": "Rachel Green <rachel.green@example.com>",
"date": "2024-01-14T16:20:10Z",
"message": "feat(payment): add Stripe payment processor integration\n\nIntegrate Stripe as a payment processor to support credit card payments.\nThis enables users to purchase premium features and subscriptions.\n\n- Add Stripe SDK integration\n- Implement payment intent flow\n- Add webhook handling for payment status updates\n- Include comprehensive error handling and logging\n- Add payment method management for users\n\nCloses #567\nCo-authored-by: Payment Team <payments@example.com>"
},
{
"hash": "k1l2m3n",
"author": "Chris Martinez <chris.martinez@example.com>",
"date": "2024-01-14T15:30:45Z",
"message": "fix(ui): resolve mobile navigation menu overflow issue\n\nFixed navigation menu overflow on mobile devices where long menu items\nwere being cut off and causing horizontal scrolling issues.\n\n- Implement responsive text wrapping\n- Add horizontal scrolling for overflowing content\n- Improve touch targets for better mobile usability\n- Fix z-index conflicts with dropdown menus\n\nFixes #678\nTested on iOS Safari, Chrome Mobile, and Firefox Mobile"
},
{
"hash": "o4p5q6r",
"author": "Anna Kowalski <anna.kowalski@example.com>",
"date": "2024-01-14T14:20:15Z",
"message": "refactor(api): extract validation logic into reusable middleware\n\nExtracted common validation logic from individual API endpoints into\nreusable middleware functions to reduce code duplication and improve\nmaintainability.\n\n- Create validation middleware for common patterns\n- Refactor user, product, and order endpoints\n- Add comprehensive error messages\n- Improve validation performance by 30%"
},
{
"hash": "s7t8u9v",
"author": "Kevin Park <kevin.park@example.com>",
"date": "2024-01-14T13:10:30Z",
"message": "feat(search): implement fuzzy search with Elasticsearch\n\nImplemented fuzzy search functionality using Elasticsearch to provide\nbetter search results for users with typos or partial matches.\n\n- Integrate Elasticsearch cluster\n- Add fuzzy matching with configurable distance\n- Implement search result ranking algorithm\n- Add search analytics and logging\n\nSearch accuracy improved by 35% in user testing.\nCloses #789"
},
{
"hash": "w1x2y3z",
"author": "Security Team <security@example.com>",
"date": "2024-01-14T12:45:22Z",
"message": "fix(security): patch SQL injection vulnerability in reports\n\nPatched SQL injection vulnerability in the reports generation endpoint\nthat could allow unauthorized access to sensitive data.\n\n- Implement parameterized queries for all report filters\n- Add input sanitization and validation\n- Update security audit logging\n- Add automated security tests\n\nSeverity: HIGH - CVE-2024-0001\nReported by: External security researcher"
}
]
FILE:assets/sample_git_log.txt
a1b2c3d feat(auth): add OAuth2 integration with Google and GitHub
e4f5g6h fix(api): resolve race condition in user creation endpoint
i7j8k9l docs(readme): update installation and deployment instructions
m1n2o3p feat(ui)!: redesign dashboard with new component library
q4r5s6t fix(db): optimize slow query in user search functionality
u7v8w9x chore(deps): upgrade React to version 18.2.0
y1z2a3b test(auth): add comprehensive tests for OAuth flow
c4d5e6f perf(image): implement WebP compression reducing size by 40%
g7h8i9j feat(payment): add Stripe payment processor integration
k1l2m3n fix(ui): resolve mobile navigation menu overflow issue
o4p5q6r refactor(api): extract validation logic into reusable middleware
s7t8u9v feat(search): implement fuzzy search with Elasticsearch
w1x2y3z fix(security): patch SQL injection vulnerability in reports
a4b5c6d build(ci): add automated security scanning to deployment pipeline
e7f8g9h feat(notification): add email and SMS notification system
i1j2k3l fix(payment): handle expired credit cards gracefully
m4n5o6p docs(api): generate OpenAPI specification for all endpoints
q7r8s9t chore(cleanup): remove deprecated user preference API endpoints
u1v2w3x feat(admin)!: redesign admin panel with role-based permissions
y4z5a6b fix(db): resolve deadlock issues in concurrent transactions
c7d8e9f perf(cache): implement Redis caching for frequent database queries
g1h2i3j feat(mobile): add biometric authentication support
k4l5m6n fix(api): validate input parameters to prevent XSS attacks
o7p8q9r style(ui): update color palette and typography consistency
s1t2u3v feat(analytics): integrate Google Analytics 4 tracking
w4x5y6z fix(memory): resolve memory leak in image processing service
a7b8c9d ci(github): add automated testing for all pull requests
e1f2g3h feat(export): add CSV and PDF export functionality for reports
i4j5k6l fix(ui): resolve accessibility issues with screen readers
m7n8o9p refactor(auth): consolidate authentication logic into single service
FILE:assets/sample_git_log_full.txt
commit a1b2c3d4e5f6789012345678901234567890abcd
Author: Sarah Johnson <sarah.johnson@example.com>
Date: Mon Jan 15 14:30:22 2024 +0000
feat(auth): add OAuth2 integration with Google and GitHub
Implement OAuth2 authentication flow supporting Google and GitHub providers.
Users can now sign in using their existing social media accounts, improving
user experience and reducing password fatigue.
- Add OAuth2 client configuration
- Implement authorization code flow
- Add user profile mapping from providers
- Include comprehensive error handling
Closes #123
Resolves #145
commit e4f5g6h7i8j9012345678901234567890123abcdef
Author: Mike Chen <mike.chen@example.com>
Date: Mon Jan 15 13:45:18 2024 +0000
fix(api): resolve race condition in user creation endpoint
Fixed a race condition that occurred when multiple requests attempted
to create users with the same email address simultaneously. This was
causing duplicate user records in some edge cases.
- Added database unique constraint on email field
- Implemented proper error handling for constraint violations
- Added retry logic with exponential backoff
Fixes #234
commit i7j8k9l0m1n2345678901234567890123456789abcd
Author: Emily Davis <emily.davis@example.com>
Date: Mon Jan 15 12:20:45 2024 +0000
docs(readme): update installation and deployment instructions
Updated README with comprehensive installation guide including:
- Docker setup instructions
- Environment variable configuration
- Database migration steps
- Troubleshooting common issues
commit m1n2o3p4q5r6789012345678901234567890abcdefg
Author: David Wilson <david.wilson@example.com>
Date: Mon Jan 15 11:15:30 2024 +0000
feat(ui)!: redesign dashboard with new component library
Complete redesign of the user dashboard using our new component library.
This provides better accessibility, improved mobile responsiveness, and
a more modern user interface.
BREAKING CHANGE: The dashboard API endpoints have changed structure.
Frontend clients must update to use the new /v2/dashboard endpoints.
The legacy /v1/dashboard endpoints will be removed in version 3.0.0.
- Implement new Card, Grid, and Chart components
- Add responsive breakpoints for mobile devices
- Improve accessibility with proper ARIA labels
- Add dark mode support
Closes #345, #367, #389
commit q4r5s6t7u8v9012345678901234567890123456abcd
Author: Lisa Rodriguez <lisa.rodriguez@example.com>
Date: Mon Jan 15 10:45:12 2024 +0000
fix(db): optimize slow query in user search functionality
Optimized the user search query that was causing performance issues
on databases with large user counts. Query time reduced from 2.5s to 150ms.
- Added composite index on (email, username, created_at)
- Refactored query to use more efficient JOIN structure
- Added query result caching for common search patterns
Fixes #456
commit u7v8w9x0y1z2345678901234567890123456789abcde
Author: Tom Anderson <tom.anderson@example.com>
Date: Mon Jan 15 09:30:55 2024 +0000
chore(deps): upgrade React to version 18.2.0
Upgrade React and related dependencies to latest stable versions.
This includes performance improvements and new concurrent features.
- React: 17.0.2 → 18.2.0
- React-DOM: 17.0.2 → 18.2.0
- React-Router: 6.8.0 → 6.8.1
- Updated all peer dependencies
commit y1z2a3b4c5d6789012345678901234567890abcdefg
Author: Jennifer Kim <jennifer.kim@example.com>
Date: Mon Jan 15 08:15:33 2024 +0000
test(auth): add comprehensive tests for OAuth flow
Added unit and integration tests for the OAuth2 authentication system
to ensure reliability and prevent regressions.
- Unit tests for OAuth client configuration
- Integration tests for complete auth flow
- Mock providers for testing without external dependencies
- Error scenario testing
Test coverage increased from 72% to 89% for auth module.
commit c4d5e6f7g8h9012345678901234567890123456abcd
Author: Alex Thompson <alex.thompson@example.com>
Date: Mon Jan 15 07:45:20 2024 +0000
perf(image): implement WebP compression reducing size by 40%
Replaced PNG compression with WebP format for uploaded images.
This reduces average image file sizes by 40% while maintaining
visual quality, improving page load times and reducing bandwidth costs.
- Add WebP encoding support
- Implement fallback to PNG for older browsers
- Add quality settings configuration
- Update image serving endpoints
Performance improvement: Page load time reduced by 25% on average.
commit g7h8i9j0k1l2345678901234567890123456789abcde
Author: Rachel Green <rachel.green@example.com>
Date: Sun Jan 14 16:20:10 2024 +0000
feat(payment): add Stripe payment processor integration
Integrate Stripe as a payment processor to support credit card payments.
This enables users to purchase premium features and subscriptions.
- Add Stripe SDK integration
- Implement payment intent flow
- Add webhook handling for payment status updates
- Include comprehensive error handling and logging
- Add payment method management for users
Closes #567
Co-authored-by: Payment Team <payments@example.com>
commit k1l2m3n4o5p6789012345678901234567890abcdefg
Author: Chris Martinez <chris.martinez@example.com>
Date: Sun Jan 14 15:30:45 2024 +0000
fix(ui): resolve mobile navigation menu overflow issue
Fixed navigation menu overflow on mobile devices where long menu items
were being cut off and causing horizontal scrolling issues.
- Implement responsive text wrapping
- Add horizontal scrolling for overflowing content
- Improve touch targets for better mobile usability
- Fix z-index conflicts with dropdown menus
Fixes #678
Tested on iOS Safari, Chrome Mobile, and Firefox Mobile
FILE:assets/sample_release_plan.json
{
"release_name": "Winter 2024 Release",
"version": "2.3.0",
"target_date": "2024-02-15T10:00:00Z",
"features": [
{
"id": "AUTH-123",
"title": "OAuth2 Integration",
"description": "Add support for Google and GitHub OAuth2 authentication",
"type": "feature",
"assignee": "sarah.johnson@example.com",
"status": "ready",
"pull_request_url": "https://github.com/ourapp/backend/pull/234",
"issue_url": "https://github.com/ourapp/backend/issues/123",
"risk_level": "medium",
"test_coverage_required": 85.0,
"test_coverage_actual": 89.5,
"requires_migration": false,
"breaking_changes": [],
"dependencies": ["AUTH-124"],
"qa_approved": true,
"security_approved": true,
"pm_approved": true
},
{
"id": "UI-345",
"title": "Dashboard Redesign",
"description": "Complete redesign of user dashboard with new component library",
"type": "breaking_change",
"assignee": "david.wilson@example.com",
"status": "ready",
"pull_request_url": "https://github.com/ourapp/frontend/pull/456",
"issue_url": "https://github.com/ourapp/frontend/issues/345",
"risk_level": "high",
"test_coverage_required": 90.0,
"test_coverage_actual": 92.3,
"requires_migration": true,
"migration_complexity": "moderate",
"breaking_changes": [
"Dashboard API endpoints changed from /v1/dashboard to /v2/dashboard",
"Dashboard widget configuration format updated"
],
"dependencies": [],
"qa_approved": true,
"security_approved": true,
"pm_approved": true
},
{
"id": "PAY-567",
"title": "Stripe Payment Integration",
"description": "Add Stripe as payment processor for premium features",
"type": "feature",
"assignee": "rachel.green@example.com",
"status": "ready",
"pull_request_url": "https://github.com/ourapp/backend/pull/678",
"issue_url": "https://github.com/ourapp/backend/issues/567",
"risk_level": "high",
"test_coverage_required": 95.0,
"test_coverage_actual": 97.2,
"requires_migration": true,
"migration_complexity": "complex",
"breaking_changes": [],
"dependencies": ["SEC-890"],
"qa_approved": true,
"security_approved": true,
"pm_approved": true
},
{
"id": "SEARCH-789",
"title": "Elasticsearch Fuzzy Search",
"description": "Implement fuzzy search functionality with Elasticsearch",
"type": "feature",
"assignee": "kevin.park@example.com",
"status": "in_progress",
"pull_request_url": "https://github.com/ourapp/backend/pull/890",
"issue_url": "https://github.com/ourapp/backend/issues/789",
"risk_level": "medium",
"test_coverage_required": 80.0,
"test_coverage_actual": 76.5,
"requires_migration": true,
"migration_complexity": "moderate",
"breaking_changes": [],
"dependencies": ["INFRA-234"],
"qa_approved": false,
"security_approved": true,
"pm_approved": true
},
{
"id": "MOBILE-456",
"title": "Biometric Authentication",
"description": "Add fingerprint and face ID support for mobile apps",
"type": "feature",
"assignee": "alex.thompson@example.com",
"status": "blocked",
"pull_request_url": null,
"issue_url": "https://github.com/ourapp/mobile/issues/456",
"risk_level": "medium",
"test_coverage_required": 85.0,
"test_coverage_actual": null,
"requires_migration": false,
"breaking_changes": [],
"dependencies": ["AUTH-123"],
"qa_approved": false,
"security_approved": false,
"pm_approved": true
},
{
"id": "PERF-678",
"title": "Redis Caching Implementation",
"description": "Implement Redis caching for frequently accessed data",
"type": "performance",
"assignee": "lisa.rodriguez@example.com",
"status": "ready",
"pull_request_url": "https://github.com/ourapp/backend/pull/901",
"issue_url": "https://github.com/ourapp/backend/issues/678",
"risk_level": "low",
"test_coverage_required": 75.0,
"test_coverage_actual": 82.1,
"requires_migration": false,
"breaking_changes": [],
"dependencies": [],
"qa_approved": true,
"security_approved": false,
"pm_approved": true
}
],
"quality_gates": [
{
"name": "Unit Test Coverage",
"required": true,
"status": "ready",
"details": "Overall test coverage above 85% threshold",
"threshold": 85.0,
"actual_value": 87.3
},
{
"name": "Integration Tests",
"required": true,
"status": "ready",
"details": "All integration tests passing"
},
{
"name": "Security Scan",
"required": true,
"status": "pending",
"details": "Waiting for security team review of payment integration"
},
{
"name": "Performance Testing",
"required": true,
"status": "ready",
"details": "Load testing shows 99th percentile response time under 500ms"
},
{
"name": "Documentation Review",
"required": true,
"status": "pending",
"details": "API documentation needs update for dashboard changes"
},
{
"name": "Dependency Audit",
"required": true,
"status": "ready",
"details": "No high or critical vulnerabilities found"
}
],
"stakeholders": [
{
"name": "Engineering Team",
"role": "developer",
"contact": "engineering@example.com",
"notification_type": "slack",
"critical_path": true
},
{
"name": "Product Team",
"role": "pm",
"contact": "product@example.com",
"notification_type": "email",
"critical_path": true
},
{
"name": "QA Team",
"role": "qa",
"contact": "qa@example.com",
"notification_type": "slack",
"critical_path": true
},
{
"name": "Security Team",
"role": "security",
"contact": "security@example.com",
"notification_type": "email",
"critical_path": false
},
{
"name": "Customer Support",
"role": "support",
"contact": "support@example.com",
"notification_type": "email",
"critical_path": false
},
{
"name": "Sales Team",
"role": "sales",
"contact": "sales@example.com",
"notification_type": "email",
"critical_path": false
},
{
"name": "Beta Users",
"role": "customer",
"contact": "beta-users@example.com",
"notification_type": "email",
"critical_path": false
}
],
"rollback_steps": [
{
"order": 1,
"description": "Alert incident response team and stakeholders",
"estimated_time": "2 minutes",
"risk_level": "low",
"verification": "Confirm team is aware and responding via Slack"
},
{
"order": 2,
"description": "Switch load balancer to previous version",
"command": "kubectl patch service app --patch '{\"spec\": {\"selector\": {\"version\": \"v2.2.1\"}}}'",
"estimated_time": "30 seconds",
"risk_level": "low",
"verification": "Check traffic routing to previous version via monitoring dashboard"
},
{
"order": 3,
"description": "Disable new feature flags",
"command": "curl -X POST https://api.example.com/feature-flags/oauth2/disable",
"estimated_time": "1 minute",
"risk_level": "low",
"verification": "Verify feature flags are disabled in admin panel"
},
{
"order": 4,
"description": "Roll back database migrations",
"command": "python manage.py migrate app 0042",
"estimated_time": "10 minutes",
"risk_level": "high",
"verification": "Verify database schema and run data integrity checks"
},
{
"order": 5,
"description": "Clear Redis cache",
"command": "redis-cli FLUSHALL",
"estimated_time": "30 seconds",
"risk_level": "medium",
"verification": "Confirm cache is cleared and application rebuilds cache properly"
},
{
"order": 6,
"description": "Verify application health",
"estimated_time": "5 minutes",
"risk_level": "low",
"verification": "Check health endpoints, error rates, and core user workflows"
},
{
"order": 7,
"description": "Update status page and notify users",
"estimated_time": "5 minutes",
"risk_level": "low",
"verification": "Confirm status page updated and notifications sent"
}
]
}
FILE:changelog_generator.py
#!/usr/bin/env python3
"""
Changelog Generator
Parses git log output in conventional commits format and generates structured changelogs
in multiple formats (Markdown, Keep a Changelog). Groups commits by type, extracts scope,
links to PRs/issues, and highlights breaking changes.
Input: git log text (piped from git log) or JSON array of commits
Output: formatted CHANGELOG.md section + release summary stats
"""
import argparse
import json
import re
import sys
from collections import defaultdict, Counter
from datetime import datetime
from typing import Dict, List, Optional, Tuple, Union
class ConventionalCommit:
"""Represents a parsed conventional commit."""
def __init__(self, raw_message: str, commit_hash: str = "", author: str = "",
date: str = "", merge_info: Optional[str] = None):
self.raw_message = raw_message
self.commit_hash = commit_hash
self.author = author
self.date = date
self.merge_info = merge_info
# Parse the commit message
self.type = ""
self.scope = ""
self.description = ""
self.body = ""
self.footers = []
self.is_breaking = False
self.breaking_change_description = ""
self._parse_commit_message()
def _parse_commit_message(self):
"""Parse conventional commit format."""
lines = self.raw_message.split('\n')
header = lines[0] if lines else ""
# Parse header: type(scope): description
header_pattern = r'^(\w+)(\([^)]+\))?(!)?:\s*(.+)$'
match = re.match(header_pattern, header)
if match:
self.type = match.group(1).lower()
scope_match = match.group(2)
self.scope = scope_match[1:-1] if scope_match else "" # Remove parentheses
self.is_breaking = bool(match.group(3)) # ! indicates breaking change
self.description = match.group(4).strip()
else:
# Fallback for non-conventional commits
self.type = "chore"
self.description = header
# Parse body and footers
if len(lines) > 1:
body_lines = []
footer_lines = []
in_footer = False
for line in lines[1:]:
if not line.strip():
continue
# Check if this is a footer (KEY: value or KEY #value format)
footer_pattern = r'^([A-Z-]+):\s*(.+)$|^([A-Z-]+)\s+#(\d+)$'
if re.match(footer_pattern, line):
in_footer = True
footer_lines.append(line)
# Check for breaking change
if line.startswith('BREAKING CHANGE:'):
self.is_breaking = True
self.breaking_change_description = line[16:].strip()
else:
if in_footer:
# Continuation of footer
footer_lines.append(line)
else:
body_lines.append(line)
self.body = '\n'.join(body_lines).strip()
self.footers = footer_lines
def extract_issue_references(self) -> List[str]:
"""Extract issue/PR references like #123, fixes #456, etc."""
text = f"{self.description} {self.body} {' '.join(self.footers)}"
# Common patterns for issue references
patterns = [
r'#(\d+)', # Simple #123
r'(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+#(\d+)', # closes #123
r'(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+(\w+/\w+)?#(\d+)' # fixes repo#123
]
references = []
for pattern in patterns:
matches = re.findall(pattern, text, re.IGNORECASE)
for match in matches:
if isinstance(match, tuple):
# Handle tuple results from more complex patterns
ref = match[-1] if match[-1] else match[0]
else:
ref = match
if ref and ref not in references:
references.append(ref)
return references
def get_changelog_category(self) -> str:
"""Map commit type to changelog category."""
category_map = {
'feat': 'Added',
'add': 'Added',
'fix': 'Fixed',
'bugfix': 'Fixed',
'security': 'Security',
'perf': 'Fixed', # Performance improvements go to Fixed
'refactor': 'Changed',
'style': 'Changed',
'docs': 'Changed',
'test': None, # Tests don't appear in user-facing changelog
'ci': None,
'build': None,
'chore': None,
'revert': 'Fixed',
'remove': 'Removed',
'deprecate': 'Deprecated'
}
return category_map.get(self.type, 'Changed')
class ChangelogGenerator:
"""Main changelog generator class."""
def __init__(self):
self.commits: List[ConventionalCommit] = []
self.version = "Unreleased"
self.date = datetime.now().strftime("%Y-%m-%d")
self.base_url = ""
def parse_git_log_output(self, git_log_text: str):
"""Parse git log output into ConventionalCommit objects."""
# Try to detect format based on patterns in the text
lines = git_log_text.strip().split('\n')
if not lines or not lines[0]:
return
# Format 1: Simple oneline format (hash message)
oneline_pattern = r'^([a-f0-9]{7,40})\s+(.+)$'
# Format 2: Full format with metadata
full_pattern = r'^commit\s+([a-f0-9]+)'
current_commit = None
commit_buffer = []
for line in lines:
line = line.strip()
if not line:
continue
# Check if this is a new commit (oneline format)
oneline_match = re.match(oneline_pattern, line)
if oneline_match:
# Process previous commit
if current_commit:
self.commits.append(current_commit)
# Start new commit
commit_hash = oneline_match.group(1)
message = oneline_match.group(2)
current_commit = ConventionalCommit(message, commit_hash)
continue
# Check if this is a new commit (full format)
full_match = re.match(full_pattern, line)
if full_match:
# Process previous commit
if current_commit:
commit_message = '\n'.join(commit_buffer).strip()
if commit_message:
current_commit = ConventionalCommit(commit_message, current_commit.commit_hash,
current_commit.author, current_commit.date)
self.commits.append(current_commit)
# Start new commit
commit_hash = full_match.group(1)
current_commit = ConventionalCommit("", commit_hash)
commit_buffer = []
continue
# Parse metadata lines in full format
if current_commit and not current_commit.raw_message:
if line.startswith('Author:'):
current_commit.author = line[7:].strip()
elif line.startswith('Date:'):
current_commit.date = line[5:].strip()
elif line.startswith('Merge:'):
current_commit.merge_info = line[6:].strip()
elif line.startswith(' '):
# Commit message line (indented)
commit_buffer.append(line[4:]) # Remove 4-space indent
# Process final commit
if current_commit:
if commit_buffer:
commit_message = '\n'.join(commit_buffer).strip()
current_commit = ConventionalCommit(commit_message, current_commit.commit_hash,
current_commit.author, current_commit.date)
self.commits.append(current_commit)
def parse_json_commits(self, json_data: Union[str, List[Dict]]):
"""Parse commits from JSON format."""
if isinstance(json_data, str):
data = json.loads(json_data)
else:
data = json_data
for commit_data in data:
commit = ConventionalCommit(
raw_message=commit_data.get('message', ''),
commit_hash=commit_data.get('hash', ''),
author=commit_data.get('author', ''),
date=commit_data.get('date', '')
)
self.commits.append(commit)
def group_commits_by_category(self) -> Dict[str, List[ConventionalCommit]]:
"""Group commits by changelog category."""
categories = defaultdict(list)
for commit in self.commits:
category = commit.get_changelog_category()
if category: # Skip None categories (internal changes)
categories[category].append(commit)
return dict(categories)
def generate_markdown_changelog(self, include_unreleased: bool = True) -> str:
"""Generate Keep a Changelog format markdown."""
grouped_commits = self.group_commits_by_category()
if not grouped_commits:
return "No notable changes.\n"
# Start with header
changelog = []
if include_unreleased and self.version == "Unreleased":
changelog.append(f"## [{self.version}]")
else:
changelog.append(f"## [{self.version}] - {self.date}")
changelog.append("")
# Order categories logically
category_order = ['Added', 'Changed', 'Deprecated', 'Removed', 'Fixed', 'Security']
# Separate breaking changes
breaking_changes = [commit for commit in self.commits if commit.is_breaking]
# Add breaking changes section first if any exist
if breaking_changes:
changelog.append("### Breaking Changes")
for commit in breaking_changes:
line = self._format_commit_line(commit, show_breaking=True)
changelog.append(f"- {line}")
changelog.append("")
# Add regular categories
for category in category_order:
if category not in grouped_commits:
continue
changelog.append(f"### {category}")
# Group by scope for better organization
scoped_commits = defaultdict(list)
for commit in grouped_commits[category]:
scope = commit.scope if commit.scope else "general"
scoped_commits[scope].append(commit)
# Sort scopes, with 'general' last
scopes = sorted(scoped_commits.keys())
if "general" in scopes:
scopes.remove("general")
scopes.append("general")
for scope in scopes:
if len(scoped_commits) > 1 and scope != "general":
changelog.append(f"#### {scope.title()}")
for commit in scoped_commits[scope]:
line = self._format_commit_line(commit)
changelog.append(f"- {line}")
changelog.append("")
return '\n'.join(changelog)
def _format_commit_line(self, commit: ConventionalCommit, show_breaking: bool = False) -> str:
"""Format a single commit line for the changelog."""
# Start with description
line = commit.description.capitalize()
# Add scope if present and not already in description
if commit.scope and commit.scope.lower() not in line.lower():
line = f"{commit.scope}: {line}"
# Add issue references
issue_refs = commit.extract_issue_references()
if issue_refs:
refs_str = ', '.join(f"#{ref}" for ref in issue_refs)
line += f" ({refs_str})"
# Add commit hash if available
if commit.commit_hash:
short_hash = commit.commit_hash[:7]
line += f" [{short_hash}]"
if self.base_url:
line += f"({self.base_url}/commit/{commit.commit_hash})"
# Add breaking change indicator
if show_breaking and commit.breaking_change_description:
line += f" - {commit.breaking_change_description}"
elif commit.is_breaking and not show_breaking:
line += " ⚠️ BREAKING"
return line
def generate_release_summary(self) -> Dict:
"""Generate summary statistics for the release."""
if not self.commits:
return {
'version': self.version,
'date': self.date,
'total_commits': 0,
'by_type': {},
'by_author': {},
'breaking_changes': 0,
'notable_changes': 0
}
# Count by type
type_counts = Counter(commit.type for commit in self.commits)
# Count by author
author_counts = Counter(commit.author for commit in self.commits if commit.author)
# Count breaking changes
breaking_count = sum(1 for commit in self.commits if commit.is_breaking)
# Count notable changes (excluding chore, ci, build, test)
notable_types = {'feat', 'fix', 'security', 'perf', 'refactor', 'remove', 'deprecate'}
notable_count = sum(1 for commit in self.commits if commit.type in notable_types)
return {
'version': self.version,
'date': self.date,
'total_commits': len(self.commits),
'by_type': dict(type_counts.most_common()),
'by_author': dict(author_counts.most_common(10)), # Top 10 contributors
'breaking_changes': breaking_count,
'notable_changes': notable_count,
'scopes': list(set(commit.scope for commit in self.commits if commit.scope)),
'issue_references': len(set().union(*(commit.extract_issue_references() for commit in self.commits)))
}
def generate_json_output(self) -> str:
"""Generate JSON representation of the changelog data."""
grouped_commits = self.group_commits_by_category()
# Convert commits to serializable format
json_data = {
'version': self.version,
'date': self.date,
'summary': self.generate_release_summary(),
'categories': {}
}
for category, commits in grouped_commits.items():
json_data['categories'][category] = []
for commit in commits:
commit_data = {
'type': commit.type,
'scope': commit.scope,
'description': commit.description,
'hash': commit.commit_hash,
'author': commit.author,
'date': commit.date,
'breaking': commit.is_breaking,
'breaking_description': commit.breaking_change_description,
'issue_references': commit.extract_issue_references()
}
json_data['categories'][category].append(commit_data)
return json.dumps(json_data, indent=2)
def main():
"""Main entry point with CLI argument parsing."""
parser = argparse.ArgumentParser(description="Generate changelog from conventional commits")
parser.add_argument('--input', '-i', type=str, help='Input file (default: stdin)')
parser.add_argument('--format', '-f', choices=['markdown', 'json', 'both'],
default='markdown', help='Output format')
parser.add_argument('--version', '-v', type=str, default='Unreleased',
help='Version for this release')
parser.add_argument('--date', '-d', type=str,
default=datetime.now().strftime("%Y-%m-%d"),
help='Release date (YYYY-MM-DD format)')
parser.add_argument('--base-url', '-u', type=str, default='',
help='Base URL for commit links')
parser.add_argument('--input-format', choices=['git-log', 'json'],
default='git-log', help='Input format')
parser.add_argument('--output', '-o', type=str, help='Output file (default: stdout)')
parser.add_argument('--summary', '-s', action='store_true',
help='Include release summary statistics')
args = parser.parse_args()
# Read input
if args.input:
with open(args.input, 'r', encoding='utf-8') as f:
input_data = f.read()
else:
input_data = sys.stdin.read()
if not input_data.strip():
print("No input data provided", file=sys.stderr)
sys.exit(1)
# Initialize generator
generator = ChangelogGenerator()
generator.version = args.version
generator.date = args.date
generator.base_url = args.base_url
# Parse input
try:
if args.input_format == 'json':
generator.parse_json_commits(input_data)
else:
generator.parse_git_log_output(input_data)
except Exception as e:
print(f"Error parsing input: {e}", file=sys.stderr)
sys.exit(1)
if not generator.commits:
print("No valid commits found in input", file=sys.stderr)
sys.exit(1)
# Generate output
output_lines = []
if args.format in ['markdown', 'both']:
changelog_md = generator.generate_markdown_changelog()
if args.format == 'both':
output_lines.append("# Markdown Changelog\n")
output_lines.append(changelog_md)
if args.format in ['json', 'both']:
changelog_json = generator.generate_json_output()
if args.format == 'both':
output_lines.append("\n# JSON Output\n")
output_lines.append(changelog_json)
if args.summary:
summary = generator.generate_release_summary()
output_lines.append(f"\n# Release Summary")
output_lines.append(f"- **Version:** {summary['version']}")
output_lines.append(f"- **Total Commits:** {summary['total_commits']}")
output_lines.append(f"- **Notable Changes:** {summary['notable_changes']}")
output_lines.append(f"- **Breaking Changes:** {summary['breaking_changes']}")
output_lines.append(f"- **Issue References:** {summary['issue_references']}")
if summary['by_type']:
output_lines.append("- **By Type:**")
for commit_type, count in summary['by_type'].items():
output_lines.append(f" - {commit_type}: {count}")
# Write output
final_output = '\n'.join(output_lines)
if args.output:
with open(args.output, 'w', encoding='utf-8') as f:
f.write(final_output)
else:
print(final_output)
if __name__ == '__main__':
main()
FILE:expected_outputs/changelog_example.md
# Expected Changelog Output
## [2.3.0] - 2024-01-15
### Breaking Changes
- ui: redesign dashboard with new component library - The dashboard API endpoints have changed structure. Frontend clients must update to use the new /v2/dashboard endpoints. The legacy /v1/dashboard endpoints will be removed in version 3.0.0. (#345, #367, #389) [m1n2o3p]
### Added
- auth: add OAuth2 integration with Google and GitHub (#123, #145) [a1b2c3d]
- payment: add Stripe payment processor integration (#567) [g7h8i9j]
- search: implement fuzzy search with Elasticsearch (#789) [s7t8u9v]
### Fixed
- api: resolve race condition in user creation endpoint (#234) [e4f5g6h]
- db: optimize slow query in user search functionality (#456) [q4r5s6t]
- ui: resolve mobile navigation menu overflow issue (#678) [k1l2m3n]
- security: patch SQL injection vulnerability in reports [w1x2y3z] ⚠️ BREAKING
### Changed
- image: implement WebP compression reducing size by 40% [c4d5e6f]
- api: extract validation logic into reusable middleware [o4p5q6r]
- readme: update installation and deployment instructions [i7j8k9l]
# Release Summary
- **Version:** 2.3.0
- **Total Commits:** 13
- **Notable Changes:** 9
- **Breaking Changes:** 2
- **Issue References:** 8
- **By Type:**
- feat: 4
- fix: 4
- perf: 1
- refactor: 1
- docs: 1
- test: 1
- chore: 1
FILE:expected_outputs/release_readiness_example.txt
Release Readiness Report
========================
Release: Winter 2024 Release v2.3.0
Status: AT_RISK
Readiness Score: 73.3%
WARNINGS:
⚠️ Feature 'Elasticsearch Fuzzy Search' (SEARCH-789) still in progress
⚠️ Feature 'Elasticsearch Fuzzy Search' has low test coverage: 76.5% < 80.0%
⚠️ Required quality gate 'Security Scan' is pending
⚠️ Required quality gate 'Documentation Review' is pending
BLOCKING ISSUES:
❌ Feature 'Biometric Authentication' (MOBILE-456) is blocked
❌ Feature 'Biometric Authentication' missing approvals: QA approval, Security approval
RECOMMENDATIONS:
💡 Obtain required approvals for pending features
💡 Improve test coverage for features below threshold
💡 Complete pending quality gate validations
FEATURE SUMMARY:
Total: 6 | Ready: 3 | Blocked: 1
Breaking Changes: 1 | Missing Approvals: 1
QUALITY GATES:
Total: 6 | Passed: 3 | Failed: 0
FILE:expected_outputs/version_bump_example.txt
Current Version: 2.2.5
Recommended Version: 3.0.0
With v prefix: v3.0.0
Bump Type: major
Commit Analysis:
- Total commits: 13
- Breaking changes: 2
- New features: 4
- Bug fixes: 4
- Ignored commits: 3
Breaking Changes:
- feat(ui): redesign dashboard with new component library
- fix(security): patch SQL injection vulnerability in reports
Bump Commands:
npm:
npm version 3.0.0 --no-git-tag-version
python:
# Update version in setup.py, __init__.py, or pyproject.toml
# pyproject.toml: version = "3.0.0"
rust:
# Update Cargo.toml
# version = "3.0.0"
git:
git tag -a v3.0.0 -m 'Release v3.0.0'
git push origin v3.0.0
docker:
docker build -t myapp:3.0.0 .
docker tag myapp:3.0.0 myapp:latest
FILE:README.md
# Release Manager
A comprehensive release management toolkit for automating changelog generation, version bumping, and release planning based on conventional commits and industry best practices.
## Overview
The Release Manager skill provides three powerful Python scripts and comprehensive documentation for managing software releases:
1. **changelog_generator.py** - Generate structured changelogs from git history
2. **version_bumper.py** - Determine correct semantic version bumps
3. **release_planner.py** - Assess release readiness and generate coordination plans
## Quick Start
### Prerequisites
- Python 3.7+
- Git repository with conventional commit messages
- No external dependencies required (uses only Python standard library)
### Basic Usage
```bash
# Generate changelog from recent commits
git log --oneline --since="1 month ago" | python changelog_generator.py
# Determine version bump from commits since last tag
git log --oneline $(git describe --tags --abbrev=0)..HEAD | python version_bumper.py -c "1.2.3"
# Assess release readiness
python release_planner.py --input assets/sample_release_plan.json
```
## Scripts Reference
### changelog_generator.py
Parses conventional commits and generates structured changelogs in multiple formats.
**Input Options:**
- Git log text (oneline or full format)
- JSON array of commits
- Stdin or file input
**Output Formats:**
- Markdown (Keep a Changelog format)
- JSON structured data
- Both with release statistics
```bash
# From git log (recommended)
git log --oneline --since="last release" | python changelog_generator.py \
--version "2.1.0" \
--date "2024-01-15" \
--base-url "https://github.com/yourorg/yourrepo"
# From JSON file
python changelog_generator.py \
--input assets/sample_commits.json \
--input-format json \
--format both \
--summary
# With custom output
git log --format="%h %s" v1.0.0..HEAD | python changelog_generator.py \
--version "1.1.0" \
--output CHANGELOG_DRAFT.md
```
**Features:**
- Parses conventional commit types (feat, fix, docs, etc.)
- Groups commits by changelog categories (Added, Fixed, Changed, etc.)
- Extracts issue references (#123, fixes #456)
- Identifies breaking changes
- Links to commits and PRs
- Generates release summary statistics
### version_bumper.py
Analyzes commits to determine semantic version bumps according to conventional commits.
**Bump Rules:**
- **MAJOR:** Breaking changes (`feat!:` or `BREAKING CHANGE:`)
- **MINOR:** New features (`feat:`)
- **PATCH:** Bug fixes (`fix:`, `perf:`, `security:`)
- **NONE:** Documentation, tests, chores only
```bash
# Basic version bump determination
git log --oneline v1.2.3..HEAD | python version_bumper.py --current-version "1.2.3"
# With pre-release version
python version_bumper.py \
--current-version "1.2.3" \
--prerelease alpha \
--input assets/sample_commits.json \
--input-format json
# Include bump commands and file updates
git log --oneline $(git describe --tags --abbrev=0)..HEAD | \
python version_bumper.py \
--current-version "$(git describe --tags --abbrev=0)" \
--include-commands \
--include-files \
--analysis
```
**Features:**
- Supports pre-release versions (alpha, beta, rc)
- Generates bump commands for npm, Python, Rust, Git
- Provides file update snippets
- Detailed commit analysis and categorization
- Custom rules for specific commit types
- JSON and text output formats
### release_planner.py
Assesses release readiness and generates comprehensive release coordination plans.
**Input:** JSON release plan with features, quality gates, and stakeholders
```bash
# Assess release readiness
python release_planner.py --input assets/sample_release_plan.json
# Generate full release package
python release_planner.py \
--input release_plan.json \
--output-format markdown \
--include-checklist \
--include-communication \
--include-rollback \
--output release_report.md
```
**Features:**
- Feature readiness assessment with approval tracking
- Quality gate validation and reporting
- Stakeholder communication planning
- Rollback procedure generation
- Risk analysis and timeline assessment
- Customizable test coverage thresholds
- Multiple output formats (text, JSON, Markdown)
## File Structure
```
release-manager/
├── SKILL.md # Comprehensive methodology guide
├── README.md # This file
├── changelog_generator.py # Changelog generation script
├── version_bumper.py # Version bump determination
├── release_planner.py # Release readiness assessment
├── references/ # Reference documentation
│ ├── conventional-commits-guide.md # Conventional commits specification
│ ├── release-workflow-comparison.md # Git Flow vs GitHub Flow vs Trunk-based
│ └── hotfix-procedures.md # Emergency release procedures
├── assets/ # Sample data for testing
│ ├── sample_git_log.txt # Sample git log output
│ ├── sample_git_log_full.txt # Detailed git log format
│ ├── sample_commits.json # JSON commit data
│ └── sample_release_plan.json # Release plan template
└── expected_outputs/ # Example script outputs
├── changelog_example.md # Expected changelog format
├── version_bump_example.txt # Version bump output
└── release_readiness_example.txt # Release assessment report
```
## Integration Examples
### CI/CD Pipeline Integration
```yaml
# .github/workflows/release.yml
name: Automated Release
on:
push:
branches: [main]
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0 # Need full history
- name: Determine version bump
id: version
run: |
CURRENT=$(git describe --tags --abbrev=0)
git log --oneline $CURRENT..HEAD | \
python scripts/version_bumper.py -c $CURRENT --output-format json > bump.json
echo "new_version=$(jq -r '.recommended_version' bump.json)" >> $GITHUB_OUTPUT
- name: Generate changelog
run: |
git log --oneline { steps.version.outputs.current_version}..HEAD | \
python scripts/changelog_generator.py \
--version "{ steps.version.outputs.new_version}" \
--base-url "https://github.com/{ github.repository}" \
--output CHANGELOG_ENTRY.md
- name: Create release
uses: actions/create-release@v1
with:
tag_name: v{ steps.version.outputs.new_version}
release_name: Release { steps.version.outputs.new_version}
body_path: CHANGELOG_ENTRY.md
```
### Git Hooks Integration
```bash
#!/bin/bash
# .git/hooks/pre-commit
# Validate conventional commit format
commit_msg_file=$1
commit_msg=$(cat $commit_msg_file)
# Simple validation (more sophisticated validation available in commitlint)
if ! echo "$commit_msg" | grep -qE "^(feat|fix|docs|style|refactor|test|chore|perf|ci|build)(\(.+\))?(!)?:"; then
echo "❌ Commit message doesn't follow conventional commits format"
echo "Expected: type(scope): description"
echo "Examples:"
echo " feat(auth): add OAuth2 integration"
echo " fix(api): resolve race condition"
echo " docs: update installation guide"
exit 1
fi
echo "✅ Commit message format is valid"
```
### Release Planning Automation
```python
#!/usr/bin/env python3
# generate_release_plan.py - Automatically generate release plans from project management tools
import json
import requests
from datetime import datetime, timedelta
def generate_release_plan_from_github(repo, milestone):
"""Generate release plan from GitHub milestone and PRs."""
# Fetch milestone details
milestone_url = f"https://api.github.com/repos/{repo}/milestones/{milestone}"
milestone_data = requests.get(milestone_url).json()
# Fetch associated issues/PRs
issues_url = f"https://api.github.com/repos/{repo}/issues?milestone={milestone}&state=all"
issues = requests.get(issues_url).json()
release_plan = {
"release_name": milestone_data["title"],
"version": "TBD", # Fill in manually or extract from milestone
"target_date": milestone_data["due_on"],
"features": []
}
for issue in issues:
if issue.get("pull_request"): # It's a PR
feature = {
"id": f"GH-{issue['number']}",
"title": issue["title"],
"description": issue["body"][:200] + "..." if len(issue["body"]) > 200 else issue["body"],
"type": "feature", # Could be parsed from labels
"assignee": issue["assignee"]["login"] if issue["assignee"] else "",
"status": "ready" if issue["state"] == "closed" else "in_progress",
"pull_request_url": issue["pull_request"]["html_url"],
"issue_url": issue["html_url"],
"risk_level": "medium", # Could be parsed from labels
"qa_approved": "qa-approved" in [label["name"] for label in issue["labels"]],
"pm_approved": "pm-approved" in [label["name"] for label in issue["labels"]]
}
release_plan["features"].append(feature)
return release_plan
# Usage
if __name__ == "__main__":
plan = generate_release_plan_from_github("yourorg/yourrepo", "5")
with open("release_plan.json", "w") as f:
json.dump(plan, f, indent=2)
print("Generated release_plan.json")
print("Run: python release_planner.py --input release_plan.json")
```
## Advanced Usage
### Custom Commit Type Rules
```bash
# Define custom rules for version bumping
python version_bumper.py \
--current-version "1.2.3" \
--custom-rules '{"security": "patch", "breaking": "major"}' \
--ignore-types "docs,style,test"
```
### Multi-repository Release Coordination
```bash
#!/bin/bash
# multi_repo_release.sh - Coordinate releases across multiple repositories
repos=("frontend" "backend" "mobile" "docs")
base_version="2.1.0"
for repo in "repos[@]"; do
echo "Processing $repo..."
cd "$repo"
# Generate changelog for this repo
git log --oneline --since="1 month ago" | \
python ../scripts/changelog_generator.py \
--version "$base_version" \
--output "CHANGELOG_$repo.md"
# Determine version bump
git log --oneline $(git describe --tags --abbrev=0)..HEAD | \
python ../scripts/version_bumper.py \
--current-version "$(git describe --tags --abbrev=0)" > "VERSION_$repo.txt"
cd ..
done
echo "Generated changelogs and version recommendations for all repositories"
```
### Integration with Slack/Teams
```python
#!/usr/bin/env python3
# notify_release_status.py
import json
import requests
import subprocess
def send_slack_notification(webhook_url, message):
payload = {"text": message}
requests.post(webhook_url, json=payload)
def get_release_status():
"""Get current release status from release planner."""
result = subprocess.run(
["python", "release_planner.py", "--input", "release_plan.json", "--output-format", "json"],
capture_output=True, text=True
)
return json.loads(result.stdout)
# Usage in CI/CD
status = get_release_status()
if status["assessment"]["overall_status"] == "blocked":
message = f"🚫 Release {status['version']} is BLOCKED\n"
message += f"Issues: {', '.join(status['assessment']['blocking_issues'])}"
send_slack_notification(SLACK_WEBHOOK_URL, message)
elif status["assessment"]["overall_status"] == "ready":
message = f"✅ Release {status['version']} is READY for deployment!"
send_slack_notification(SLACK_WEBHOOK_URL, message)
```
## Best Practices
### Commit Message Guidelines
1. **Use conventional commits consistently** across your team
2. **Be specific** in commit descriptions: "fix: resolve race condition in user creation" vs "fix: bug"
3. **Reference issues** when applicable: "Closes #123" or "Fixes #456"
4. **Mark breaking changes** clearly with `!` or `BREAKING CHANGE:` footer
5. **Keep first line under 50 characters** when possible
### Release Planning
1. **Plan releases early** with clear feature lists and target dates
2. **Set quality gates** and stick to them (test coverage, security scans, etc.)
3. **Track approvals** from all relevant stakeholders
4. **Document rollback procedures** before deployment
5. **Communicate clearly** with both internal teams and external users
### Version Management
1. **Follow semantic versioning** strictly for predictable releases
2. **Use pre-release versions** for beta testing and gradual rollouts
3. **Tag releases consistently** with proper version numbers
4. **Maintain backwards compatibility** when possible to avoid major version bumps
5. **Document breaking changes** thoroughly with migration guides
## Troubleshooting
### Common Issues
**"No valid commits found"**
- Ensure git log contains commit messages
- Check that commits follow conventional format
- Verify input format (git-log vs json)
**"Invalid version format"**
- Use semantic versioning: 1.2.3, not 1.2 or v1.2.3.beta
- Pre-release format: 1.2.3-alpha.1
**"Missing required approvals"**
- Check feature risk levels in release plan
- High/critical risk features require additional approvals
- Update approval status in JSON file
### Debug Mode
All scripts support verbose output for debugging:
```bash
# Add debug logging
python changelog_generator.py --input sample.txt --debug
# Validate input data
python -c "import json; print(json.load(open('release_plan.json')))"
# Test with sample data first
python release_planner.py --input assets/sample_release_plan.json
```
## Contributing
When extending these scripts:
1. **Maintain backwards compatibility** for existing command-line interfaces
2. **Add comprehensive tests** for new features
3. **Update documentation** including this README and SKILL.md
4. **Follow Python standards** (PEP 8, type hints where helpful)
5. **Use only standard library** to avoid dependencies
## License
This skill is part of the claude-skills repository and follows the same license terms.
---
For detailed methodology and background information, see [SKILL.md](SKILL.md).
For specific workflow guidance, see the [references](references/) directory.
For testing the scripts, use the sample data in the [assets](assets/) directory.
FILE:references/conventional-commits-guide.md
# Conventional Commits Guide
## Overview
Conventional Commits is a specification for adding human and machine readable meaning to commit messages. The specification provides an easy set of rules for creating an explicit commit history, which makes it easier to write automated tools for version management, changelog generation, and release planning.
## Basic Format
```
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
```
## Commit Types
### Primary Types
- **feat**: A new feature for the user (correlates with MINOR in semantic versioning)
- **fix**: A bug fix for the user (correlates with PATCH in semantic versioning)
### Secondary Types
- **build**: Changes that affect the build system or external dependencies (webpack, npm, etc.)
- **ci**: Changes to CI configuration files and scripts (Travis, Circle, BrowserStack, SauceLabs)
- **docs**: Documentation only changes
- **perf**: A code change that improves performance
- **refactor**: A code change that neither fixes a bug nor adds a feature
- **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc.)
- **test**: Adding missing tests or correcting existing tests
- **chore**: Other changes that don't modify src or test files
- **revert**: Reverts a previous commit
### Breaking Changes
Any commit can introduce a breaking change by:
1. Adding `!` after the type: `feat!: remove deprecated API`
2. Including `BREAKING CHANGE:` in the footer
## Scopes
Scopes provide additional contextual information about the change. They should be noun describing a section of the codebase:
- `auth` - Authentication and authorization
- `api` - API changes
- `ui` - User interface
- `db` - Database related changes
- `config` - Configuration changes
- `deps` - Dependency updates
## Examples
### Simple Feature
```
feat(auth): add OAuth2 integration
Integrate OAuth2 authentication with Google and GitHub providers.
Users can now log in using their existing social media accounts.
```
### Bug Fix
```
fix(api): resolve race condition in user creation
When multiple requests tried to create users with the same email
simultaneously, duplicate records were sometimes created. Added
proper database constraints and error handling.
Fixes #234
```
### Breaking Change with !
```
feat(api)!: remove deprecated /v1/users endpoint
The deprecated /v1/users endpoint has been removed. All clients
should migrate to /v2/users which provides better performance
and additional features.
BREAKING CHANGE: /v1/users endpoint removed, use /v2/users instead
```
### Breaking Change with Footer
```
feat(auth): implement new authentication flow
Add support for multi-factor authentication and improved session
management. This change requires all users to re-authenticate.
BREAKING CHANGE: Authentication tokens issued before this release
are no longer valid. Users must log in again.
```
### Performance Improvement
```
perf(image): optimize image compression algorithm
Replaced PNG compression with WebP format, reducing image sizes
by 40% on average while maintaining visual quality.
Closes #456
```
### Dependency Update
```
build(deps): upgrade React to version 18.2.0
Updates React and related packages to latest stable versions.
Includes performance improvements and new concurrent features.
```
### Documentation
```
docs(readme): add deployment instructions
Added comprehensive deployment guide including Docker setup,
environment variables configuration, and troubleshooting tips.
```
### Revert
```
revert: feat(payment): add cryptocurrency support
This reverts commit 667ecc1654a317a13331b17617d973392f415f02.
Reverting due to security concerns identified in code review.
The feature will be re-implemented with proper security measures.
```
## Multi-paragraph Body
For complex changes, use multiple paragraphs in the body:
```
feat(search): implement advanced search functionality
Add support for complex search queries including:
- Boolean operators (AND, OR, NOT)
- Field-specific searches (title:, author:, date:)
- Fuzzy matching with configurable threshold
- Search result highlighting
The search index has been restructured to support these new
features while maintaining backward compatibility with existing
simple search queries.
Performance testing shows less than 10ms impact on search
response times even with complex queries.
Closes #789, #823, #901
```
## Footers
### Issue References
```
Fixes #123
Closes #234, #345
Resolves #456
```
### Breaking Changes
```
BREAKING CHANGE: The `authenticate` function now requires a second
parameter for the authentication method. Update all calls from
`authenticate(token)` to `authenticate(token, 'bearer')`.
```
### Co-authors
```
Co-authored-by: Jane Doe <jane@example.com>
Co-authored-by: John Smith <john@example.com>
```
### Reviewed By
```
Reviewed-by: Senior Developer <senior@example.com>
Acked-by: Tech Lead <lead@example.com>
```
## Automation Benefits
Using conventional commits enables:
### Automatic Version Bumping
- `fix` commits trigger PATCH version bump (1.0.0 → 1.0.1)
- `feat` commits trigger MINOR version bump (1.0.0 → 1.1.0)
- `BREAKING CHANGE` triggers MAJOR version bump (1.0.0 → 2.0.0)
### Changelog Generation
```markdown
## [1.2.0] - 2024-01-15
### Added
- OAuth2 integration (auth)
- Advanced search functionality (search)
### Fixed
- Race condition in user creation (api)
- Memory leak in image processing (image)
### Breaking Changes
- Authentication tokens issued before this release are no longer valid
```
### Release Notes
Generate user-friendly release notes automatically from commit history, filtering out internal changes and highlighting user-facing improvements.
## Best Practices
### Writing Good Descriptions
- Use imperative mood: "add feature" not "added feature"
- Start with lowercase letter
- No period at the end
- Limit to 50 characters when possible
- Be specific and descriptive
### Good Examples
```
feat(auth): add password reset functionality
fix(ui): resolve mobile navigation menu overflow
perf(db): optimize user query with proper indexing
```
### Bad Examples
```
feat: stuff
fix: bug
update: changes
```
### Body Guidelines
- Separate subject from body with blank line
- Wrap body at 72 characters
- Use body to explain what and why, not how
- Reference issues and PRs when relevant
### Scope Guidelines
- Use consistent scope naming across the team
- Keep scopes short and meaningful
- Document your team's scope conventions
- Consider using scopes that match your codebase structure
## Tools and Integration
### Git Hooks
Use tools like `commitizen` or `husky` to enforce conventional commit format:
```bash
# Install commitizen
npm install -g commitizen cz-conventional-changelog
# Configure
echo '{ "path": "cz-conventional-changelog" }' > ~/.czrc
# Use
git cz
```
### Automated Validation
Add commit message validation to prevent non-conventional commits:
```javascript
// commitlint.config.js
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
2, 'always',
['feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'build', 'ci', 'chore', 'revert']
],
'subject-case': [2, 'always', 'lower-case'],
'subject-max-length': [2, 'always', 50]
}
};
```
### CI/CD Integration
Integrate with release automation tools:
- **semantic-release**: Automated version management and package publishing
- **standard-version**: Generate changelog and tag releases
- **release-please**: Google's release automation tool
## Common Mistakes
### Mixing Multiple Changes
```
# Bad: Multiple unrelated changes
feat: add login page and fix CSS bug and update dependencies
# Good: Separate commits
feat(auth): add login page
fix(ui): resolve CSS styling issue
build(deps): update React to version 18
```
### Vague Descriptions
```
# Bad: Not descriptive
fix: bug in code
feat: new stuff
# Good: Specific and clear
fix(api): resolve null pointer exception in user validation
feat(search): implement fuzzy matching algorithm
```
### Missing Breaking Change Indicators
```
# Bad: Breaking change not marked
feat(api): update user authentication
# Good: Properly marked breaking change
feat(api)!: update user authentication
BREAKING CHANGE: All API clients must now include authentication
headers in every request. Anonymous access is no longer supported.
```
## Team Guidelines
### Establishing Conventions
1. **Define scope vocabulary**: Create a list of approved scopes for your project
2. **Document examples**: Provide team-specific examples of good commits
3. **Set up tooling**: Use linters and hooks to enforce standards
4. **Review process**: Include commit message quality in code reviews
5. **Training**: Ensure all team members understand the format
### Scope Examples by Project Type
**Web Application:**
- `auth`, `ui`, `api`, `db`, `config`, `deploy`
**Library/SDK:**
- `core`, `utils`, `docs`, `examples`, `tests`
**Mobile App:**
- `ios`, `android`, `shared`, `ui`, `network`, `storage`
By following conventional commits consistently, your team will have a clear, searchable commit history that enables powerful automation and improves the overall development workflow.
FILE:references/hotfix-procedures.md
# Hotfix Procedures
## Overview
Hotfixes are emergency releases designed to address critical production issues that cannot wait for the regular release cycle. This document outlines classification, procedures, and best practices for managing hotfixes across different development workflows.
## Severity Classification
### P0 - Critical (Production Down)
**Definition:** Complete system outage, data corruption, or security breach affecting all users.
**Examples:**
- Server crashes preventing any user access
- Database corruption causing data loss
- Security vulnerability being actively exploited
- Payment system completely non-functional
- Authentication system failure preventing all logins
**Response Requirements:**
- **Timeline:** Fix deployed within 2 hours
- **Approval:** Engineering Lead + On-call Manager (verbal approval acceptable)
- **Process:** Emergency deployment bypassing normal gates
- **Communication:** Immediate notification to all stakeholders
- **Documentation:** Post-incident review required within 24 hours
**Escalation:**
- Page on-call engineer immediately
- Escalate to Engineering Lead within 15 minutes
- Notify CEO/CTO if resolution exceeds 4 hours
### P1 - High (Major Feature Broken)
**Definition:** Critical functionality broken affecting significant portion of users.
**Examples:**
- Core user workflow completely broken
- Payment processing failures affecting >50% of transactions
- Search functionality returning no results
- Mobile app crashes on startup
- API returning 500 errors for main endpoints
**Response Requirements:**
- **Timeline:** Fix deployed within 24 hours
- **Approval:** Engineering Lead + Product Manager
- **Process:** Expedited review and testing
- **Communication:** Stakeholder notification within 1 hour
- **Documentation:** Root cause analysis within 48 hours
**Escalation:**
- Notify on-call engineer within 30 minutes
- Escalate to Engineering Lead within 2 hours
- Daily updates to Product/Business stakeholders
### P2 - Medium (Minor Feature Issues)
**Definition:** Non-critical functionality issues with limited user impact.
**Examples:**
- Cosmetic UI issues affecting user experience
- Non-essential features not working properly
- Performance degradation not affecting core workflows
- Minor API inconsistencies
- Reporting/analytics data inaccuracies
**Response Requirements:**
- **Timeline:** Include in next regular release
- **Approval:** Standard pull request review process
- **Process:** Normal development and testing cycle
- **Communication:** Include in regular release notes
- **Documentation:** Standard issue tracking
**Escalation:**
- Create ticket in normal backlog
- No special escalation required
- Include in release planning discussions
## Hotfix Workflows by Development Model
### Git Flow Hotfix Process
#### Branch Structure
```
main (v1.2.3) ← hotfix/security-patch → main (v1.2.4)
→ develop
```
#### Step-by-Step Process
1. **Create Hotfix Branch**
```bash
git checkout main
git pull origin main
git checkout -b hotfix/security-patch
```
2. **Implement Fix**
- Make minimal changes addressing only the specific issue
- Include tests to prevent regression
- Update version number (patch increment)
```bash
# Fix the issue
git add .
git commit -m "fix: resolve SQL injection vulnerability"
# Version bump
echo "1.2.4" > VERSION
git add VERSION
git commit -m "chore: bump version to 1.2.4"
```
3. **Test Fix**
- Run automated test suite
- Manual testing of affected functionality
- Security review if applicable
```bash
# Run tests
npm test
python -m pytest
# Security scan
npm audit
bandit -r src/
```
4. **Deploy to Staging**
```bash
# Deploy hotfix branch to staging
git push origin hotfix/security-patch
# Trigger staging deployment via CI/CD
```
5. **Merge to Production**
```bash
# Merge to main
git checkout main
git merge --no-ff hotfix/security-patch
git tag -a v1.2.4 -m "Hotfix: Security vulnerability patch"
git push origin main --tags
# Merge back to develop
git checkout develop
git merge --no-ff hotfix/security-patch
git push origin develop
# Clean up
git branch -d hotfix/security-patch
git push origin --delete hotfix/security-patch
```
### GitHub Flow Hotfix Process
#### Branch Structure
```
main ← hotfix/critical-fix → main (immediate deploy)
```
#### Step-by-Step Process
1. **Create Fix Branch**
```bash
git checkout main
git pull origin main
git checkout -b hotfix/payment-gateway-fix
```
2. **Implement and Test**
```bash
# Make the fix
git add .
git commit -m "fix(payment): resolve gateway timeout issue"
git push origin hotfix/payment-gateway-fix
```
3. **Create Emergency PR**
```bash
# Use GitHub CLI or web interface
gh pr create --title "HOTFIX: Payment gateway timeout" \
--body "Critical fix for payment processing failures" \
--reviewer engineering-team \
--label hotfix
```
4. **Deploy Branch for Testing**
```bash
# Deploy branch to staging for validation
./deploy.sh hotfix/payment-gateway-fix staging
# Quick smoke tests
```
5. **Emergency Merge and Deploy**
```bash
# After approval, merge and deploy
gh pr merge --squash
# Automatic deployment to production via CI/CD
```
### Trunk-based Hotfix Process
#### Direct Commit Approach
```bash
# For small fixes, commit directly to main
git checkout main
git pull origin main
# Make fix
git add .
git commit -m "fix: resolve memory leak in user session handling"
git push origin main
# Automatic deployment triggers
```
#### Feature Flag Rollback
```bash
# For feature-related issues, disable via feature flag
curl -X POST api/feature-flags/new-search/disable
# Verify issue resolved
# Plan proper fix for next deployment
```
## Emergency Response Procedures
### Incident Declaration Process
1. **Detection and Assessment** (0-5 minutes)
- Monitor alerts or user reports identify issue
- Assess severity using classification matrix
- Determine if hotfix is required
2. **Team Assembly** (5-10 minutes)
- Page appropriate on-call engineer
- Assemble incident response team
- Establish communication channel (Slack, Teams)
3. **Initial Response** (10-30 minutes)
- Create incident ticket/document
- Begin investigating root cause
- Implement immediate mitigations if possible
4. **Hotfix Development** (30 minutes - 2 hours)
- Create hotfix branch
- Implement minimal fix
- Test fix in isolation
5. **Deployment** (15-30 minutes)
- Deploy to staging for validation
- Deploy to production
- Monitor for successful resolution
6. **Verification** (15-30 minutes)
- Confirm issue is resolved
- Monitor system stability
- Update stakeholders
### Communication Templates
#### P0 Initial Alert
```
🚨 CRITICAL INCIDENT - Production Down
Status: Investigating
Impact: Complete service outage
Affected Users: All users
Started: 2024-01-15 14:30 UTC
Incident Commander: @john.doe
Current Actions:
- Investigating root cause
- Preparing emergency fix
- Will update every 15 minutes
Status Page: https://status.ourapp.com
Incident Channel: #incident-2024-001
```
#### P0 Resolution Notice
```
✅ RESOLVED - Production Restored
Status: Resolved
Resolution Time: 1h 23m
Root Cause: Database connection pool exhaustion
Fix: Increased connection limits and restarted services
Timeline:
14:30 UTC - Issue detected
14:45 UTC - Root cause identified
15:20 UTC - Fix deployed
15:35 UTC - Full functionality restored
Post-incident review scheduled for tomorrow 10:00 AM.
Thank you for your patience.
```
#### P1 Status Update
```
⚠️ Issue Update - Payment Processing
Status: Fix deployed, monitoring
Impact: Payment failures reduced from 45% to <2%
ETA: Complete resolution within 2 hours
Actions taken:
- Deployed hotfix to address timeout issues
- Increased monitoring on payment gateway
- Contacting affected customers
Next update in 30 minutes or when resolved.
```
### Rollback Procedures
#### When to Rollback
- Fix doesn't resolve the issue
- Fix introduces new problems
- System stability is compromised
- Data corruption is detected
#### Rollback Process
1. **Immediate Assessment** (2-5 minutes)
```bash
# Check system health
curl -f https://api.ourapp.com/health
# Review error logs
kubectl logs deployment/app --tail=100
# Check key metrics
```
2. **Rollback Execution** (5-15 minutes)
```bash
# Git-based rollback
git checkout main
git revert HEAD
git push origin main
# Or container-based rollback
kubectl rollout undo deployment/app
# Or load balancer switch
aws elbv2 modify-target-group --target-group-arn arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/previous-version
```
3. **Verification** (5-10 minutes)
```bash
# Confirm rollback successful
# Check system health endpoints
# Verify core functionality working
# Monitor error rates and performance
```
4. **Communication**
```
🔄 ROLLBACK COMPLETE
The hotfix has been rolled back due to [reason].
System is now stable on previous version.
We are investigating the issue and will provide updates.
```
## Testing Strategies for Hotfixes
### Pre-deployment Testing
#### Automated Testing
```bash
# Run full test suite
npm test
pytest tests/
go test ./...
# Security scanning
npm audit --audit-level high
bandit -r src/
gosec ./...
# Integration tests
./run_integration_tests.sh
# Load testing (if performance-related)
artillery quick --count 100 --num 10 https://staging.ourapp.com
```
#### Manual Testing Checklist
- [ ] Core user workflow functions correctly
- [ ] Authentication and authorization working
- [ ] Payment processing (if applicable)
- [ ] Data integrity maintained
- [ ] No new error logs or exceptions
- [ ] Performance within acceptable range
- [ ] Mobile app functionality (if applicable)
- [ ] Third-party integrations working
#### Staging Validation
```bash
# Deploy to staging
./deploy.sh hotfix/critical-fix staging
# Run smoke tests
curl -f https://staging.ourapp.com/api/health
./smoke_tests.sh
# Manual verification of specific issue
# Document test results
```
### Post-deployment Monitoring
#### Immediate Monitoring (First 30 minutes)
- Error rate and count
- Response time and latency
- CPU and memory usage
- Database connection counts
- Key business metrics
#### Extended Monitoring (First 24 hours)
- User activity patterns
- Feature usage statistics
- Customer support tickets
- Performance trends
- Security log analysis
#### Monitoring Scripts
```bash
#!/bin/bash
# monitor_hotfix.sh - Post-deployment monitoring
echo "=== Hotfix Deployment Monitoring ==="
echo "Deployment time: $(date)"
echo
# Check application health
echo "--- Application Health ---"
curl -s https://api.ourapp.com/health | jq '.'
# Check error rates
echo "--- Error Rates (last 30min) ---"
curl -s "https://api.datadog.com/api/v1/query?query=sum:application.errors{*}" \
-H "DD-API-KEY: $DATADOG_API_KEY" | jq '.series[0].pointlist[-1][1]'
# Check response times
echo "--- Response Times ---"
curl -s "https://api.datadog.com/api/v1/query?query=avg:application.response_time{*}" \
-H "DD-API-KEY: $DATADOG_API_KEY" | jq '.series[0].pointlist[-1][1]'
# Check database connections
echo "--- Database Status ---"
psql -h db.ourapp.com -U readonly -c "SELECT count(*) as active_connections FROM pg_stat_activity;"
echo "=== Monitoring Complete ==="
```
## Documentation and Learning
### Incident Documentation Template
```markdown
# Incident Report: [Brief Description]
## Summary
- **Incident ID:** INC-2024-001
- **Severity:** P0/P1/P2
- **Start Time:** 2024-01-15 14:30 UTC
- **End Time:** 2024-01-15 15:45 UTC
- **Duration:** 1h 15m
- **Impact:** [Description of user/business impact]
## Root Cause
[Detailed explanation of what went wrong and why]
## Timeline
| Time | Event |
|------|-------|
| 14:30 | Issue detected via monitoring alert |
| 14:35 | Incident team assembled |
| 14:45 | Root cause identified |
| 15:00 | Fix developed and tested |
| 15:20 | Fix deployed to production |
| 15:45 | Issue confirmed resolved |
## Resolution
[What was done to fix the issue]
## Lessons Learned
### What went well
- Quick detection through monitoring
- Effective team coordination
- Minimal user impact
### What could be improved
- Earlier detection possible with better alerting
- Testing could have caught this issue
- Communication could be more proactive
## Action Items
- [ ] Improve monitoring for [specific area]
- [ ] Add automated test for [specific scenario]
- [ ] Update documentation for [specific process]
- [ ] Training on [specific topic] for team
## Prevention Measures
[How we'll prevent this from happening again]
```
### Post-Incident Review Process
1. **Schedule Review** (within 24-48 hours)
- Involve all key participants
- Book 60-90 minute session
- Prepare incident timeline
2. **Blameless Analysis**
- Focus on systems and processes, not individuals
- Understand contributing factors
- Identify improvement opportunities
3. **Action Plan**
- Concrete, assignable tasks
- Realistic timelines
- Clear success criteria
4. **Follow-up**
- Track action item completion
- Share learnings with broader team
- Update procedures based on insights
### Knowledge Sharing
#### Runbook Updates
After each hotfix, update relevant runbooks:
- Add new troubleshooting steps
- Update contact information
- Refine escalation procedures
- Document new tools or processes
#### Team Training
- Share incident learnings in team meetings
- Conduct tabletop exercises for common scenarios
- Update onboarding materials with hotfix procedures
- Create decision trees for severity classification
#### Automation Improvements
- Add alerts for new failure modes
- Automate manual steps where possible
- Improve deployment and rollback processes
- Enhance monitoring and observability
## Common Pitfalls and Best Practices
### Common Pitfalls
❌ **Over-engineering the fix**
- Making broad changes instead of minimal targeted fix
- Adding features while fixing bugs
- Refactoring unrelated code
❌ **Insufficient testing**
- Skipping automated tests due to time pressure
- Not testing the exact scenario that caused the issue
- Deploying without staging validation
❌ **Poor communication**
- Not notifying stakeholders promptly
- Unclear or infrequent status updates
- Forgetting to announce resolution
❌ **Inadequate monitoring**
- Not watching system health after deployment
- Missing secondary effects of the fix
- Failing to verify the issue is actually resolved
### Best Practices
✅ **Keep fixes minimal and focused**
- Address only the specific issue
- Avoid scope creep or improvements
- Save refactoring for regular releases
✅ **Maintain clear communication**
- Set up dedicated incident channel
- Provide regular status updates
- Use clear, non-technical language for business stakeholders
✅ **Test thoroughly but efficiently**
- Focus testing on affected functionality
- Use automated tests where possible
- Validate in staging before production
✅ **Document everything**
- Maintain timeline of events
- Record decisions and rationale
- Share lessons learned with team
✅ **Plan for rollback**
- Always have a rollback plan ready
- Test rollback procedure in advance
- Monitor closely after deployment
By following these procedures and continuously improving based on experience, teams can handle production emergencies effectively while minimizing impact and learning from each incident.
FILE:references/release-workflow-comparison.md
# Release Workflow Comparison
## Overview
This document compares the three most popular branching and release workflows: Git Flow, GitHub Flow, and Trunk-based Development. Each approach has distinct advantages and trade-offs depending on your team size, deployment frequency, and risk tolerance.
## Git Flow
### Structure
```
main (production)
↑
release/1.2.0 ← develop (integration) ← feature/user-auth
↑ ← feature/payment-api
hotfix/critical-fix
```
### Branch Types
- **main**: Production-ready code, tagged releases
- **develop**: Integration branch for next release
- **feature/***: Individual features, merged to develop
- **release/X.Y.Z**: Release preparation, branched from develop
- **hotfix/***: Critical fixes, branched from main
### Typical Flow
1. Create feature branch from develop: `git checkout -b feature/login develop`
2. Work on feature, commit changes
3. Merge feature to develop when complete
4. When ready for release, create release branch: `git checkout -b release/1.2.0 develop`
5. Finalize release (version bump, changelog, bug fixes)
6. Merge release branch to both main and develop
7. Tag release: `git tag v1.2.0`
8. Deploy from main branch
### Advantages
- **Clear separation** between production and development code
- **Stable main branch** always represents production state
- **Parallel development** of features without interference
- **Structured release process** with dedicated release branches
- **Hotfix support** without disrupting development work
- **Good for scheduled releases** and traditional release cycles
### Disadvantages
- **Complex workflow** with many branch types
- **Merge overhead** from multiple integration points
- **Delayed feedback** from long-lived feature branches
- **Integration conflicts** when merging large features
- **Slower deployment** due to process overhead
- **Not ideal for continuous deployment**
### Best For
- Large teams (10+ developers)
- Products with scheduled release cycles
- Enterprise software with formal testing phases
- Projects requiring stable release branches
- Teams comfortable with complex Git workflows
### Example Commands
```bash
# Start new feature
git checkout develop
git checkout -b feature/user-authentication
# Finish feature
git checkout develop
git merge --no-ff feature/user-authentication
git branch -d feature/user-authentication
# Start release
git checkout develop
git checkout -b release/1.2.0
# Version bump and changelog updates
git commit -am "Bump version to 1.2.0"
# Finish release
git checkout main
git merge --no-ff release/1.2.0
git tag -a v1.2.0 -m "Release version 1.2.0"
git checkout develop
git merge --no-ff release/1.2.0
git branch -d release/1.2.0
# Hotfix
git checkout main
git checkout -b hotfix/security-patch
# Fix the issue
git commit -am "Fix security vulnerability"
git checkout main
git merge --no-ff hotfix/security-patch
git tag -a v1.2.1 -m "Hotfix version 1.2.1"
git checkout develop
git merge --no-ff hotfix/security-patch
```
## GitHub Flow
### Structure
```
main ← feature/user-auth
← feature/payment-api
← hotfix/critical-fix
```
### Branch Types
- **main**: Production-ready code, deployed automatically
- **feature/***: All changes, regardless of size or type
### Typical Flow
1. Create feature branch from main: `git checkout -b feature/login main`
2. Work on feature with regular commits and pushes
3. Open pull request when ready for feedback
4. Deploy feature branch to staging for testing
5. Merge to main when approved and tested
6. Deploy main to production automatically
7. Delete feature branch
### Advantages
- **Simple workflow** with only two branch types
- **Fast deployment** with minimal process overhead
- **Continuous integration** with frequent merges to main
- **Early feedback** through pull request reviews
- **Deploy from branches** allows testing before merge
- **Good for continuous deployment**
### Disadvantages
- **Main can be unstable** if testing is insufficient
- **No release branches** for coordinating multiple features
- **Limited hotfix process** requires careful coordination
- **Requires strong testing** and CI/CD infrastructure
- **Not suitable for scheduled releases**
- **Can be chaotic** with many simultaneous features
### Best For
- Small to medium teams (2-10 developers)
- Web applications with continuous deployment
- Products with rapid iteration cycles
- Teams with strong testing and CI/CD practices
- Projects where main is always deployable
### Example Commands
```bash
# Start new feature
git checkout main
git pull origin main
git checkout -b feature/user-authentication
# Regular work
git add .
git commit -m "feat(auth): add login form validation"
git push origin feature/user-authentication
# Deploy branch for testing
# (Usually done through CI/CD)
./deploy.sh feature/user-authentication staging
# Merge when ready
git checkout main
git merge feature/user-authentication
git push origin main
git branch -d feature/user-authentication
# Automatic deployment to production
# (Triggered by push to main)
```
## Trunk-based Development
### Structure
```
main ← short-feature-branch (1-3 days max)
← another-short-branch
← direct-commits
```
### Branch Types
- **main**: The single source of truth, always deployable
- **Short-lived branches**: Optional, for changes taking >1 day
### Typical Flow
1. Commit directly to main for small changes
2. Create short-lived branch for larger changes (max 2-3 days)
3. Merge to main frequently (multiple times per day)
4. Use feature flags to hide incomplete features
5. Deploy main to production multiple times per day
6. Release by enabling feature flags, not code deployment
### Advantages
- **Simplest workflow** with minimal branching
- **Fastest integration** with continuous merges
- **Reduced merge conflicts** from short-lived branches
- **Always deployable main** through feature flags
- **Fastest feedback loop** with immediate integration
- **Excellent for CI/CD** and DevOps practices
### Disadvantages
- **Requires discipline** to keep main stable
- **Needs feature flags** for incomplete features
- **Limited code review** for direct commits
- **Can be destabilizing** without proper testing
- **Requires advanced CI/CD** infrastructure
- **Not suitable for teams** uncomfortable with frequent changes
### Best For
- Expert teams with strong DevOps culture
- Products requiring very fast iteration
- Microservices architectures
- Teams practicing continuous deployment
- Organizations with mature testing practices
### Example Commands
```bash
# Small change - direct to main
git checkout main
git pull origin main
# Make changes
git add .
git commit -m "fix(ui): resolve button alignment issue"
git push origin main
# Larger change - short branch
git checkout main
git pull origin main
git checkout -b payment-integration
# Work for 1-2 days maximum
git add .
git commit -m "feat(payment): add Stripe integration"
git push origin payment-integration
# Immediate merge
git checkout main
git merge payment-integration
git push origin main
git branch -d payment-integration
# Feature flag usage
if (featureFlags.enabled('stripe_payments', userId)) {
return renderStripePayment();
} else {
return renderLegacyPayment();
}
```
## Feature Comparison Matrix
| Aspect | Git Flow | GitHub Flow | Trunk-based |
|--------|----------|-------------|-------------|
| **Complexity** | High | Medium | Low |
| **Learning Curve** | Steep | Moderate | Gentle |
| **Deployment Frequency** | Weekly/Monthly | Daily | Multiple/day |
| **Branch Lifetime** | Weeks/Months | Days/Weeks | Hours/Days |
| **Main Stability** | Very High | High | High* |
| **Release Coordination** | Excellent | Limited | Feature Flags |
| **Hotfix Support** | Built-in | Manual | Direct |
| **Merge Conflicts** | High | Medium | Low |
| **Team Size** | 10+ | 3-10 | Any |
| **CI/CD Requirements** | Medium | High | Very High |
*With proper feature flags and testing
## Release Strategies by Workflow
### Git Flow Releases
```bash
# Scheduled release every 2 weeks
git checkout develop
git checkout -b release/2.3.0
# Version management
echo "2.3.0" > VERSION
npm version 2.3.0 --no-git-tag-version
python setup.py --version 2.3.0
# Changelog generation
git log --oneline release/2.2.0..HEAD --pretty=format:"%s" > CHANGELOG_DRAFT.md
# Testing and bug fixes in release branch
git commit -am "fix: resolve issue found in release testing"
# Finalize release
git checkout main
git merge --no-ff release/2.3.0
git tag -a v2.3.0 -m "Release 2.3.0"
# Deploy tagged version
docker build -t app:2.3.0 .
kubectl set image deployment/app app=app:2.3.0
```
### GitHub Flow Releases
```bash
# Deploy every merge to main
git checkout main
git merge feature/new-payment-method
# Automatic deployment via CI/CD
# .github/workflows/deploy.yml triggers on push to main
# Tag releases for tracking (optional)
git tag -a v2.3.$(date +%Y%m%d%H%M) -m "Production deployment"
# Rollback if needed
git revert HEAD
git push origin main # Triggers automatic rollback deployment
```
### Trunk-based Releases
```bash
# Continuous deployment with feature flags
git checkout main
git add feature_flags.json
git commit -m "feat: enable new payment method for 10% of users"
git push origin main
# Gradual rollout
curl -X POST api/feature-flags/payment-v2/rollout/25 # 25% of users
# Monitor metrics...
curl -X POST api/feature-flags/payment-v2/rollout/50 # 50% of users
# Monitor metrics...
curl -X POST api/feature-flags/payment-v2/rollout/100 # Full rollout
# Remove flag after successful rollout
git rm old_payment_code.js
git commit -m "cleanup: remove legacy payment code"
```
## Choosing the Right Workflow
### Decision Matrix
**Choose Git Flow if:**
- ✅ Team size > 10 developers
- ✅ Scheduled release cycles (weekly/monthly)
- ✅ Multiple versions supported simultaneously
- ✅ Formal testing and QA processes
- ✅ Complex enterprise software
- ❌ Need rapid deployment
- ❌ Small team or startup
**Choose GitHub Flow if:**
- ✅ Team size 3-10 developers
- ✅ Web applications or APIs
- ✅ Strong CI/CD and testing
- ✅ Daily or continuous deployment
- ✅ Simple release requirements
- ❌ Complex release coordination needed
- ❌ Multiple release branches required
**Choose Trunk-based Development if:**
- ✅ Expert development team
- ✅ Mature DevOps practices
- ✅ Microservices architecture
- ✅ Feature flag infrastructure
- ✅ Multiple deployments per day
- ✅ Strong automated testing
- ❌ Junior developers
- ❌ Complex integration requirements
### Migration Strategies
#### From Git Flow to GitHub Flow
1. **Simplify branching**: Eliminate develop branch, work directly with main
2. **Increase deployment frequency**: Move from scheduled to continuous releases
3. **Strengthen testing**: Improve automated test coverage and CI/CD
4. **Reduce branch lifetime**: Limit feature branches to 1-2 weeks maximum
5. **Train team**: Educate on simpler workflow and increased responsibility
#### From GitHub Flow to Trunk-based
1. **Implement feature flags**: Add feature toggle infrastructure
2. **Improve CI/CD**: Ensure all tests run in <10 minutes
3. **Increase commit frequency**: Encourage multiple commits per day
4. **Reduce branch usage**: Start committing small changes directly to main
5. **Monitor stability**: Ensure main remains deployable at all times
#### From Trunk-based to Git Flow
1. **Add structure**: Introduce develop and release branches
2. **Reduce deployment frequency**: Move to scheduled release cycles
3. **Extend branch lifetime**: Allow longer feature development cycles
4. **Formalize process**: Add approval gates and testing phases
5. **Coordinate releases**: Plan features for specific release versions
## Anti-patterns to Avoid
### Git Flow Anti-patterns
- **Long-lived feature branches** (>2 weeks)
- **Skipping release branches** for small releases
- **Direct commits to main** bypassing develop
- **Forgetting to merge back** to develop after hotfixes
- **Complex merge conflicts** from delayed integration
### GitHub Flow Anti-patterns
- **Unstable main branch** due to insufficient testing
- **Long-lived feature branches** defeating the purpose
- **Skipping pull request reviews** for speed
- **Direct production deployment** without staging validation
- **No rollback plan** when deployments fail
### Trunk-based Anti-patterns
- **Committing broken code** to main branch
- **Feature branches lasting weeks** defeating the philosophy
- **No feature flags** for incomplete features
- **Insufficient automated testing** leading to instability
- **Poor CI/CD pipeline** causing deployment delays
## Conclusion
The choice of release workflow significantly impacts your team's productivity, code quality, and deployment reliability. Consider your team size, technical maturity, deployment requirements, and organizational culture when making this decision.
**Start conservative** (Git Flow) and evolve toward more agile approaches (GitHub Flow, Trunk-based) as your team's skills and infrastructure mature. The key is consistency within your team and alignment with your organization's goals and constraints.
Remember: **The best workflow is the one your team can execute consistently and reliably**.
FILE:release_planner.py
#!/usr/bin/env python3
"""
Release Planner
Takes a list of features/PRs/tickets planned for release and assesses release readiness.
Checks for required approvals, test coverage thresholds, breaking change documentation,
dependency updates, migration steps needed. Generates release checklist, communication
plan, and rollback procedures.
Input: release plan JSON (features, PRs, target date)
Output: release readiness report + checklist + rollback runbook + announcement draft
"""
import argparse
import json
import sys
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Any, Union
from dataclasses import dataclass, asdict
from enum import Enum
class RiskLevel(Enum):
"""Risk levels for release components."""
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
CRITICAL = "critical"
class ComponentStatus(Enum):
"""Status of release components."""
PENDING = "pending"
IN_PROGRESS = "in_progress"
READY = "ready"
BLOCKED = "blocked"
FAILED = "failed"
@dataclass
class Feature:
"""Represents a feature in the release."""
id: str
title: str
description: str
type: str # feature, bugfix, security, breaking_change, etc.
assignee: str
status: ComponentStatus
pull_request_url: Optional[str] = None
issue_url: Optional[str] = None
risk_level: RiskLevel = RiskLevel.MEDIUM
test_coverage_required: float = 80.0
test_coverage_actual: Optional[float] = None
requires_migration: bool = False
migration_complexity: str = "simple" # simple, moderate, complex
breaking_changes: List[str] = None
dependencies: List[str] = None
qa_approved: bool = False
security_approved: bool = False
pm_approved: bool = False
def __post_init__(self):
if self.breaking_changes is None:
self.breaking_changes = []
if self.dependencies is None:
self.dependencies = []
@dataclass
class QualityGate:
"""Quality gate requirements."""
name: str
required: bool
status: ComponentStatus
details: Optional[str] = None
threshold: Optional[float] = None
actual_value: Optional[float] = None
@dataclass
class Stakeholder:
"""Stakeholder for release communication."""
name: str
role: str
contact: str
notification_type: str # email, slack, teams
critical_path: bool = False
@dataclass
class RollbackStep:
"""Individual rollback step."""
order: int
description: str
command: Optional[str] = None
estimated_time: str = "5 minutes"
risk_level: RiskLevel = RiskLevel.LOW
verification: str = ""
class ReleasePlanner:
"""Main release planning and assessment logic."""
def __init__(self):
self.release_name: str = ""
self.version: str = ""
self.target_date: Optional[datetime] = None
self.features: List[Feature] = []
self.quality_gates: List[QualityGate] = []
self.stakeholders: List[Stakeholder] = []
self.rollback_steps: List[RollbackStep] = []
# Configuration
self.min_test_coverage = 80.0
self.required_approvals = ['pm_approved', 'qa_approved']
self.high_risk_approval_requirements = ['pm_approved', 'qa_approved', 'security_approved']
def load_release_plan(self, plan_data: Union[str, Dict]):
"""Load release plan from JSON."""
if isinstance(plan_data, str):
data = json.loads(plan_data)
else:
data = plan_data
self.release_name = data.get('release_name', 'Unnamed Release')
self.version = data.get('version', '1.0.0')
if 'target_date' in data:
self.target_date = datetime.fromisoformat(data['target_date'].replace('Z', '+00:00'))
# Load features
self.features = []
for feature_data in data.get('features', []):
try:
status = ComponentStatus(feature_data.get('status', 'pending'))
risk_level = RiskLevel(feature_data.get('risk_level', 'medium'))
feature = Feature(
id=feature_data['id'],
title=feature_data['title'],
description=feature_data.get('description', ''),
type=feature_data.get('type', 'feature'),
assignee=feature_data.get('assignee', ''),
status=status,
pull_request_url=feature_data.get('pull_request_url'),
issue_url=feature_data.get('issue_url'),
risk_level=risk_level,
test_coverage_required=feature_data.get('test_coverage_required', 80.0),
test_coverage_actual=feature_data.get('test_coverage_actual'),
requires_migration=feature_data.get('requires_migration', False),
migration_complexity=feature_data.get('migration_complexity', 'simple'),
breaking_changes=feature_data.get('breaking_changes', []),
dependencies=feature_data.get('dependencies', []),
qa_approved=feature_data.get('qa_approved', False),
security_approved=feature_data.get('security_approved', False),
pm_approved=feature_data.get('pm_approved', False)
)
self.features.append(feature)
except Exception as e:
print(f"Warning: Error parsing feature {feature_data.get('id', 'unknown')}: {e}",
file=sys.stderr)
# Load quality gates
self.quality_gates = []
for gate_data in data.get('quality_gates', []):
try:
status = ComponentStatus(gate_data.get('status', 'pending'))
gate = QualityGate(
name=gate_data['name'],
required=gate_data.get('required', True),
status=status,
details=gate_data.get('details'),
threshold=gate_data.get('threshold'),
actual_value=gate_data.get('actual_value')
)
self.quality_gates.append(gate)
except Exception as e:
print(f"Warning: Error parsing quality gate {gate_data.get('name', 'unknown')}: {e}",
file=sys.stderr)
# Load stakeholders
self.stakeholders = []
for stakeholder_data in data.get('stakeholders', []):
stakeholder = Stakeholder(
name=stakeholder_data['name'],
role=stakeholder_data['role'],
contact=stakeholder_data['contact'],
notification_type=stakeholder_data.get('notification_type', 'email'),
critical_path=stakeholder_data.get('critical_path', False)
)
self.stakeholders.append(stakeholder)
# Load or generate default quality gates if none provided
if not self.quality_gates:
self._generate_default_quality_gates()
# Load or generate default rollback steps
if 'rollback_steps' in data:
self.rollback_steps = []
for step_data in data['rollback_steps']:
risk_level = RiskLevel(step_data.get('risk_level', 'low'))
step = RollbackStep(
order=step_data['order'],
description=step_data['description'],
command=step_data.get('command'),
estimated_time=step_data.get('estimated_time', '5 minutes'),
risk_level=risk_level,
verification=step_data.get('verification', '')
)
self.rollback_steps.append(step)
else:
self._generate_default_rollback_steps()
def _generate_default_quality_gates(self):
"""Generate default quality gates."""
default_gates = [
{
'name': 'Unit Test Coverage',
'required': True,
'threshold': self.min_test_coverage,
'details': f'Minimum {self.min_test_coverage}% code coverage required'
},
{
'name': 'Integration Tests',
'required': True,
'details': 'All integration tests must pass'
},
{
'name': 'Security Scan',
'required': True,
'details': 'No high or critical security vulnerabilities'
},
{
'name': 'Performance Testing',
'required': True,
'details': 'Performance metrics within acceptable thresholds'
},
{
'name': 'Documentation Review',
'required': True,
'details': 'API docs and user docs updated for new features'
},
{
'name': 'Dependency Audit',
'required': True,
'details': 'All dependencies scanned for vulnerabilities'
}
]
self.quality_gates = []
for gate_data in default_gates:
gate = QualityGate(
name=gate_data['name'],
required=gate_data['required'],
status=ComponentStatus.PENDING,
details=gate_data['details'],
threshold=gate_data.get('threshold')
)
self.quality_gates.append(gate)
def _generate_default_rollback_steps(self):
"""Generate default rollback procedure."""
default_steps = [
{
'order': 1,
'description': 'Alert on-call team and stakeholders',
'estimated_time': '2 minutes',
'verification': 'Confirm team is aware and responding'
},
{
'order': 2,
'description': 'Switch load balancer to previous version',
'command': 'kubectl patch service app --patch \'{"spec": {"selector": {"version": "previous"}}}\'',
'estimated_time': '30 seconds',
'verification': 'Check that traffic is routing to old version'
},
{
'order': 3,
'description': 'Verify application health after rollback',
'estimated_time': '5 minutes',
'verification': 'Check error rates, response times, and health endpoints'
},
{
'order': 4,
'description': 'Roll back database migrations if needed',
'command': 'python manage.py migrate app 0001',
'estimated_time': '10 minutes',
'risk_level': 'high',
'verification': 'Verify data integrity and application functionality'
},
{
'order': 5,
'description': 'Update monitoring dashboards and alerts',
'estimated_time': '5 minutes',
'verification': 'Confirm metrics reflect rollback state'
},
{
'order': 6,
'description': 'Notify stakeholders of successful rollback',
'estimated_time': '5 minutes',
'verification': 'All stakeholders acknowledge rollback completion'
}
]
self.rollback_steps = []
for step_data in default_steps:
risk_level = RiskLevel(step_data.get('risk_level', 'low'))
step = RollbackStep(
order=step_data['order'],
description=step_data['description'],
command=step_data.get('command'),
estimated_time=step_data.get('estimated_time', '5 minutes'),
risk_level=risk_level,
verification=step_data.get('verification', '')
)
self.rollback_steps.append(step)
def assess_release_readiness(self) -> Dict:
"""Assess overall release readiness."""
assessment = {
'overall_status': 'ready',
'readiness_score': 0.0,
'blocking_issues': [],
'warnings': [],
'recommendations': [],
'feature_summary': {},
'quality_gate_summary': {},
'timeline_assessment': {}
}
total_score = 0
max_score = 0
# Assess features
feature_stats = {
'total': len(self.features),
'ready': 0,
'blocked': 0,
'in_progress': 0,
'pending': 0,
'high_risk': 0,
'breaking_changes': 0,
'missing_approvals': 0,
'low_test_coverage': 0
}
for feature in self.features:
max_score += 10 # Each feature worth 10 points
if feature.status == ComponentStatus.READY:
feature_stats['ready'] += 1
total_score += 10
elif feature.status == ComponentStatus.BLOCKED:
feature_stats['blocked'] += 1
assessment['blocking_issues'].append(
f"Feature '{feature.title}' ({feature.id}) is blocked"
)
elif feature.status == ComponentStatus.IN_PROGRESS:
feature_stats['in_progress'] += 1
total_score += 5 # Partial credit
assessment['warnings'].append(
f"Feature '{feature.title}' ({feature.id}) still in progress"
)
else:
feature_stats['pending'] += 1
assessment['warnings'].append(
f"Feature '{feature.title}' ({feature.id}) is pending"
)
# Check risk level
if feature.risk_level in [RiskLevel.HIGH, RiskLevel.CRITICAL]:
feature_stats['high_risk'] += 1
# Check breaking changes
if feature.breaking_changes:
feature_stats['breaking_changes'] += 1
# Check approvals
missing_approvals = self._check_feature_approvals(feature)
if missing_approvals:
feature_stats['missing_approvals'] += 1
assessment['blocking_issues'].append(
f"Feature '{feature.title}' missing approvals: {', '.join(missing_approvals)}"
)
# Check test coverage
if (feature.test_coverage_actual is not None and
feature.test_coverage_actual < feature.test_coverage_required):
feature_stats['low_test_coverage'] += 1
assessment['warnings'].append(
f"Feature '{feature.title}' has low test coverage: "
f"{feature.test_coverage_actual}% < {feature.test_coverage_required}%"
)
assessment['feature_summary'] = feature_stats
# Assess quality gates
gate_stats = {
'total': len(self.quality_gates),
'passed': 0,
'failed': 0,
'pending': 0,
'required_failed': 0
}
for gate in self.quality_gates:
max_score += 5 # Each gate worth 5 points
if gate.status == ComponentStatus.READY:
gate_stats['passed'] += 1
total_score += 5
elif gate.status == ComponentStatus.FAILED:
gate_stats['failed'] += 1
if gate.required:
gate_stats['required_failed'] += 1
assessment['blocking_issues'].append(
f"Required quality gate '{gate.name}' failed"
)
else:
gate_stats['pending'] += 1
if gate.required:
assessment['warnings'].append(
f"Required quality gate '{gate.name}' is pending"
)
assessment['quality_gate_summary'] = gate_stats
# Timeline assessment
if self.target_date:
# Handle timezone-aware datetime comparison
now = datetime.now(self.target_date.tzinfo) if self.target_date.tzinfo else datetime.now()
days_until_release = (self.target_date - now).days
assessment['timeline_assessment'] = {
'target_date': self.target_date.isoformat(),
'days_remaining': days_until_release,
'timeline_status': 'on_track' if days_until_release > 0 else 'overdue'
}
if days_until_release < 0:
assessment['blocking_issues'].append(f"Release is {abs(days_until_release)} days overdue")
elif days_until_release < 3 and feature_stats['blocked'] > 0:
assessment['blocking_issues'].append("Not enough time to resolve blocked features")
# Calculate overall readiness score
if max_score > 0:
assessment['readiness_score'] = (total_score / max_score) * 100
# Determine overall status
if assessment['blocking_issues']:
assessment['overall_status'] = 'blocked'
elif assessment['warnings']:
assessment['overall_status'] = 'at_risk'
else:
assessment['overall_status'] = 'ready'
# Generate recommendations
if feature_stats['missing_approvals'] > 0:
assessment['recommendations'].append("Obtain required approvals for pending features")
if feature_stats['low_test_coverage'] > 0:
assessment['recommendations'].append("Improve test coverage for features below threshold")
if gate_stats['pending'] > 0:
assessment['recommendations'].append("Complete pending quality gate validations")
if feature_stats['high_risk'] > 0:
assessment['recommendations'].append("Review high-risk features for additional validation")
return assessment
def _check_feature_approvals(self, feature: Feature) -> List[str]:
"""Check which approvals are missing for a feature."""
missing = []
# Determine required approvals based on risk level
required = self.required_approvals.copy()
if feature.risk_level in [RiskLevel.HIGH, RiskLevel.CRITICAL]:
required = self.high_risk_approval_requirements.copy()
if 'pm_approved' in required and not feature.pm_approved:
missing.append('PM approval')
if 'qa_approved' in required and not feature.qa_approved:
missing.append('QA approval')
if 'security_approved' in required and not feature.security_approved:
missing.append('Security approval')
return missing
def generate_release_checklist(self) -> List[Dict]:
"""Generate comprehensive release checklist."""
checklist = []
# Pre-release validation
checklist.extend([
{
'category': 'Pre-Release Validation',
'item': 'All features implemented and tested',
'status': 'ready' if all(f.status == ComponentStatus.READY for f in self.features) else 'pending',
'details': f"{len([f for f in self.features if f.status == ComponentStatus.READY])}/{len(self.features)} features ready"
},
{
'category': 'Pre-Release Validation',
'item': 'Breaking changes documented',
'status': 'ready' if self._check_breaking_change_docs() else 'pending',
'details': f"{len([f for f in self.features if f.breaking_changes])} features have breaking changes"
},
{
'category': 'Pre-Release Validation',
'item': 'Migration scripts tested',
'status': 'ready' if self._check_migrations() else 'pending',
'details': f"{len([f for f in self.features if f.requires_migration])} features require migrations"
}
])
# Quality gates
for gate in self.quality_gates:
checklist.append({
'category': 'Quality Gates',
'item': gate.name,
'status': gate.status.value,
'details': gate.details,
'required': gate.required
})
# Approvals
approval_items = [
('Product Manager sign-off', self._check_pm_approvals()),
('QA validation complete', self._check_qa_approvals()),
('Security team clearance', self._check_security_approvals())
]
for item, status in approval_items:
checklist.append({
'category': 'Approvals',
'item': item,
'status': 'ready' if status else 'pending'
})
# Documentation
doc_items = [
'CHANGELOG.md updated',
'API documentation updated',
'User documentation updated',
'Migration guide written',
'Rollback procedure documented'
]
for item in doc_items:
checklist.append({
'category': 'Documentation',
'item': item,
'status': 'pending' # Would need integration with docs system to check
})
# Deployment preparation
deployment_items = [
'Database migrations prepared',
'Environment variables configured',
'Monitoring alerts updated',
'Rollback plan tested',
'Stakeholders notified'
]
for item in deployment_items:
checklist.append({
'category': 'Deployment',
'item': item,
'status': 'pending'
})
return checklist
def _check_breaking_change_docs(self) -> bool:
"""Check if breaking changes are properly documented."""
features_with_breaking_changes = [f for f in self.features if f.breaking_changes]
return all(len(f.breaking_changes) > 0 for f in features_with_breaking_changes)
def _check_migrations(self) -> bool:
"""Check migration readiness."""
features_with_migrations = [f for f in self.features if f.requires_migration]
return all(f.status == ComponentStatus.READY for f in features_with_migrations)
def _check_pm_approvals(self) -> bool:
"""Check PM approvals."""
return all(f.pm_approved for f in self.features if f.risk_level != RiskLevel.LOW)
def _check_qa_approvals(self) -> bool:
"""Check QA approvals."""
return all(f.qa_approved for f in self.features)
def _check_security_approvals(self) -> bool:
"""Check security approvals."""
high_risk_features = [f for f in self.features if f.risk_level in [RiskLevel.HIGH, RiskLevel.CRITICAL]]
return all(f.security_approved for f in high_risk_features)
def generate_communication_plan(self) -> Dict:
"""Generate stakeholder communication plan."""
plan = {
'internal_notifications': [],
'external_notifications': [],
'timeline': [],
'channels': {},
'templates': {}
}
# Group stakeholders by type
internal_stakeholders = [s for s in self.stakeholders if s.role in
['developer', 'qa', 'pm', 'devops', 'security']]
external_stakeholders = [s for s in self.stakeholders if s.role in
['customer', 'partner', 'support']]
# Internal notifications
for stakeholder in internal_stakeholders:
plan['internal_notifications'].append({
'recipient': stakeholder.name,
'role': stakeholder.role,
'method': stakeholder.notification_type,
'content_type': 'technical_details',
'timing': 'T-24h and T-0'
})
# External notifications
for stakeholder in external_stakeholders:
plan['external_notifications'].append({
'recipient': stakeholder.name,
'role': stakeholder.role,
'method': stakeholder.notification_type,
'content_type': 'user_facing_changes',
'timing': 'T-48h and T+1h'
})
# Communication timeline
if self.target_date:
timeline_items = [
(timedelta(days=-2), 'Send pre-release notification to external stakeholders'),
(timedelta(days=-1), 'Send deployment notification to internal teams'),
(timedelta(hours=-2), 'Final go/no-go decision'),
(timedelta(hours=0), 'Begin deployment'),
(timedelta(hours=1), 'Post-deployment status update'),
(timedelta(hours=24), 'Post-release summary')
]
for delta, description in timeline_items:
notification_time = self.target_date + delta
plan['timeline'].append({
'time': notification_time.isoformat(),
'description': description,
'recipients': 'all' if 'all' in description.lower() else 'internal'
})
# Communication channels
channels = {}
for stakeholder in self.stakeholders:
if stakeholder.notification_type not in channels:
channels[stakeholder.notification_type] = []
channels[stakeholder.notification_type].append(stakeholder.contact)
plan['channels'] = channels
# Message templates
plan['templates'] = self._generate_message_templates()
return plan
def _generate_message_templates(self) -> Dict:
"""Generate message templates for different audiences."""
breaking_changes = [f for f in self.features if f.breaking_changes]
new_features = [f for f in self.features if f.type == 'feature']
bug_fixes = [f for f in self.features if f.type == 'bugfix']
templates = {
'internal_pre_release': {
'subject': f'Release {self.version} - Pre-deployment Notification',
'body': f"""Team,
We are preparing to deploy {self.release_name} version {self.version} on {self.target_date.strftime('%Y-%m-%d %H:%M UTC') if self.target_date else 'TBD'}.
Key Changes:
- {len(new_features)} new features
- {len(bug_fixes)} bug fixes
- {len(breaking_changes)} breaking changes
Please review the release notes and prepare for any needed support activities.
Rollback plan: Available in release documentation
On-call: Please be available during deployment window
Best regards,
Release Team"""
},
'external_user_notification': {
'subject': f'Product Update - Version {self.version} Now Available',
'body': f"""Dear Users,
We're excited to announce version {self.version} of {self.release_name} is now available!
What's New:
{chr(10).join(f"- {f.title}" for f in new_features[:5])}
Bug Fixes:
{chr(10).join(f"- {f.title}" for f in bug_fixes[:3])}
{'Important: This release includes breaking changes. Please review the migration guide.' if breaking_changes else ''}
For full release notes and migration instructions, visit our documentation.
Thank you for using our product!
The Development Team"""
},
'rollback_notification': {
'subject': f'URGENT: Release {self.version} Rollback Initiated',
'body': f"""ATTENTION: Release rollback in progress.
Release: {self.version}
Reason: [TO BE FILLED]
Rollback initiated: {datetime.now().strftime('%Y-%m-%d %H:%M UTC')}
Estimated completion: [TO BE FILLED]
Current status: Rolling back to previous stable version
Impact: [TO BE FILLED]
We will provide updates every 15 minutes until rollback is complete.
Incident Commander: [TO BE FILLED]
Status page: [TO BE FILLED]"""
}
}
return templates
def generate_rollback_runbook(self) -> Dict:
"""Generate detailed rollback runbook."""
runbook = {
'overview': {
'purpose': f'Emergency rollback procedure for {self.release_name} v{self.version}',
'triggers': [
'Error rate spike (>2x baseline for >15 minutes)',
'Critical functionality failure',
'Security incident',
'Data corruption detected',
'Performance degradation (>50% latency increase)',
'Manual decision by incident commander'
],
'decision_makers': ['On-call Engineer', 'Engineering Lead', 'Incident Commander'],
'estimated_total_time': self._calculate_rollback_time()
},
'prerequisites': [
'Confirm rollback is necessary (check with incident commander)',
'Notify stakeholders of rollback decision',
'Ensure database backups are available',
'Verify monitoring systems are operational',
'Have communication channels ready'
],
'steps': [],
'verification': {
'health_checks': [
'Application responds to health endpoint',
'Database connectivity confirmed',
'Authentication system functional',
'Core user workflows working',
'Error rates back to baseline',
'Performance metrics within normal range'
],
'rollback_confirmation': [
'Previous version fully deployed',
'Database in consistent state',
'All services communicating properly',
'Monitoring shows stable metrics',
'Sample user workflows tested'
]
},
'post_rollback': [
'Update status page with resolution',
'Notify all stakeholders of successful rollback',
'Schedule post-incident review',
'Document issues encountered during rollback',
'Plan investigation of root cause',
'Determine timeline for next release attempt'
],
'emergency_contacts': []
}
# Convert rollback steps to detailed format
for step in sorted(self.rollback_steps, key=lambda x: x.order):
step_data = {
'order': step.order,
'title': step.description,
'estimated_time': step.estimated_time,
'risk_level': step.risk_level.value,
'instructions': step.description,
'command': step.command,
'verification': step.verification,
'rollback_possible': step.risk_level != RiskLevel.CRITICAL
}
runbook['steps'].append(step_data)
# Add emergency contacts
critical_stakeholders = [s for s in self.stakeholders if s.critical_path]
for stakeholder in critical_stakeholders:
runbook['emergency_contacts'].append({
'name': stakeholder.name,
'role': stakeholder.role,
'contact': stakeholder.contact,
'method': stakeholder.notification_type
})
return runbook
def _calculate_rollback_time(self) -> str:
"""Calculate estimated total rollback time."""
total_minutes = 0
for step in self.rollback_steps:
# Parse time estimates like "5 minutes", "30 seconds", "1 hour"
time_str = step.estimated_time.lower()
if 'minute' in time_str:
minutes = int(re.search(r'(\d+)', time_str).group(1))
total_minutes += minutes
elif 'hour' in time_str:
hours = int(re.search(r'(\d+)', time_str).group(1))
total_minutes += hours * 60
elif 'second' in time_str:
# Round up seconds to minutes
total_minutes += 1
if total_minutes < 60:
return f"{total_minutes} minutes"
else:
hours = total_minutes // 60
minutes = total_minutes % 60
return f"{hours}h {minutes}m"
def main():
"""Main CLI entry point."""
parser = argparse.ArgumentParser(description="Assess release readiness and generate release plans")
parser.add_argument('--input', '-i', required=True,
help='Release plan JSON file')
parser.add_argument('--output-format', '-f',
choices=['json', 'markdown', 'text'],
default='text', help='Output format')
parser.add_argument('--output', '-o', type=str,
help='Output file (default: stdout)')
parser.add_argument('--include-checklist', action='store_true',
help='Include release checklist in output')
parser.add_argument('--include-communication', action='store_true',
help='Include communication plan')
parser.add_argument('--include-rollback', action='store_true',
help='Include rollback runbook')
parser.add_argument('--min-coverage', type=float, default=80.0,
help='Minimum test coverage threshold')
args = parser.parse_args()
# Load release plan
try:
with open(args.input, 'r', encoding='utf-8') as f:
plan_data = f.read()
except Exception as e:
print(f"Error reading input file: {e}", file=sys.stderr)
sys.exit(1)
# Initialize planner
planner = ReleasePlanner()
planner.min_test_coverage = args.min_coverage
try:
planner.load_release_plan(plan_data)
except Exception as e:
print(f"Error loading release plan: {e}", file=sys.stderr)
sys.exit(1)
# Generate assessment
assessment = planner.assess_release_readiness()
# Generate optional components
checklist = planner.generate_release_checklist() if args.include_checklist else None
communication = planner.generate_communication_plan() if args.include_communication else None
rollback = planner.generate_rollback_runbook() if args.include_rollback else None
# Generate output
if args.output_format == 'json':
output_data = {
'assessment': assessment,
'checklist': checklist,
'communication_plan': communication,
'rollback_runbook': rollback
}
output_text = json.dumps(output_data, indent=2, default=str)
elif args.output_format == 'markdown':
output_lines = [
f"# Release Readiness Report - {planner.release_name} v{planner.version}",
"",
f"**Overall Status:** {assessment['overall_status'].upper()}",
f"**Readiness Score:** {assessment['readiness_score']:.1f}%",
""
]
if assessment['blocking_issues']:
output_lines.extend([
"## 🚫 Blocking Issues",
""
])
for issue in assessment['blocking_issues']:
output_lines.append(f"- {issue}")
output_lines.append("")
if assessment['warnings']:
output_lines.extend([
"## ⚠️ Warnings",
""
])
for warning in assessment['warnings']:
output_lines.append(f"- {warning}")
output_lines.append("")
# Feature summary
fs = assessment['feature_summary']
output_lines.extend([
"## Features Summary",
"",
f"- **Total:** {fs['total']}",
f"- **Ready:** {fs['ready']}",
f"- **In Progress:** {fs['in_progress']}",
f"- **Blocked:** {fs['blocked']}",
f"- **Breaking Changes:** {fs['breaking_changes']}",
""
])
if checklist:
output_lines.extend([
"## Release Checklist",
""
])
current_category = ""
for item in checklist:
if item['category'] != current_category:
current_category = item['category']
output_lines.append(f"### {current_category}")
output_lines.append("")
status_icon = "✅" if item['status'] == 'ready' else "❌" if item['status'] == 'failed' else "⏳"
output_lines.append(f"- {status_icon} {item['item']}")
output_lines.append("")
output_text = '\n'.join(output_lines)
else: # text format
output_lines = [
f"Release Readiness Report",
f"========================",
f"Release: {planner.release_name} v{planner.version}",
f"Status: {assessment['overall_status'].upper()}",
f"Readiness Score: {assessment['readiness_score']:.1f}%",
""
]
if assessment['blocking_issues']:
output_lines.extend(["BLOCKING ISSUES:", ""])
for issue in assessment['blocking_issues']:
output_lines.append(f" ❌ {issue}")
output_lines.append("")
if assessment['warnings']:
output_lines.extend(["WARNINGS:", ""])
for warning in assessment['warnings']:
output_lines.append(f" ⚠️ {warning}")
output_lines.append("")
if assessment['recommendations']:
output_lines.extend(["RECOMMENDATIONS:", ""])
for rec in assessment['recommendations']:
output_lines.append(f" 💡 {rec}")
output_lines.append("")
# Summary stats
fs = assessment['feature_summary']
gs = assessment['quality_gate_summary']
output_lines.extend([
f"FEATURE SUMMARY:",
f" Total: {fs['total']} | Ready: {fs['ready']} | Blocked: {fs['blocked']}",
f" Breaking Changes: {fs['breaking_changes']} | Missing Approvals: {fs['missing_approvals']}",
"",
f"QUALITY GATES:",
f" Total: {gs['total']} | Passed: {gs['passed']} | Failed: {gs['failed']}",
""
])
output_text = '\n'.join(output_lines)
# Write output
if args.output:
with open(args.output, 'w', encoding='utf-8') as f:
f.write(output_text)
else:
print(output_text)
if __name__ == '__main__':
main()
FILE:version_bumper.py
#!/usr/bin/env python3
"""
Version Bumper
Analyzes commits since last tag to determine the correct version bump (major/minor/patch)
based on conventional commits. Handles pre-release versions (alpha, beta, rc) and generates
version bump commands for various package files.
Input: current version + commit list JSON or git log
Output: recommended new version + bump commands + updated file snippets
"""
import argparse
import json
import re
import sys
from typing import Dict, List, Optional, Tuple, Union
from enum import Enum
from dataclasses import dataclass
class BumpType(Enum):
"""Version bump types."""
NONE = "none"
PATCH = "patch"
MINOR = "minor"
MAJOR = "major"
class PreReleaseType(Enum):
"""Pre-release types."""
ALPHA = "alpha"
BETA = "beta"
RC = "rc"
@dataclass
class Version:
"""Semantic version representation."""
major: int
minor: int
patch: int
prerelease_type: Optional[PreReleaseType] = None
prerelease_number: Optional[int] = None
@classmethod
def parse(cls, version_str: str) -> 'Version':
"""Parse version string into Version object."""
# Remove 'v' prefix if present
clean_version = version_str.lstrip('v')
# Pattern for semantic versioning with optional pre-release
pattern = r'^(\d+)\.(\d+)\.(\d+)(?:-(\w+)\.?(\d+)?)?$'
match = re.match(pattern, clean_version)
if not match:
raise ValueError(f"Invalid version format: {version_str}")
major, minor, patch = int(match.group(1)), int(match.group(2)), int(match.group(3))
prerelease_type = None
prerelease_number = None
if match.group(4): # Pre-release identifier
prerelease_str = match.group(4).lower()
try:
prerelease_type = PreReleaseType(prerelease_str)
except ValueError:
# Handle variations like 'alpha1' -> 'alpha'
if prerelease_str.startswith('alpha'):
prerelease_type = PreReleaseType.ALPHA
elif prerelease_str.startswith('beta'):
prerelease_type = PreReleaseType.BETA
elif prerelease_str.startswith('rc'):
prerelease_type = PreReleaseType.RC
else:
raise ValueError(f"Unknown pre-release type: {prerelease_str}")
if match.group(5):
prerelease_number = int(match.group(5))
else:
# Extract number from combined string like 'alpha1'
number_match = re.search(r'(\d+)$', prerelease_str)
if number_match:
prerelease_number = int(number_match.group(1))
else:
prerelease_number = 1 # Default to 1
return cls(major, minor, patch, prerelease_type, prerelease_number)
def to_string(self, include_v_prefix: bool = False) -> str:
"""Convert version to string representation."""
base = f"{self.major}.{self.minor}.{self.patch}"
if self.prerelease_type:
if self.prerelease_number is not None:
base += f"-{self.prerelease_type.value}.{self.prerelease_number}"
else:
base += f"-{self.prerelease_type.value}"
return f"v{base}" if include_v_prefix else base
def bump(self, bump_type: BumpType, prerelease_type: Optional[PreReleaseType] = None) -> 'Version':
"""Create new version with specified bump."""
if bump_type == BumpType.NONE:
return Version(self.major, self.minor, self.patch, self.prerelease_type, self.prerelease_number)
new_major = self.major
new_minor = self.minor
new_patch = self.patch
new_prerelease_type = None
new_prerelease_number = None
# Handle pre-release versions
if prerelease_type:
if bump_type == BumpType.MAJOR:
new_major += 1
new_minor = 0
new_patch = 0
elif bump_type == BumpType.MINOR:
new_minor += 1
new_patch = 0
elif bump_type == BumpType.PATCH:
new_patch += 1
new_prerelease_type = prerelease_type
new_prerelease_number = 1
# Handle existing pre-release -> next pre-release
elif self.prerelease_type:
# If we're already in pre-release, increment or promote
if prerelease_type is None:
# Promote to stable release
# Don't change version numbers, just remove pre-release
pass
else:
# Move to next pre-release type or increment
if prerelease_type == self.prerelease_type:
# Same pre-release type, increment number
new_prerelease_type = self.prerelease_type
new_prerelease_number = (self.prerelease_number or 0) + 1
else:
# Different pre-release type
new_prerelease_type = prerelease_type
new_prerelease_number = 1
# Handle stable version bumps
else:
if bump_type == BumpType.MAJOR:
new_major += 1
new_minor = 0
new_patch = 0
elif bump_type == BumpType.MINOR:
new_minor += 1
new_patch = 0
elif bump_type == BumpType.PATCH:
new_patch += 1
return Version(new_major, new_minor, new_patch, new_prerelease_type, new_prerelease_number)
@dataclass
class ConventionalCommit:
"""Represents a parsed conventional commit for version analysis."""
type: str
scope: str
description: str
is_breaking: bool
breaking_description: str
hash: str = ""
author: str = ""
date: str = ""
@classmethod
def parse_message(cls, message: str, commit_hash: str = "",
author: str = "", date: str = "") -> 'ConventionalCommit':
"""Parse conventional commit message."""
lines = message.split('\n')
header = lines[0] if lines else ""
# Parse header: type(scope): description
header_pattern = r'^(\w+)(\([^)]+\))?(!)?:\s*(.+)$'
match = re.match(header_pattern, header)
commit_type = "chore"
scope = ""
description = header
is_breaking = False
breaking_description = ""
if match:
commit_type = match.group(1).lower()
scope_match = match.group(2)
scope = scope_match[1:-1] if scope_match else ""
is_breaking = bool(match.group(3)) # ! indicates breaking change
description = match.group(4).strip()
# Check for breaking change in body/footers
if len(lines) > 1:
body_text = '\n'.join(lines[1:])
if 'BREAKING CHANGE:' in body_text:
is_breaking = True
breaking_match = re.search(r'BREAKING CHANGE:\s*(.+)', body_text)
if breaking_match:
breaking_description = breaking_match.group(1).strip()
return cls(commit_type, scope, description, is_breaking, breaking_description,
commit_hash, author, date)
class VersionBumper:
"""Main version bumping logic."""
def __init__(self):
self.current_version: Optional[Version] = None
self.commits: List[ConventionalCommit] = []
self.custom_rules: Dict[str, BumpType] = {}
self.ignore_types: List[str] = ['test', 'ci', 'build', 'chore', 'docs', 'style']
def set_current_version(self, version_str: str):
"""Set the current version."""
self.current_version = Version.parse(version_str)
def add_custom_rule(self, commit_type: str, bump_type: BumpType):
"""Add custom rule for commit type to bump type mapping."""
self.custom_rules[commit_type] = bump_type
def parse_commits_from_json(self, json_data: Union[str, List[Dict]]):
"""Parse commits from JSON format."""
if isinstance(json_data, str):
data = json.loads(json_data)
else:
data = json_data
self.commits = []
for commit_data in data:
commit = ConventionalCommit.parse_message(
message=commit_data.get('message', ''),
commit_hash=commit_data.get('hash', ''),
author=commit_data.get('author', ''),
date=commit_data.get('date', '')
)
self.commits.append(commit)
def parse_commits_from_git_log(self, git_log_text: str):
"""Parse commits from git log output."""
lines = git_log_text.strip().split('\n')
if not lines or not lines[0]:
return
# Simple oneline format (hash message)
oneline_pattern = r'^([a-f0-9]{7,40})\s+(.+)$'
self.commits = []
for line in lines:
line = line.strip()
if not line:
continue
match = re.match(oneline_pattern, line)
if match:
commit_hash = match.group(1)
message = match.group(2)
commit = ConventionalCommit.parse_message(message, commit_hash)
self.commits.append(commit)
def determine_bump_type(self) -> BumpType:
"""Determine version bump type based on commits."""
if not self.commits:
return BumpType.NONE
has_breaking = False
has_feature = False
has_fix = False
for commit in self.commits:
# Check for breaking changes
if commit.is_breaking:
has_breaking = True
continue
# Apply custom rules first
if commit.type in self.custom_rules:
bump_type = self.custom_rules[commit.type]
if bump_type == BumpType.MAJOR:
has_breaking = True
elif bump_type == BumpType.MINOR:
has_feature = True
elif bump_type == BumpType.PATCH:
has_fix = True
continue
# Standard rules
if commit.type in ['feat', 'add']:
has_feature = True
elif commit.type in ['fix', 'security', 'perf', 'bugfix']:
has_fix = True
# Ignore types in ignore_types list
# Determine bump type by priority
if has_breaking:
return BumpType.MAJOR
elif has_feature:
return BumpType.MINOR
elif has_fix:
return BumpType.PATCH
else:
return BumpType.NONE
def recommend_version(self, prerelease_type: Optional[PreReleaseType] = None) -> Version:
"""Recommend new version based on commits."""
if not self.current_version:
raise ValueError("Current version not set")
bump_type = self.determine_bump_type()
return self.current_version.bump(bump_type, prerelease_type)
def generate_bump_commands(self, new_version: Version) -> Dict[str, List[str]]:
"""Generate version bump commands for different package managers."""
version_str = new_version.to_string()
version_with_v = new_version.to_string(include_v_prefix=True)
commands = {
'npm': [
f"npm version {version_str} --no-git-tag-version",
f"# Or manually edit package.json version field to '{version_str}'"
],
'python': [
f"# Update version in setup.py, __init__.py, or pyproject.toml",
f"# setup.py: version='{version_str}'",
f"# pyproject.toml: version = '{version_str}'",
f"# __init__.py: __version__ = '{version_str}'"
],
'rust': [
f"# Update Cargo.toml",
f"# [package]",
f"# version = '{version_str}'"
],
'git': [
f"git tag -a {version_with_v} -m 'Release {version_with_v}'",
f"git push origin {version_with_v}"
],
'docker': [
f"docker build -t myapp:{version_str} .",
f"docker tag myapp:{version_str} myapp:latest"
]
}
return commands
def generate_file_updates(self, new_version: Version) -> Dict[str, str]:
"""Generate file update snippets for common package files."""
version_str = new_version.to_string()
updates = {}
# package.json
updates['package.json'] = json.dumps({
"name": "your-package",
"version": version_str,
"description": "Your package description",
"main": "index.js"
}, indent=2)
# pyproject.toml
updates['pyproject.toml'] = f'''[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "your-package"
version = "{version_str}"
description = "Your package description"
authors = [
{{name = "Your Name", email = "your.email@example.com"}},
]
'''
# setup.py
updates['setup.py'] = f'''from setuptools import setup, find_packages
setup(
name="your-package",
version="{version_str}",
description="Your package description",
packages=find_packages(),
python_requires=">=3.8",
)
'''
# Cargo.toml
updates['Cargo.toml'] = f'''[package]
name = "your-package"
version = "{version_str}"
edition = "2021"
description = "Your package description"
'''
# __init__.py
updates['__init__.py'] = f'''"""Your package."""
__version__ = "{version_str}"
__author__ = "Your Name"
__email__ = "your.email@example.com"
'''
return updates
def analyze_commits(self) -> Dict:
"""Provide detailed analysis of commits for version bumping."""
if not self.commits:
return {
'total_commits': 0,
'by_type': {},
'breaking_changes': [],
'features': [],
'fixes': [],
'ignored': []
}
analysis = {
'total_commits': len(self.commits),
'by_type': {},
'breaking_changes': [],
'features': [],
'fixes': [],
'ignored': []
}
type_counts = {}
for commit in self.commits:
type_counts[commit.type] = type_counts.get(commit.type, 0) + 1
if commit.is_breaking:
analysis['breaking_changes'].append({
'type': commit.type,
'scope': commit.scope,
'description': commit.description,
'breaking_description': commit.breaking_description,
'hash': commit.hash
})
elif commit.type in ['feat', 'add']:
analysis['features'].append({
'scope': commit.scope,
'description': commit.description,
'hash': commit.hash
})
elif commit.type in ['fix', 'security', 'perf', 'bugfix']:
analysis['fixes'].append({
'scope': commit.scope,
'description': commit.description,
'hash': commit.hash
})
elif commit.type in self.ignore_types:
analysis['ignored'].append({
'type': commit.type,
'scope': commit.scope,
'description': commit.description,
'hash': commit.hash
})
analysis['by_type'] = type_counts
return analysis
def main():
"""Main CLI entry point."""
parser = argparse.ArgumentParser(description="Determine version bump based on conventional commits")
parser.add_argument('--current-version', '-c', required=True,
help='Current version (e.g., 1.2.3, v1.2.3)')
parser.add_argument('--input', '-i', type=str,
help='Input file with commits (default: stdin)')
parser.add_argument('--input-format', choices=['git-log', 'json'],
default='git-log', help='Input format')
parser.add_argument('--prerelease', '-p',
choices=['alpha', 'beta', 'rc'],
help='Generate pre-release version')
parser.add_argument('--output-format', '-f',
choices=['text', 'json', 'commands'],
default='text', help='Output format')
parser.add_argument('--output', '-o', type=str,
help='Output file (default: stdout)')
parser.add_argument('--include-commands', action='store_true',
help='Include bump commands in output')
parser.add_argument('--include-files', action='store_true',
help='Include file update snippets')
parser.add_argument('--custom-rules', type=str,
help='JSON string with custom type->bump rules')
parser.add_argument('--ignore-types', type=str,
help='Comma-separated list of types to ignore')
parser.add_argument('--analysis', '-a', action='store_true',
help='Include detailed commit analysis')
args = parser.parse_args()
# Read input
if args.input:
with open(args.input, 'r', encoding='utf-8') as f:
input_data = f.read()
else:
input_data = sys.stdin.read()
if not input_data.strip():
print("No input data provided", file=sys.stderr)
sys.exit(1)
# Initialize version bumper
bumper = VersionBumper()
try:
bumper.set_current_version(args.current_version)
except ValueError as e:
print(f"Invalid current version: {e}", file=sys.stderr)
sys.exit(1)
# Apply custom rules
if args.custom_rules:
try:
custom_rules = json.loads(args.custom_rules)
for commit_type, bump_type_str in custom_rules.items():
bump_type = BumpType(bump_type_str.lower())
bumper.add_custom_rule(commit_type, bump_type)
except Exception as e:
print(f"Invalid custom rules: {e}", file=sys.stderr)
sys.exit(1)
# Set ignore types
if args.ignore_types:
bumper.ignore_types = [t.strip() for t in args.ignore_types.split(',')]
# Parse commits
try:
if args.input_format == 'json':
bumper.parse_commits_from_json(input_data)
else:
bumper.parse_commits_from_git_log(input_data)
except Exception as e:
print(f"Error parsing commits: {e}", file=sys.stderr)
sys.exit(1)
# Determine pre-release type
prerelease_type = None
if args.prerelease:
prerelease_type = PreReleaseType(args.prerelease)
# Generate recommendation
try:
recommended_version = bumper.recommend_version(prerelease_type)
bump_type = bumper.determine_bump_type()
except Exception as e:
print(f"Error determining version: {e}", file=sys.stderr)
sys.exit(1)
# Generate output
output_data = {}
if args.output_format == 'json':
output_data = {
'current_version': args.current_version,
'recommended_version': recommended_version.to_string(),
'recommended_version_with_v': recommended_version.to_string(include_v_prefix=True),
'bump_type': bump_type.value,
'prerelease': args.prerelease
}
if args.analysis:
output_data['analysis'] = bumper.analyze_commits()
if args.include_commands:
output_data['commands'] = bumper.generate_bump_commands(recommended_version)
if args.include_files:
output_data['file_updates'] = bumper.generate_file_updates(recommended_version)
output_text = json.dumps(output_data, indent=2)
elif args.output_format == 'commands':
commands = bumper.generate_bump_commands(recommended_version)
output_lines = [
f"# Version Bump Commands",
f"# Current: {args.current_version}",
f"# New: {recommended_version.to_string()}",
f"# Bump Type: {bump_type.value}",
""
]
for category, cmd_list in commands.items():
output_lines.append(f"## {category.upper()}")
for cmd in cmd_list:
output_lines.append(cmd)
output_lines.append("")
output_text = '\n'.join(output_lines)
else: # text format
output_lines = [
f"Current Version: {args.current_version}",
f"Recommended Version: {recommended_version.to_string()}",
f"With v prefix: {recommended_version.to_string(include_v_prefix=True)}",
f"Bump Type: {bump_type.value}",
""
]
if args.analysis:
analysis = bumper.analyze_commits()
output_lines.extend([
"Commit Analysis:",
f"- Total commits: {analysis['total_commits']}",
f"- Breaking changes: {len(analysis['breaking_changes'])}",
f"- New features: {len(analysis['features'])}",
f"- Bug fixes: {len(analysis['fixes'])}",
f"- Ignored commits: {len(analysis['ignored'])}",
""
])
if analysis['breaking_changes']:
output_lines.append("Breaking Changes:")
for change in analysis['breaking_changes']:
scope = f"({change['scope']})" if change['scope'] else ""
output_lines.append(f" - {change['type']}{scope}: {change['description']}")
output_lines.append("")
if args.include_commands:
commands = bumper.generate_bump_commands(recommended_version)
output_lines.append("Bump Commands:")
for category, cmd_list in commands.items():
output_lines.append(f" {category}:")
for cmd in cmd_list:
if not cmd.startswith('#'):
output_lines.append(f" {cmd}")
output_lines.append("")
output_text = '\n'.join(output_lines)
# Write output
if args.output:
with open(args.output, 'w', encoding='utf-8') as f:
f.write(output_text)
else:
print(output_text)
if __name__ == '__main__':
main()Chủ động lưu tri thức quan trọng vào bộ nhớ tự động kèm thời gian và ngữ cảnh, khi phát hiện quá quan trọng để dựa vào tự động ghi nhận.
---
name: "remember"
description: "Explicitly save important knowledge to auto-memory with timestamp and context. Use when a discovery is too important to rely on auto-capture."
---
# /si:remember — Save Knowledge Explicitly
Writes an explicit entry to auto-memory when something is important enough that you don't want to rely on Claude noticing it automatically.
## Usage
```
/si:remember <what to remember>
/si:remember "This project's CI requires Node 20 LTS — v22 breaks the build"
/si:remember "The /api/auth endpoint uses a custom JWT library, not passport"
/si:remember "Reza prefers explicit error handling over try-catch-all patterns"
```
## When to Use
| Situation | Example |
|-----------|---------|
| Hard-won debugging insight | "CORS errors on /api/upload are caused by the CDN, not the backend" |
| Project convention not in CLAUDE.md | "We use barrel exports in src/components/" |
| Tool-specific gotcha | "Jest needs `--forceExit` flag or it hangs on DB tests" |
| Architecture decision | "We chose Drizzle over Prisma for type-safe SQL" |
| Preference you want Claude to learn | "Don't add comments explaining obvious code" |
## Workflow
### Step 1: Parse the knowledge
Extract from the user's input:
- **What**: The concrete fact or pattern
- **Why it matters**: Context (if provided)
- **Scope**: Project-specific or global?
### Step 2: Check for duplicates
```bash
MEMORY_DIR="$HOME/.claude/projects/$(pwd | sed 's|/|%2F|g; s|%2F|/|; s|^/||')/memory"
grep -ni "<keywords>" "$MEMORY_DIR/MEMORY.md" 2>/dev/null
```
If a similar entry exists:
- Show it to the user
- Ask: "Update the existing entry or add a new one?"
### Step 3: Write to MEMORY.md
Append to the end of `MEMORY.md`:
```markdown
- {{concise fact or pattern}}
```
Keep entries concise — one line when possible. Auto-memory entries don't need timestamps, IDs, or metadata. They're notes, not database records.
If MEMORY.md is over 180 lines, warn the user:
```
⚠️ MEMORY.md is at {{n}}/200 lines. Consider running /si:review to free space.
```
### Step 4: Suggest promotion
If the knowledge sounds like a rule (imperative, always/never, convention):
```
💡 This sounds like it could be a CLAUDE.md rule rather than a memory entry.
Rules are enforced with higher priority. Want to /si:promote it instead?
```
### Step 5: Confirm
```
✅ Saved to auto-memory
"{{entry}}"
MEMORY.md: {{n}}/200 lines
Claude will see this at the start of every session in this project.
```
## What NOT to use /si:remember for
- **Temporary context**: Use session memory or just tell Claude in conversation
- **Enforced rules**: Use `/si:promote` to write directly to CLAUDE.md
- **Cross-project knowledge**: Use `~/.claude/CLAUDE.md` for global rules
- **Sensitive data**: Never store credentials, tokens, or secrets in memory files
## Tips
- Be concise — one line beats a paragraph
- Include the concrete command or value, not just the concept
- ✅ "Build with `pnpm build`, tests with `pnpm test:e2e`"
- ❌ "The project uses pnpm for building and testing"
- If you're remembering the same thing twice, promote it to CLAUDE.md
Tối ưu luồng đăng ký, tạo tài khoản và kích hoạt dùng thử để tăng chuyển đổi và giảm tỷ lệ bỏ dở.
---
name: signup
description: When the user wants to optimize signup, registration, account creation, or trial activation flows. Also use when the user mentions "signup conversions," "registration friction," "signup form optimization," "free trial signup," "reduce signup dropoff," "account creation flow," "people aren't signing up," "signup abandonment," "trial conversion rate," "nobody completes registration," "too many steps to sign up," or "simplify our signup." Use this whenever the user has a signup or registration flow that isn't performing. For post-signup onboarding, see onboarding. For lead capture forms (not account creation), see cro.
metadata:
version: 2.0.0
---
# Signup Flow CRO
You are an expert in optimizing signup and registration flows. Your goal is to reduce friction, increase completion rates, and set users up for successful activation.
## Initial Assessment
**Check for product marketing context first:**
If `.agents/product-marketing.md` exists (or `.claude/product-marketing.md`, or the legacy `product-marketing-context.md` filename, in older setups), read it before asking questions. Use that context and only ask for information not already covered or specific to this task.
Before providing recommendations, understand:
1. **Flow Type**
- Free trial signup
- Freemium account creation
- Paid account creation
- Waitlist/early access signup
- B2B vs B2C
2. **Current State**
- How many steps/screens?
- What fields are required?
- What's the current completion rate?
- Where do users drop off?
3. **Business Constraints**
- What data is genuinely needed at signup?
- Are there compliance requirements?
- What happens immediately after signup?
---
## Core Principles
### 1. Minimize Required Fields
Every field reduces conversion. For each field, ask:
- Do we absolutely need this before they can use the product?
- Can we collect this later through progressive profiling?
- Can we infer this from other data?
**Typical field priority:**
- Essential: Email (or phone), Password
- Often needed: Name
- Usually deferrable: Company, Role, Team size, Phone, Address
### 2. Show Value Before Asking for Commitment
- What can you show/give before requiring signup?
- Can they experience the product before creating an account?
- Reverse the order: value first, signup second
### 3. Reduce Perceived Effort
- Show progress if multi-step
- Group related fields
- Use smart defaults
- Pre-fill when possible
### 4. Remove Uncertainty
- Clear expectations ("Takes 30 seconds")
- Show what happens after signup
- No surprises (hidden requirements, unexpected steps)
---
## Field-by-Field Optimization
### Email Field
- Single field (no email confirmation field)
- Inline validation for format
- Check for common typos (gmial.com → gmail.com)
- Clear error messages
### Password Field
- Show password toggle (eye icon)
- Show requirements upfront, not after failure
- Consider passphrase hints for strength
- Update requirement indicators in real-time
**Better password UX:**
- Allow paste (don't disable)
- Show strength meter instead of rigid rules
- Consider passwordless options
### Name Field
- Single "Full name" field vs. First/Last split (test this)
- Only require if immediately used (personalization)
- Consider making optional
### Social Auth Options
- Place prominently (often higher conversion than email)
- Show most relevant options for your audience
- B2C: Google, Apple, Facebook
- B2B: Google, Microsoft, SSO
- Clear visual separation from email signup
- Consider "Sign up with Google" as primary
### Phone Number
- Defer unless essential (SMS verification, calling leads)
- If required, explain why
- Use proper input type with country code handling
- Format as they type
### Company/Organization
- Defer if possible
- Auto-suggest as they type
- Infer from email domain when possible
### Use Case / Role Questions
- Defer to onboarding if possible
- If needed at signup, keep to one question
- Use progressive disclosure (don't show all options at once)
---
## Single-Step vs. Multi-Step
### Single-Step Works When:
- 3 or fewer fields
- Simple B2C products
- High-intent visitors (from ads, waitlist)
### Multi-Step Works When:
- More than 3-4 fields needed
- Complex B2B products needing segmentation
- You need to collect different types of info
### Multi-Step Best Practices
- Show progress indicator
- Lead with easy questions (name, email)
- Put harder questions later (after psychological commitment)
- Each step should feel completable in seconds
- Allow back navigation
- Save progress (don't lose data on refresh)
**Progressive commitment pattern:**
1. Email only (lowest barrier)
2. Password + name
3. Customization questions (optional)
---
## Trust and Friction Reduction
### At the Form Level
- "No credit card required" (if true)
- "Free forever" or "14-day free trial"
- Privacy note: "We'll never share your email"
- Security badges if relevant
- Testimonial near signup form
### Error Handling
- Inline validation (not just on submit)
- Specific error messages ("Email already registered" + recovery path)
- Don't clear the form on error
- Focus on the problem field
### Microcopy
- Placeholder text: Use for examples, not labels
- Labels: Keep visible (not just placeholders) — placeholders disappear when typing, leaving users unsure what they're filling in
- Help text: Only when needed, placed close to field
---
## Mobile Signup Optimization
- Larger touch targets (44px+ height)
- Appropriate keyboard types (email, tel, etc.)
- Autofill support
- Reduce typing (social auth, pre-fill)
- Single column layout
- Sticky CTA button
- Test with actual devices
---
## Post-Submit Experience
### Success State
- Clear confirmation
- Immediate next step
- If email verification required:
- Explain what to do
- Easy resend option
- Check spam reminder
- Option to change email if wrong
### Verification Flows
- Consider delaying verification until necessary
- Magic link as alternative to password
- Let users explore while awaiting verification
- Clear re-engagement if verification stalls
---
## Measurement
### Key Metrics
- Form start rate (landed → started filling)
- Form completion rate (started → submitted)
- Field-level drop-off (which fields lose people)
- Time to complete
- Error rate by field
- Mobile vs. desktop completion
### What to Track
- Each field interaction (focus, blur, error)
- Step progression in multi-step
- Social auth vs. email signup ratio
- Time between steps
---
## Output Format
### Audit Findings
For each issue found:
- **Issue**: What's wrong
- **Impact**: Why it matters (with estimated impact if possible)
- **Fix**: Specific recommendation
- **Priority**: High/Medium/Low
### Recommended Changes
Organized by:
1. Quick wins (same-day fixes)
2. High-impact changes (week-level effort)
3. Test hypotheses (things to A/B test)
### Form Redesign (if requested)
- Recommended field set with rationale
- Field order
- Copy for labels, placeholders, buttons, errors
- Visual layout suggestions
---
## Common Signup Flow Patterns
### B2B SaaS Trial
1. Email + Password (or Google auth)
2. Name + Company (optional: role)
3. → Onboarding flow
### B2C App
1. Google/Apple auth OR Email
2. → Product experience
3. Profile completion later
### Waitlist/Early Access
1. Email only
2. Optional: Role/use case question
3. → Waitlist confirmation
### E-commerce Account
1. Guest checkout as default
2. Account creation optional post-purchase
3. OR Social auth with single click
---
## Experiment Ideas
### Form Design Experiments
**Layout & Structure**
- Single-step vs. multi-step signup flow
- Multi-step with progress bar vs. without
- 1-column vs. 2-column field layout
- Form embedded on page vs. separate signup page
- Horizontal vs. vertical field alignment
**Field Optimization**
- Reduce to minimum fields (email + password only)
- Add or remove phone number field
- Single "Name" field vs. "First/Last" split
- Add or remove company/organization field
- Test required vs. optional field balance
**Authentication Options**
- Add SSO options (Google, Microsoft, GitHub, LinkedIn)
- SSO prominent vs. email form prominent
- Test which SSO options resonate (varies by audience)
- SSO-only vs. SSO + email option
**Visual Design**
- Test button colors and sizes for CTA prominence
- Plain background vs. product-related visuals
- Test form container styling (card vs. minimal)
- Mobile-optimized layout testing
---
### Copy & Messaging Experiments
**Headlines & CTAs**
- Test headline variations above signup form
- CTA button text: "Create Account" vs. "Start Free Trial" vs. "Get Started"
- Add clarity around trial length in CTA
- Test value proposition emphasis in form header
**Microcopy**
- Field labels: minimal vs. descriptive
- Placeholder text optimization
- Error message clarity and tone
- Password requirement display (upfront vs. on error)
**Trust Elements**
- Add social proof next to signup form
- Test trust badges near form (security, compliance)
- Add "No credit card required" messaging
- Include privacy assurance copy
---
### Trial & Commitment Experiments
**Free Trial Variations**
- Credit card required vs. not required for trial
- Test trial length impact (7 vs. 14 vs. 30 days)
- Freemium vs. free trial model
- Trial with limited features vs. full access
**Friction Points**
- Email verification required vs. delayed vs. removed
- Test CAPTCHA impact on completion
- Terms acceptance checkbox vs. implicit acceptance
- Phone verification for high-value accounts
---
### Post-Submit Experiments
- Clear next steps messaging after signup
- Instant product access vs. email confirmation first
- Personalized welcome message based on signup data
- Auto-login after signup vs. require login
---
## Task-Specific Questions
1. What's your current signup completion rate?
2. Do you have field-level analytics on drop-off?
3. What data is absolutely required before they can use the product?
4. Are there compliance or verification requirements?
5. What happens immediately after signup?
---
## Related Skills
- **onboarding**: For optimizing what happens after signup
- **cro**: For non-signup forms (lead capture, contact)
- **cro**: For the landing page leading to signup
- **ab-testing**: For testing signup flow changes
FILE:evals/evals.json
{
"skill_name": "signup",
"evals": [
{
"id": 1,
"prompt": "Audit our signup flow. We have a 3-step process: Step 1 asks for email, password, and full name. Step 2 asks for company name, company size, role, and industry. Step 3 asks for use case and how they heard about us. Current completion rate is 45%.",
"expected_output": "Should check for product-marketing.md first. Should identify the flow type (likely B2B SaaS trial). Should apply the core principles: minimize required fields (which of these are genuinely needed before they can use the product?). Should evaluate each step: Step 1 is reasonable, Step 2 fields are mostly deferrable to progressive profiling, Step 3 is entirely deferrable. Should recommend cutting to Step 1 only or at most 2 steps. Should provide audit findings in structured format (Issue, Impact, Fix, Priority). Should include Quick Wins, High-Impact Changes, and Test Hypotheses.",
"assertions": [
"Checks for product-marketing.md",
"Identifies flow type",
"Applies minimize required fields principle",
"Evaluates each field for necessity",
"Recommends deferring most Step 2 and all Step 3 fields",
"Provides findings in structured format",
"Includes Quick Wins, High-Impact Changes, Test Hypotheses"
],
"files": []
},
{
"id": 2,
"prompt": "Should we add Google and Microsoft SSO to our signup page? We're a B2B project management tool and currently only have email/password signup.",
"expected_output": "Should apply the social auth options guidance. For B2B, should recommend Google and Microsoft as the primary SSO options (matching the B2B recommendation). Should explain benefits: higher conversion (less friction), pre-verified email, faster onboarding. Should recommend placing SSO prominently (often higher conversion than email). Should address implementation considerations: clear visual separation from email signup, button copy ('Sign up with Google' not just Google icon), consider which option to emphasize based on audience.",
"assertions": [
"Applies social auth options guidance",
"Recommends Google and Microsoft for B2B",
"Explains conversion benefits of SSO",
"Recommends prominent placement",
"Addresses visual separation from email signup",
"Provides implementation recommendations"
],
"files": []
},
{
"id": 3,
"prompt": "our signup form is just email and password but we still only get 35% of visitors to complete it. what else could be wrong?",
"expected_output": "Should trigger on casual phrasing. Should investigate beyond just form fields since the form is already minimal. Should apply trust and friction reduction guidance: is there a 'No credit card required' message? Privacy assurance? Testimonial near the form? Should check form-level issues: error handling, password requirements clarity, submit button copy. Should also look at pre-form factors: is the value proposition clear? Is the page optimized? (cross-reference cro). Should provide diagnostic checklist and recommendations.",
"assertions": [
"Triggers on casual phrasing",
"Investigates beyond form fields",
"Applies trust and friction reduction",
"Checks for 'No credit card required' messaging",
"Checks error handling and password UX",
"Considers pre-form factors (value prop, page CRO)",
"Provides diagnostic checklist"
],
"files": []
},
{
"id": 4,
"prompt": "We require email verification before users can access the product. Is that hurting our conversion? Should we change it?",
"expected_output": "Should apply the verification flows guidance. Should explain that requiring verification before product access does create friction and likely reduces activation. Should recommend alternatives: delay verification until needed (let users explore first), magic link as alternative to password, let users start while verification is pending. Should discuss when email verification IS required (compliance, preventing abuse). Should provide specific recommendations for improving the verification experience if kept.",
"assertions": [
"Applies verification flows guidance",
"Explains verification friction impact",
"Recommends delaying verification",
"Suggests letting users explore while pending",
"Discusses when verification is required",
"Provides improvements if verification is kept"
],
"files": []
},
{
"id": 5,
"prompt": "What experiments should we run on our signup page? We want to improve our trial signup rate.",
"expected_output": "Should apply the experiment ideas section. Should provide experiments across categories: form design experiments (layout, field count, SSO), copy and messaging experiments (headline, CTA text, trust elements), trial and commitment experiments (credit card required vs not, trial length), and post-submit experiments. Should prioritize experiments by likely impact. Should cross-reference ab-testing for proper experiment design.",
"assertions": [
"Applies experiment ideas section",
"Covers form design experiments",
"Covers copy and messaging experiments",
"Covers trial and commitment experiments",
"Prioritizes by likely impact",
"Cross-references ab-testing skill"
],
"files": []
},
{
"id": 6,
"prompt": "Users sign up fine but then never activate. Only 20% complete onboarding. What do we do?",
"expected_output": "Should recognize this is a post-signup onboarding problem, not a signup flow problem. Should defer to or cross-reference the onboarding skill, which handles post-signup activation and onboarding optimization. Signup-flow-cro covers getting users through the signup form, not what happens after.",
"assertions": [
"Recognizes this as post-signup onboarding, not signup flow",
"References or defers to onboarding skill",
"Explains signup covers the signup form, not post-signup"
],
"files": []
}
]
}
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`
Quản lý pháp quy cho HealthTech/MedTech: hồ sơ FDA 510(k), De Novo, PMA, trả lời thư thiếu sót, hồ sơ kỹ thuật CE marking và truy vấn Notified Body.
---
name: "regulatory-affairs-head"
description: Senior Regulatory Affairs Manager for HealthTech and MedTech companies. Prepares FDA 510(k), De Novo, and PMA submission packages; analyzes regulatory pathways for new medical devices; drafts responses to FDA deficiency letters and Notified Body queries; develops CE marking technical documentation under EU MDR 2017/745; coordinates multi-market approval strategies across FDA, EU, Health Canada, PMDA, and NMPA; and maintains regulatory intelligence on evolving standards. Use when users need to plan or execute FDA submissions, navigate 510(k) or PMA approval processes, achieve CE marking, prepare pre-submission meeting materials, write regulatory strategy documents, respond to agency queries, or manage compliance documentation for medical device market access.
triggers:
- regulatory strategy
- FDA submission
- EU MDR
- 510(k)
- PMA approval
- CE marking
- regulatory pathway
- market access
- clinical evidence
- regulatory intelligence
- submission planning
- notified body
---
# Head of Regulatory Affairs
Regulatory strategy development, submission management, and global market access for medical device organizations.
---
## Table of Contents
- [Regulatory Strategy Workflow](#regulatory-strategy-workflow)
- [FDA Submission Workflow](#fda-submission-workflow)
- [EU MDR Submission Workflow](#eu-mdr-submission-workflow)
- [Global Market Access Workflow](#global-market-access-workflow)
- [Regulatory Intelligence Workflow](#regulatory-intelligence-workflow)
- [Decision Frameworks](#decision-frameworks)
- [Tools and References](#tools-and-references)
---
## Regulatory Strategy Workflow
Develop regulatory strategy aligned with business objectives and product characteristics.
### Workflow: New Product Regulatory Strategy
1. Gather product information:
- Intended use and indications
- Device classification (risk level)
- Technology platform
- Target markets and timeline
2. Identify applicable regulations per target market:
- FDA (US): 21 CFR Part 820, 510(k)/PMA/De Novo
- EU: MDR 2017/745, Notified Body requirements
- Other markets: Health Canada, PMDA, NMPA, TGA
3. Determine optimal regulatory pathway:
- Compare submission types (510(k) vs De Novo vs PMA)
- Assess predicate device availability
- Evaluate clinical evidence requirements
4. Develop regulatory timeline with milestones
5. Estimate resource requirements and budget
6. Identify regulatory risks and mitigation strategies
7. Obtain stakeholder alignment and approval
8. **Validation:** Strategy document approved; timeline accepted; resources allocated
### Regulatory Pathway Selection Matrix
| Factor | 510(k) | De Novo | PMA |
|--------|--------|---------|-----|
| Predicate Available | Yes | No | N/A |
| Risk Level | Low-Moderate | Low-Moderate | High |
| Clinical Data | Usually not required | May be required | Required |
| Review Time | 90 days (MDUFA) | 150 days | 180 days |
| User Fee | ~$22K (2024) | ~$135K | ~$440K |
| Best For | Me-too devices | Novel low-risk | High-risk, novel |
### Regulatory Strategy Document Template
```
REGULATORY STRATEGY
Product: [Name] Version: [X.X] Date: [Date]
1. PRODUCT OVERVIEW
Intended use: [One-sentence statement of intended patient population, body site, and clinical purpose]
Device classification: [Class I / II / III]
Technology: [Brief description, e.g., "AI-powered wound-imaging software, SaMD"]
2. TARGET MARKETS & TIMELINE
| Market | Pathway | Priority | Target Date |
|--------|----------------|----------|-------------|
| USA | 510(k) / PMA | 1 | Q1 20XX |
| EU | Class [X] MDR | 2 | Q2 20XX |
3. REGULATORY PATHWAY RATIONALE
FDA: [510(k) / De Novo / PMA] — Predicate: [K-number or "none"]
EU: Class [X] via [Annex IX / X / XI] — NB: [Name or TBD]
Rationale: [2–3 sentences on key factors driving pathway choice]
4. CLINICAL EVIDENCE STRATEGY
Requirements: [Summarize what each market needs, e.g., "510(k): bench + usability; EU Class IIb: PMCF study"]
Approach: [Literature review / Prospective study / Combination]
5. RISKS AND MITIGATION
| Risk | Prob | Impact | Mitigation |
|------------------------------|------|--------|-----------------------------------|
| Predicate delisted by FDA | Low | High | Identify secondary predicate now |
| NB audit backlog | Med | Med | Engage NB 6 months before target |
6. RESOURCE REQUIREMENTS
Budget: $[Amount] Personnel: [FTEs] External: [Consultants / CRO]
```
---
## FDA Submission Workflow
Prepare and submit FDA regulatory applications.
### Workflow: 510(k) Submission
1. Confirm 510(k) pathway suitability:
- Predicate device identified (note K-number, e.g., K213456)
- Substantial equivalence (SE) argument supportable on intended use and technological characteristics
- No new intended use or technology concerns triggering De Novo
2. Schedule and conduct Pre-Submission (Q-Sub) meeting if needed (see [Pre-Sub Decision](#pre-submission-meeting-decision))
3. Compile submission package checklist:
- [ ] Cover letter with device name, product code, and predicate K-number
- [ ] Section 1: Administrative information (applicant, contact, 510(k) type)
- [ ] Section 2: Device description — include photos, dimensions, materials list
- [ ] Section 3: Intended use and indications for use
- [ ] Section 4: Substantial equivalence comparison table (see example below)
- [ ] Section 5: Performance testing — protocols, standards cited, pass/fail results
- [ ] Section 6: Biocompatibility summary (ISO 10993-1 risk assessment, if patient contact)
- [ ] Section 7: Software documentation (IEC 62304 level, cybersecurity per FDA guidance, if applicable)
- [ ] Section 8: Labeling — final draft IFU, device label
- [ ] Section 9: Summary and conclusion
4. Conduct internal review and quality check against FDA RTA checklist
5. Prepare eCopy per FDA format requirements (PDF bookmarked, eCopy cover page)
6. Submit via FDA ESG portal with user fee payment
7. Monitor MDUFA clock and respond to AI/RTA requests within deadlines
8. **Validation:** Submission accepted; MDUFA date received; tracking system updated
#### Substantial Equivalence Comparison Example
| Characteristic | Predicate (K213456) | Subject Device | Same? | Notes |
|----------------|---------------------|----------------|-------|-------|
| Intended use | Wound measurement | Wound measurement | ✓ | Identical |
| Technology | 2D camera | 2D + AI analysis | ✗ | New TC; address below |
| Energy type | Non-energized | Non-energized | ✓ | |
| Patient contact | No | No | ✓ | |
| SE conclusion | New TC does not raise new safety/effectiveness questions; bench data demonstrates equivalent accuracy (±2mm vs ±3mm predicate) |
### Workflow: PMA Submission
1. Confirm PMA pathway:
- Class III device or no suitable predicate
- Clinical data strategy defined
2. Complete IDE clinical study if required:
- IDE approval
- Clinical protocol execution
- Study report completion
3. Conduct Pre-Submission meeting
4. Compile PMA submission checklist:
- [ ] Volume I: Administrative, device description, manufacturing
- [ ] Volume II: Nonclinical studies (bench, animal, biocompatibility)
- [ ] Volume III: Clinical studies (IDE protocol, data, statistical analysis)
- [ ] Volume IV: Labeling
- [ ] Volume V: Manufacturing information, sterilization
5. Submit original PMA application
6. Address FDA questions and deficiencies
7. Prepare for FDA facility inspection
8. **Validation:** PMA approved; approval letter received; post-approval requirements documented
### FDA Submission Timeline
| Milestone | 510(k) | De Novo | PMA |
|-----------|--------|---------|-----|
| Pre-Sub Meeting | Day -90 | Day -90 | Day -120 |
| Submission | Day 0 | Day 0 | Day 0 |
| RTA Review | Day 15 | Day 15 | Day 45 |
| Substantive Review | Days 15–90 | Days 15–150 | Days 45–180 |
| Decision | Day 90 | Day 150 | Day 180 |
### Common FDA Deficiencies and Prevention
| Category | Common Issues | Prevention |
|----------|---------------|------------|
| Substantial Equivalence | Weak predicate comparison; no performance data | Build SE table with data column; cite recognized standards |
| Performance Testing | Incomplete protocols; missing worst-case rationale | Follow FDA-recognized standards; document worst-case justification |
| Biocompatibility | Missing endpoints; no ISO 10993-1 risk assessment | Complete ISO 10993-1 matrix before testing |
| Software | Inadequate hazard analysis; no cybersecurity bill of materials | IEC 62304 compliance + FDA cybersecurity guidance checklist |
| Labeling | Inconsistent claims vs. IFU; missing symbols standard | Cross-check label against IFU; cite ISO 15223-1 for symbols |
See: [references/fda-submission-guide.md](references/fda-submission-guide.md)
---
## EU MDR Submission Workflow
Achieve CE marking under EU MDR 2017/745.
### Workflow: MDR Technical Documentation
1. Confirm device classification per MDR Annex VIII
2. Select conformity assessment route based on class:
- Class I: Self-declaration
- Class IIa/IIb: Notified Body involvement
- Class III: Full NB assessment
3. Select and engage Notified Body (for Class IIa+) — see selection criteria below
4. Compile Technical Documentation per Annex II checklist:
- [ ] Annex II §1: Device description, intended purpose, UDI
- [ ] Annex II §2: Design and manufacturing information (drawings, BoM, process flows)
- [ ] Annex II §3: GSPR checklist — each requirement mapped to evidence (standard, test report, or justification)
- [ ] Annex II §4: Benefit-risk analysis and risk management file (ISO 14971)
- [ ] Annex II §5: Product verification and validation (test reports)
- [ ] Annex II §6: Post-market surveillance plan
- [ ] Annex XIV: Clinical evaluation report (CER) — literature, clinical data, equivalence justification
5. Establish and document QMS per ISO 13485
6. Submit application to Notified Body
7. Address NB questions and coordinate audit
8. **Validation:** CE certificate issued; Declaration of Conformity signed; EUDAMED registration complete
#### GSPR Checklist Row Example
| GSPR Ref | Requirement | Standard / Guidance | Evidence Document | Status |
|----------|-------------|---------------------|-------------------|--------|
| Annex I §1 | Safe design and manufacture | ISO 14971:2019 | Risk Management File v2.1 | Complete |
| Annex I §11.1 | Devices with measuring function ±accuracy | EN ISO 15223-1 | Performance Test Report PT-003 | Complete |
| Annex I §17 | Cybersecurity | MDCG 2019-16 | Cybersecurity Assessment CS-001 | In progress |
### Clinical Evidence Requirements by Class
| Class | Clinical Requirement | Documentation |
|-------|---------------------|---------------|
| I | Clinical evaluation (CE) | CE report |
| IIa | CE with literature focus | CE report + PMCF plan |
| IIb | CE with clinical data | CE report + PMCF + clinical study (some) |
| III | CE with clinical investigation | CE report + PMCF + clinical investigation |
### Notified Body Selection Criteria
- **Scope:** Designated for your specific device category
- **Capacity:** Confirmed availability within target timeline
- **Experience:** Track record with your technology type
- **Geography:** Proximity for on-site audits
- **Cost:** Fee structure transparency
- **Communication:** Responsiveness and query turnaround
See: [references/eu-mdr-submission-guide.md](references/eu-mdr-submission-guide.md)
---
## Global Market Access Workflow
Coordinate regulatory approvals across international markets.
### Workflow: Multi-Market Submission Strategy
1. Define target markets based on business priorities
2. Sequence markets for efficient evidence leverage:
- Phase 1: FDA + EU (reference markets)
- Phase 2: Recognition markets (Canada, Australia)
- Phase 3: Major markets (Japan, China)
- Phase 4: Emerging markets
3. Identify local requirements per market:
- Clinical data acceptability
- Local agent/representative needs
- Language and labeling requirements
4. Develop master technical file with localization plan
5. Establish in-country regulatory support
6. Execute parallel or sequential submissions
7. Track approvals and coordinate launches
8. **Validation:** All target market approvals obtained; registration database updated
### Market Priority Matrix
| Market | Size | Complexity | Recognition | Priority |
|--------|------|------------|-------------|----------|
| USA | Large | High | N/A | 1 |
| EU | Large | High | N/A | 1–2 |
| Canada | Medium | Medium | MDSAP | 2 |
| Australia | Medium | Low | EU accepted | 2 |
| Japan | Large | High | Local clinical | 3 |
| China | Large | Very High | Local testing | 3 |
| Brazil | Medium | High | GMP inspection | 3–4 |
### Documentation Efficiency Strategy
| Document Type | Single Source | Localization Required |
|---------------|---------------|----------------------|
| Technical file core | Yes | Format adaptation |
| Risk management | Yes | None |
| Clinical data | Yes | Bridging assessment |
| QMS certificate | Yes (ISO 13485) | Market-specific audit |
| Labeling | Master label | Translation, local requirements |
| IFU | Master content | Translation, local symbols |
See: [references/global-regulatory-pathways.md](references/global-regulatory-pathways.md)
---
## Regulatory Intelligence Workflow
Monitor and respond to regulatory changes affecting product portfolio.
### Workflow: Regulatory Change Management
1. Monitor regulatory sources:
- FDA Federal Register, guidance documents
- EU Official Journal, MDCG guidance
- Notified Body communications
- Industry associations (AdvaMed, MedTech Europe)
2. Assess relevance to product portfolio
3. Evaluate impact:
- Timeline to compliance
- Resource requirements
- Product changes needed
4. Develop compliance action plan
5. Communicate to affected stakeholders
6. Implement required changes
7. Document compliance status
8. **Validation:** Compliance action plan approved; changes implemented on schedule
### Regulatory Monitoring Sources
| Source | Type | Frequency |
|--------|------|-----------|
| FDA Federal Register | Regulations, guidance | Daily |
| FDA Device Database | 510(k), PMA, recalls | Weekly |
| EU Official Journal | MDR/IVDR updates | Weekly |
| MDCG Guidance | EU implementation | As published |
| ISO/IEC | Standards updates | Quarterly |
| Notified Body | Audit findings, trends | Per interaction |
### Impact Assessment Template
```
REGULATORY CHANGE IMPACT ASSESSMENT
Change: [Description] Source: [Regulation/Guidance]
Effective Date: [Date] Assessment Date: [Date] Assessed By: [Name]
AFFECTED PRODUCTS
| Product | Impact (H/M/L) | Action Required | Due Date |
|---------|----------------|------------------------|----------|
| [Name] | [H/M/L] | [Specific action] | [Date] |
COMPLIANCE ACTIONS
1. [Action] — Owner: [Name] — Due: [Date]
2. [Action] — Owner: [Name] — Due: [Date]
RESOURCE REQUIREMENTS: Budget $[X] | Personnel [X] hrs
APPROVAL: Regulatory _____________ Date _______ / Management _____________ Date _______
```
---
## Decision Frameworks
### Pathway Selection and Classification Reference
**FDA Pathway Selection**
```
Is predicate device available?
│
Yes─┴─No
│ │
▼ ▼
Is device Is risk level
substantially Low-Moderate?
equivalent? │
│ Yes─┴─No
Yes─┴─No │ │
│ │ ▼ ▼
▼ ▼ De Novo PMA
510(k) Consider required
De Novo
or PMA
```
**EU MDR Classification**
```
Is the device active?
│
Yes─┴─No
│ │
▼ ▼
Is it an Does it contact
implant? the body?
│ │
Yes─┴─No Yes─┴─No
│ │ │ │
▼ ▼ ▼ ▼
III IIb Check Class I
contact (measuring/
type sterile if
and applicable)
duration
```
### Pre-Submission Meeting Decision
| Factor | Schedule Pre-Sub | Skip Pre-Sub |
|--------|------------------|--------------|
| Novel Technology | ✓ | |
| New Intended Use | ✓ | |
| Complex Testing | ✓ | |
| Uncertain Predicate | ✓ | |
| Clinical Data Needed | ✓ | |
| Well-established | | ✓ |
| Clear Predicate | | ✓ |
| Standard Testing | | ✓ |
### Regulatory Escalation Criteria
| Situation | Escalation Level | Action |
|-----------|------------------|--------|
| Submission rejection | VP Regulatory | Root cause analysis, strategy revision |
| Major deficiency | Director | Cross-functional response team |
| Timeline at risk | Management | Resource reallocation review |
| Regulatory change | VP Regulatory | Portfolio impact assessment |
| Safety signal | Executive | Immediate containment and reporting |
---
## Tools and References
### Scripts
| Tool | Purpose | Usage |
|------|---------|-------|
| [regulatory_tracker.py](scripts/regulatory_tracker.py) | Track submission status and timelines | `python regulatory_tracker.py` |
**Regulatory Tracker Features:**
- Track multiple submissions across markets
- Monitor status and target dates
- Identify overdue submissions
- Generate status reports
**Example usage:**
```bash
$ python regulatory_tracker.py --report status
Submission Status Report — 2024-11-01
┌──────────────────┬──────────┬────────────┬─────────────┬──────────┐
│ Product │ Market │ Type │ Target Date │ Status │
├──────────────────┼──────────┼────────────┼─────────────┼──────────┤
│ WoundScan Pro │ USA │ 510(k) │ 2024-12-01 │ On Track │
│ WoundScan Pro │ EU │ MDR IIb │ 2025-03-01 │ At Risk │
│ CardioMonitor X1 │ Canada │ Class II │ 2025-01-15 │ On Track │
└──────────────────┴──────────┴────────────┴─────────────┴──────────┘
1 submission at risk: WoundScan Pro EU — NB engagement not confirmed.
```
### References
| Document | Content |
|----------|---------|
| [fda-submission-guide.md](references/fda-submission-guide.md) | FDA pathways, requirements, review process |
| [eu-mdr-submission-guide.md](references/eu-mdr-submission-guide.md) | MDR classification, technical documentation, clinical evidence |
| [global-regulatory-pathways.md](references/global-regulatory-pathways.md) | Canada, Japan, China, Australia, Brazil requirements |
| [iso-regulatory-requirements.md](references/iso-regulatory-requirements.md) | ISO 13485, 14971, 10993, IEC 62304, 62366 requirements |
### Key Performance Indicators
| KPI | Target | Calculation |
|-----|--------|-------------|
| First-time approval rate | >85% | (Approved without major deficiency / Total submitted) × 100 |
| On-time submission | >90% | (Submitted by target date / Total submissions) × 100 |
| Review cycle compliance | >95% | (Responses within deadline / Total requests) × 100 |
| Regulatory hold time | <20% | (Days on hold / Total review days) × 100 |
---
## Related Skills
| Skill | Integration Point |
|-------|-------------------|
| [mdr-745-specialist](../mdr-745-specialist/) | Detailed EU MDR technical requirements |
| [fda-consultant-specialist](../fda-consultant-specialist/) | FDA submission deep expertise |
| [quality-manager-qms-iso13485](../quality-manager-qms-iso13485/) | QMS for regulatory compliance |
| [risk-management-specialist](../risk-management-specialist/) | ISO 14971 risk management |
FILE:references/eu-mdr-submission-guide.md
# EU MDR 2017/745 Submission Guide
## MDR Classification and Conformity Assessment Routes
### Class I Devices
- **Self-certification** under Annex II
- **Technical documentation** requirements per Annex II
- **Declaration of Conformity** mandatory
- **UDI registration** required
### Class IIa Devices
- **Notified Body involvement** for Annex III Module C2 + Annex V
- **Quality management system** assessment
- **Technical documentation** review
- **Ongoing surveillance** requirements
### Class IIb Devices
- **Notified Body certification** under Annex III Module B + C or D
- **Type examination** or **Full quality assurance** route
- **Design examination** requirements
- **Production surveillance** obligations
### Class III Devices
- **Comprehensive Notified Body assessment**
- **Type examination** + production surveillance OR
- **Full quality assurance** system approach
- **Design dossier** requirements per Annex II
## Key MDR Submission Requirements
### 1. Technical Documentation (Annex II)
- Device description and intended purpose
- Risk management documentation (ISO 14971)
- Clinical evidence per Annex XIV
- Post-market surveillance plan
- Performance evaluation reports
### 2. Quality Management System (Annex I, Chapter II)
- ISO 13485 compliant QMS
- Design controls implementation
- Risk management integration
- Clinical evaluation procedures
- Post-market surveillance system
### 3. Clinical Evidence Requirements
- **Clinical evaluation plan** per Annex XIV
- **Literature review** and gap analysis
- **Clinical investigation** if required
- **Post-market clinical follow-up** plan
- **Clinical evaluation report** updating
### 4. UDI System Implementation
- **UDI-DI assignment** and registration
- **UDI-PI requirements** for higher risk devices
- **EUDAMED registration** obligations
- **Labeling compliance** with UDI requirements
## Submission Timeline Framework
### Pre-Submission Phase (6-12 months)
1. **Gap analysis** against MDR requirements
2. **Classification confirmation** with regulatory experts
3. **Notified Body selection** and preliminary discussions
4. **Clinical evidence strategy** development
5. **UDI strategy** and EUDAMED preparation
### Submission Preparation (3-6 months)
1. **Technical documentation** compilation
2. **QMS documentation** review and update
3. **Clinical evaluation** completion
4. **Risk management** file finalization
5. **Notified Body application** submission
### Review and Certification (6-18 months)
1. **Initial assessment** by Notified Body
2. **Questions and clarifications** response
3. **Audit activities** coordination
4. **Certificate issuance** and market access
5. **Post-market obligations** activation
## Critical Success Factors
- **Early engagement** with chosen Notified Body
- **Robust clinical evidence** strategy and execution
- **Comprehensive risk management** throughout lifecycle
- **Proactive post-market surveillance** system
- **Regular monitoring** of regulatory updates and guidance
## Common Pitfalls to Avoid
- **Insufficient clinical evidence** planning
- **Late Notified Body engagement**
- **Inadequate post-market surveillance** systems
- **Poor documentation quality** and traceability
- **Underestimating timeline** and resource requirements
FILE:references/fda-submission-guide.md
# FDA Submission Guide
## FDA Medical Device Classification and Pathways
### Class I Devices
- **510(k) Exempt** - Most Class I devices
- **General Controls** apply (21 CFR 820)
- **FDA registration** required
- **Device listing** mandatory
### Class II Devices
- **510(k) Clearance** - Premarket notification
- **General + Special Controls** apply
- **Predicate device** identification required
- **Substantial equivalence** demonstration
### Class III Devices
- **PMA (Premarket Approval)** - Full safety and effectiveness review
- **IDE (Investigational Device Exemption)** for clinical studies
- **Clinical data** typically required
- **Post-market surveillance** obligations
### De Novo Classification
- **Novel devices** without predicate
- **Low to moderate risk** profile
- **Creates new device classification**
- **Special controls** development
## Submission Pathways and Requirements
### 1. 510(k) Premarket Notification
**Traditional 510(k)**
- Predicate device comparison
- Performance testing documentation
- Software documentation (if applicable)
- Labeling and indications for use
**Special 510(k)**
- Modifications to cleared devices
- Design controls documentation
- Risk analysis of changes
- Performance validation
**Abbreviated 510(k)**
- Guidance document compliance
- Recognized standards conformance
- Special controls adherence
- Reduced documentation requirements
### 2. PMA (Premarket Approval)
**Clinical Investigation Requirements**
- IDE study protocol approval
- GCP compliance documentation
- Clinical study reports
- Statistical analysis plans
**Manufacturing Information**
- ISO 13485 QMS compliance
- Manufacturing process validation
- Facility inspection readiness
- Supply chain documentation
### 3. De Novo Classification Request
**Risk-based Classification**
- Benefit-risk profile analysis
- Predicate device absence justification
- Special controls recommendations
- Clinical evidence strategy
## FDA Submission Process
### Pre-Submission Activities
1. **Q-Sub Meeting** - Pre-submission consultation
2. **Classification determination** confirmation
3. **Predicate device** identification and analysis
4. **Testing strategy** development and validation
5. **FDA guidance** review and compliance assessment
### Submission Preparation
1. **Technical documentation** compilation per FDA format
2. **Quality system** documentation and readiness
3. **Clinical evidence** compilation (if required)
4. **Labeling** and indications for use finalization
5. **eCopy submission** preparation
### FDA Review Process
1. **Administrative review** (15 days for completeness)
2. **Substantive review** (90 days for 510(k), 180 days for PMA)
3. **Additional information** requests and responses
4. **FDA questions** and clarifications
5. **Clearance/approval** or denial decision
## Special Considerations
### Software as Medical Device (SaMD)
- **Software documentation** per FDA guidance
- **Cybersecurity** considerations and risk management
- **Software lifecycle** process documentation
- **Change control** procedures
### Combination Products
- **OPDP assignment** determination
- **Lead center** coordination
- **Intercenter agreement** requirements
- **Combination product** specific guidance
### HIPAA Compliance
- **Protected Health Information** safeguards
- **Business associate** agreements
- **Risk assessment** and management
- **Breach notification** procedures
## Quality System Requirements
### 21 CFR Part 820 (QSR)
- **Design controls** (21 CFR 820.30)
- **Document controls** (21 CFR 820.40)
- **Management responsibility** (21 CFR 820.20)
- **Corrective and preventive actions** (21 CFR 820.100)
## Key Performance Metrics
- **Review timeline** adherence and predictability
- **First-time clearance** rates and success factors
- **Additional information** request frequency
- **Post-market compliance** effectiveness
- **FDA inspection** readiness and outcomes
FILE:references/global-regulatory-pathways.md
# Global Regulatory Pathways
International regulatory requirements for medical devices beyond FDA and EU MDR markets.
---
## Table of Contents
- [Canada (Health Canada)](#canada-health-canada)
- [Japan (PMDA)](#japan-pmda)
- [China (NMPA)](#china-nmpa)
- [Australia (TGA)](#australia-tga)
- [Brazil (ANVISA)](#brazil-anvisa)
- [Market Entry Strategy](#market-entry-strategy)
---
## Canada (Health Canada)
### Device Classification
| Class | Risk Level | Examples | Review Type |
|-------|------------|----------|-------------|
| I | Lowest | Tongue depressors, bandages | Establishment license only |
| II | Low-moderate | Contact lenses, pregnancy tests | Declaration of conformity |
| III | Moderate-high | Orthopedic implants, ventilators | Pre-market review |
| IV | Highest | Pacemakers, HIV tests | In-depth pre-market review |
### Medical Device License (MDL) Requirements
**Class II-IV Devices:**
1. Device license application via MDALL (Medical Devices Active License Listing)
2. Quality management system documentation (ISO 13485)
3. Device safety and effectiveness evidence
4. Canadian labeling requirements (French/English bilingual)
5. Canadian Medical Device Single Audit Program (CMDCAS) certificate
**Review Timelines:**
| Class | Standard Review | Priority Review |
|-------|-----------------|-----------------|
| II | 15 days | N/A |
| III | 60 days | 30 days |
| IV | 75 days | 45 days |
### Key Requirements
| Requirement | Details |
|-------------|---------|
| QMS Audit | MDSAP or ISO 13485 audit by recognized body |
| UDI | Canadian UDI-DI required in MDALL |
| Labeling | Bilingual (English/French) mandatory |
| Incident Reporting | Mandatory problem reporting within 10-30 days |
| Post-Market | Annual license maintenance |
---
## Japan (PMDA)
### Device Classification (Pharmaceutical and Medical Device Act)
| Class | Japanese Term | Examples | Regulatory Path |
|-------|---------------|----------|-----------------|
| I | General | Scalpels, X-ray film | Self-certification |
| II | Controlled | MRI, ultrasound | Third-party certification |
| III | Specially Controlled | Pacemaker leads, dialyzers | PMDA Shonin approval |
| IV | Specially Controlled | Pacemakers, artificial hearts | PMDA Shonin approval |
### Shonin Approval Process
**Pre-Application:**
1. Classification consultation with PMDA
2. Pre-submission meeting (recommended for Class III/IV)
3. Japanese clinical data requirements assessment
4. Marketing Authorization Holder (MAH) designation
**Application Requirements:**
- Technical documentation per MHLW format
- Japanese clinical data (bridging study may be required)
- QMS compliance certificate (ISO 13485)
- GCP compliance for clinical studies
- Japanese labeling and IFU
**Review Timelines:**
| Application Type | Standard | Priority |
|------------------|----------|----------|
| New Shonin | 12 months | 6 months |
| Partial Change | 6-9 months | 3-4 months |
### Special Considerations
| Factor | Requirement |
|--------|-------------|
| Clinical Data | Japanese patient data often required |
| MAH | Requires Japanese MAH or Designated MAH (D-MAH) |
| QMS | MHLW Minister certification or ISO 13485 |
| Language | All documents in Japanese |
| Foreign Manufacturer | Accreditation required |
---
## China (NMPA)
### Device Classification
| Class | Risk Level | Examples | Regulatory Path |
|-------|------------|----------|-----------------|
| I | Low | Surgical instruments | Provincial filing |
| II | Moderate | Diagnostic ultrasound, ECG | Provincial registration |
| III | High | Pacemakers, implants | NMPA registration |
### Registration Requirements
**Class II/III Registration:**
1. Clinical evaluation or trial (China-specific requirements)
2. Product technical requirements document
3. Type testing by NMPA-designated lab
4. Quality management system (ISO 13485 + Chinese requirements)
5. Chinese agent appointment (CSRC holder)
**Review Process:**
| Stage | Class II | Class III |
|-------|----------|-----------|
| Technical Review | 60 working days | 90 working days |
| Administrative Review | 20 working days | 20 working days |
| Registration Certificate | 5 years validity | 5 years validity |
### Key Requirements
| Requirement | Details |
|-------------|---------|
| Clinical Trial | Required for most Class III; China-specific data |
| Testing | NMPA-designated testing laboratory |
| Agent | Chinese Service Representative Certificate (CSRC) holder |
| Labeling | Simplified Chinese mandatory |
| QMS | Chinese GMP compliance in addition to ISO 13485 |
### China Clinical Trial Requirements
| Device Type | Clinical Requirement |
|-------------|---------------------|
| First-of-kind | Full clinical trial in China |
| Well-established | Literature + clinical evaluation |
| Equivalent device | Comparative analysis + limited data |
---
## Australia (TGA)
### Device Classification (TGO 41)
| Class | Risk Level | Examples | Conformity Route |
|-------|------------|----------|------------------|
| I | Lowest | Surgical retractors | Manufacturer declaration |
| I (measuring) | Low | Clinical thermometers | EU/MDSAP certificate |
| I (sterile) | Low | Sterile gloves | EU/MDSAP certificate |
| IIa | Low-moderate | Hearing aids, ultrasound | EU/MDSAP certificate |
| IIb | Moderate-high | Ventilators, X-ray | EU/MDSAP certificate |
| III | High | Pacemakers, implants | EU/MDSAP certificate |
| AIMD | Active implants | Cochlear implants | EU/MDSAP certificate |
### Australian Register of Therapeutic Goods (ARTG)
**Registration Requirements:**
1. Australian sponsor (manufacturer or importer)
2. Conformity assessment evidence (EU certificate or MDSAP)
3. Australian labeling compliance
4. Adverse event reporting system
5. ARTG application and fees
**Pathways:**
| Pathway | Applicable Devices | Documentation |
|---------|-------------------|---------------|
| Conformity Assessment | All classes | EU/MDSAP certificates accepted |
| Comparable Overseas Regulator | Established devices | Recognition of FDA/EU approval |
| TGA Audit | No overseas certificate | TGA conducts assessment |
### Key Requirements
| Requirement | Details |
|-------------|---------|
| Sponsor | Australian-based sponsor mandatory |
| Conformity | EU MDR/IVDR or MDSAP certificate |
| Labeling | English, Australian-specific requirements |
| Incident Reporting | Mandatory within 48 hours (serious) |
| Annual Charges | Based on ARTG listing |
---
## Brazil (ANVISA)
### Device Classification (RDC 185/2001)
| Class | Risk Level | Examples | Registration |
|-------|------------|----------|--------------|
| I | Low | Tongue depressors | Notification (cadastro) |
| II | Low-moderate | Wheelchairs, syringes | Notification (cadastro) |
| III | Moderate-high | Hemodialysis, implants | Registration (registro) |
| IV | High | Pacemakers, stents | Registration (registro) |
### Registration Process
**Cadastro (Class I/II):**
- Brazilian Registration Holder (BRH) application
- Technical documentation
- Good Manufacturing Practice (GMP) certificate
- Free sale certificate from country of origin
**Registro (Class III/IV):**
- Full technical dossier submission
- ANVISA GMP inspection (if not MDSAP)
- Clinical data requirements
- Brazilian labeling and IFU
- Registration validity: 5 years (Class III) or 10 years (Class IV)
### Key Requirements
| Requirement | Details |
|-------------|---------|
| BRH | Brazilian Registration Holder mandatory |
| GMP | ANVISA inspection or MDSAP certificate |
| INMETRO | Certification for specific device categories |
| Language | Portuguese labeling and IFU |
| Clinical | Brazilian clinical data may be required |
**Review Timelines:**
| Type | Standard | Priority |
|------|----------|----------|
| Cadastro | 30-60 days | N/A |
| Registro | 180-365 days | 90-180 days |
---
## Market Entry Strategy
### Prioritization Framework
| Factor | Weight | Considerations |
|--------|--------|----------------|
| Market Size | 25% | Revenue potential, growth rate |
| Regulatory Complexity | 25% | Timeline, cost, local requirements |
| Competitive Landscape | 20% | Existing players, differentiation |
| Reimbursement | 20% | Payer coverage, pricing |
| Strategic Value | 10% | Reference market, regional hub |
### Recommended Entry Sequence
**Phase 1: Priority Markets (Year 1)**
- United States (FDA)
- European Union (MDR)
- Leverage for downstream approvals
**Phase 2: Recognition Markets (Year 1-2)**
- Australia (TGA) - accepts EU/MDSAP
- Canada (Health Canada) - MDSAP pathway
- Faster approval using existing evidence
**Phase 3: Major Markets (Year 2-3)**
- Japan (PMDA) - may require local clinical
- China (NMPA) - local testing and clinical
**Phase 4: Emerging Markets (Year 3+)**
- Brazil (ANVISA)
- Other Latin America
- Middle East, Southeast Asia
### Documentation Efficiency
| Document Type | Create Once | Localize Per Market |
|---------------|-------------|---------------------|
| Technical file | Core technical documentation | Specific format requirements |
| Clinical data | Global clinical study | Local bridging studies |
| QMS certificate | ISO 13485 / MDSAP | Market-specific audits |
| Labeling | Master label content | Language, local requirements |
### Common Pitfalls
| Pitfall | Impact | Prevention |
|---------|--------|------------|
| Underestimating local clinical requirements | 12-24 month delay | Early regulatory intelligence |
| Inadequate in-country representation | Registration rejection | Qualified local partner |
| Language/labeling non-compliance | Market rejection | Professional translation review |
| Ignoring post-market requirements | License suspension | Establish vigilance system |
| Sequential vs. parallel submissions | Extended timeline | Plan parallel submissions where possible |
FILE:references/iso-regulatory-requirements.md
# ISO Regulatory Requirements for Medical Devices
Key ISO standards applicable to medical device development, quality management, and regulatory compliance.
---
## Table of Contents
- [ISO 13485 Quality Management](#iso-13485-quality-management)
- [ISO 14971 Risk Management](#iso-14971-risk-management)
- [ISO 10993 Biocompatibility](#iso-10993-biocompatibility)
- [IEC 62304 Software Lifecycle](#iec-62304-software-lifecycle)
- [IEC 62366 Usability Engineering](#iec-62366-usability-engineering)
- [ISO 11607 Packaging Validation](#iso-11607-packaging-validation)
- [Sterilization Standards](#sterilization-standards)
- [Standards Cross-Reference](#standards-cross-reference)
---
## ISO 13485 Quality Management
### ISO 13485:2016 Overview
| Aspect | Requirement |
|--------|-------------|
| Scope | QMS for design, development, production, installation, and servicing |
| Certification | Third-party certification required for most markets |
| Regulatory Status | Harmonized under EU MDR; recognized by FDA QSIT |
| Validity | 3-year certification cycle with annual surveillance |
### Key Clause Requirements
| Clause | Title | Regulatory Focus |
|--------|-------|------------------|
| 4.1 | General Requirements | Process-based QMS, outsourcing control |
| 4.2 | Documentation | Quality Manual, procedures, records |
| 5.1-5.6 | Management Responsibility | Policy, planning, review |
| 6.1-6.4 | Resource Management | Competence, infrastructure, environment |
| 7.1 | Planning | Risk management integration |
| 7.2 | Customer-Related | Requirements determination and review |
| 7.3 | Design and Development | Design controls (critical for FDA) |
| 7.4 | Purchasing | Supplier controls |
| 7.5 | Production | Process validation, identification, traceability |
| 7.6 | Monitoring Equipment | Calibration |
| 8.2 | Monitoring | Feedback, complaints, audits |
| 8.3 | Nonconforming Product | Control and disposition |
| 8.5 | Improvement | CAPA |
### Design Control Requirements (Clause 7.3)
| Stage | Clause | Deliverables |
|-------|--------|--------------|
| Planning | 7.3.2 | Design plan, stages, responsibilities |
| Inputs | 7.3.3 | Requirements specification |
| Outputs | 7.3.4 | Design specifications, acceptance criteria |
| Review | 7.3.5 | Design review records |
| Verification | 7.3.6 | Verification testing reports |
| Validation | 7.3.7 | Validation protocols and reports |
| Transfer | 7.3.8 | Transfer verification records |
| Changes | 7.3.9 | Change control records |
### Regulatory Mapping
| Regulation | ISO 13485 Recognition |
|------------|----------------------|
| EU MDR 2017/745 | Harmonized standard (presumption of conformity) |
| FDA 21 CFR 820 | Substantially equivalent; QSIT alignment |
| Health Canada | MDSAP or direct recognition |
| PMDA Japan | Recognized with MHLW certification |
| TGA Australia | Accepted as conformity evidence |
| ANVISA Brazil | Required for GMP compliance |
---
## ISO 14971 Risk Management
### ISO 14971:2019 Overview
| Aspect | Requirement |
|--------|-------------|
| Scope | Risk management throughout medical device lifecycle |
| Regulatory Status | Harmonized under EU MDR; referenced by FDA |
| Key Change (2019) | Enhanced benefit-risk analysis emphasis |
| Documentation | Risk management file required |
### Risk Management Process
| Stage | Activities | Outputs |
|-------|------------|---------|
| Planning | Define scope, responsibilities, criteria | Risk management plan |
| Risk Analysis | Identify hazards, estimate risk | Hazard analysis, risk estimation |
| Risk Evaluation | Compare against acceptability criteria | Risk evaluation records |
| Risk Control | Select and implement controls | Risk control measures |
| Residual Risk | Evaluate remaining risk | Residual risk evaluation |
| Risk-Benefit | Assess overall benefit-risk | Benefit-risk analysis |
| Review | Periodic risk management review | Risk management report |
### Risk Analysis Methods
| Method | Application | Standard Reference |
|--------|-------------|-------------------|
| FMEA | Component/process failure modes | IEC 60812 |
| FTA | System-level failure analysis | IEC 61025 |
| HAZOP | Process hazard identification | IEC 61882 |
| PHA | Preliminary hazard assessment | - |
### Risk Acceptability Matrix
| Severity | Probability | Risk Level | Action |
|----------|-------------|------------|--------|
| Catastrophic | Frequent | Unacceptable | Design change required |
| Critical | Probable | ALARP | Risk reduction required |
| Serious | Occasional | ALARP | Risk reduction if practicable |
| Minor | Remote | Acceptable | Monitor |
| Negligible | Improbable | Acceptable | Document |
### Post-Production Risk Management
| Activity | Frequency | Sources |
|----------|-----------|---------|
| Complaint Analysis | Continuous | Customer complaints |
| Vigilance Review | Continuous | Adverse event reports |
| Literature Review | Annual | Scientific publications |
| Standards Review | Annual | Updated standards |
| Risk File Update | As needed | New information |
---
## ISO 10993 Biocompatibility
### ISO 10993-1:2018 Biological Evaluation Framework
| Contact Type | Duration | Required Tests |
|--------------|----------|----------------|
| Surface - Skin | Limited (<24h) | Cytotoxicity, sensitization, irritation |
| Surface - Mucosal | Prolonged (24h-30d) | + Acute systemic toxicity |
| Surface - Breached | Permanent (>30d) | + Subchronic toxicity, genotoxicity |
| External Communicating | Limited | Cytotoxicity, sensitization, irritation, hemolysis |
| External Communicating | Prolonged | + Subchronic toxicity, implantation |
| External Communicating | Permanent | + Chronic toxicity, carcinogenicity |
| Implant | Limited | Full biological evaluation |
| Implant | Prolonged/Permanent | Comprehensive testing including implantation |
### Key Test Standards
| Standard | Test |
|----------|------|
| ISO 10993-3 | Genotoxicity, carcinogenicity, reproductive toxicity |
| ISO 10993-4 | Hemocompatibility |
| ISO 10993-5 | Cytotoxicity (in vitro) |
| ISO 10993-6 | Local effects after implantation |
| ISO 10993-10 | Irritation and skin sensitization |
| ISO 10993-11 | Systemic toxicity |
| ISO 10993-12 | Sample preparation and reference materials |
| ISO 10993-18 | Chemical characterization |
### Biocompatibility Evaluation Workflow
1. Define device contact nature and duration
2. Identify materials in contact with body
3. Perform chemical characterization (ISO 10993-18)
4. Conduct gap analysis against required endpoints
5. Plan and execute required testing
6. Document biological evaluation report
7. Update for material or design changes
8. **Validation:** All endpoints addressed; testing per GLP; BE report complete
---
## IEC 62304 Software Lifecycle
### IEC 62304:2006/AMD1:2015 Overview
| Aspect | Requirement |
|--------|-------------|
| Scope | Medical device software development lifecycle |
| Regulatory Status | Harmonized under EU MDR; FDA guidance reference |
| Key Concept | Safety classification drives rigor |
| Documentation | Software development plan, architecture, testing |
### Software Safety Classification
| Class | Definition | Documentation Rigor |
|-------|------------|---------------------|
| A | No injury or damage possible | Basic |
| B | Non-serious injury possible | Moderate |
| C | Death or serious injury possible | High |
### Required Processes by Class
| Process | Class A | Class B | Class C |
|---------|---------|---------|---------|
| Software Development Planning | Required | Required | Required |
| Software Requirements Analysis | Required | Required | Required |
| Software Architecture Design | - | Required | Required |
| Software Detailed Design | - | - | Required |
| Software Unit Implementation | Required | Required | Required |
| Software Unit Verification | - | Required | Required |
| Software Integration Testing | Required | Required | Required |
| Software System Testing | Required | Required | Required |
| Software Release | Required | Required | Required |
| Software Maintenance | Required | Required | Required |
| Software Risk Management | Required | Required | Required |
| Software Configuration Management | Required | Required | Required |
| Software Problem Resolution | Required | Required | Required |
### Documentation Requirements
| Document | Class A | Class B | Class C |
|----------|---------|---------|---------|
| Software Development Plan | ✓ | ✓ | ✓ |
| Software Requirements Specification | ✓ | ✓ | ✓ |
| Software Architecture Document | - | ✓ | ✓ |
| Software Detailed Design | - | - | ✓ |
| Software Unit Test Records | - | ✓ | ✓ |
| Integration Test Records | ✓ | ✓ | ✓ |
| System Test Records | ✓ | ✓ | ✓ |
| Traceability Matrix | - | ✓ | ✓ |
---
## IEC 62366 Usability Engineering
### IEC 62366-1:2015 Overview
| Aspect | Requirement |
|--------|-------------|
| Scope | Usability engineering process for medical devices |
| Regulatory Status | Harmonized under EU MDR; FDA HFE guidance |
| Key Concept | Use-related risk identification and mitigation |
| Documentation | Usability engineering file |
### Usability Engineering Process
| Stage | Activities | Outputs |
|-------|------------|---------|
| Use Specification | Define users, use environments, user interface | Use specification document |
| User Interface Design | Design UI with task analysis input | UI specifications |
| Hazard Analysis | Identify use-related hazards | Use-related risk analysis |
| Formative Evaluation | Iterative design testing | Formative evaluation reports |
| Summative Evaluation | Final design validation | Summative evaluation report |
| Documentation | Compile usability engineering file | UEF |
### Usability Testing Requirements
| Test Type | Purpose | Participants |
|-----------|---------|--------------|
| Formative | Identify usability issues during design | Representative users (5-8 per iteration) |
| Summative | Validate final design | Representative users (15+ per user group) |
| Simulated Use | Test under realistic conditions | Trained users in simulated environment |
| Actual Use | Validate in clinical setting | Actual users in actual environment |
### Usability Engineering File Contents
| Section | Content |
|---------|---------|
| Use Specification | User profiles, use environments, user interface |
| Use-Related Risk Analysis | Hazard identification, risk evaluation |
| UI Design Specifications | Design requirements, rationale |
| Formative Evaluation | Test protocols, results, design changes |
| Summative Evaluation | Validation protocol, results, conclusions |
| Residual Risk | Remaining use-related risks |
---
## ISO 11607 Packaging Validation
### ISO 11607-1:2019 and ISO 11607-2:2019
| Part | Scope |
|------|-------|
| Part 1 | Requirements for materials, sterile barrier systems, packaging systems |
| Part 2 | Validation requirements for forming, sealing, and assembly processes |
### Packaging Validation Stages
| Stage | Activities | Documentation |
|-------|------------|---------------|
| IQ | Equipment installation verification | Installation records |
| OQ | Process parameter verification | OQ protocol and report |
| PQ | Performance under production conditions | PQ protocol and report |
### Required Testing
| Test | Standard | Purpose |
|------|----------|---------|
| Seal Strength | ASTM F88 | Peel strength measurement |
| Seal Integrity | ASTM F2095 | Bubble leak test |
| Visual Inspection | ISO 11607-1 | Defect identification |
| Package Integrity | ASTM D4169 | Distribution simulation |
| Accelerated Aging | ASTM F1980 | Shelf life validation |
| Real-Time Aging | - | Stability confirmation |
### Shelf Life Validation
| Method | Approach | Considerations |
|--------|----------|----------------|
| Accelerated Aging | Q10 = 2 (typically) | Per ASTM F1980 |
| Real-Time Aging | Concurrent with accelerated | Required for final claim |
| Worst-Case Testing | Post-aging integrity testing | Distribution + storage conditions |
---
## Sterilization Standards
### Common Sterilization Methods
| Method | Standard | Applications |
|--------|----------|--------------|
| EO (Ethylene Oxide) | ISO 11135:2014 | Heat/moisture sensitive |
| Steam | ISO 17665-1:2006 | Heat/moisture tolerant |
| Radiation | ISO 11137:2017 | Heat sensitive, high volume |
| Dry Heat | ISO 20857:2010 | Moisture sensitive |
| Aseptic Processing | ISO 13408 | Prefilled syringes |
### Sterilization Validation Requirements
| Phase | Activities | Documentation |
|-------|------------|---------------|
| IQ | Equipment installation | Installation records |
| OQ | Process parameter qualification | OQ protocol and report |
| PQ | Microbiological performance | Bioburden, SAL demonstration |
| Routine Control | Process monitoring | Batch records, BI results |
### Sterility Assurance Level (SAL)
| SAL | Probability of Non-Sterile | Application |
|-----|----------------------------|-------------|
| 10⁻⁶ | 1 in 1 million | Most medical devices |
| 10⁻³ | 1 in 1,000 | Aseptically processed |
---
## Standards Cross-Reference
### Regulatory Alignment
| Standard | EU MDR | FDA | Health Canada | TGA |
|----------|--------|-----|---------------|-----|
| ISO 13485 | Harmonized | Recognized | Required | Accepted |
| ISO 14971 | Harmonized | Referenced | Required | Accepted |
| ISO 10993 | Harmonized | Referenced | Required | Accepted |
| IEC 62304 | Harmonized | Referenced | Required | Accepted |
| IEC 62366 | Harmonized | Referenced | Required | Accepted |
### Version Requirements
| Standard | Current Version | Transition Deadline |
|----------|-----------------|---------------------|
| ISO 13485 | 2016 | Active |
| ISO 14971 | 2019 | Active |
| ISO 10993-1 | 2018 | Active |
| IEC 62304 | 2006/Amd1:2015 | Active |
| IEC 62366-1 | 2015/Amd1:2020 | Active |
### Certification Bodies
| Region | Certification Body Type |
|--------|------------------------|
| EU | Notified Bodies (per MDR) |
| USA | FDA-recognized accreditation bodies |
| MDSAP | Authorized auditing organizations |
| Global | ISO certification bodies (IATF, DNV, BSI, TÜV) |
FILE:scripts/regulatory_pathway_analyzer.py
#!/usr/bin/env python3
"""
Regulatory Pathway Analyzer - Determines optimal regulatory pathway for medical devices.
Analyzes device characteristics and recommends the most efficient regulatory pathway
across multiple markets (FDA, EU MDR, UK UKCA, Health Canada, TGA, PMDA).
Supports:
- FDA: 510(k), De Novo, PMA, Breakthrough Device
- EU MDR: Class I, IIa, IIb, III, AIMDD
- UK: UKCA marking
- Health Canada: Class I-IV
- TGA: Class I, IIa, IIb, III
- Japan PMDA: Class I-IV
Usage:
python regulatory_pathway_analyzer.py --device-class II --predicate yes --market all
python regulatory_pathway_analyzer.py --interactive
python regulatory_pathway_analyzer.py --data device_profile.json --output json
"""
import argparse
import json
import sys
from dataclasses import dataclass, field, asdict
from typing import List, Dict, Optional, Tuple
from enum import Enum
class RiskClass(Enum):
CLASS_I = "I"
CLASS_IIA = "IIa"
CLASS_IIB = "IIb"
CLASS_III = "III"
CLASS_IV = "IV"
class MarketRegion(Enum):
US_FDA = "US-FDA"
EU_MDR = "EU-MDR"
UK_UKCA = "UK-UKCA"
HEALTH_CANADA = "Health-Canada"
AUSTRALIA_TGA = "Australia-TGA"
JAPAN_PMDA = "Japan-PMDA"
@dataclass
class DeviceProfile:
"""Medical device profile for pathway analysis."""
device_name: str
intended_use: str
device_class: str # I, IIa, IIb, III
novel_technology: bool = False
predicate_available: bool = True
implantable: bool = False
life_sustaining: bool = False
software_component: bool = False
ai_ml_component: bool = False
sterile: bool = False
measuring_function: bool = False
target_markets: List[str] = field(default_factory=lambda: ["US-FDA", "EU-MDR"])
@dataclass
class PathwayOption:
"""A regulatory pathway option."""
pathway_name: str
market: str
estimated_timeline_months: Tuple[int, int]
estimated_cost_usd: Tuple[int, int]
key_requirements: List[str]
advantages: List[str]
risks: List[str]
recommendation_level: str # "Recommended", "Alternative", "Not Recommended"
@dataclass
class PathwayAnalysis:
"""Complete pathway analysis result."""
device: DeviceProfile
recommended_pathways: List[PathwayOption]
optimal_sequence: List[str] # Recommended submission order
total_timeline_months: Tuple[int, int]
total_estimated_cost: Tuple[int, int]
critical_success_factors: List[str]
warnings: List[str]
class RegulatoryPathwayAnalyzer:
"""Analyzes and recommends regulatory pathways for medical devices."""
# FDA pathway decision matrix
FDA_PATHWAYS = {
"I": {
"pathway": "510(k) Exempt / Registration & Listing",
"timeline": (1, 3),
"cost": (5000, 15000),
"requirements": ["Establishment registration", "Device listing", "GMP compliance (if non-exempt)"]
},
"II": {
"pathway": "510(k)",
"timeline": (6, 12),
"cost": (50000, 250000),
"requirements": ["Predicate device identification", "Substantial equivalence demonstration", "Performance testing", "Biocompatibility (if applicable)", "Software documentation (if applicable)"]
},
"II-novel": {
"pathway": "De Novo",
"timeline": (12, 18),
"cost": (150000, 400000),
"requirements": ["Risk-based classification request", "Special controls development", "Performance testing", "Clinical data (potentially)"]
},
"III": {
"pathway": "PMA",
"timeline": (18, 36),
"cost": (500000, 2000000),
"requirements": ["Clinical investigations", "Manufacturing information", "Performance testing", "Risk-benefit analysis", "Post-approval studies"]
},
"III-breakthrough": {
"pathway": "Breakthrough Device Program + PMA",
"timeline": (12, 24),
"cost": (500000, 2000000),
"requirements": ["Breakthrough designation request", "More flexible clinical evidence", "Iterative FDA engagement", "Post-market data collection"]
}
}
# EU MDR pathway decision matrix
EU_MDR_PATHWAYS = {
"I": {
"pathway": "Self-declaration (Class I)",
"timeline": (2, 4),
"cost": (10000, 30000),
"requirements": ["Technical documentation", "EU Declaration of Conformity", "UDI assignment", "EUDAMED registration", "Authorized Representative (if non-EU)"]
},
"IIa": {
"pathway": "Notified Body assessment (Class IIa)",
"timeline": (12, 18),
"cost": (80000, 200000),
"requirements": ["QMS certification (ISO 13485)", "Technical documentation", "Clinical evaluation", "Notified Body audit", "Post-market surveillance plan"]
},
"IIb": {
"pathway": "Notified Body assessment (Class IIb)",
"timeline": (15, 24),
"cost": (150000, 400000),
"requirements": ["Full QMS certification", "Comprehensive technical documentation", "Clinical evaluation (may need clinical investigation)", "Type examination or product verification", "Notified Body scrutiny"]
},
"III": {
"pathway": "Notified Body assessment (Class III)",
"timeline": (18, 30),
"cost": (300000, 800000),
"requirements": ["Full QMS certification", "Complete technical documentation", "Clinical investigation (typically required)", "Notified Body clinical evaluation review", "Scrutiny procedure (possible)", "PMCF plan"]
}
}
def __init__(self):
self.analysis_warnings = []
def analyze_fda_pathway(self, device: DeviceProfile) -> PathwayOption:
"""Determine optimal FDA pathway."""
device_class = device.device_class.upper().replace("IIA", "II").replace("IIB", "II")
if device_class == "I":
pathway_data = self.FDA_PATHWAYS["I"]
return PathwayOption(
pathway_name=pathway_data["pathway"],
market="US-FDA",
estimated_timeline_months=pathway_data["timeline"],
estimated_cost_usd=pathway_data["cost"],
key_requirements=pathway_data["requirements"],
advantages=["Fastest path to market", "Minimal regulatory burden", "No premarket submission required (if exempt)"],
risks=["Limited to exempt product codes", "Still requires GMP compliance"],
recommendation_level="Recommended"
)
elif device_class == "III" or device.implantable or device.life_sustaining:
if device.novel_technology:
pathway_data = self.FDA_PATHWAYS["III-breakthrough"]
rec_level = "Recommended" if device.novel_technology else "Alternative"
else:
pathway_data = self.FDA_PATHWAYS["III"]
rec_level = "Recommended"
else: # Class II
if device.predicate_available and not device.novel_technology:
pathway_data = self.FDA_PATHWAYS["II"]
rec_level = "Recommended"
else:
pathway_data = self.FDA_PATHWAYS["II-novel"]
rec_level = "Recommended"
return PathwayOption(
pathway_name=pathway_data["pathway"],
market="US-FDA",
estimated_timeline_months=pathway_data["timeline"],
estimated_cost_usd=pathway_data["cost"],
key_requirements=pathway_data["requirements"],
advantages=self._get_fda_advantages(pathway_data["pathway"], device),
risks=self._get_fda_risks(pathway_data["pathway"], device),
recommendation_level=rec_level
)
def analyze_eu_mdr_pathway(self, device: DeviceProfile) -> PathwayOption:
"""Determine optimal EU MDR pathway."""
device_class = device.device_class.lower().replace("iia", "IIa").replace("iib", "IIb")
if device_class in ["i", "1"]:
pathway_data = self.EU_MDR_PATHWAYS["I"]
class_key = "I"
elif device_class in ["iia", "2a"]:
pathway_data = self.EU_MDR_PATHWAYS["IIa"]
class_key = "IIa"
elif device_class in ["iib", "2b"]:
pathway_data = self.EU_MDR_PATHWAYS["IIb"]
class_key = "IIb"
else:
pathway_data = self.EU_MDR_PATHWAYS["III"]
class_key = "III"
# Adjust for implantables
if device.implantable and class_key in ["IIa", "IIb"]:
pathway_data = self.EU_MDR_PATHWAYS["III"]
self.analysis_warnings.append(
f"Implantable devices are typically upclassified to Class III under EU MDR"
)
return PathwayOption(
pathway_name=pathway_data["pathway"],
market="EU-MDR",
estimated_timeline_months=pathway_data["timeline"],
estimated_cost_usd=pathway_data["cost"],
key_requirements=pathway_data["requirements"],
advantages=self._get_eu_advantages(pathway_data["pathway"], device),
risks=self._get_eu_risks(pathway_data["pathway"], device),
recommendation_level="Recommended"
)
def _get_fda_advantages(self, pathway: str, device: DeviceProfile) -> List[str]:
advantages = []
if "510(k)" in pathway:
advantages.extend([
"Well-established pathway with clear guidance",
"Predictable review timeline",
"Lower clinical evidence requirements vs PMA"
])
if device.predicate_available:
advantages.append("Predicate device identified - streamlined review")
elif "De Novo" in pathway:
advantages.extend([
"Creates new predicate for future 510(k) submissions",
"Appropriate for novel low-moderate risk devices",
"Can result in Class I or II classification"
])
elif "PMA" in pathway:
advantages.extend([
"Strongest FDA approval - highest market credibility",
"Difficult for competitors to challenge",
"May qualify for breakthrough device benefits"
])
elif "Breakthrough" in pathway:
advantages.extend([
"Priority review and interactive FDA engagement",
"Flexible clinical evidence requirements",
"Faster iterative development with FDA feedback"
])
return advantages
def _get_fda_risks(self, pathway: str, device: DeviceProfile) -> List[str]:
risks = []
if "510(k)" in pathway:
risks.extend([
"Predicate device may be challenged",
"SE determination can be subjective"
])
if device.software_component:
risks.append("Software documentation requirements increasing (Cybersecurity, AI/ML)")
elif "De Novo" in pathway:
risks.extend([
"Less predictable than 510(k)",
"May require more clinical data than expected",
"New special controls may be imposed"
])
elif "PMA" in pathway:
risks.extend([
"Very expensive and time-consuming",
"Clinical trial risks and delays",
"Post-approval study requirements"
])
if device.ai_ml_component:
risks.append("AI/ML components face evolving regulatory requirements")
return risks
def _get_eu_advantages(self, pathway: str, device: DeviceProfile) -> List[str]:
advantages = ["Access to entire EU/EEA market (27+ countries)"]
if "Self-declaration" in pathway:
advantages.extend([
"No Notified Body involvement required",
"Fastest path to EU market",
"Lowest cost option"
])
elif "IIa" in pathway:
advantages.append("Moderate regulatory burden with broad market access")
elif "IIb" in pathway or "III" in pathway:
advantages.extend([
"Strong market credibility with NB certification",
"Recognized globally for regulatory quality"
])
return advantages
def _get_eu_risks(self, pathway: str, device: DeviceProfile) -> List[str]:
risks = []
if "Self-declaration" not in pathway:
risks.extend([
"Limited Notified Body capacity - long wait times",
"Notified Body costs increasing under MDR"
])
risks.append("MDR transition still creating uncertainty")
if device.software_component:
risks.append("EU AI Act may apply to AI/ML medical devices")
return risks
def determine_optimal_sequence(self, pathways: List[PathwayOption], device: DeviceProfile) -> List[str]:
"""Determine optimal submission sequence across markets."""
# General principle: Start with fastest/cheapest, use data for subsequent submissions
sequence = []
# Sort by timeline (fastest first)
sorted_pathways = sorted(pathways, key=lambda p: p.estimated_timeline_months[0])
# FDA first if 510(k) - well recognized globally
fda_pathway = next((p for p in pathways if p.market == "US-FDA"), None)
eu_pathway = next((p for p in pathways if p.market == "EU-MDR"), None)
if fda_pathway and "510(k)" in fda_pathway.pathway_name:
sequence.append("1. US-FDA 510(k) first - clearance recognized globally, data reusable")
if eu_pathway:
sequence.append("2. EU-MDR - use FDA data in clinical evaluation")
elif eu_pathway and "Self-declaration" in eu_pathway.pathway_name:
sequence.append("1. EU-MDR (Class I self-declaration) - fastest market entry")
if fda_pathway:
sequence.append("2. US-FDA - use EU experience and data")
else:
for i, p in enumerate(sorted_pathways, 1):
sequence.append(f"{i}. {p.market} ({p.pathway_name})")
return sequence
def analyze(self, device: DeviceProfile) -> PathwayAnalysis:
"""Perform complete pathway analysis."""
self.analysis_warnings = []
pathways = []
for market in device.target_markets:
if "FDA" in market or "US" in market:
pathways.append(self.analyze_fda_pathway(device))
elif "MDR" in market or "EU" in market:
pathways.append(self.analyze_eu_mdr_pathway(device))
# Additional markets can be added here
sequence = self.determine_optimal_sequence(pathways, device)
total_timeline_min = sum(p.estimated_timeline_months[0] for p in pathways)
total_timeline_max = sum(p.estimated_timeline_months[1] for p in pathways)
total_cost_min = sum(p.estimated_cost_usd[0] for p in pathways)
total_cost_max = sum(p.estimated_cost_usd[1] for p in pathways)
csf = [
"Early engagement with regulators (Pre-Sub/Scientific Advice)",
"Robust QMS (ISO 13485) in place before submissions",
"Clinical evidence strategy aligned with target markets",
"Cybersecurity and software documentation (if applicable)"
]
if device.ai_ml_component:
csf.append("AI/ML transparency and bias documentation")
return PathwayAnalysis(
device=device,
recommended_pathways=pathways,
optimal_sequence=sequence,
total_timeline_months=(total_timeline_min, total_timeline_max),
total_estimated_cost=(total_cost_min, total_cost_max),
critical_success_factors=csf,
warnings=self.analysis_warnings
)
def format_analysis_text(analysis: PathwayAnalysis) -> str:
"""Format analysis as readable text report."""
lines = [
"=" * 70,
"REGULATORY PATHWAY ANALYSIS REPORT",
"=" * 70,
f"Device: {analysis.device.device_name}",
f"Intended Use: {analysis.device.intended_use}",
f"Device Class: {analysis.device.device_class}",
f"Target Markets: {', '.join(analysis.device.target_markets)}",
"",
"DEVICE CHARACTERISTICS",
"-" * 40,
f" Novel Technology: {'Yes' if analysis.device.novel_technology else 'No'}",
f" Predicate Available: {'Yes' if analysis.device.predicate_available else 'No'}",
f" Implantable: {'Yes' if analysis.device.implantable else 'No'}",
f" Life-Sustaining: {'Yes' if analysis.device.life_sustaining else 'No'}",
f" Software/AI Component: {'Yes' if analysis.device.software_component or analysis.device.ai_ml_component else 'No'}",
f" Sterile: {'Yes' if analysis.device.sterile else 'No'}",
"",
"RECOMMENDED PATHWAYS",
"-" * 40,
]
for pathway in analysis.recommended_pathways:
lines.extend([
"",
f" [{pathway.market}] {pathway.pathway_name}",
f" Recommendation: {pathway.recommendation_level}",
f" Timeline: {pathway.estimated_timeline_months[0]}-{pathway.estimated_timeline_months[1]} months",
f" Estimated Cost: , - ,",
f" Key Requirements:",
])
for req in pathway.key_requirements:
lines.append(f" • {req}")
lines.append(f" Advantages:")
for adv in pathway.advantages:
lines.append(f" + {adv}")
lines.append(f" Risks:")
for risk in pathway.risks:
lines.append(f" ! {risk}")
lines.extend([
"",
"OPTIMAL SUBMISSION SEQUENCE",
"-" * 40,
])
for step in analysis.optimal_sequence:
lines.append(f" {step}")
lines.extend([
"",
"TOTAL ESTIMATES",
"-" * 40,
f" Combined Timeline: {analysis.total_timeline_months[0]}-{analysis.total_timeline_months[1]} months",
f" Combined Cost: , - ,",
"",
"CRITICAL SUCCESS FACTORS",
"-" * 40,
])
for i, factor in enumerate(analysis.critical_success_factors, 1):
lines.append(f" {i}. {factor}")
if analysis.warnings:
lines.extend([
"",
"WARNINGS",
"-" * 40,
])
for warning in analysis.warnings:
lines.append(f" ⚠ {warning}")
lines.append("=" * 70)
return "\n".join(lines)
def interactive_mode():
"""Interactive device profiling."""
print("=" * 60)
print("Regulatory Pathway Analyzer - Interactive Mode")
print("=" * 60)
device = DeviceProfile(
device_name=input("\nDevice Name: ").strip(),
intended_use=input("Intended Use: ").strip(),
device_class=input("Device Class (I/IIa/IIb/III): ").strip(),
novel_technology=input("Novel technology? (y/n): ").strip().lower() == 'y',
predicate_available=input("Predicate device available? (y/n): ").strip().lower() == 'y',
implantable=input("Implantable? (y/n): ").strip().lower() == 'y',
life_sustaining=input("Life-sustaining? (y/n): ").strip().lower() == 'y',
software_component=input("Software component? (y/n): ").strip().lower() == 'y',
ai_ml_component=input("AI/ML component? (y/n): ").strip().lower() == 'y',
)
markets = input("Target markets (comma-separated, e.g., US-FDA,EU-MDR): ").strip()
if markets:
device.target_markets = [m.strip() for m in markets.split(",")]
analyzer = RegulatoryPathwayAnalyzer()
analysis = analyzer.analyze(device)
print("\n" + format_analysis_text(analysis))
def main():
parser = argparse.ArgumentParser(description="Regulatory Pathway Analyzer for Medical Devices")
parser.add_argument("--device-name", type=str, help="Device name")
parser.add_argument("--device-class", type=str, choices=["I", "IIa", "IIb", "III"], help="Device classification")
parser.add_argument("--predicate", type=str, choices=["yes", "no"], help="Predicate device available")
parser.add_argument("--novel", action="store_true", help="Novel technology")
parser.add_argument("--implantable", action="store_true", help="Implantable device")
parser.add_argument("--software", action="store_true", help="Software component")
parser.add_argument("--ai-ml", action="store_true", help="AI/ML component")
parser.add_argument("--market", type=str, default="all", help="Target market(s)")
parser.add_argument("--data", type=str, help="JSON file with device profile")
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()
if args.interactive:
interactive_mode()
return
if args.data:
with open(args.data) as f:
data = json.load(f)
device = DeviceProfile(**data)
elif args.device_class:
device = DeviceProfile(
device_name=args.device_name or "Unnamed Device",
intended_use="Medical device",
device_class=args.device_class,
novel_technology=args.novel,
predicate_available=args.predicate == "yes" if args.predicate else True,
implantable=args.implantable,
software_component=args.software,
ai_ml_component=args.ai_ml,
)
if args.market != "all":
device.target_markets = [m.strip() for m in args.market.split(",")]
else:
# Demo mode
device = DeviceProfile(
device_name="SmartGlucose Monitor Pro",
intended_use="Continuous glucose monitoring for diabetes management",
device_class="II",
novel_technology=False,
predicate_available=True,
software_component=True,
ai_ml_component=True,
target_markets=["US-FDA", "EU-MDR"]
)
analyzer = RegulatoryPathwayAnalyzer()
analysis = analyzer.analyze(device)
if args.output == "json":
result = {
"device": asdict(analysis.device),
"pathways": [asdict(p) for p in analysis.recommended_pathways],
"optimal_sequence": analysis.optimal_sequence,
"total_timeline_months": list(analysis.total_timeline_months),
"total_estimated_cost": list(analysis.total_estimated_cost),
"critical_success_factors": analysis.critical_success_factors,
"warnings": analysis.warnings
}
print(json.dumps(result, indent=2))
else:
print(format_analysis_text(analysis))
if __name__ == "__main__":
main()
FILE:scripts/regulatory_tracker.py
#!/usr/bin/env python3
"""
Regulatory Submission Tracking System
Automates monitoring and reporting of regulatory submission status
"""
import json
import datetime
from typing import Dict, List, Optional
from dataclasses import dataclass, asdict
from enum import Enum
class SubmissionType(Enum):
FDA_510K = "FDA_510K"
FDA_PMA = "FDA_PMA"
FDA_DE_NOVO = "FDA_DE_NOVO"
EU_MDR_CE = "EU_MDR_CE"
ISO_CERTIFICATION = "ISO_CERTIFICATION"
GLOBAL_REGULATORY = "GLOBAL_REGULATORY"
class SubmissionStatus(Enum):
PLANNING = "PLANNING"
IN_PREPARATION = "IN_PREPARATION"
SUBMITTED = "SUBMITTED"
UNDER_REVIEW = "UNDER_REVIEW"
ADDITIONAL_INFO_REQUESTED = "ADDITIONAL_INFO_REQUESTED"
APPROVED = "APPROVED"
REJECTED = "REJECTED"
WITHDRAWN = "WITHDRAWN"
@dataclass
class RegulatorySubmission:
submission_id: str
product_name: str
submission_type: SubmissionType
submission_status: SubmissionStatus
target_market: str
submission_date: Optional[datetime.date] = None
target_approval_date: Optional[datetime.date] = None
actual_approval_date: Optional[datetime.date] = None
regulatory_authority: str = ""
responsible_person: str = ""
notes: str = ""
last_updated: datetime.date = datetime.date.today()
class RegulatoryTracker:
def __init__(self, data_file: str = "regulatory_submissions.json"):
self.data_file = data_file
self.submissions: Dict[str, RegulatorySubmission] = {}
self.load_data()
def load_data(self):
"""Load existing submission data from JSON file"""
try:
with open(self.data_file, 'r') as f:
data = json.load(f)
for sub_id, sub_data in data.items():
# Convert date strings back to date objects
for date_field in ['submission_date', 'target_approval_date',
'actual_approval_date', 'last_updated']:
if sub_data.get(date_field):
sub_data[date_field] = datetime.datetime.strptime(
sub_data[date_field], '%Y-%m-%d').date()
# Convert enums
sub_data['submission_type'] = SubmissionType(sub_data['submission_type'])
sub_data['submission_status'] = SubmissionStatus(sub_data['submission_status'])
self.submissions[sub_id] = RegulatorySubmission(**sub_data)
except FileNotFoundError:
print(f"No existing data file found. Starting fresh.")
except Exception as e:
print(f"Error loading data: {e}")
def save_data(self):
"""Save submission data to JSON file"""
data = {}
for sub_id, submission in self.submissions.items():
sub_dict = asdict(submission)
# Convert date objects to strings
for date_field in ['submission_date', 'target_approval_date',
'actual_approval_date', 'last_updated']:
if sub_dict.get(date_field):
sub_dict[date_field] = sub_dict[date_field].strftime('%Y-%m-%d')
# Convert enums to strings
sub_dict['submission_type'] = sub_dict['submission_type'].value
sub_dict['submission_status'] = sub_dict['submission_status'].value
data[sub_id] = sub_dict
with open(self.data_file, 'w') as f:
json.dump(data, f, indent=2)
def add_submission(self, submission: RegulatorySubmission):
"""Add new regulatory submission"""
self.submissions[submission.submission_id] = submission
self.save_data()
print(f"Added submission: {submission.submission_id}")
def update_submission_status(self, submission_id: str,
new_status: SubmissionStatus,
notes: str = ""):
"""Update submission status"""
if submission_id in self.submissions:
self.submissions[submission_id].submission_status = new_status
self.submissions[submission_id].notes = notes
self.submissions[submission_id].last_updated = datetime.date.today()
self.save_data()
print(f"Updated {submission_id} status to {new_status.value}")
else:
print(f"Submission {submission_id} not found")
def get_submissions_by_status(self, status: SubmissionStatus) -> List[RegulatorySubmission]:
"""Get all submissions with specific status"""
return [sub for sub in self.submissions.values() if sub.submission_status == status]
def get_overdue_submissions(self) -> List[RegulatorySubmission]:
"""Get submissions that are overdue"""
today = datetime.date.today()
overdue = []
for submission in self.submissions.values():
if (submission.target_approval_date and
submission.target_approval_date < today and
submission.submission_status not in [SubmissionStatus.APPROVED,
SubmissionStatus.REJECTED,
SubmissionStatus.WITHDRAWN]):
overdue.append(submission)
return overdue
def generate_status_report(self) -> str:
"""Generate comprehensive status report"""
report = []
report.append("REGULATORY SUBMISSION STATUS REPORT")
report.append("=" * 50)
report.append(f"Generated: {datetime.date.today()}")
report.append("")
# Summary by status
status_counts = {}
for status in SubmissionStatus:
count = len(self.get_submissions_by_status(status))
if count > 0:
status_counts[status] = count
report.append("SUBMISSION STATUS SUMMARY:")
for status, count in status_counts.items():
report.append(f" {status.value}: {count}")
report.append("")
# Overdue submissions
overdue = self.get_overdue_submissions()
if overdue:
report.append("OVERDUE SUBMISSIONS:")
for submission in overdue:
days_overdue = (datetime.date.today() - submission.target_approval_date).days
report.append(f" {submission.submission_id} - {days_overdue} days overdue")
report.append("")
# Active submissions requiring attention
active_statuses = [SubmissionStatus.SUBMITTED, SubmissionStatus.UNDER_REVIEW,
SubmissionStatus.ADDITIONAL_INFO_REQUESTED]
active_submissions = []
for status in active_statuses:
active_submissions.extend(self.get_submissions_by_status(status))
if active_submissions:
report.append("ACTIVE SUBMISSIONS REQUIRING ATTENTION:")
for submission in active_submissions:
report.append(f" {submission.submission_id} - {submission.product_name}")
report.append(f" Status: {submission.submission_status.value}")
report.append(f" Target Date: {submission.target_approval_date}")
report.append(f" Authority: {submission.regulatory_authority}")
report.append("")
return "\n".join(report)
def main():
"""Main function for command-line usage"""
tracker = RegulatoryTracker()
# Generate and print status report
print(tracker.generate_status_report())
# Example: Add a new submission
# new_submission = RegulatorySubmission(
# submission_id="SUB-2024-001",
# product_name="HealthTech Device X",
# submission_type=SubmissionType.FDA_510K,
# submission_status=SubmissionStatus.PLANNING,
# target_market="United States",
# target_approval_date=datetime.date(2024, 12, 31),
# regulatory_authority="FDA",
# responsible_person="John Doe"
# )
# tracker.add_submission(new_submission)
if __name__ == "__main__":
main()
Phân tích các buổi retrospective của sprint để tìm mẫu lặp lại và theo dõi các hạng mục hành động từ file dữ liệu JSON.
---
name: retro
description: Analyze sprint retrospectives for patterns and action item tracking. Usage: /retro analyze <retro_data.json>
---
# /retro
Analyze retrospective data for recurring themes, sentiment trends, and action item effectiveness.
## Usage
```
/retro analyze <retro_data.json> Full retrospective analysis
```
## Input Format
```json
{
"sprint_name": "Sprint 24",
"went_well": ["CI pipeline improvements", "Pair programming sessions"],
"improvements": ["Too many meetings", "Flaky integration tests"],
"action_items": [
{"description": "Reduce standup to 10 min", "owner": "SM", "status": "done"},
{"description": "Fix flaky tests", "owner": "QA Lead", "status": "in_progress"}
],
"participants": 8
}
```
## Examples
```
/retro analyze sprint-24-retro.json
/retro analyze sprint-24-retro.json --format json
```
## Scripts
- `project-management/scrum-master/scripts/retrospective_analyzer.py` — Retrospective analyzer (`<data_file> [--format text|json]`)
## Skill Reference
> `project-management/scrum-master/SKILL.md`
Xây dựng phản hồi có cấu trúc cho RFP, RFI, RFQ hoặc bảng câu hỏi bảo mật, gồm phân tích yêu cầu và ma trận bằng chứng theo phương pháp Shipley.
---
name: rfp-responder
description: "Use when an RFP, RFI, RFQ, security questionnaire, vendor questionnaire, or proposal request arrives and the team needs a structured response — parsing multi-section buyer-dictated requirements (MANDATORY vs WEIGHTED vs NICE-TO-HAVE), building a Shipley-method proof-point matrix mapping each requirement to a verifiable proof point, articulating 3-5 win-themes that ladder up across requirements, and producing a Shipley-derived winrate estimate that informs a bid / no-bid / partner-bid recommendation. For Bid Managers, Proposal Leads, Directors of Sales, and Sales Engineers at the response-strategy moment. Surfaces GAP requirements explicitly — never invents claims. NOT free-form proposal narrative authoring, NOT contract redline, NOT marketing collateral."
context: fork
version: 2.8.0
author: claude-code-skills
license: MIT
tags: [commercial, rfp, rfi, rfq, shipley, win-theme, proof-points, structured-response, bid-management]
compatible_tools: [claude-code, codex-cli, cursor, antigravity, opencode, gemini-cli]
---
# rfp-responder
## Purpose
Help Bid Managers, Proposal Leads, and Directors of Sales answer five questions at the response-strategy moment:
1. **What is this RFP actually asking?** (parse sections, tag every requirement MANDATORY / WEIGHTED / NICE-TO-HAVE, extract scoring criteria, surface deadlines and format constraints)
2. **What is our true fit?** (proof-point matrix per requirement: STRONG / PARTIAL / GAP, each backed by a verifiable source — case study, certification, customer quote, technical attestation, benchmark)
3. **What is our win-theme strategy?** (Shipley method: 3-5 themes that ladder up across requirements, not generic value-prop bullets)
4. **What is our realistic winrate?** (Shipley-derived factor model: fit, incumbent, relationship strength, decision-criteria alignment, late-entry, competitor count, deal size — produces estimate + confidence band)
5. **Should we bid?** (deterministic verdict: BID / PARTNER-BID / NO-BID with named factors driving the call)
The skill surfaces GAPs explicitly. Leadership decides whether to close them, partner around them, or no-bid. **It never invents claims.**
## When to use
- A 30+ page RFP / RFI / RFQ has landed with a 7-14 day response deadline
- A security questionnaire (SIG, CAIQ, custom-buyer) needs structured Q&A — not prose
- The team is preparing a bid / no-bid review and needs a defensible winrate estimate
- Sales Engineering has a proof-point library but no system to map proofs to requirements
- Leadership wants to see fit % (STRONG / PARTIAL / GAP) before committing pursuit budget
- A late-entry opportunity needs honest assessment of the relationship deficit
**Do not use for:**
- Free-form proposal narrative authoring → `business-growth/contract-and-proposal-writer`
- Contract redline AFTER award → `c-level-advisor/general-counsel-advisor`
- Marketing collateral / category content → `marketing-skill/*`
- Discount approval on the awarded deal → `commercial/deal-desk`
- Pricing-model design for a new product → `commercial/pricing-strategist`
## Workflow
### Step 1 — Parse the RFP
Drop the RFP markdown / text into `scripts/rfp_parser.py`. Output: structured JSON listing every requirement, tagged MANDATORY / WEIGHTED / NICE-TO-HAVE based on cue words (must / shall = MANDATORY; should / weighted scoring numbers = WEIGHTED; may / preferred / desired = NICE-TO-HAVE). Captures section structure, scoring criteria if disclosed, deadline, submission format constraints.
```bash
python scripts/rfp_parser.py --input rfp.md --output json > parsed.json
```
### Step 2 — Score fit per requirement
Fill `assets/rfp_intake_template.md` with your proof-point library (each proof tagged with type + verifiable source + which requirement-tags it covers) and proposed win-themes. Feed parsed RFP + intake into `scripts/response_drafter.py`. Output: proof-point matrix per requirement with STRONG / PARTIAL / GAP, win-theme injection, GAP audit.
```bash
python scripts/response_drafter.py --input draft_input.json --output markdown > matrix.md
```
**Hard rule:** GAP requirements are surfaced, never invented around. Leadership reads the GAP audit and decides: close the gap, partner-bid, or no-bid.
### Step 3 — Apply win-theme strategy
Shipley method: 3-5 themes that span requirements. Each theme answers "why us over the incumbent / competitor on the criteria the buyer named." `response_drafter.py` shows which themes thread through which requirements — a theme appearing in <2 requirements is decorative, not strategic, and gets flagged.
### Step 4 — Estimate winrate
Feed deal context (fit %, incumbent strength, relationship, decision-criteria alignment, late-entry, competitor count, deal size vs. average) into `scripts/winrate_predictor.py`. Output: Shipley-derived estimate 0-100% + confidence band + factor breakdown + BID / PARTNER-BID / NO-BID verdict.
```bash
python scripts/winrate_predictor.py --input deal_context.json --profile enterprise-software --output markdown
```
**No-bid threshold:** estimate < 20% triggers automatic no-bid recommendation.
### Step 5 — Decide
Take parsed RFP + proof-point matrix + GAP audit + winrate estimate into the go / no-go review. Skill does not commit pursuit budget — leadership does.
## Scripts
- `scripts/rfp_parser.py` — section + requirement extractor (regex + cue-word heuristics, stdlib only)
- `scripts/response_drafter.py` — proof-point matrix + win-theme injection + GAP audit
- `scripts/winrate_predictor.py` — Shipley-derived factor model + bid/no-bid verdict, industry-profile-tuned
All scripts: stdlib only (argparse, json, sys, pathlib, re, collections, statistics). `--help` and `--sample` work on all three.
## References
- `references/shipley_method_canon.md` — Shipley Proposal Guide v6, Shipley Capture Guide, APMP BoK, Tom Sant, Tom Searcy + Henry DeVries, Strategic Proposals research, Larry Newman
- `references/rfp_strategy_canon.md` — FAR, GSA, Forrester, Gartner, Bain, McKinsey, B2B International on RFP win-rates and buyer behavior
- `references/rfp_anti_patterns.md` — Shipley failure modes, APMP cases, Strategic Proposals research, federal loss reviews, MIT Sloan, Bain commercial-discipline, Gartner
## Assumptions
- **The RFP is the ground truth.** If the buyer asked it, answer it — in the order they asked, in the format they specified. Re-organizing for narrative flow is for proposals, not RFPs.
- **Proof points must be verifiable.** A claim is only as strong as the case study, certification, customer reference, or technical attestation backing it. Unsourced claims become GAPs.
- **Win-themes are buyer-side, not seller-side.** "We're the leader in X" is a marketing claim; "Your operations team reduces incident MTTR by 60% with the same headcount" is a win-theme. Shipley canon, not optional.
- **Winrate estimates are directional.** The model is a discipline tool to force honest pursuit-qualification — not an oracle. Confidence band always wider than the point estimate suggests.
- **Industry profiles tune base rates** — government RFPs reward compliance discipline; enterprise SaaS rewards reference accounts; healthcare rewards regulatory + security depth.
- **Late entry is a structural disadvantage.** Entering after the RFP issued, with no relationship history, drops base rate ~15%. The skill names this, doesn't hide it.
## Anti-patterns
- **Inventing a proof point to fill a GAP.** Hard rule violation. GAPs surface for leadership decision, not for prose-laundering. See `references/rfp_anti_patterns.md`.
- **Responding to every RFP.** Without a qualified bid / no-bid gate, the team burns capacity on <20% winrate pursuits and loses the 50%+ pursuits to lack of focus. Bain commercial-discipline research.
- **Generic response with no win-theme.** A proposal that could be sent verbatim by any competitor is decorative. Shipley failure mode #1.
- **Missing a mandatory disqualifier late.** FedRAMP / HIPAA / ISO 27001 / SOC 2 / on-shore data residency caught on Day 12 of a 14-day response = wasted pursuit. Parser surfaces these on Day 1.
- **Answering the question YOU wanted asked.** RFP responder discipline: answer what they asked, in their words, in their order. Re-framing belongs in cover letters, not in the compliance matrix.
- **No compliance matrix.** Every requirement should map to a response section + page number. Evaluators score on a matrix; respondents who don't provide one self-disqualify on traceability.
- **Late-entry without acknowledging the relationship deficit.** Entering cold against an incumbent with a 3-year relationship and no champion = sub-20% winrate. Pretending otherwise wastes Sales Engineering capacity.
- **Treating WEIGHTED requirements like MANDATORY.** Score-weighted requirements reward depth on the high-weight items, not uniform mediocrity across all. Shipley capture method.
## Distinct from
- **`business-growth/contract-and-proposal-writer`** — free-form narrative proposals where YOU set the structure (executive briefs, capability statements, unsolicited proposals). RFP-responder handles **buyer-dictated structured Q&A** where the buyer set the questions, sections, scoring criteria, and format. Different artifact, different decision logic.
- **`c-level-advisor/general-counsel-advisor`** — contract redline and IP/risk review AFTER award. RFP-responder operates BEFORE award, on the response strategy.
- **`marketing-skill/*`** — external marketing assets (web copy, content, ASO, SEO, brand voice) for many-to-many audiences. RFP-responder produces a **single-buyer artifact** with deterministic compliance requirements.
- **`commercial/deal-desk`** — per-deal discount routing on a closing opportunity. RFP-responder is pursuit-stage; deal-desk is close-stage.
- **`commercial/pricing-strategist`** — pricing-model design for a new product. RFP-responder consumes existing pricing as input to the commercial-terms section.
## Forcing-question library (Matt Pocock grill discipline)
Walked one at a time before any script runs. Recommended answer + canon citation per question. Never bundled.
1. **"What's your STRONG / PARTIAL / GAP split on the MANDATORY requirements?"**
Recommended: STRONG ≥ 70% on MANDATORY before bidding. PARTIAL/GAP on any MANDATORY = either close the gap pre-submission or no-bid.
Canon: Shipley *Proposal Guide v6* — capture-management discipline, "Pgw (probability of win) is bounded by your weakest MANDATORY."
2. **"Is there an incumbent, and how strong is their position?"**
Recommended: strong incumbent (3+ years, no displacement event) drops base winrate ~30%. Don't bid without a named displacement trigger.
Canon: Forrester B2B-RFP research — incumbents win 70-80% of renewal RFPs absent a named failure event.
3. **"Did you enter the conversation before or after the RFP issued?"**
Recommended: late-entry (after RFP issued, no prior engagement) drops winrate ~15% and signals the RFP was scoped to someone else's strengths.
Canon: Tom Searcy + Henry DeVries *How to Win Big Business* — "If you didn't help write the RFP, you're column fodder."
4. **"What are your 3-5 win-themes, and does each thread through ≥2 requirements?"**
Recommended: themes that appear in only one requirement are decorative. Themes must ladder up across MANDATORY + WEIGHTED sections.
Canon: Shipley *Capture Guide* — win-themes are the buyer-side answer to "why us" across the evaluation criteria, not seller-side feature lists.
5. **"For every claim in the response, can you name the verifiable source?"**
Recommended: every claim → case study / certification / customer reference / technical attestation / benchmark. Unsourced claims = GAPs.
Canon: APMP BoK — "Substantiation: every assertion in a proposal must be backed by evidence the evaluator can independently verify."
6. **"What's the bid / no-bid threshold you committed to BEFORE seeing this RFP?"**
Recommended: pre-committed threshold (e.g., winrate ≥ 25%, STRONG ≥ 70% on MANDATORY, named champion). Post-hoc rationalization is how teams end up bidding 5% pursuits.
Canon: Bain RFP-win-rate studies — disciplined bid/no-bid gates lift win-rate from ~15% to ~35%.
7. **"What does the buyer's evaluation team actually score on?"**
Recommended: if the RFP discloses scoring criteria, weight your response effort proportionally. If undisclosed, ask. If you can't ask, that itself is a relationship-deficit signal.
Canon: Strategic Proposals proposal-management research — evaluators score on the rubric they were given, not on your narrative.
Walk depth-first. Lock 1-3 before opening 4-7. After all 7 are answered, invoke `rfp_parser.py` → `response_drafter.py` → `winrate_predictor.py` in sequence. If question 6 lands on "we don't have a threshold," set one now or no-bid.
FILE:assets/rfp_intake_template.md
# RFP Intake Template
Fill this in BEFORE running `scripts/response_drafter.py` and `scripts/winrate_predictor.py`. Save as `rfp_intake.json` — the JSON skeleton at the bottom of this file is the canonical input format.
## Step 1 — Deal context
| Field | Value | Notes |
|---|---|---|
| Buyer organization | | |
| RFP title / ID | | |
| Submission deadline | | Date format YYYY-MM-DD |
| Estimated deal size (ACV / TCV) | | |
| Deal size vs. our average | below / at / above | Above-average deals attract more competitors |
| Incumbent | name or "none" | |
| Incumbent strength | none / weak / strong | Strong = 3+ years, no displacement event |
| Relationship strength | cold / warm / champion | Champion = internal advocate willing to push for us |
| Champion name + role | | If relationship_strength = "warm" or "champion" |
| Late entry? | yes / no | "Yes" if we entered AFTER the RFP issued |
| Decision-criteria alignment | 0-100% | How well our strengths match what the buyer says they're scoring on |
| Competitor count | integer | Best estimate; ask the buyer if you can |
| Industry profile | saas / enterprise-software / services / government / healthcare | Tunes `winrate_predictor.py` |
## Step 2 — Proof-point library
For every proof point your team can produce, fill in a row. Verifiable source is **mandatory** — if you can't name where the evaluator could verify it, the proof point doesn't qualify as STRONG.
| Name | Type | Tags (match against requirement text) | Verifiable source |
|---|---|---|---|
| SOC 2 Type II report (2026) | cert | soc, 2, type, ii, certification | trust.example.com/soc2-2026.pdf |
| 24/7 SOC staffing attestation | technical_attestation | soc, 24/7, coverage, on-call, rotation | SecOps runbook v3.2 |
| ... | ... | ... | ... |
**Proof-point types:**
- `case_study` — full customer story with quantified outcome
- `cert` — third-party certification
- `customer_quote` — attributed, approved customer quote
- `technical_attestation` — internal but verifiable (runbook, architecture doc)
- `benchmark` — quantified peer comparison (Gartner, Forrester, internal)
## Step 3 — Win-themes (3-5)
Shipley discipline: each theme must thread through ≥2 requirements. Themes appearing once are decorative.
1. **Theme:** _________
**Threads through which requirement IDs:** _________
2. **Theme:** _________
3. **Theme:** _________
4. **Theme:** _________
5. **Theme:** _________
## Step 4 — Bid/no-bid threshold (set BEFORE seeing the RFP)
Pre-commit your threshold to avoid post-hoc rationalization:
- [ ] Winrate estimate ≥ ___ %
- [ ] STRONG match ≥ ___ % on MANDATORY requirements
- [ ] Named champion at buyer org
- [ ] MANDATORY GAP count ≤ ___
- [ ] Industry profile permits (e.g., do we no-bid government RFPs by default?)
## JSON skeleton — for `response_drafter.py --input`
```json
{
"rfp_requirements_path": "parsed.json",
"proof_points_library": [
{
"name": "SOC 2 Type II report (2026)",
"type": "cert",
"requirement_match_tags": ["soc", "2", "type", "ii", "certification"],
"verifiable_source": "https://trust.example.com/soc2-2026.pdf"
},
{
"name": "AWS/GCP/Azure logging case study (Globex)",
"type": "case_study",
"requirement_match_tags": ["aws", "gcp", "azure", "logging", "integrate"],
"verifiable_source": "globex-cs-2025.pdf"
}
],
"win_themes": [
"operational simplicity at scale",
"financial-services regulatory depth",
"MTTD leadership vs Gartner peer cohort"
]
}
```
## JSON skeleton — for `winrate_predictor.py --input`
```json
{
"requirement_fit_pct_strong": 60.0,
"requirement_fit_pct_partial": 25.0,
"requirement_fit_pct_gap": 15.0,
"incumbent_advantage": "weak",
"relationship_strength": "warm",
"decision_criteria_alignment_pct": 75.0,
"late_entry": false,
"competitor_count": 3,
"deal_size_vs_avg": "at"
}
```
## Running the pipeline
```bash
# 1. Parse the RFP
python scripts/rfp_parser.py --input rfp.md --output json > parsed.json
# 2. Build the proof-point matrix + GAP audit + win-theme report
python scripts/response_drafter.py --input rfp_intake.json --output markdown > matrix.md
# 3. Compute fit % from the matrix, fill into deal_context.json, then:
python scripts/winrate_predictor.py --input deal_context.json --profile enterprise-software --output markdown
# 4. Take parsed RFP + matrix + winrate into the go/no-go review.
```
## Hard rule reminder
**Never invent claims for GAP requirements.** Surface them. Leadership decides: close the gap, partner-bid, or no-bid.
FILE:references/rfp_anti_patterns.md
# RFP Anti-Patterns — Failure Modes the Skill Refuses to Enable
Eight RFP-response failure modes documented across Shipley failure-mode analyses, APMP case studies, Strategic Proposals research, federal loss reviews, MIT Sloan B2B research, Bain commercial-discipline studies, and Gartner. Each anti-pattern names what goes wrong, why teams fall into it, and how the skill prevents it.
## 1. Inventing claims to fill GAP requirements
**Failure mode:** A MANDATORY requirement has no matching proof point. Under deadline pressure, the proposal team writes prose that implies coverage without naming a verifiable source.
**Why it happens:** The team confuses "we could probably do this" with "we have done this and can prove it." Sales pressure to bid combines with no-one-wants-to-be-the-one-who-said-no dynamics.
**Why it loses:** Evaluators verify. When references, certifications, or technical attestations don't substantiate the claim, the response loses on credibility AND on the original requirement. APMP case-study data: invented claims are detected in 60-80% of evaluations and cause loss-of-trust effects that cascade across other sections.
**How the skill prevents it:** `response_drafter.py` surfaces GAP requirements explicitly. Leadership decides: close the gap pre-submission, partner-bid, or no-bid. The skill refuses to generate proof-point language for GAP rows. **Hard rule.**
## 2. No bid/no-bid review — respond to every RFP
**Failure mode:** Every RFP gets a response. Win-rate collapses to 5-12%; sales-engineering capacity burns on pursuits with no relationship, no fit, no champion.
**Why it happens:** Sales teams optimize for activity metrics, not win-rate. Marketing measures responses-sent, not responses-won.
**Why it loses:** Bain research: disciplined bid/no-bid gates lift win-rate from ~15% to ~35%. Without a gate, the team is structurally outperformed by competitors who qualified out and concentrated resources on winnable pursuits.
**How the skill prevents it:** `winrate_predictor.py` produces an explicit BID / PARTNER-BID / NO-BID verdict. <20% estimate triggers automatic NO-BID. The skill names this in writing — leadership cannot override silently.
## 3. Missing mandatory disqualifiers until Day 12
**Failure mode:** FedRAMP, HIPAA, ISO 27001, SOC 2, on-shore data residency — a MANDATORY certification or compliance requirement is buried on page 47 of the RFP and discovered after 10 days of proposal work.
**Why it happens:** No parse-pass on Day 1. The team reads the RFP as prose, not as a structured requirement set.
**Why it loses:** The pursuit is unrecoverable. All work product to date is wasted. Worse, the team loses 2 weeks of capacity that could have been spent on winnable pursuits.
**How the skill prevents it:** `rfp_parser.py` runs on Day 1, tags every MANDATORY requirement, and produces a compliance-matrix view. MANDATORY GAPs surface immediately, not on Day 12.
## 4. No win-theme — generic response
**Failure mode:** The response could be sent verbatim by any competitor. Capabilities are listed; differentiation is implicit; the "why us" answer is decorative ("we're the leader in X").
**Why it happens:** Win-themes are hard. They require buyer-side framing ("your team reduces X by Y") rather than seller-side feature lists. Teams default to feature lists because they're easy to write.
**Why it loses:** Shipley failure-mode analysis: generic responses lose 70%+ of evaluations where any competitor produced a buyer-anchored win-theme. Evaluators ladder themes back to evaluation criteria; generic responses can't do this.
**How the skill prevents it:** `response_drafter.py` threads each declared win-theme through the requirements list. Themes appearing in <2 requirements are flagged **DECORATIVE**. The skill forces theme-discipline.
## 5. Answering the question you wanted asked, not the question they asked
**Failure mode:** The team reframes buyer questions to match their proposal narrative. Section structure is re-ordered for "flow." Buyer-specific terminology is replaced with seller-preferred vocabulary.
**Why it happens:** Habit. Proposal teams trained on free-form proposals carry that discipline into RFP responses. Marketing prefers branded vocabulary.
**Why it loses:** Strategic Proposals research: evaluators score on traceability. A response that doesn't visibly answer the buyer's question in the buyer's order loses 20-30 points of available score before content quality is assessed.
**How the skill prevents it:** `rfp_parser.py` extracts requirements in the buyer's order with the buyer's text preserved. `response_drafter.py` builds the compliance matrix on the buyer's requirement IDs. Reframing is not supported.
## 6. No compliance matrix — no traceability
**Failure mode:** The response is a long prose document. No table shows which requirement is answered on which page. Evaluators scoring against a 60-row rubric give up after 15 minutes of search and default-score.
**Why it happens:** Compliance matrices are tedious to maintain when content changes. Teams skip them under deadline pressure.
**Why it loses:** APMP BoK: response traceability is one of the top-3 evaluator-cited differentiators. Without a matrix, the evaluator scores on what they can find — which is less than what you wrote.
**How the skill prevents it:** `response_drafter.py` outputs a markdown compliance matrix as its primary artifact. Every requirement → match level → proof point → verifiable source. The matrix IS the response architecture.
## 7. Late-entry without acknowledging the relationship deficit
**Failure mode:** The team enters the RFP cold. No prior engagement, no champion, no executive sponsor at the buyer. The proposal is written as if entry timing didn't matter.
**Why it happens:** Optimism bias. The team believes content quality can overcome structural disadvantage.
**Why it loses:** Forrester: late-entry vendors win 8-12% of RFPs vs 25-35% for capture-engaged vendors. Federal RFP loss reviews show late-entry as the #1 named factor in 40%+ of post-mortems.
**How the skill prevents it:** `winrate_predictor.py` requires `late_entry` as input. Setting it to `true` applies a −15% penalty. The estimate honestly reflects the structural deficit; leadership decides whether to spend pursuit budget anyway.
## 8. Treating WEIGHTED requirements like MANDATORY
**Failure mode:** The team gives equal effort to every WEIGHTED requirement. A 25-point requirement and a 5-point requirement get the same proof-depth, the same page count, the same proof-point recruitment effort.
**Why it happens:** No effort-weighting against the scoring rubric. Either the rubric wasn't disclosed and the team didn't ask, or the rubric was disclosed and the team ignored it.
**Why it loses:** Shipley capture math: WEIGHTED scores compound. Optimizing the top-3 weighted requirements (typically 60-70% of available points) wins more often than uniform-mediocrity across all weighted requirements. McKinsey B2B research: rubric-weighted-effort respondents win 1.6x more than equal-effort respondents.
**How the skill prevents it:** `rfp_parser.py` extracts disclosed scoring weights into the requirement evidence. `response_drafter.py` shows weights in the compliance matrix. Forcing-question #7 ("What does the buyer's evaluation team actually score on?") interrogates whether the weighting was even requested.
## Sources
1. **Shipley Associates failure-mode analyses** — internal post-loss reviews published in *Proposal Guide v6* appendix and in *Capture Guide* case studies. Source for anti-patterns 1, 4, 5.
2. **APMP (Association of Proposal Management Professionals) case studies** — APMP BoK appendix and APMP Journal case studies. Source for anti-pattern 1 (invented-claim detection rates) and anti-pattern 6 (traceability as top-3 differentiator).
3. **Strategic Proposals (strategicproposals.com) research and benchmarks** — published rubric-replication-gap data; source for anti-pattern 5 (evaluator-traceability scoring).
4. **Federal RFP loss reviews** — debrief reports available through FOIA and GSA's procurement transparency programs. Source for anti-pattern 7 (late-entry as #1 named loss factor in 40%+ of post-mortems).
5. **MIT Sloan B2B sales research**, MIT Sloan Management Review archives. Source for anti-pattern 8 (rubric-weighted-effort win-rate multiplier).
6. **Bain & Company commercial-discipline studies** — Bain B2B sales practice publications and conference presentations. Source for anti-pattern 2 (disciplined-pursuit win-rate of ~35% vs respond-to-everything ~12%).
7. **Gartner, RFP Best Practices and IT Buyer Studies**. Source for anti-pattern 6 (compliance-matrix presence as evaluator-cited differentiator) and the general industry-vertical evaluation-cycle benchmarks.
8. **Patrick Lencioni, *Getting Naked* (Jossey-Bass, 2010)**. Source for the "tell the kind truth" principle that operationalizes the skill's GAP-honesty hard rule (anti-pattern 1).
FILE:references/rfp_strategy_canon.md
# RFP Strategy Canon — Industry Research on RFP Win-Rates and Buyer Behavior
This reference grounds the `winrate_predictor.py` factor weights in published industry research. The model is opinionated but defensible: every factor maps to a citation below.
## Headline findings the skill encodes
### Base win-rates are honestly grim
- Average competitive B2B RFP win-rate: 15-25% across industries (Bain, Gartner).
- With disciplined bid/no-bid qualification: 35-45%.
- Without qualification: 5-12% — sales-engineering capacity burned on unwinnable pursuits.
The skill's 20% NO-BID threshold is calibrated to land below the disciplined-pursuit floor.
### Incumbents win renewal RFPs 70-80% of the time
Absent a named failure event (security breach, missed SLA, executive turnover at the incumbent), incumbents win 70-80% of renewal RFPs (Forrester B2B-RFP research). This is the empirical basis for the −30% incumbent penalty when incumbent_advantage is "strong."
### Late entry is structurally penalized
If you weren't part of the conversation before the RFP issued, the RFP was scoped to someone else's strengths. Forrester data: late-entry vendors win 8-12% of RFPs vs 25-35% for vendors who engaged in capture. The skill's −15% late-entry penalty is the midpoint of this gap.
### Relationship strength dominates content quality at the margin
Bain: in deals where the named champion advocates internally, win-rate lifts 20-30 percentage points over the "warm but no champion" baseline. The skill's +25% champion factor is the lower bound of this range.
### Decision-criteria alignment is bimodal
When buyer decision criteria align >80% with your strengths, win-rate is roughly 2x the base rate. When alignment is <50%, win-rate collapses to ~30% of base (McKinsey B2B sales research). The skill encodes this as a +10 / 0 / −10 step function rather than a continuous curve, because the bimodality is the honest reality.
### Competitor count compresses win-rate predictably
- 1 competitor (sole-source consideration): 60-80% win-rate
- 2 competitors: 35-50%
- 3 competitors: 20-30%
- 4-5 competitors: 12-18%
- 6+ competitors: 5-10%
The skill's competitor-count factor (+20 / +5 / 0 / -10 / -20) tracks this curve.
## Industry profile tuning
The skill exposes 5 profiles via `--profile`. Each shifts the base rate:
- **enterprise-software (+5)**: longer sales cycles, deeper technical evaluation, but disciplined buyers reward fit-honest vendors. Base rate slightly above average.
- **saas (0)**: market baseline.
- **services (−5)**: commoditized for many engagement types, weaker differentiation moats, harder to defend price.
- **government (−15)**: FAR-governed, compliance-heavy, incumbent-favored, evaluation timelines extend 2-4x. Forrester / GSA data.
- **healthcare (−10)**: regulatory overhead (HIPAA, FDA, HITRUST), risk-averse procurement, longer pilot cycles. Gartner healthcare-vertical research.
## What this skill deliberately does NOT model
- **Pricing positioning** — outside scope; consume from `commercial/pricing-strategist`.
- **Proposal aesthetics / production quality** — Shipley canon says these matter at the margin (3-5 percentage points) but never override fit, win-themes, and relationship. Skill omits.
- **Evaluator psychology** — Strategic Proposals research shows evaluators score on the rubric they were given. The skill assumes the rubric is the source of truth; theme-injection happens within rubric constraints.
## Sources
1. **Federal Acquisition Regulation (FAR)**, especially Parts 14 (Sealed Bidding) and 15 (Contracting by Negotiation), at acquisition.gov/far. Governs US federal RFPs. Defines the compliance-matrix requirement, evaluation-factor disclosure rules, and proposal-format constraints that drive the "government" profile penalty.
2. **GSA (General Services Administration) RFP and procurement guidance**, at gsa.gov. Quantifies federal evaluation timelines (typically 90-180 days) and the disproportionate weight federal evaluators give to past-performance citations — relevant to proof-point substantiation discipline.
3. **Forrester Research, B2B Buyer Studies** — recurring annual research on B2B buying behavior. Sources the 70-80% incumbent renewal-win-rate, the late-entry penalty, and the "5-10 vendor longlist" reality of modern RFP processes.
4. **Gartner, RFP Best Practices** — published guidance for IT-buyer organizations. Quantifies vendor-shortlist sizes by deal value, evaluation-cycle length by industry, and the structural advantage of fit-honest responses over feature-checklist responses.
5. **Bain & Company, B2B Sales and RFP-Win-Rate Research** — Bain's commercial-discipline practice publishes regular benchmarks on disciplined-pursuit win-rates (35-45%) vs respond-to-everything win-rates (5-12%). The 20% NO-BID threshold in `winrate_predictor.py` is calibrated against this data.
6. **McKinsey & Company, B2B Sales Practice** — McKinsey research on decision-criteria alignment and win-rate. Sources the bimodal alignment effect (>80% alignment doubles base rate; <50% collapses to 30% of base) encoded in `alignment_factor()`.
7. **B2B International (now Kantar B2B), Buyer Behavior in RFP Processes** — research on how B2B evaluation committees actually score responses. Confirms that compliance-matrix presence, proof-point substantiation, and rubric-aligned response structure are the top-3 evaluator-cited differentiators.
8. **Patrick Lencioni, *Getting Naked: A Business Fable About Shedding the Three Fears That Sabotage Client Loyalty*** (Jossey-Bass, 2010). The "we don't have a proof point for this — here's what we'd do instead" honesty discipline that informs the skill's hard rule: surface GAPs, never invent. Lencioni's "tell the kind truth" principle operationalized as a refusal to fabricate evidence.
FILE:references/shipley_method_canon.md
# Shipley Method Canon — RFP Response Discipline
The Shipley method is the dominant industry methodology for capture management and proposal development. This reference distils what `rfp-responder` consumes from it: capture-stage qualification, win-theme construction, proof-point substantiation, and the discipline that separates structured responses from prose proposals.
## What Shipley actually claims
Shipley's central claim is that **proposals are won in capture, not in writing**. By the time the RFP issues, 70-80% of the eventual outcome is determined by the capture work done in the preceding 6-18 months. The RFP-response phase executes a strategy — it does not create one from scratch.
This skill operationalizes the capture-output side: parsing the RFP into discrete requirements, scoring fit honestly (STRONG / PARTIAL / GAP), threading win-themes across requirements, and producing a defensible winrate estimate.
## Core concepts the skill implements
### 1. Compliance matrix
Every requirement must map to a response section + page number. Evaluators score on a matrix; respondents who don't provide one self-disqualify on traceability. `response_drafter.py` builds this matrix; `rfp_parser.py` extracts the requirement IDs that anchor it.
### 2. Win-themes (buyer-side, not seller-side)
A win-theme is the buyer-side answer to "why us over the competitor on the criteria the buyer named." It is NOT "we're the leader in X." Win-themes ladder up across multiple requirements — Shipley canon is that a theme appearing in only one requirement is **decorative**, not strategic. The skill flags these explicitly.
### 3. Proof points with substantiation
APMP BoK: "every assertion in a proposal must be backed by evidence the evaluator can independently verify." Five proof-point types the skill recognizes:
- **case_study** — full customer story with quantified outcome
- **cert** — third-party certification (SOC 2, ISO 27001, FedRAMP, HIPAA)
- **customer_quote** — attributed quote, customer-approved
- **technical_attestation** — internal but verifiable (runbook, architecture doc, SOC staffing rotation)
- **benchmark** — quantified comparison vs peers (Gartner, Forrester, internal)
STRONG = ≥2 tag matches AND proof type in {case_study, cert, technical_attestation, benchmark}.
PARTIAL = 1 match, or proof type is customer_quote.
GAP = 0 matches → surfaced for leadership, **never invented around**.
### 4. Pgw (probability of win) bounded by weakest MANDATORY
Shipley capture discipline: Pgw cannot exceed the score on your weakest MANDATORY requirement. A 90% fit on 9 of 10 MANDATORY items and a GAP on the 10th is not a 90% bid — it is a 0% bid until the GAP is closed or partnered around.
### 5. Bid / no-bid gate
A disciplined bid/no-bid gate lifts win-rate from ~15% to ~35% (Bain). The skill enforces this: winrate <20% → automatic NO-BID; 20-34% → PARTNER-BID; ≥35% → BID with full pursuit budget.
## What Shipley is NOT
- Not a prose-writing methodology — Shipley is structured, requirement-anchored, scoreable.
- Not optional for federal/regulated RFPs — FAR-governed RFPs are essentially Shipley-compatible by procurement design.
- Not a substitute for relationship capital — late-entry without prior engagement still penalizes ~15% even with perfect Shipley execution.
## Sources
1. **Shipley Associates, *Proposal Guide v6***, Larry Newman (Ed.), Shipley Associates Press. The canonical book. Defines capture-management, compliance matrix, win-themes, ghosting, theme statements, proof-point substantiation.
2. **Shipley Associates, *Capture Guide***. The capture-stage companion to the Proposal Guide. Defines the 6-stage capture lifecycle (opportunity identification → capture planning → solution development → preliminary bid decision → solution validation → final bid decision) the skill assumes has been done before it runs.
3. **APMP (Association of Proposal Management Professionals) *Body of Knowledge (BoK)***. International proposal-management standard. Defines substantiation discipline, evaluator-side scoring rubrics, compliance-matrix traceability requirements, and the Foundation / Practitioner / Professional certification tiers that anchor the industry.
4. **Tom Sant, *Persuasive Business Proposals: Writing to Win More Customers, Clients, and Contracts*** (3rd ed., AMACOM, 2012). Defines the NOSE pattern (Need, Outcome, Solution, Evidence) that the skill's proof-point matrix operationalizes. Sant's discipline: every solution claim must close with evidence.
5. **Tom Searcy & Henry DeVries, *How to Win Big Business: How to Sell Multi-Million Dollar Contracts***. Defines the relationship-deficit principle the skill encodes in the late-entry penalty: "If you didn't help write the RFP, you're column fodder." The skill's −15% late-entry factor comes from this canon.
6. **Strategic Proposals (proposal-management consultancy) — published research and benchmarks (strategicproposals.com)**. Quantifies the evaluator-rubric gap: respondents who don't replicate the evaluator's scoring weights in their response structure lose 20-30 percentage points of available score regardless of content quality.
7. **Larry Newman, "The Shipley Method"** — the methodology articulation that anchors *Proposal Guide v6*. Defines the 7-step proposal-development process (kickoff → blue team → pink team → red team → gold team → submission → debrief) and the color-team review discipline.
8. **CapturePlanning.com / FederalProposalLibrary** — community-maintained resources synthesizing Shipley + federal-acquisition discipline. Useful complement for government RFP profile tuning in `winrate_predictor.py --profile government`.
FILE:scripts/response_drafter.py
#!/usr/bin/env python3
"""response_drafter.py - Build a Shipley-method proof-point matrix + GAP audit + win-theme injection.
Stdlib only. Deterministic logic. NEVER invents claims to fill GAP requirements.
Inputs (JSON):
{
"rfp_requirements": [...] OR "rfp_requirements_path": "parsed.json"
"proof_points_library": [
{
"name": "...",
"type": "case_study|cert|customer_quote|technical_attestation|benchmark",
"requirement_match_tags": ["soc2", "saml", "aws", ...],
"verifiable_source": "..."
}, ...
],
"win_themes": ["operational simplicity", "financial-services depth", ...]
}
For each requirement:
- Tokenize the requirement text (lowercase, strip punctuation, dedupe, drop stopwords).
- For each proof point, intersect proof.requirement_match_tags with requirement tokens.
- If 2+ tag matches AND proof.type in {case_study, cert, technical_attestation, benchmark}
-> STRONG
- If 1 tag match OR proof.type in {customer_quote}
-> PARTIAL
- If 0 matches
-> GAP
For each win-theme: count how many requirements it threads through.
Theme appearing in <2 requirements -> flag as "DECORATIVE", not strategic.
Output: response-draft markdown (or JSON) with:
- Compliance matrix (every requirement -> proof + match level)
- GAP audit (explicit, no inventing)
- Win-theme coverage report
Usage:
python response_drafter.py --sample
python response_drafter.py --input draft_input.json --output markdown
python response_drafter.py --input draft_input.json --output json
"""
from __future__ import annotations
import argparse
import json
import re
import sys
from collections import Counter
from pathlib import Path
from typing import Any
STOPWORDS = {
"the", "a", "an", "is", "are", "was", "were", "be", "been", "being",
"and", "or", "but", "of", "to", "in", "on", "at", "for", "with", "by",
"must", "shall", "should", "may", "will", "would", "could",
"vendor", "vendors", "platform", "provide", "provides", "support", "supports",
"this", "that", "these", "those", "it", "its", "our", "your",
"required", "mandatory", "optional", "preferred", "desired",
"from", "as", "if", "than", "then", "do", "does", "did",
"have", "has", "had",
}
STRONG_PROOF_TYPES = {"case_study", "cert", "technical_attestation", "benchmark"}
PARTIAL_PROOF_TYPES = {"customer_quote"}
SAMPLE_INPUT = {
"rfp_requirements": [
{"id": "R001", "section": "Mandatory", "tag": "MANDATORY",
"text": "Vendor must hold SOC 2 Type II certification.", "evidence": {}},
{"id": "R002", "section": "Mandatory", "tag": "MANDATORY",
"text": "Vendor shall provide 24/7 SOC coverage with named on-call rotation.", "evidence": {}},
{"id": "R003", "section": "Mandatory", "tag": "MANDATORY",
"text": "Vendor is required to support SAML 2.0 and SCIM provisioning.", "evidence": {}},
{"id": "R004", "section": "Mandatory", "tag": "MANDATORY",
"text": "The platform must integrate with AWS, GCP, and Azure native logging.", "evidence": {}},
{"id": "R005", "section": "Weighted", "tag": "WEIGHTED",
"text": "Mean Time to Detect benchmarks vs peers.", "evidence": {"points": 25}},
{"id": "R006", "section": "Weighted", "tag": "WEIGHTED",
"text": "Customer references in financial services.", "evidence": {"points": 20}},
{"id": "R007", "section": "Nice-to-Have", "tag": "NICE-TO-HAVE",
"text": "FedRAMP authorization is preferred but not required.", "evidence": {}},
],
"proof_points_library": [
{"name": "SOC 2 Type II report (2026)", "type": "cert",
"requirement_match_tags": ["soc", "2", "type", "ii", "certification"],
"verifiable_source": "https://trust.example.com/soc2-2026.pdf"},
{"name": "24/7 SOC staffing attestation", "type": "technical_attestation",
"requirement_match_tags": ["soc", "24/7", "coverage", "on-call", "rotation"],
"verifiable_source": "internal SecOps runbook v3.2"},
{"name": "SAML/SCIM integration guide", "type": "technical_attestation",
"requirement_match_tags": ["saml", "scim", "provisioning"],
"verifiable_source": "docs.example.com/saml-scim"},
{"name": "AWS/GCP/Azure logging case study (Globex)", "type": "case_study",
"requirement_match_tags": ["aws", "gcp", "azure", "logging", "integrate", "native"],
"verifiable_source": "globex-cs-2025.pdf"},
{"name": "MTTD benchmark vs Gartner peer cohort", "type": "benchmark",
"requirement_match_tags": ["mttd", "mean", "time", "detect", "benchmarks", "peers"],
"verifiable_source": "Gartner MQ supplement 2026"},
{"name": "Financial services customer quote (FNB)", "type": "customer_quote",
"requirement_match_tags": ["financial", "services", "customer", "references"],
"verifiable_source": "FNB CISO quote, approved 2026-03"},
],
"win_themes": [
"operational simplicity at scale",
"financial-services regulatory depth",
"MTTD leadership vs Gartner peer cohort",
"AWS/GCP/Azure native logging without bolt-ons",
],
}
def tokenize(text: str) -> set[str]:
tokens = re.findall(r"[a-zA-Z0-9./]+", text.lower())
return {t for t in tokens if t not in STOPWORDS and len(t) > 1}
def score_match(requirement: dict[str, Any], proof: dict[str, Any]) -> tuple[int, list[str]]:
"""Return (match_count, matched_tags)."""
req_tokens = tokenize(requirement["text"])
matched = [tag for tag in proof.get("requirement_match_tags", []) if tag.lower() in req_tokens]
return len(matched), matched
def assign_proof(requirement: dict[str, Any], library: list[dict[str, Any]]) -> dict[str, Any]:
best_count = 0
best_proof: dict[str, Any] | None = None
best_matched: list[str] = []
for proof in library:
count, matched = score_match(requirement, proof)
if count > best_count:
best_count = count
best_proof = proof
best_matched = matched
if best_proof is None or best_count == 0:
return {"level": "GAP", "proof": None, "matched_tags": []}
if best_count >= 2 and best_proof["type"] in STRONG_PROOF_TYPES:
level = "STRONG"
elif best_count >= 1 and best_proof["type"] in STRONG_PROOF_TYPES:
level = "PARTIAL"
elif best_count >= 1 and best_proof["type"] in PARTIAL_PROOF_TYPES:
level = "PARTIAL"
else:
level = "PARTIAL"
return {"level": level, "proof": best_proof, "matched_tags": best_matched}
def thread_themes(requirements: list[dict[str, Any]], themes: list[str]) -> dict[str, dict[str, Any]]:
"""For each theme, list requirements whose text overlaps theme tokens."""
report: dict[str, dict[str, Any]] = {}
for theme in themes:
theme_tokens = tokenize(theme)
threaded: list[str] = []
for req in requirements:
req_tokens = tokenize(req["text"])
if theme_tokens & req_tokens:
threaded.append(req["id"])
verdict = "STRATEGIC" if len(threaded) >= 2 else "DECORATIVE"
report[theme] = {
"requirement_ids": threaded,
"count": len(threaded),
"verdict": verdict,
}
return report
def build_matrix(payload: dict[str, Any]) -> dict[str, Any]:
if "rfp_requirements_path" in payload and "rfp_requirements" not in payload:
p = Path(payload["rfp_requirements_path"])
loaded = json.loads(p.read_text(encoding="utf-8"))
requirements = loaded.get("requirements", loaded if isinstance(loaded, list) else [])
else:
requirements = payload.get("rfp_requirements", [])
library = payload.get("proof_points_library", [])
themes = payload.get("win_themes", [])
matrix: list[dict[str, Any]] = []
for req in requirements:
assignment = assign_proof(req, library)
matrix.append({
"requirement_id": req["id"],
"tag": req["tag"],
"section": req.get("section", ""),
"text": req["text"],
"match_level": assignment["level"],
"proof_name": assignment["proof"]["name"] if assignment["proof"] else None,
"proof_type": assignment["proof"]["type"] if assignment["proof"] else None,
"verifiable_source": assignment["proof"]["verifiable_source"] if assignment["proof"] else None,
"matched_tags": assignment["matched_tags"],
})
level_counts = Counter(row["match_level"] for row in matrix)
mandatory_gaps = [row for row in matrix if row["tag"] == "MANDATORY" and row["match_level"] == "GAP"]
theme_report = thread_themes(requirements, themes)
return {
"matrix": matrix,
"level_counts": dict(level_counts),
"mandatory_gap_count": len(mandatory_gaps),
"mandatory_gaps": mandatory_gaps,
"win_theme_report": theme_report,
"requirement_total": len(requirements),
}
def render_markdown(result: dict[str, Any]) -> str:
out: list[str] = []
out.append("# RFP Response Draft — Proof-Point Matrix\n")
total = result["requirement_total"]
counts = result["level_counts"]
out.append(f"**Requirements:** {total}")
if total > 0:
strong = counts.get("STRONG", 0)
partial = counts.get("PARTIAL", 0)
gap = counts.get("GAP", 0)
out.append(f"**STRONG:** {strong} ({100*strong/total:.0f}%) | "
f"**PARTIAL:** {partial} ({100*partial/total:.0f}%) | "
f"**GAP:** {gap} ({100*gap/total:.0f}%)")
out.append(f"\n**MANDATORY GAPs:** {result['mandatory_gap_count']} "
"(LEADERSHIP DECISION REQUIRED — close gap, partner-bid, or no-bid)\n")
out.append("## Compliance matrix\n")
out.append("| Req | Tag | Match | Proof | Source |")
out.append("|---|---|---|---|---|")
for row in result["matrix"]:
proof = row["proof_name"] or "**(NO PROOF — GAP)**"
source = row["verifiable_source"] or "—"
out.append(f"| {row['requirement_id']} | {row['tag']} | {row['match_level']} | {proof} | {source} |")
out.append("")
if result["mandatory_gaps"]:
out.append("## GAP audit (MANDATORY requirements without proof)\n")
out.append("> HARD RULE: do NOT invent claims for these. Leadership decides: "
"close the gap pre-submission, partner-bid, or no-bid.\n")
for row in result["mandatory_gaps"]:
out.append(f"- **{row['requirement_id']}** ({row['section']}): {row['text']}")
out.append("")
out.append("## Win-theme coverage\n")
for theme, info in result["win_theme_report"].items():
ids = ", ".join(info["requirement_ids"]) or "(none)"
out.append(f"- **{theme}** — threads through {info['count']} req(s): {ids} → **{info['verdict']}**")
out.append("")
decorative = [t for t, info in result["win_theme_report"].items() if info["verdict"] == "DECORATIVE"]
if decorative:
out.append("### Decorative themes (flagged)\n")
out.append("These themes appear in <2 requirements and are decorative, not strategic. "
"Either remove or strengthen so they thread across multiple sections.\n")
for t in decorative:
out.append(f"- {t}")
out.append("")
return "\n".join(out) + "\n"
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description="Build proof-point matrix + GAP audit + win-theme report.")
parser.add_argument("--input", help="Path to draft-input JSON.")
parser.add_argument("--output", choices=["json", "markdown"], default="markdown")
parser.add_argument("--sample", action="store_true", help="Use built-in synthetic input.")
args = parser.parse_args(argv)
if args.sample:
payload = SAMPLE_INPUT
elif args.input:
path = Path(args.input)
if not path.exists():
print(f"ERROR: input file not found: {args.input}", file=sys.stderr)
return 1
payload = json.loads(path.read_text(encoding="utf-8"))
else:
parser.print_help()
return 0
result = build_matrix(payload)
if args.output == "json":
print(json.dumps(result, indent=2))
else:
print(render_markdown(result))
return 0
if __name__ == "__main__":
sys.exit(main())
FILE:scripts/rfp_parser.py
#!/usr/bin/env python3
"""rfp_parser.py - Parse an RFP / RFI / RFQ / security questionnaire into structured requirements.
Stdlib only. Regex + cue-word heuristics. No NLP libraries, no LLM calls.
The parser:
1. Splits the document into sections (executive summary, technical requirements,
security questionnaire, commercial terms, timeline, etc.) using common heading
patterns.
2. Extracts requirements as discrete numbered / bulleted / "must/shall/should" lines.
3. Tags each requirement MANDATORY / WEIGHTED / NICE-TO-HAVE based on cue words:
MANDATORY - must, shall, required, mandatory, "is required to"
WEIGHTED - should, weighted scoring numbers present (e.g., "[20 points]"),
"evaluation criteria", "scored"
NICE-TO-HAVE - may, preferred, desired, nice-to-have, optional
4. Captures disclosed scoring criteria (lines that look like "X points" / "X%" weights).
5. Captures submission deadline + format requirements (regex on common date patterns
+ "format" / "submission" cue words).
Usage:
python rfp_parser.py --sample
python rfp_parser.py --input rfp.md --output json
python rfp_parser.py --input rfp.md --output markdown
"""
from __future__ import annotations
import argparse
import json
import re
import sys
from collections import Counter
from pathlib import Path
from typing import Any
SAMPLE_RFP = """\
# RFP-2026-CLOUD-SECURITY-007
## 1. Executive Summary
Acme Holdings is seeking a cloud security platform vendor.
Total contract value: $1.5M over 3 years.
Submission deadline: 2026-06-14.
Format: PDF, max 80 pages, 11pt font minimum.
## 2. Mandatory Requirements
2.1 Vendor must hold SOC 2 Type II certification.
2.2 Vendor shall provide 24/7 SOC coverage with named on-call rotation.
2.3 Vendor is required to support SAML 2.0 and SCIM provisioning.
2.4 The platform must integrate with AWS, GCP, and Azure native logging.
2.5 Vendor shall meet a 99.9% platform uptime SLA.
## 3. Weighted Requirements (100 points total)
3.1 Threat detection coverage breadth [30 points]
3.2 Mean Time to Detect (MTTD) benchmarks vs peers [25 points]
3.3 Customer references in financial services [20 points]
3.4 Implementation timeline shorter than 90 days [15 points]
3.5 Quality of executive briefing materials [10 points]
The platform should support custom detection rule authoring.
Vendor should provide quarterly threat intelligence reports.
## 4. Nice-to-Have Capabilities
4.1 FedRAMP authorization is preferred but not required.
4.2 ISO 27001 certification is desired.
4.3 The platform may offer AI-assisted triage capabilities.
4.4 Vendor support for on-premises deployment is optional.
## 5. Commercial Terms
Multi-year discount expected. Payment terms NET-45.
## 6. Submission Format
Responses must be submitted via the procurement portal by 2026-06-14 17:00 ET.
Late submissions will not be accepted.
"""
MANDATORY_CUES = re.compile(
r"\b(must|shall|required|mandatory|is required to|are required to|will be required)\b",
re.IGNORECASE,
)
WEIGHTED_CUES = re.compile(
r"\b(should|evaluation criteria|scored|weighted|preferred)\b",
re.IGNORECASE,
)
NICE_CUES = re.compile(
r"\b(may|preferred but not required|desired|nice[- ]to[- ]have|optional|is desired)\b",
re.IGNORECASE,
)
POINTS_PATTERN = re.compile(r"\[(\d+)\s*(?:points?|pts?|%)\]", re.IGNORECASE)
DEADLINE_PATTERN = re.compile(
r"(deadline|due|submission|submit by|responses? (?:are )?due)\s*[:\-]?\s*"
r"(\d{4}[-/]\d{1,2}[-/]\d{1,2}|\d{1,2}[-/]\d{1,2}[-/]\d{2,4})",
re.IGNORECASE,
)
FORMAT_PATTERN = re.compile(
r"\b(format|page limit|max(?:imum)? \d+ pages?|font|portal|pdf|word|submitted via)\b",
re.IGNORECASE,
)
HEADING_PATTERN = re.compile(r"^(#{1,3})\s+(.+?)\s*$")
REQ_LINE_PATTERN = re.compile(r"^\s*(\d+\.\d+|\d+\)|-|\*)\s+(.+?)\s*$")
def classify_requirement(text: str) -> tuple[str, dict[str, Any]]:
"""Return (tag, evidence_dict). Precedence: NICE > MANDATORY > WEIGHTED.
NICE-TO-HAVE is checked first because phrases like "preferred but not required"
contain the word "required" but are NOT mandatory.
"""
evidence: dict[str, Any] = {"matched_cues": []}
nice_match = NICE_CUES.search(text)
if nice_match:
evidence["matched_cues"].append(nice_match.group(0).lower())
return "NICE-TO-HAVE", evidence
mand_match = MANDATORY_CUES.search(text)
if mand_match:
evidence["matched_cues"].append(mand_match.group(0).lower())
return "MANDATORY", evidence
points_match = POINTS_PATTERN.search(text)
if points_match:
evidence["points"] = int(points_match.group(1))
evidence["matched_cues"].append(f"[{points_match.group(1)} points]")
return "WEIGHTED", evidence
weight_match = WEIGHTED_CUES.search(text)
if weight_match:
evidence["matched_cues"].append(weight_match.group(0).lower())
return "WEIGHTED", evidence
return "UNCLASSIFIED", evidence
def split_sections(text: str) -> list[dict[str, Any]]:
"""Split document into sections by markdown headings."""
sections: list[dict[str, Any]] = []
current = {"heading": "(preamble)", "level": 0, "body": []}
for line in text.splitlines():
m = HEADING_PATTERN.match(line)
if m:
if current["body"] or current["heading"] != "(preamble)":
sections.append(current)
current = {
"heading": m.group(2).strip(),
"level": len(m.group(1)),
"body": [],
}
else:
current["body"].append(line)
sections.append(current)
return [s for s in sections if s["body"] or s["heading"] != "(preamble)"]
def extract_requirements(sections: list[dict[str, Any]]) -> list[dict[str, Any]]:
"""Extract individual requirements from sections."""
reqs: list[dict[str, Any]] = []
req_counter = 0
for sec in sections:
section_label = sec["heading"]
for raw_line in sec["body"]:
line = raw_line.strip()
if not line or line.startswith("#"):
continue
m = REQ_LINE_PATTERN.match(raw_line)
text = m.group(2).strip() if m else line
# Only count lines that contain at least one classification cue or a points tag.
if not (
MANDATORY_CUES.search(text)
or WEIGHTED_CUES.search(text)
or NICE_CUES.search(text)
or POINTS_PATTERN.search(text)
):
continue
tag, evidence = classify_requirement(text)
req_counter += 1
reqs.append({
"id": f"R{req_counter:03d}",
"section": section_label,
"text": text,
"tag": tag,
"evidence": evidence,
})
return reqs
def extract_scoring(text: str) -> list[dict[str, Any]]:
"""Find lines with explicit point weights."""
scoring: list[dict[str, Any]] = []
for line in text.splitlines():
m = POINTS_PATTERN.search(line)
if m:
scoring.append({"weight": int(m.group(1)), "line": line.strip()})
return scoring
def extract_deadline(text: str) -> str | None:
m = DEADLINE_PATTERN.search(text)
return m.group(2) if m else None
def extract_format_notes(text: str) -> list[str]:
notes: list[str] = []
for line in text.splitlines():
if FORMAT_PATTERN.search(line) and len(line.strip()) < 200:
notes.append(line.strip())
# Dedupe while preserving order.
seen: set[str] = set()
out: list[str] = []
for n in notes:
if n not in seen:
seen.add(n)
out.append(n)
return out
def parse(text: str) -> dict[str, Any]:
sections = split_sections(text)
reqs = extract_requirements(sections)
tag_counts = Counter(r["tag"] for r in reqs)
return {
"section_count": len(sections),
"sections": [{"heading": s["heading"], "level": s["level"]} for s in sections],
"requirement_count": len(reqs),
"tag_breakdown": dict(tag_counts),
"requirements": reqs,
"scoring_criteria": extract_scoring(text),
"deadline": extract_deadline(text),
"format_notes": extract_format_notes(text),
}
def render_markdown(parsed: dict[str, Any]) -> str:
out: list[str] = []
out.append("# RFP Parse Report\n")
out.append(f"**Sections detected:** {parsed['section_count']}")
out.append(f"**Requirements detected:** {parsed['requirement_count']}")
out.append(f"**Deadline:** {parsed['deadline'] or '(not detected)'}\n")
out.append("## Requirement breakdown\n")
for tag, count in parsed["tag_breakdown"].items():
out.append(f"- {tag}: {count}")
out.append("\n## Requirements\n")
for r in parsed["requirements"]:
out.append(f"### {r['id']} — [{r['tag']}]")
out.append(f"**Section:** {r['section']}")
out.append(f"**Text:** {r['text']}")
if r["evidence"].get("points"):
out.append(f"**Points:** {r['evidence']['points']}")
out.append(f"**Matched cues:** {', '.join(r['evidence']['matched_cues']) or '(none)'}")
out.append("")
out.append("## Scoring criteria detected\n")
if parsed["scoring_criteria"]:
for s in parsed["scoring_criteria"]:
out.append(f"- [{s['weight']} pts] {s['line']}")
else:
out.append("(none disclosed)")
out.append("\n## Format notes\n")
if parsed["format_notes"]:
for n in parsed["format_notes"]:
out.append(f"- {n}")
else:
out.append("(none detected)")
return "\n".join(out) + "\n"
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description="Parse an RFP into structured requirements.")
parser.add_argument("--input", help="Path to RFP markdown/text file.")
parser.add_argument("--output", choices=["json", "markdown"], default="markdown")
parser.add_argument("--sample", action="store_true", help="Use built-in synthetic RFP.")
args = parser.parse_args(argv)
if args.sample:
text = SAMPLE_RFP
elif args.input:
path = Path(args.input)
if not path.exists():
print(f"ERROR: input file not found: {args.input}", file=sys.stderr)
return 1
text = path.read_text(encoding="utf-8")
else:
parser.print_help()
return 0
parsed = parse(text)
if args.output == "json":
print(json.dumps(parsed, indent=2))
else:
print(render_markdown(parsed))
return 0
if __name__ == "__main__":
sys.exit(main())
FILE:scripts/winrate_predictor.py
#!/usr/bin/env python3
"""winrate_predictor.py - Shipley-derived winrate estimate + bid/no-bid verdict.
Stdlib only. Deterministic factor model.
Inputs (JSON):
{
"requirement_fit_pct_strong": 60.0, # % of requirements matched at STRONG
"requirement_fit_pct_partial": 30.0, # % at PARTIAL
"requirement_fit_pct_gap": 10.0, # % at GAP
"incumbent_advantage": "none|weak|strong",
"relationship_strength": "cold|warm|champion",
"decision_criteria_alignment_pct": 75.0,
"late_entry": true|false, # entered after RFP issued, no prior engagement
"competitor_count": 3,
"deal_size_vs_avg": "below|at|above"
}
Factor model (Shipley-derived, opinionated, industry-tunable):
base = 0.03 * fit_strong - 0.02 * fit_gap + 0.005 * fit_partial
(STRONG counts 3x, PARTIAL 1x, GAP -2x in Shipley capture math;
encoded here as a linear bounded score centered to produce a
baseline win-rate in the 5-80% range)
Incumbent penalty:
none -> 0
weak -> -10
strong -> -30
Relationship lift:
cold -> 0
warm -> +10
champion -> +25
Late entry: -15 if true, 0 otherwise
Decision-criteria alignment:
pct >= 80 -> +10
50 <= pct < 80 -> 0
pct < 50 -> -10
Competitor count:
1 (you're sole vendor) -> +20
2 -> +5
3 -> 0
4-5 -> -10
6+ -> -20
Deal size vs avg:
at -> 0
above -> -5 (bigger deals attract more scrutiny + more competitors)
below -> 0
Industry profile shifts the base rate (the structural reality that government RFPs
are harder than enterprise software):
enterprise-software: base_shift = +5
saas: base_shift = 0
services: base_shift = -5
government: base_shift = -15
healthcare: base_shift = -10
Verdict:
< 20% -> NO-BID
20-34% -> PARTNER-BID (find a partner who closes the structural gap)
35-100% -> BID
Confidence band: +/- 12 percentage points (wider on small-sample factor inputs).
Usage:
python winrate_predictor.py --sample
python winrate_predictor.py --input deal.json --profile enterprise-software
python winrate_predictor.py --input deal.json --profile government --output json
"""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
from typing import Any
PROFILES: dict[str, dict[str, float]] = {
"enterprise-software": {"base_shift": 5.0},
"saas": {"base_shift": 0.0},
"services": {"base_shift": -5.0},
"government": {"base_shift": -15.0},
"healthcare": {"base_shift": -10.0},
}
SAMPLE_INPUT = {
"requirement_fit_pct_strong": 60.0,
"requirement_fit_pct_partial": 25.0,
"requirement_fit_pct_gap": 15.0,
"incumbent_advantage": "weak",
"relationship_strength": "warm",
"decision_criteria_alignment_pct": 75.0,
"late_entry": False,
"competitor_count": 3,
"deal_size_vs_avg": "at",
}
def incumbent_factor(level: str) -> float:
return {"none": 0.0, "weak": -10.0, "strong": -30.0}.get(level, 0.0)
def relationship_factor(level: str) -> float:
return {"cold": 0.0, "warm": 10.0, "champion": 25.0}.get(level, 0.0)
def alignment_factor(pct: float) -> float:
if pct >= 80.0:
return 10.0
if pct < 50.0:
return -10.0
return 0.0
def competitor_factor(count: int) -> float:
if count <= 1:
return 20.0
if count == 2:
return 5.0
if count == 3:
return 0.0
if count <= 5:
return -10.0
return -20.0
def deal_size_factor(size: str) -> float:
return {"at": 0.0, "above": -5.0, "below": 0.0}.get(size, 0.0)
def base_from_fit(strong: float, partial: float, gap: float) -> float:
"""STRONG 3x, PARTIAL 1x, GAP -2x; calibrated to land in 5-80% range at extremes."""
raw = 0.03 * strong * 3.0 + 0.01 * partial - 0.02 * gap * 2.0
# Center to a sensible baseline. raw of 9 = 100% strong -> ~45 baseline.
return max(0.0, min(80.0, raw * 5.0))
def predict(payload: dict[str, Any], profile: str) -> dict[str, Any]:
prof = PROFILES.get(profile, PROFILES["saas"])
strong = float(payload.get("requirement_fit_pct_strong", 0.0))
partial = float(payload.get("requirement_fit_pct_partial", 0.0))
gap = float(payload.get("requirement_fit_pct_gap", 0.0))
base = base_from_fit(strong, partial, gap)
inc = incumbent_factor(payload.get("incumbent_advantage", "none"))
rel = relationship_factor(payload.get("relationship_strength", "cold"))
late = -15.0 if payload.get("late_entry", False) else 0.0
align = alignment_factor(float(payload.get("decision_criteria_alignment_pct", 50.0)))
comp = competitor_factor(int(payload.get("competitor_count", 3)))
size = deal_size_factor(payload.get("deal_size_vs_avg", "at"))
estimate = base + inc + rel + late + align + comp + size + prof["base_shift"]
estimate = max(0.0, min(100.0, estimate))
band_lo = max(0.0, estimate - 12.0)
band_hi = min(100.0, estimate + 12.0)
if estimate < 20.0:
verdict = "NO-BID"
rationale = ("Estimated winrate below the 20% no-bid threshold. "
"Pursuing this RFP burns sales-engineering capacity without "
"a credible path to win.")
elif estimate < 35.0:
verdict = "PARTNER-BID"
rationale = ("Estimate in the 20-34% band. Bid only with a partner who closes "
"the structural gap (incumbent, late-entry, MANDATORY-GAP, or "
"regulatory-fit deficit). Solo bid not recommended.")
else:
verdict = "BID"
rationale = ("Estimate above 35%. Pursue with full Shipley capture discipline: "
"win-themes laddered across requirements, MANDATORY GAPs closed pre-submission, "
"proof-points sourced, executive sponsor named.")
return {
"profile": profile,
"winrate_estimate_pct": round(estimate, 1),
"confidence_band_pct": [round(band_lo, 1), round(band_hi, 1)],
"verdict": verdict,
"rationale": rationale,
"factor_breakdown": {
"base_from_fit": round(base, 1),
"incumbent_advantage": round(inc, 1),
"relationship_strength": round(rel, 1),
"late_entry": round(late, 1),
"decision_criteria_alignment": round(align, 1),
"competitor_count": round(comp, 1),
"deal_size_vs_avg": round(size, 1),
"industry_profile_shift": round(prof["base_shift"], 1),
},
}
def render_markdown(result: dict[str, Any]) -> str:
out: list[str] = []
out.append("# Shipley-Derived Winrate Estimate\n")
out.append(f"**Profile:** {result['profile']}")
band = result["confidence_band_pct"]
out.append(f"**Estimate:** {result['winrate_estimate_pct']}% (band: {band[0]}% – {band[1]}%)")
out.append(f"**Verdict:** **{result['verdict']}**\n")
out.append(f"> {result['rationale']}\n")
out.append("## Factor breakdown\n")
out.append("| Factor | Contribution (pp) |")
out.append("|---|---|")
for k, v in result["factor_breakdown"].items():
sign = "+" if v >= 0 else ""
out.append(f"| {k} | {sign}{v} |")
out.append("")
out.append("## Reading the estimate\n")
out.append("- Estimate is **directional**, not an oracle. Treat the band as the honest range.")
out.append("- A high score does NOT override a MANDATORY GAP — close the gap or no-bid.")
out.append("- A low score with a champion + named executive sponsor can be reconsidered, "
"but document the rationale before committing pursuit budget.")
return "\n".join(out) + "\n"
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(
description="Shipley-derived winrate estimate + bid/no-bid verdict."
)
parser.add_argument("--input", help="Path to deal-context JSON.")
parser.add_argument("--profile", choices=list(PROFILES.keys()), default="saas",
help="Industry profile (default: saas).")
parser.add_argument("--output", choices=["json", "markdown"], default="markdown")
parser.add_argument("--sample", action="store_true", help="Use built-in synthetic input.")
args = parser.parse_args(argv)
if args.sample:
payload = SAMPLE_INPUT
elif args.input:
path = Path(args.input)
if not path.exists():
print(f"ERROR: input file not found: {args.input}", file=sys.stderr)
return 1
payload = json.loads(path.read_text(encoding="utf-8"))
else:
parser.print_help()
return 0
result = predict(payload, args.profile)
if args.output == "json":
print(json.dumps(result, indent=2))
else:
print(render_markdown(result))
return 0
if __name__ == "__main__":
sys.exit(main())
Soạn nội dung lộ trình, ghi chú phát hành, changelog và cập nhật cho các bên liên quan, điều chỉnh theo lãnh đạo, kỹ thuật và khách hàng.
---
name: roadmap-communicator
description: Use when preparing roadmap narratives, release notes, changelogs, or stakeholder updates tailored for executives, engineering teams, and customers.
---
# Roadmap Communicator
Create clear roadmap communication artifacts for internal and external stakeholders.
## When To Use
Use this skill for:
- Building roadmap presentations in different formats
- Writing stakeholder updates (board, engineering, customers)
- Producing release notes (user-facing and internal)
- Generating changelogs from git history
- Structuring feature announcements
## Roadmap Formats
1. Now / Next / Later
- Best for uncertainty and strategic flexibility.
- Communicate direction without false precision.
2. Timeline roadmap
- Best for fixed-date commitments and launch coordination.
- Requires active risk and dependency management.
3. Theme-based roadmap
- Best for outcome-led planning and cross-team alignment.
- Groups initiatives by problem space or strategic objective.
See `references/roadmap-templates.md` for templates.
## Stakeholder Update Patterns
### Board / Executive
- Outcome and risk oriented
- Focus on progress against strategic goals
- Highlight trade-offs and required decisions
### Engineering
- Scope, dependencies, and sequencing clarity
- Status, blockers, and resourcing implications
### Customers
- Value narrative and timing window
- What is available now vs upcoming
- Clear expectation setting
See `references/communication-templates.md` for reusable templates.
## Release Notes Guidance
### User-Facing Release Notes
- Lead with user value, not internal implementation details.
- Group by workflows or user jobs.
- Include migration/behavior changes explicitly.
### Internal Release Notes
- Include technical details, operational impact, and known issues.
- Capture rollout plan, rollback criteria, and monitoring notes.
## Changelog Generation
Use:
```bash
python3 scripts/changelog_generator.py --from v1.0.0 --to HEAD
```
Features:
- Reads git log range
- Parses conventional commit prefixes
- Groups entries by type (`feat`, `fix`, `chore`, etc.)
- Outputs markdown or plain text
## Feature Announcement Framework
1. Problem context
2. What changed
3. Why it matters
4. Who benefits most
5. How to get started
6. Call to action and feedback channel
## Communication Quality Checklist
- [ ] Audience-specific framing is explicit.
- [ ] Outcomes and trade-offs are clear.
- [ ] Terminology is consistent across artifacts.
- [ ] Risks and dependencies are not hidden.
- [ ] Next actions and owners are specified.
FILE:references/communication-templates.md
# Communication Templates
## Stakeholder Update Email
Subject: Product roadmap update - [Period]
Hi [Audience],
Here is the [weekly/monthly/quarterly] product update.
- Progress:
- KPI movement:
- Risks/blockers:
- Decisions needed:
- Next period focus:
Thanks,
[Owner]
## User-Facing Release Notes Template
# Release [Version/Date]
## Highlights
- [User value outcome]
## New
- [Feature + benefit]
## Improved
- [Improvement + impact]
## Fixed
- [Issue + user-facing resolution]
## Known Limitations
- [If applicable]
## Internal Release Notes Template
# Internal Release [Version/Date]
## Scope
- Included workstreams and commit range
## Operational Notes
- Rollout plan
- Monitoring checks
- Rollback criteria
## Risks
- Known issues and mitigations
## Feature Announcement Template
Title: [Outcome-focused headline]
1. The problem:
2. The new capability:
3. Why this matters:
4. Who should use it:
5. How to start:
6. Feedback channel:
FILE:references/roadmap-templates.md
# Roadmap Templates
## Now / Next / Later Template
### Now (0-1 quarter)
- Committed initiatives in active execution
- Success metrics and owners
- Dependencies and known risks
### Next (1-2 quarters)
- Prioritized bets with confidence levels
- Discovery items needed before commit
- Resource assumptions
### Later (2+ quarters)
- Strategic themes and directional intent
- Explicitly marked as non-commitment
## Quarterly Roadmap Template
| Quarter | Theme | Key Initiatives | Success Metrics | Risks |
|---|---|---|---|---|
| Q1 | | | | |
| Q2 | | | | |
| Q3 | | | | |
| Q4 | | | | |
## Theme-Based Roadmap Template
| Theme | Problem Statement | Initiatives | KPI Link | Owner |
|---|---|---|---|---|
| Activation | | | | |
| Retention | | | | |
| Expansion | | | | |
## OKR-Aligned Roadmap Template
| Objective | Key Result | Initiative | Milestone | Team |
|---|---|---|---|---|
| | | | | |
Guideline:
- Every initiative should map to an objective or key result.
- Mark items without alignment as candidate de-scope.
FILE:scripts/changelog_generator.py
#!/usr/bin/env python3
"""Generate changelog sections from git log or piped commit messages using conventional commit prefixes."""
import argparse
import shutil
import subprocess
import sys
from collections import defaultdict
SECTIONS = {
"feat": "Features",
"fix": "Fixes",
"docs": "Documentation",
"refactor": "Refactors",
"test": "Tests",
"chore": "Chores",
"perf": "Performance",
"ci": "CI",
"build": "Build",
"style": "Style",
"revert": "Reverts",
}
DEMO_COMMITS = [
"feat: add user dashboard with analytics widgets",
"feat: implement dark mode toggle",
"fix: resolve crash on empty CSV import",
"fix: correct timezone offset in calendar view",
"docs: update API reference for v2 endpoints",
"refactor: extract shared validation into utils module",
"chore: bump dependencies to latest patch versions",
"perf: optimize database queries for user listing",
]
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Generate changelog from git commits or piped input.",
epilog="Examples:\n"
" %(prog)s --from v1.0.0 --to HEAD\n"
" git log --pretty=format:%%s v1.0..HEAD | %(prog)s --stdin\n"
" %(prog)s --demo\n",
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument("--from", dest="from_ref", default="HEAD~50",
help="Start ref for git log (default: HEAD~50)")
parser.add_argument("--to", dest="to_ref", default="HEAD",
help="End ref for git log (default: HEAD)")
parser.add_argument("--format", choices=["markdown", "text"], default="markdown",
help="Output format (default: markdown)")
parser.add_argument("--stdin", action="store_true",
help="Read commit subjects from stdin instead of git log")
parser.add_argument("--demo", action="store_true",
help="Run with sample data (no git required)")
return parser.parse_args()
def get_git_log(from_ref: str, to_ref: str) -> list[str]:
"""Get commit subjects from git log. Requires git on PATH and a git repo."""
if not shutil.which("git"):
print("Error: git not found on PATH. Use --stdin or --demo instead.", file=sys.stderr)
sys.exit(1)
commit_range = f"{from_ref}..{to_ref}"
cmd = ["git", "log", "--pretty=format:%s", commit_range]
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
except subprocess.TimeoutExpired:
print("Error: git log timed out.", file=sys.stderr)
sys.exit(1)
if result.returncode != 0:
print(f"Error: git log failed: {result.stderr.strip()}", file=sys.stderr)
sys.exit(1)
lines = [line.strip() for line in result.stdout.splitlines() if line.strip()]
return lines
def read_stdin() -> list[str]:
"""Read commit subjects from stdin, one per line."""
return [line.strip() for line in sys.stdin if line.strip()]
def group_commits(subjects: list[str]) -> dict[str, list[str]]:
grouped: dict[str, list[str]] = defaultdict(list)
for subject in subjects:
commit_type = "other"
for prefix in SECTIONS:
if subject.startswith(f"{prefix}:") or subject.startswith(f"{prefix}("):
commit_type = prefix
break
grouped[commit_type].append(subject)
return grouped
def render_markdown(grouped: dict[str, list[str]]) -> str:
out = ["# Changelog", ""]
ordered_types = list(SECTIONS.keys()) + ["other"]
for commit_type in ordered_types:
commits = grouped.get(commit_type, [])
if not commits:
continue
header = SECTIONS.get(commit_type, "Other")
out.append(f"## {header}")
for item in commits:
out.append(f"- {item}")
out.append("")
return "\n".join(out).rstrip() + "\n"
def render_text(grouped: dict[str, list[str]]) -> str:
out: list[str] = []
ordered_types = list(SECTIONS.keys()) + ["other"]
for commit_type in ordered_types:
commits = grouped.get(commit_type, [])
if not commits:
continue
header = SECTIONS.get(commit_type, "Other")
out.append(header.upper())
for item in commits:
out.append(f"* {item}")
out.append("")
return "\n".join(out).rstrip() + "\n"
def main() -> int:
args = parse_args()
if args.demo:
subjects = DEMO_COMMITS
elif args.stdin:
subjects = read_stdin()
else:
subjects = get_git_log(args.from_ref, args.to_ref)
if not subjects:
print("No commits found.", file=sys.stderr)
return 0
grouped = group_commits(subjects)
if args.format == "markdown":
print(render_markdown(grouped), end="")
else:
print(render_text(grouped), end="")
return 0
if __name__ == "__main__":
raise SystemExit(main())
Tạo runbook vận hành từ tên dịch vụ, gồm triển khai, xử lý sự cố, bảo trì và rollback, tùy chỉnh theo môi trường.
---
name: "runbook-generator"
description: "Generate operational runbooks from a service name — deployment, incident response, maintenance, and rollback workflows. Templated structure customizable per environment. Use when documenting on-call procedures for a new service, standardizing incident response across teams, or producing runbooks before launching to production."
---
# Runbook Generator
**Tier:** POWERFUL
**Category:** Engineering
**Domain:** DevOps / Site Reliability Engineering
---
## Overview
Generate operational runbooks quickly from a service name, then customize for deployment, incident response, maintenance, and rollback workflows.
## Core Capabilities
- Runbook skeleton generation from a CLI
- Standard sections for start/stop/health/rollback
- Structured escalation and incident handling placeholders
- Reference templates for deployment and incident playbooks
---
## When to Use
- A service has no runbook and needs a baseline immediately
- Existing runbooks are inconsistent across teams
- On-call onboarding requires standardized operations docs
- You need repeatable runbook scaffolding for new services
---
## Quick Start
```bash
# Print runbook to stdout
python3 scripts/runbook_generator.py payments-api
# Write runbook file
python3 scripts/runbook_generator.py payments-api --owner platform --output docs/runbooks/payments-api.md
```
---
## Recommended Workflow
1. Generate the initial skeleton with `scripts/runbook_generator.py`.
2. Fill in service-specific commands and URLs.
3. Add verification checks and rollback triggers.
4. Dry-run in staging.
5. Store runbook in version control near service code.
---
## Reference Docs
- `references/runbook-templates.md`
---
## Common Pitfalls
- Missing rollback triggers or rollback commands
- Steps without expected output checks
- Stale ownership/escalation contacts
- Runbooks never tested outside of incidents
## Best Practices
1. Keep every command copy-pasteable.
2. Include health checks after every critical step.
3. Validate runbooks on a fixed review cadence.
4. Update runbook content after incidents and postmortems.
FILE:references/runbook-templates.md
# Runbook Templates
## Deployment Runbook Template
- Pre-deployment checks
- Deploy steps with expected output
- Smoke tests
- Rollback plan with explicit triggers
- Escalation and communication notes
## Incident Response Template
- Triage phase (first 5 minutes)
- Diagnosis phase (logs, metrics, recent deploys)
- Mitigation phase (containment and restoration)
- Resolution and postmortem actions
## Database Maintenance Template
- Backup and restore verification
- Migration sequencing and lock-risk notes
- Vacuum/reindex routines
- Verification queries and performance checks
## Staleness Detection Template
Track referenced config files and update runbooks whenever these change:
- deployment config (`vercel.json`, Helm charts, Terraform)
- CI pipelines (`.github/workflows/*`, `.gitlab-ci.yml`)
- data schema/migration definitions
- service runtime/env configuration
## Quarterly Validation Checklist
1. Execute commands in staging.
2. Validate expected outputs.
3. Test rollback paths.
4. Confirm contact/escalation ownership.
5. Update `Last verified` date.
FILE:scripts/runbook_generator.py
#!/usr/bin/env python3
"""Generate an operational runbook skeleton for a service."""
from __future__ import annotations
import argparse
from datetime import date
from pathlib import Path
def build_runbook(service: str, owner: str, environment: str) -> str:
today = date.today().isoformat()
return f"""# Runbook - {service}
- Service: {service}
- Owner: {owner}
- Environment: {environment}
- Last verified: {today}
## Overview
Describe the service purpose, dependencies, and critical user impact.
## Preconditions
- Access to deployment platform
- Access to logs/metrics
- Access to secret/config manager
## Start Procedure
1. Pull latest config/secrets.
2. Start service process.
3. Confirm process is healthy.
```bash
# Example
# systemctl start {service}
```
## Stop Procedure
1. Drain traffic if applicable.
2. Stop service process.
3. Confirm no active workers remain.
```bash
# Example
# systemctl stop {service}
```
## Health Checks
- HTTP health endpoint
- Dependency connectivity checks
- Error-rate and latency checks
```bash
# Example
# curl -sf https://{service}.example.com/health
```
## Deployment Checklist
1. Verify CI status and artifact integrity.
2. Apply migrations (if required) in safe order.
3. Deploy service revision.
4. Run smoke checks.
5. Observe metrics for 10-15 minutes.
## Rollback
1. Identify last known good release.
2. Re-deploy previous version.
3. Re-run health checks.
4. Communicate rollback status to stakeholders.
```bash
# Example
# deployctl rollback --service {service}
```
## Incident Response
1. Classify severity.
2. Contain user impact.
3. Triage likely failing component.
4. Escalate if SLA risk is high.
## Escalation
- L1: On-call engineer
- L2: Service owner ({owner})
- L3: Platform/Engineering leadership
## Post-Incident
1. Write timeline and root cause.
2. Define corrective actions with owners.
3. Update this runbook with missing steps.
"""
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Generate a markdown runbook skeleton.")
parser.add_argument("service", help="Service name")
parser.add_argument("--owner", default="platform-team", help="Service owner label")
parser.add_argument("--environment", default="production", help="Primary environment")
parser.add_argument("--output", help="Optional output path (prints to stdout if omitted)")
return parser.parse_args()
def main() -> int:
args = parse_args()
markdown = build_runbook(args.service, owner=args.owner, environment=args.environment)
if args.output:
path = Path(args.output)
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(markdown, encoding="utf-8")
print(f"Wrote runbook skeleton to {path}")
else:
print(markdown)
return 0
if __name__ == "__main__":
raise SystemExit(main())
Skill mẫu dùng để tham khảo cấu trúc khi tạo skill mới.
# Sample Text Processor
---
**Name**: sample-text-processor
**Tier**: BASIC
**Category**: Text Processing
**Dependencies**: None (Python Standard Library Only)
**Author**: Claude Skills Engineering Team
**Version**: 1.0.0
**Last Updated**: 2026-02-16
---
## Description
The Sample Text Processor is a simple skill designed to demonstrate the basic structure and functionality expected in the claude-skills ecosystem. This skill provides fundamental text processing capabilities including word counting, character analysis, and basic text transformations.
This skill serves as a reference implementation for BASIC tier requirements and can be used as a template for creating new skills. It demonstrates proper file structure, documentation standards, and implementation patterns that align with ecosystem best practices.
The skill processes text files and provides statistics and transformations in both human-readable and JSON formats, showcasing the dual output requirement for skills in the claude-skills repository.
## Features
### Core Functionality
- **Word Count Analysis**: Count total words, unique words, and word frequency
- **Character Statistics**: Analyze character count, line count, and special characters
- **Text Transformations**: Convert text to uppercase, lowercase, or title case
- **File Processing**: Process single text files or batch process directories
- **Dual Output Formats**: Generate results in both JSON and human-readable formats
### Technical Features
- Command-line interface with comprehensive argument parsing
- Error handling for common file and processing issues
- Progress reporting for batch operations
- Configurable output formatting and verbosity levels
- Cross-platform compatibility with standard library only dependencies
## Usage
### Basic Text Analysis
```bash
python text_processor.py analyze document.txt
python text_processor.py analyze document.txt --output results.json
```
### Text Transformation
```bash
python text_processor.py transform document.txt --mode uppercase
python text_processor.py transform document.txt --mode title --output transformed.txt
```
### Batch Processing
```bash
python text_processor.py batch text_files/ --output results/
python text_processor.py batch text_files/ --format json --output batch_results.json
```
## Examples
### Example 1: Basic Word Count
```bash
$ python text_processor.py analyze sample.txt
=== TEXT ANALYSIS RESULTS ===
File: sample.txt
Total words: 150
Unique words: 85
Total characters: 750
Lines: 12
Most frequent word: "the" (8 occurrences)
```
### Example 2: JSON Output
```bash
$ python text_processor.py analyze sample.txt --format json
{
"file": "sample.txt",
"statistics": {
"total_words": 150,
"unique_words": 85,
"total_characters": 750,
"lines": 12,
"most_frequent": {
"word": "the",
"count": 8
}
}
}
```
### Example 3: Text Transformation
```bash
$ python text_processor.py transform sample.txt --mode title
Original: "hello world from the text processor"
Transformed: "Hello World From The Text Processor"
```
## Installation
This skill requires only Python 3.7 or later with the standard library. No external dependencies are required.
1. Clone or download the skill directory
2. Navigate to the scripts directory
3. Run the text processor directly with Python
```bash
cd scripts/
python text_processor.py --help
```
## Configuration
The text processor supports various configuration options through command-line arguments:
- `--format`: Output format (json, text)
- `--verbose`: Enable verbose output and progress reporting
- `--output`: Specify output file or directory
- `--encoding`: Specify text file encoding (default: utf-8)
## Architecture
The skill follows a simple modular architecture:
- **TextProcessor Class**: Core processing logic and statistics calculation
- **OutputFormatter Class**: Handles dual output format generation
- **FileManager Class**: Manages file I/O operations and batch processing
- **CLI Interface**: Command-line argument parsing and user interaction
## Error Handling
The skill includes comprehensive error handling for:
- File not found or permission errors
- Invalid encoding or corrupted text files
- Memory limitations for very large files
- Output directory creation and write permissions
- Invalid command-line arguments and parameters
## Performance Considerations
- Efficient memory usage for large text files through streaming
- Optimized word counting using dictionary lookups
- Batch processing with progress reporting for large datasets
- Configurable encoding detection for international text
## Contributing
This skill serves as a reference implementation and contributions are welcome to demonstrate best practices:
1. Follow PEP 8 coding standards
2. Include comprehensive docstrings
3. Add test cases with sample data
4. Update documentation for any new features
5. Ensure backward compatibility
## Limitations
As a BASIC tier skill, some advanced features are intentionally omitted:
- Complex text analysis (sentiment, language detection)
- Advanced file format support (PDF, Word documents)
- Database integration or external API calls
- Parallel processing for very large datasets
This skill demonstrates the essential structure and quality standards required for BASIC tier skills in the claude-skills ecosystem while remaining simple and focused on core functionality.
FILE:assets/sample_text.txt
This is a sample text file for testing the text processor skill.
It contains multiple lines of text with various words and punctuation.
The quick brown fox jumps over the lazy dog.
This sentence contains all 26 letters of the English alphabet.
Some additional content:
- Numbers: 123, 456, 789
- Special characters: !@#$%^&*()
- Mixed case: CamelCase, snake_case, PascalCase
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco.
This file serves as a basic test case for:
1. Word counting functionality
2. Character analysis
3. Line counting
4. Text transformations
5. Statistical analysis
The text processor should handle this content correctly and produce
meaningful statistics and transformations for testing purposes.
FILE:assets/test_data.csv
name,age,city,country
John Doe,25,New York,USA
Jane Smith,30,London,UK
Bob Johnson,22,Toronto,Canada
Alice Brown,28,Sydney,Australia
Charlie Wilson,35,Berlin,Germany
This CSV file contains sample data with headers and multiple rows.
It can be used to test the text processor's ability to handle
structured data formats and count words across different content types.
The file includes:
- Header row with column names
- Data rows with mixed text and numbers
- Various city and country names
- Different age values for statistical analysis
FILE:expected_outputs/sample_text_analysis.json
{
"file": "assets/sample_text.txt",
"file_size": 855,
"total_words": 116,
"unique_words": 87,
"total_characters": 855,
"lines": 19,
"average_word_length": 4.7,
"most_frequent": {
"word": "the",
"count": 5
}
}
FILE:README.md
# Sample Text Processor
A basic text processing skill that demonstrates BASIC tier requirements for the claude-skills ecosystem.
## Quick Start
```bash
# Analyze a text file
python scripts/text_processor.py analyze sample.txt
# Get JSON output
python scripts/text_processor.py analyze sample.txt --format json
# Transform text to uppercase
python scripts/text_processor.py transform sample.txt --mode upper
# Process multiple files
python scripts/text_processor.py batch text_files/ --verbose
```
## Features
- Word count and text statistics
- Text transformations (upper, lower, title, reverse)
- Batch file processing
- JSON and human-readable output formats
- Comprehensive error handling
## Requirements
- Python 3.7 or later
- No external dependencies (standard library only)
## Usage
See [SKILL.md](SKILL.md) for comprehensive documentation and examples.
## Testing
Sample data files are provided in the `assets/` directory for testing the functionality.
FILE:references/api-reference.md
# Text Processor API Reference
## Classes
### TextProcessor
Main class for text processing operations.
#### `__init__(self, encoding: str = 'utf-8')`
Initialize the text processor with specified encoding.
**Parameters:**
- `encoding` (str): Character encoding for file operations. Default: 'utf-8'
#### `analyze_text(self, text: str) -> Dict[str, Any]`
Analyze text and return comprehensive statistics.
**Parameters:**
- `text` (str): Text content to analyze
**Returns:**
- `dict`: Statistics including word count, character count, lines, most frequent word
**Example:**
```python
processor = TextProcessor()
stats = processor.analyze_text("Hello world")
# Returns: {'total_words': 2, 'unique_words': 2, ...}
```
#### `transform_text(self, text: str, mode: str) -> str`
Transform text according to specified mode.
**Parameters:**
- `text` (str): Text to transform
- `mode` (str): Transformation mode ('upper', 'lower', 'title', 'reverse')
**Returns:**
- `str`: Transformed text
**Raises:**
- `ValueError`: If mode is not supported
### OutputFormatter
Static methods for output formatting.
#### `format_json(data: Dict[str, Any]) -> str`
Format data as JSON string.
#### `format_human_readable(data: Dict[str, Any]) -> str`
Format data as human-readable text.
### FileManager
Handles file operations and batch processing.
#### `find_text_files(self, directory: str) -> List[str]`
Find all text files in a directory recursively.
**Supported Extensions:**
- .txt
- .md
- .rst
- .csv
- .log
## Command Line Interface
### Commands
#### `analyze`
Analyze text file statistics.
```bash
python text_processor.py analyze <file> [options]
```
#### `transform`
Transform text file content.
```bash
python text_processor.py transform <file> --mode <mode> [options]
```
#### `batch`
Process multiple files in a directory.
```bash
python text_processor.py batch <directory> [options]
```
### Global Options
- `--format {json,text}`: Output format (default: text)
- `--output FILE`: Output file path (default: stdout)
- `--encoding ENCODING`: Text file encoding (default: utf-8)
- `--verbose`: Enable verbose output
## Error Handling
The text processor handles several error conditions:
- **FileNotFoundError**: When input file doesn't exist
- **UnicodeDecodeError**: When file encoding doesn't match specified encoding
- **PermissionError**: When file access is denied
- **ValueError**: When invalid transformation mode is specified
All errors are reported to stderr with descriptive messages.
FILE:scripts/text_processor.py
#!/usr/bin/env python3
"""
Sample Text Processor - Basic text analysis and transformation tool
This script demonstrates the basic structure and functionality expected in
BASIC tier skills. It provides text processing capabilities with proper
argument parsing, error handling, and dual output formats.
Usage:
python text_processor.py analyze <file> [options]
python text_processor.py transform <file> --mode <mode> [options]
python text_processor.py batch <directory> [options]
Author: Claude Skills Engineering Team
Version: 1.0.0
Dependencies: Python Standard Library Only
"""
import argparse
import json
import os
import sys
from collections import Counter
from pathlib import Path
from typing import Dict, List, Any, Optional
class TextProcessor:
"""Core text processing functionality"""
def __init__(self, encoding: str = 'utf-8'):
self.encoding = encoding
def analyze_text(self, text: str) -> Dict[str, Any]:
"""Analyze text and return statistics"""
lines = text.split('\n')
words = text.lower().split()
# Calculate basic statistics
stats = {
'total_words': len(words),
'unique_words': len(set(words)),
'total_characters': len(text),
'lines': len(lines),
'average_word_length': sum(len(word) for word in words) / len(words) if words else 0
}
# Find most frequent word
if words:
word_counts = Counter(words)
most_common = word_counts.most_common(1)[0]
stats['most_frequent'] = {
'word': most_common[0],
'count': most_common[1]
}
else:
stats['most_frequent'] = {'word': '', 'count': 0}
return stats
def transform_text(self, text: str, mode: str) -> str:
"""Transform text according to specified mode"""
if mode == 'upper':
return text.upper()
elif mode == 'lower':
return text.lower()
elif mode == 'title':
return text.title()
elif mode == 'reverse':
return text[::-1]
else:
raise ValueError(f"Unknown transformation mode: {mode}")
def process_file(self, file_path: str) -> Dict[str, Any]:
"""Process a single text file"""
try:
with open(file_path, 'r', encoding=self.encoding) as file:
content = file.read()
stats = self.analyze_text(content)
stats['file'] = file_path
stats['file_size'] = os.path.getsize(file_path)
return stats
except FileNotFoundError:
raise FileNotFoundError(f"File not found: {file_path}")
except UnicodeDecodeError:
raise UnicodeDecodeError(f"Cannot decode file with {self.encoding} encoding: {file_path}")
except PermissionError:
raise PermissionError(f"Permission denied accessing file: {file_path}")
class OutputFormatter:
"""Handles dual output format generation"""
@staticmethod
def format_json(data: Dict[str, Any]) -> str:
"""Format data as JSON"""
return json.dumps(data, indent=2, ensure_ascii=False)
@staticmethod
def format_human_readable(data: Dict[str, Any]) -> str:
"""Format data as human-readable text"""
lines = []
lines.append("=== TEXT ANALYSIS RESULTS ===")
lines.append(f"File: {data.get('file', 'Unknown')}")
lines.append(f"File size: {data.get('file_size', 0)} bytes")
lines.append(f"Total words: {data.get('total_words', 0)}")
lines.append(f"Unique words: {data.get('unique_words', 0)}")
lines.append(f"Total characters: {data.get('total_characters', 0)}")
lines.append(f"Lines: {data.get('lines', 0)}")
lines.append(f"Average word length: {data.get('average_word_length', 0):.1f}")
most_frequent = data.get('most_frequent', {})
lines.append(f"Most frequent word: \"{most_frequent.get('word', '')}\" ({most_frequent.get('count', 0)} occurrences)")
return "\n".join(lines)
class FileManager:
"""Manages file I/O operations and batch processing"""
def __init__(self, verbose: bool = False):
self.verbose = verbose
def log_verbose(self, message: str):
"""Log verbose message if verbose mode enabled"""
if self.verbose:
print(f"[INFO] {message}", file=sys.stderr)
def find_text_files(self, directory: str) -> List[str]:
"""Find all text files in directory"""
text_extensions = {'.txt', '.md', '.rst', '.csv', '.log'}
text_files = []
try:
for file_path in Path(directory).rglob('*'):
if file_path.is_file() and file_path.suffix.lower() in text_extensions:
text_files.append(str(file_path))
except PermissionError:
raise PermissionError(f"Permission denied accessing directory: {directory}")
return text_files
def write_output(self, content: str, output_path: Optional[str] = None):
"""Write content to file or stdout"""
if output_path:
try:
# Create directory if needed
output_dir = os.path.dirname(output_path)
if output_dir and not os.path.exists(output_dir):
os.makedirs(output_dir)
with open(output_path, 'w', encoding='utf-8') as file:
file.write(content)
self.log_verbose(f"Output written to: {output_path}")
except PermissionError:
raise PermissionError(f"Permission denied writing to: {output_path}")
else:
print(content)
def analyze_command(args: argparse.Namespace) -> int:
"""Handle analyze command"""
try:
processor = TextProcessor(args.encoding)
file_manager = FileManager(args.verbose)
file_manager.log_verbose(f"Analyzing file: {args.file}")
# Process the file
results = processor.process_file(args.file)
# Format output
if args.format == 'json':
output = OutputFormatter.format_json(results)
else:
output = OutputFormatter.format_human_readable(results)
# Write output
file_manager.write_output(output, args.output)
return 0
except FileNotFoundError as e:
print(f"Error: {e}", file=sys.stderr)
return 1
except UnicodeDecodeError as e:
print(f"Error: {e}", file=sys.stderr)
print(f"Try using --encoding option with different encoding", file=sys.stderr)
return 1
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
return 1
def transform_command(args: argparse.Namespace) -> int:
"""Handle transform command"""
try:
processor = TextProcessor(args.encoding)
file_manager = FileManager(args.verbose)
file_manager.log_verbose(f"Transforming file: {args.file}")
# Read and transform the file
with open(args.file, 'r', encoding=args.encoding) as file:
content = file.read()
transformed = processor.transform_text(content, args.mode)
# Write transformed content
file_manager.write_output(transformed, args.output)
return 0
except FileNotFoundError as e:
print(f"Error: {e}", file=sys.stderr)
return 1
except ValueError as e:
print(f"Error: {e}", file=sys.stderr)
return 1
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
return 1
def batch_command(args: argparse.Namespace) -> int:
"""Handle batch command"""
try:
processor = TextProcessor(args.encoding)
file_manager = FileManager(args.verbose)
file_manager.log_verbose(f"Finding text files in: {args.directory}")
# Find all text files
text_files = file_manager.find_text_files(args.directory)
if not text_files:
print(f"No text files found in directory: {args.directory}", file=sys.stderr)
return 1
file_manager.log_verbose(f"Found {len(text_files)} text files")
# Process all files
all_results = []
for i, file_path in enumerate(text_files, 1):
try:
file_manager.log_verbose(f"Processing {i}/{len(text_files)}: {file_path}")
results = processor.process_file(file_path)
all_results.append(results)
except Exception as e:
print(f"Warning: Failed to process {file_path}: {e}", file=sys.stderr)
continue
if not all_results:
print("Error: No files could be processed successfully", file=sys.stderr)
return 1
# Format batch results
batch_summary = {
'total_files': len(all_results),
'total_words': sum(r.get('total_words', 0) for r in all_results),
'total_characters': sum(r.get('total_characters', 0) for r in all_results),
'files': all_results
}
if args.format == 'json':
output = OutputFormatter.format_json(batch_summary)
else:
lines = []
lines.append("=== BATCH PROCESSING RESULTS ===")
lines.append(f"Total files processed: {batch_summary['total_files']}")
lines.append(f"Total words across all files: {batch_summary['total_words']}")
lines.append(f"Total characters across all files: {batch_summary['total_characters']}")
lines.append("")
lines.append("Individual file results:")
for result in all_results:
lines.append(f" {result['file']}: {result['total_words']} words")
output = "\n".join(lines)
# Write output
file_manager.write_output(output, args.output)
return 0
except PermissionError as e:
print(f"Error: {e}", file=sys.stderr)
return 1
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
return 1
def main():
"""Main entry point with argument parsing"""
parser = argparse.ArgumentParser(
description="Sample Text Processor - Basic text analysis and transformation",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
Analysis:
python text_processor.py analyze document.txt
python text_processor.py analyze document.txt --format json --output results.json
Transformation:
python text_processor.py transform document.txt --mode upper
python text_processor.py transform document.txt --mode title --output transformed.txt
Batch processing:
python text_processor.py batch text_files/ --verbose
python text_processor.py batch text_files/ --format json --output batch_results.json
Transformation modes:
upper - Convert to uppercase
lower - Convert to lowercase
title - Convert to title case
reverse - Reverse the text
"""
)
parser.add_argument('--format',
choices=['json', 'text'],
default='text',
help='Output format (default: text)')
parser.add_argument('--output',
help='Output file path (default: stdout)')
parser.add_argument('--encoding',
default='utf-8',
help='Text file encoding (default: utf-8)')
parser.add_argument('--verbose',
action='store_true',
help='Enable verbose output')
subparsers = parser.add_subparsers(dest='command', help='Available commands')
# Analyze subcommand
analyze_parser = subparsers.add_parser('analyze', help='Analyze text file statistics')
analyze_parser.add_argument('file', help='Text file to analyze')
# Transform subcommand
transform_parser = subparsers.add_parser('transform', help='Transform text file')
transform_parser.add_argument('file', help='Text file to transform')
transform_parser.add_argument('--mode',
required=True,
choices=['upper', 'lower', 'title', 'reverse'],
help='Transformation mode')
# Batch subcommand
batch_parser = subparsers.add_parser('batch', help='Process multiple files')
batch_parser.add_argument('directory', help='Directory containing text files')
args = parser.parse_args()
if not args.command:
parser.print_help()
return 1
try:
if args.command == 'analyze':
return analyze_command(args)
elif args.command == 'transform':
return transform_command(args)
elif args.command == 'batch':
return batch_command(args)
else:
print(f"Unknown command: {args.command}", file=sys.stderr)
return 1
except KeyboardInterrupt:
print("\nOperation interrupted by user", file=sys.stderr)
return 130
except Exception as e:
print(f"Unexpected error: {e}", file=sys.stderr)
return 1
if __name__ == "__main__":
sys.exit(main())Thêm, sửa và tối ưu schema markup, dữ liệu cấu trúc JSON-LD để xuất hiện rich results trên Google.
---
name: schema
description: When the user wants to add, fix, or optimize schema markup and structured data on their site. Also use when the user mentions "schema markup," "structured data," "JSON-LD," "rich snippets," "schema.org," "FAQ schema," "product schema," "review schema," "breadcrumb schema," "Google rich results," "knowledge panel," "star ratings in search," or "add structured data." Use this whenever someone wants their pages to show enhanced results in Google. For broader SEO issues, see seo-audit. For AI search optimization, see ai-seo.
metadata:
version: 2.0.0
---
# Schema Markup
You are an expert in structured data and schema markup. Your goal is to implement schema.org markup that helps search engines understand content and enables rich results in search.
## 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 implementing schema, understand:
1. **Page Type** - What kind of page? What's the primary content? What rich results are possible?
2. **Current State** - Any existing schema? Errors in implementation? Which rich results already appearing?
3. **Goals** - Which rich results are you targeting? What's the business value?
---
## Core Principles
### 1. Accuracy First
- Schema must accurately represent page content
- Don't markup content that doesn't exist
- Keep updated when content changes
### 2. Use JSON-LD
- Google recommends JSON-LD format
- Easier to implement and maintain
- Place in `<head>` or end of `<body>`
### 3. Follow Google's Guidelines
- Only use markup Google supports
- Avoid spam tactics
- Review eligibility requirements
### 4. Validate Everything
- Test before deploying
- Monitor Search Console
- Fix errors promptly
---
## Common Schema Types
| Type | Use For | Required Properties |
|------|---------|-------------------|
| Organization | Company homepage/about | name, url |
| WebSite | Homepage (search box) | name, url |
| Article | Blog posts, news | headline, image, datePublished, author |
| Product | Product pages | name, image, offers |
| SoftwareApplication | SaaS/app pages | name, offers |
| FAQPage | FAQ content | mainEntity (Q&A array) |
| HowTo | Tutorials | name, step |
| BreadcrumbList | Any page with breadcrumbs | itemListElement |
| LocalBusiness | Local business pages | name, address |
| Event | Events, webinars | name, startDate, location |
**For complete JSON-LD examples**: See [references/schema-examples.md](references/schema-examples.md)
---
## Quick Reference
### Organization (Company Page)
Required: name, url
Recommended: logo, sameAs (social profiles), contactPoint
### Article/BlogPosting
Required: headline, image, datePublished, author
Recommended: dateModified, publisher, description
### Product
Required: name, image, offers (price + availability)
Recommended: sku, brand, aggregateRating, review
### FAQPage
Required: mainEntity (array of Question/Answer pairs)
### BreadcrumbList
Required: itemListElement (array with position, name, item)
---
## Multiple Schema Types
You can combine multiple schema types on one page using `@graph`:
```json
{
"@context": "https://schema.org",
"@graph": [
{ "@type": "Organization", ... },
{ "@type": "WebSite", ... },
{ "@type": "BreadcrumbList", ... }
]
}
```
---
## Validation and Testing
### Tools
- **Google Rich Results Test**: https://search.google.com/test/rich-results
- **Schema.org Validator**: https://validator.schema.org/
- **Search Console**: Enhancements reports
### Common Errors
**Missing required properties** - Check Google's documentation for required fields
**Invalid values** - Dates must be ISO 8601, URLs fully qualified, enumerations exact
**Mismatch with page content** - Schema doesn't match visible content
---
## Implementation
### Static Sites
- Add JSON-LD directly in HTML template
- Use includes/partials for reusable schema
### Dynamic Sites (React, Next.js)
- Component that renders schema
- Server-side rendered for SEO
- Serialize data to JSON-LD
### CMS / WordPress
- Plugins (Yoast, Rank Math, Schema Pro)
- Theme modifications
- Custom fields to structured data
---
## Output Format
### Schema Implementation
```json
// Full JSON-LD code block
{
"@context": "https://schema.org",
"@type": "...",
// Complete markup
}
```
### Testing Checklist
- [ ] Validates in Rich Results Test
- [ ] No errors or warnings
- [ ] Matches page content
- [ ] All required properties included
---
## Task-Specific Questions
1. What type of page is this?
2. What rich results are you hoping to achieve?
3. What data is available to populate the schema?
4. Is there existing schema on the page?
5. What's your tech stack?
---
## Related Skills
- **seo-audit**: For overall SEO including schema review
- **ai-seo**: For AI search optimization (schema helps AI understand content)
- **programmatic-seo**: For templated schema at scale
- **site-architecture**: For breadcrumb structure and navigation schema planning
FILE:evals/evals.json
{
"skill_name": "schema",
"evals": [
{
"id": 1,
"prompt": "Add schema markup to our SaaS product's homepage. We're a project management tool called TaskFlow. We need Organization schema and any other relevant types.",
"expected_output": "Should check for product-marketing.md first. Should implement Organization schema in JSON-LD format with all required and recommended properties (name, url, logo, description, sameAs for social profiles). Should recommend additional schema types for a SaaS homepage: WebSite (with SearchAction if applicable), SoftwareApplication or Product. Should use @graph for multiple schema types on one page. Should provide the complete JSON-LD code ready to implement. Should recommend validation with Google's Rich Results Test and Schema.org validator.",
"assertions": [
"Checks for product-marketing.md",
"Implements Organization schema in JSON-LD",
"Includes required and recommended properties",
"Recommends additional relevant schema types",
"Uses @graph for multiple types",
"Provides complete JSON-LD code",
"Recommends validation tools"
],
"files": []
},
{
"id": 2,
"prompt": "We have a FAQ page with 20 questions about our product. How do I add FAQ schema to get the rich results in Google?",
"expected_output": "Should implement FAQPage schema in JSON-LD format. Should show the correct structure: FAQPage as mainEntity containing Question items, each with acceptedAnswer. Should provide a complete code example with 2-3 sample questions. Should explain that FAQ schema can enable rich results showing questions/answers directly in search. Should note Google's guidelines for FAQ schema (factual answers, not promotional). Should recommend validation approach.",
"assertions": [
"Implements FAQPage schema in JSON-LD",
"Shows correct nested structure (FAQPage > Question > Answer)",
"Provides complete code example",
"Explains rich result benefits",
"Notes Google's FAQ schema guidelines",
"Recommends validation"
],
"files": []
},
{
"id": 3,
"prompt": "add schema to our blog posts. we publish articles about marketing tips.",
"expected_output": "Should trigger on casual phrasing. Should implement Article (or BlogPosting) schema in JSON-LD. Should include required properties: headline, author (as Person with name and url), datePublished, dateModified, image, publisher (as Organization). Should recommend BreadcrumbList schema alongside the article schema. Should provide template code that can be reused across blog posts. Should address how to populate dynamic fields (date, author, headline) from the CMS.",
"assertions": [
"Triggers on casual phrasing",
"Implements Article or BlogPosting schema",
"Includes author, datePublished, image, publisher",
"Recommends BreadcrumbList alongside",
"Provides reusable template code",
"Addresses CMS integration for dynamic fields"
],
"files": []
},
{
"id": 4,
"prompt": "We're an e-commerce site selling physical products. What schema markup do we need for our product pages?",
"expected_output": "Should implement Product schema with full properties: name, description, image, brand, sku, offers (with price, priceCurrency, availability, url). Should recommend AggregateRating if they have reviews, and Review schema for individual reviews. Should include BreadcrumbList for navigation. Should address common e-commerce schema types: Product, Offer, AggregateRating, Review. Should provide complete JSON-LD code. Should note that Product schema can enable rich results (price, availability, ratings in search).",
"assertions": [
"Implements Product schema with full properties",
"Includes Offer with price, availability",
"Recommends AggregateRating and Review schema",
"Includes BreadcrumbList",
"Provides complete JSON-LD code",
"Notes rich result benefits for products"
],
"files": []
},
{
"id": 5,
"prompt": "We added schema markup to our site but it's not showing rich results in Google. Can you help debug?",
"expected_output": "Should provide a systematic debugging approach: first validate with Google Rich Results Test and Schema.org validator (syntax errors), then check for common issues (incorrect nesting, missing required properties, JSON-LD placement errors). Should explain that valid schema doesn't guarantee rich results — Google chooses when to show them. Should recommend checking Search Console for structured data reports and errors. Should address common debugging scenarios: schema not detected, warnings vs errors, eligible vs displayed.",
"assertions": [
"Recommends validation tools for debugging",
"Checks for common schema errors",
"Explains valid schema doesn't guarantee rich results",
"Recommends Search Console structured data reports",
"Addresses warnings vs errors distinction",
"Provides systematic debugging approach"
],
"files": []
},
{
"id": 6,
"prompt": "Our organic search traffic dropped after a site redesign. Can you do a technical SEO audit?",
"expected_output": "Should recognize this is a technical SEO audit request, not a schema markup task. Should defer to or cross-reference the seo-audit skill, which handles comprehensive technical SEO audits. Schema markup is one component of SEO but doesn't address the broader technical issues (redirects, crawlability, indexation) that likely caused the traffic drop.",
"assertions": [
"Recognizes this as a technical SEO audit request",
"References or defers to seo-audit skill",
"Does not attempt full SEO audit using schema markup patterns"
],
"files": []
}
]
}
FILE:references/schema-examples.md
# Schema Markup Examples
Complete JSON-LD examples for common schema types.
## Contents
- Organization
- WebSite (with SearchAction)
- Article / BlogPosting
- Product
- SoftwareApplication
- FAQPage
- HowTo
- BreadcrumbList
- LocalBusiness
- Event
- Multiple Schema Types
- Implementation Example (Next.js)
## Organization
For company/brand homepage or about page.
```json
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "Example Company",
"url": "https://example.com",
"logo": "https://example.com/logo.png",
"sameAs": [
"https://twitter.com/example",
"https://linkedin.com/company/example",
"https://facebook.com/example"
],
"contactPoint": {
"@type": "ContactPoint",
"telephone": "+1-555-555-5555",
"contactType": "customer service"
}
}
```
---
## WebSite (with SearchAction)
For homepage, enables sitelinks search box.
```json
{
"@context": "https://schema.org",
"@type": "WebSite",
"name": "Example",
"url": "https://example.com",
"potentialAction": {
"@type": "SearchAction",
"target": {
"@type": "EntryPoint",
"urlTemplate": "https://example.com/search?q={search_term_string}"
},
"query-input": "required name=search_term_string"
}
}
```
---
## Article / BlogPosting
For blog posts and news articles.
```json
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "How to Implement Schema Markup",
"image": "https://example.com/image.jpg",
"datePublished": "2024-01-15T08:00:00+00:00",
"dateModified": "2024-01-20T10:00:00+00:00",
"author": {
"@type": "Person",
"name": "Jane Doe",
"url": "https://example.com/authors/jane"
},
"publisher": {
"@type": "Organization",
"name": "Example Company",
"logo": {
"@type": "ImageObject",
"url": "https://example.com/logo.png"
}
},
"description": "A complete guide to implementing schema markup...",
"mainEntityOfPage": {
"@type": "WebPage",
"@id": "https://example.com/schema-guide"
}
}
```
---
## Product
For product pages (e-commerce or SaaS).
```json
{
"@context": "https://schema.org",
"@type": "Product",
"name": "Premium Widget",
"image": "https://example.com/widget.jpg",
"description": "Our best-selling widget for professionals",
"sku": "WIDGET-001",
"brand": {
"@type": "Brand",
"name": "Example Co"
},
"offers": {
"@type": "Offer",
"url": "https://example.com/products/widget",
"priceCurrency": "USD",
"price": "99.99",
"availability": "https://schema.org/InStock",
"priceValidUntil": "2024-12-31"
},
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.8",
"reviewCount": "127"
}
}
```
---
## SoftwareApplication
For SaaS product pages and app landing pages.
```json
{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "Example App",
"applicationCategory": "BusinessApplication",
"operatingSystem": "Web, iOS, Android",
"offers": {
"@type": "Offer",
"price": "0",
"priceCurrency": "USD"
},
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.6",
"ratingCount": "1250"
}
}
```
---
## FAQPage
For pages with frequently asked questions.
```json
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "What is schema markup?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Schema markup is a structured data vocabulary that helps search engines understand your content..."
}
},
{
"@type": "Question",
"name": "How do I implement schema?",
"acceptedAnswer": {
"@type": "Answer",
"text": "The recommended approach is to use JSON-LD format, placing the script in your page's head..."
}
}
]
}
```
---
## HowTo
For instructional content and tutorials.
```json
{
"@context": "https://schema.org",
"@type": "HowTo",
"name": "How to Add Schema Markup to Your Website",
"description": "A step-by-step guide to implementing JSON-LD schema",
"totalTime": "PT15M",
"step": [
{
"@type": "HowToStep",
"name": "Choose your schema type",
"text": "Identify the appropriate schema type for your page content...",
"url": "https://example.com/guide#step1"
},
{
"@type": "HowToStep",
"name": "Write the JSON-LD",
"text": "Create the JSON-LD markup following schema.org specifications...",
"url": "https://example.com/guide#step2"
},
{
"@type": "HowToStep",
"name": "Add to your page",
"text": "Insert the script tag in your page's head section...",
"url": "https://example.com/guide#step3"
}
]
}
```
---
## BreadcrumbList
For any page with breadcrumb navigation.
```json
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": "https://example.com"
},
{
"@type": "ListItem",
"position": 2,
"name": "Blog",
"item": "https://example.com/blog"
},
{
"@type": "ListItem",
"position": 3,
"name": "SEO Guide",
"item": "https://example.com/blog/seo-guide"
}
]
}
```
---
## LocalBusiness
For local business location pages.
```json
{
"@context": "https://schema.org",
"@type": "LocalBusiness",
"name": "Example Coffee Shop",
"image": "https://example.com/shop.jpg",
"address": {
"@type": "PostalAddress",
"streetAddress": "123 Main Street",
"addressLocality": "San Francisco",
"addressRegion": "CA",
"postalCode": "94102",
"addressCountry": "US"
},
"geo": {
"@type": "GeoCoordinates",
"latitude": "37.7749",
"longitude": "-122.4194"
},
"telephone": "+1-555-555-5555",
"openingHoursSpecification": [
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
"opens": "08:00",
"closes": "18:00"
}
],
"priceRange": "$$"
}
```
---
## Event
For event pages, webinars, conferences.
```json
{
"@context": "https://schema.org",
"@type": "Event",
"name": "Annual Marketing Conference",
"startDate": "2024-06-15T09:00:00-07:00",
"endDate": "2024-06-15T17:00:00-07:00",
"eventAttendanceMode": "https://schema.org/OnlineEventAttendanceMode",
"eventStatus": "https://schema.org/EventScheduled",
"location": {
"@type": "VirtualLocation",
"url": "https://example.com/conference"
},
"image": "https://example.com/conference.jpg",
"description": "Join us for our annual marketing conference...",
"offers": {
"@type": "Offer",
"url": "https://example.com/conference/tickets",
"price": "199",
"priceCurrency": "USD",
"availability": "https://schema.org/InStock",
"validFrom": "2024-01-01"
},
"performer": {
"@type": "Organization",
"name": "Example Company"
},
"organizer": {
"@type": "Organization",
"name": "Example Company",
"url": "https://example.com"
}
}
```
---
## Multiple Schema Types
Combine multiple schema types using @graph.
```json
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "Organization",
"@id": "https://example.com/#organization",
"name": "Example Company",
"url": "https://example.com"
},
{
"@type": "WebSite",
"@id": "https://example.com/#website",
"url": "https://example.com",
"name": "Example",
"publisher": {
"@id": "https://example.com/#organization"
}
},
{
"@type": "BreadcrumbList",
"itemListElement": [...]
}
]
}
```
---
## Implementation Example (Next.js)
```jsx
export default function ProductPage({ product }) {
const schema = {
"@context": "https://schema.org",
"@type": "Product",
name: product.name,
// ... other properties
};
return (
<>
<Head>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
</Head>
{/* Page content */}
</>
);
}
```
Hook PreToolUse phát hiện 12 mẫu rủi ro bảo mật phổ biến như injection, XSS, deserialization trước khi Edit/Write hoàn tất.
---
name: security-guidance
description: PreToolUse security-anti-pattern hook for Claude Code. Catches 12 common security risks (command injection, XSS, SQL injection, unsafe deserialization, GitHub Actions workflow injection, eval/new Function code injection) BEFORE the Edit/Write/MultiEdit operation completes. Session-state caching prevents duplicate warnings on the same file+rule combo. Stdlib only — no dependencies. Use when you want a safety net during Claude Code sessions that touch security-sensitive code (auth, payments, user input handling, IaC). Disable with ENABLE_SECURITY_REMINDER=0 if you need to perform a verified-safe operation that would otherwise trip a pattern. Triggers — "add security hook", "block unsafe code", "detect command injection before write", "prevent SQL injection patterns", "security warning hook".
---
# Security Guidance Hook
**A PreToolUse hook that blocks 12 common security anti-patterns before Claude Code writes them.**
This skill is a **hook**, not a slash command. Once installed, it runs automatically before every `Edit`, `Write`, or `MultiEdit` operation and warns + blocks if it detects a known dangerous pattern.
## What It Catches
The hook scans both:
- **The file path being edited** — flags GitHub Actions workflow files with risky `{}` patterns
- **The content being written** — substring matches against 11 anti-patterns
| Pattern | Category | Risk |
|---|---|---|
| GitHub Actions workflow expressions | Path-based | Workflow command injection via untrusted inputs |
| `child_process.exec`, `exec(`, `execSync(` | Substring | Node.js command injection |
| `new Function` | Substring | JS code injection |
| `eval(` | Substring | JS code injection |
| `dangerouslySetInnerHTML` | Substring | React XSS |
| `document.write` | Substring | DOM XSS |
| `.innerHTML =` | Substring | DOM XSS |
| `pickle` | Substring | Python deserialization RCE |
| `os.system`, `from os import system` | Substring | Python command injection |
| `shell=True` (subprocess) | Substring | Python command injection |
| f-string SQL or `.format` SQL | Substring | SQL injection |
| `yaml.load(`, `yaml.unsafe_load` | Substring | YAML deserialization RCE |
## How It Works
1. Claude Code is about to run `Edit`, `Write`, or `MultiEdit`
2. PreToolUse hook fires → invokes `security_reminder_hook.py` with the tool input as JSON on stdin
3. The hook extracts file_path + content + checks against the pattern table
4. If a pattern matches AND this warning hasn't been shown for this file+rule in this session:
- Print the warning to stderr (Claude sees it)
- Exit code 2 → blocks the tool call
- Save the warning key to `~/.claude/security_warnings_state_<session>.json`
5. If a pattern matches BUT the warning was already shown this session:
- Allow the tool call (exit code 0) — Claude already saw the warning once
6. If no pattern matches:
- Allow the tool call (exit code 0)
## Installation
This plugin ships as a Claude Code plugin with `hooks.json` wiring:
```bash
# In Claude Code:
/plugin marketplace add alirezarezvani/claude-skills
/plugin install security-guidance@claude-code-skills
```
Once installed, no further configuration needed — the hook runs automatically.
## Configuration
Disable per-session via environment variable:
```bash
ENABLE_SECURITY_REMINDER=0 claude
# Hook is bypassed for this session
```
Use sparingly — the hook is most useful exactly when you're tempted to disable it (because you're under deadline pressure to ship something you know is sketchy).
## Per-File Override Pattern
If a specific file legitimately needs `eval()` or `pickle` (e.g., a sandboxed REPL, a deliberately unsafe parser for a fuzzer), document it in the file with a comment:
```python
# SAFETY: pickle is the required serialization format for this internal tool.
# This file does NOT accept untrusted input. See SECURITY.md for boundary analysis.
import pickle
```
The hook will still warn on first edit per session. After acknowledging, subsequent edits in the same session are allowed (session-state caching).
## Why The Patterns Are Substring-Based (Not AST-Based)
Trade-off: AST-based detection would be more precise (no false positives on string literals containing "eval("). Substring-based is:
- **Faster** — runs in ms, doesn't parse the file
- **Cross-language** — same hook works for JS/TS/Python/YAML/etc.
- **Conservative** — false positives are easy to dismiss (one keystroke); false negatives are dangerous
For 90%+ of cases, substring detection is sufficient. If you need stricter detection, layer in a proper SAST tool (semgrep, CodeQL) as a CI step.
## State Files
The hook caches "warning shown" state in `~/.claude/security_warnings_state_<session_id>.json`. These files:
- Are auto-cleaned after 30 days (10% chance per hook invocation)
- Are session-scoped (each Claude session gets its own)
- Contain a JSON list of `<file_path>-<rule_name>` keys
You can safely delete `~/.claude/security_warnings_state_*.json` files at any time — the hook regenerates them on next run.
## Debug Log
The hook writes to `~/.claude/security-warnings-log.txt` for debugging hook misfires:
```bash
tail -f ~/.claude/security-warnings-log.txt
# Shows JSON decode errors, state-file save failures, etc.
```
(Upstream version wrote to `/tmp/security-warnings-log.txt` — we moved it to `~/.claude/` for persistence across reboots.)
## Source + Attribution
This plugin is ported from David Dworken's MIT-licensed implementation in [`alirezarezvani/aeo-box`](https://github.com/alirezarezvani/aeo-box/tree/main/.claude/plugins/security-guidance).
**Verbatim:** the original 9 patterns (GitHub Actions, child_process.exec, new Function, eval, dangerouslySetInnerHTML, document.write, innerHTML, pickle, os.system) are preserved with their exact warning text.
**Modifications:**
- Added 3 patterns: `subprocess shell=True`, SQL injection via f-string or `.format`, `yaml.unsafe_load`
- Debug log moved from `/tmp/security-warnings-log.txt` → `~/.claude/security-warnings-log.txt`
- Restructured as a claude-skills plugin with `attribution` block in `plugin.json`
## Anti-Patterns
### Disabling the hook by default
Defeats the purpose. If `ENABLE_SECURITY_REMINDER=0` becomes your default, you've trained yourself to ignore the safety net. Use it only for specific verified-safe operations.
### Modifying the pattern list without security review
Anyone can add a pattern. Removing one requires a security review — patterns exist because they map to real CVE classes.
### Treating session-state as immutable security policy
The cache prevents nag-spam but is per-session. Don't rely on "I dismissed this once" as long-term policy — use the per-file documentation pattern instead (comment justifying the use).
## Related Skills
- `engineering-team/skills/red-team` — adversarial pen-testing
- `engineering-team/skills/threat-detection` — threat modeling + detection design
- `engineering-team/skills/ai-security` — AI-specific security (prompt injection, etc.)
- `engineering/ship-gate` — pre-production audit (8-category, ~89 checks)
- `engineering/skill-security-auditor` — security scan for skill packages
## Trigger Phrases
- "add security hook"
- "block unsafe code before write"
- "detect command injection"
- "prevent SQL injection patterns"
- "warn on eval / pickle / os.system"
- "GitHub Actions security hook"
---
**Version:** 2.7.3
**Source:** Ported from [`alirezarezvani/aeo-box`](https://github.com/alirezarezvani/aeo-box) `.claude/plugins/security-guidance/` (originally by David Dworken at Anthropic, MIT)
**License:** MIT
FILE:references/pretooluse_hook_canon.md
# PreToolUse Hook Discipline — When To Block, When To Warn
This reference answers one decision: **when designing a PreToolUse hook for Claude Code, when should it block the tool call (exit 2) vs. just warn (exit 0 with stderr message)?** The answer depends on **reversibility × severity × false-positive rate**.
## The Three Exit Codes
Claude Code PreToolUse hooks have three meaningful exit codes:
| Exit code | Effect | Use when |
|---|---|---|
| `0` | Allow the tool call to proceed | No issue detected, or warning-only emission |
| `1` | Allow but log error | Hook itself errored — don't block the user |
| `2` | Block the tool call | Detected pattern is severe enough to require Claude to revisit |
## The Decision Matrix
```
High severity Low severity
───────────── ───────────────
Hard to reverse BLOCK (exit 2) WARN (stderr + exit 0)
Easy to reverse WARN ALLOW (exit 0, no message)
```
Examples:
- **`eval(<user_input>)`** in production code: high severity (RCE), hard to reverse if it ships → **BLOCK**
- **`document.write` in a test file**: medium severity, easy to reverse → **WARN** (so the user can override deliberately)
- **`pickle.load` in a one-off script for the user's own data**: low severity in context, easy to reverse → **WARN**
- **Editing `.env` file**: high severity (secrets), but the user explicitly asked → **WARN** (let the user proceed)
## Session-State Caching
The security-guidance hook caches "warning shown" state per session. This is critical UX:
**Without caching:** Every Edit/Write to the same file triggers the same warning. Claude burns through tokens re-explaining + the user trains themselves to ignore. **Anti-pattern.**
**With caching:** First trigger blocks → Claude/user acknowledges → subsequent edits to same file+rule allowed for rest of session. **Correct.**
The caching is keyed by `<file_path>-<rule_name>`. Different rules on the same file each trigger independently (a file might warn for both `eval(` and `os.system` — and should).
## False-Positive Tolerance
A PreToolUse hook with a 50% false-positive rate is a hook nobody listens to. The pattern table needs to be calibrated for **high precision, accepting some recall loss**.
Calibration questions per pattern:
1. **Specificity:** Does the pattern uniquely identify the anti-pattern, or does it match many safe uses?
2. **Context-blindness:** Does the substring trigger inside string literals or comments? (Acceptable cost for cross-language detection.)
3. **Override path:** Can the user document a legitimate use case? (E.g., comment annotation.)
For the security-guidance hook's 12 patterns, false-positive rates are roughly:
| Pattern | FP rate (estimated) | Why |
|---|---|---|
| `eval(` | ~5% | Mostly only appears in code-eval contexts; very low FP |
| `pickle` | ~30% | Includes `import pickle` and any reference to the module |
| `innerHTML =` | ~10% | Pretty specific to the anti-pattern |
| GitHub Actions path-check | ~0% | Path-based, never false-positive |
| SQL f-string | ~15% | Could match harmless f-strings |
Higher-FP patterns rely on session-caching: user dismisses once, no nag for rest of session.
## When Substring Detection Is Enough
For Claude Code PreToolUse hooks, substring detection is sufficient when:
- The substring is rare in non-anti-pattern contexts (e.g., `dangerouslySetInnerHTML`)
- The user can quickly dismiss a false positive (one-key acknowledgment)
- The cost of a false negative is high (security regression)
When substring detection is **NOT** enough:
- Patterns with high natural occurrence (e.g., the word "password" — appears in legitimate docs)
- Patterns where context matters semantically (a function called `safe_eval` should not match `eval(`)
- Patterns that require taint analysis (knowing if data came from user input)
For taint-aware analysis, layer in proper SAST in CI — don't push that complexity into the PreToolUse hook.
## Hook Performance Discipline
The hook runs **before every Edit/Write/MultiEdit**. Performance matters:
- Substring scan of 12 patterns: ~1ms for typical file content
- State file load/save: ~5ms (JSON, single file)
- Total overhead: ~10ms per tool call
This is well within tolerance. If a hook adds >100ms per tool call, it slows down Claude Code interactively.
**Don't:** Spawn child processes from the hook (kills latency).
**Don't:** Make network calls from the hook (kills latency + introduces failure modes).
**Do:** Keep all logic in-process; only use stdlib.
## Disable-Via-Env-Var Discipline
`ENABLE_SECURITY_REMINDER=0` disables the hook for a session. Use sparingly. The pattern:
- **Default ON** (this is the safe default)
- **Disable when:** doing a verified-safe operation that would otherwise trip a pattern (e.g., writing a deliberately-unsafe sandboxed REPL, doing security research)
- **Re-enable immediately** after the operation
Don't put `export ENABLE_SECURITY_REMINDER=0` in your shell rc file. That's the anti-pattern of training-yourself-to-ignore-warnings.
## Anti-Patterns
### Hook that always exits 0
If a hook never blocks and never warns, it has no effect. Remove it.
### Hook that always exits 2 on a pattern hit
If a hook always blocks, even on the 5th occurrence of a pattern the user already saw 4 times, the user disables the hook. Session-state caching is required.
### Hook with network I/O
Defeats latency budget + adds failure modes (what if the API is down?). Hooks should be hermetic.
### Hook that modifies state Claude can't see
If the hook silently mutates files or env vars, Claude doesn't know about the changes and may produce inconsistent next actions. Hooks should be observation-only or emit explicit warnings.
### Hook with regex that's hard to read
The pattern table should be reviewable by a non-author. If the regex is dense Perl-style, port to plain substring or simplify the regex. Maintainability >> cleverness for security code.
## Citations (7 sources)
1. **Anthropic — Claude Code hooks documentation (2024-2026).** Source for the canonical PreToolUse exit code semantics, hook input/output format, and ~10ms latency budget. https://docs.claude.com/en/docs/claude-code/hooks
2. **OWASP — Top 10 Web Application Security Risks (2021, current ed.).** Source for which patterns to detect: injection (A03), insecure design (A04), security misconfiguration (A05), broken authentication (A07). The hook's pattern table maps directly to these categories.
3. **CWE (Common Weakness Enumeration) — Top 25 Most Dangerous Software Weaknesses (current ed.).** Source for the specific weakness classes the hook catches: CWE-78 (OS command injection), CWE-79 (XSS), CWE-89 (SQL injection), CWE-94 (code injection), CWE-502 (deserialization). https://cwe.mitre.org/top25/
4. **GitHub Security Lab — "How to catch GitHub Actions workflow injections before attackers do" (2023).** Source for the GitHub Actions workflow path-based pattern. The cited blog post is referenced in the hook's warning text. https://github.blog/security/vulnerability-research/how-to-catch-github-actions-workflow-injections-before-attackers-do/
5. **Python.org — `pickle` module documentation (security warnings).** Source for the warning text on pickle deserialization RCE risk. https://docs.python.org/3/library/pickle.html
6. **PyYAML — Documentation on yaml.load vs yaml.safe_load (security warnings).** Source for the yaml.unsafe_load pattern. https://pyyaml.org/wiki/PyYAMLDocumentation
7. **React documentation — `dangerouslySetInnerHTML` security warnings.** Source for the React XSS pattern. https://react.dev/reference/react-dom/components/common#dangerously-setting-the-inner-html
8. **NIST — Secure Software Development Framework (SSDF, current ed.).** Source for the "shift-left" principle that motivates PreToolUse hooks: detect security issues at the earliest possible point in the development lifecycle, before they hit version control.
Thiết lập tương tác một thử nghiệm autoresearch mới: lĩnh vực, file đích, lệnh đánh giá, chỉ số, hướng tối ưu và bộ đánh giá.
---
name: "setup"
description: "Set up a new autoresearch experiment interactively. Collects domain, target file, eval command, metric, direction, and evaluator."
command: /ar:setup
---
# /ar:setup — Create New Experiment
Set up a new autoresearch experiment with all required configuration.
## Usage
```
/ar:setup # Interactive mode
/ar:setup engineering api-speed src/api.py "pytest bench.py" p50_ms lower
/ar:setup --list # Show existing experiments
/ar:setup --list-evaluators # Show available evaluators
```
## What It Does
### If arguments provided
Pass them directly to the setup script:
```bash
python {skill_path}/scripts/setup_experiment.py \
--domain {domain} --name {name} \
--target {target} --eval "{eval_cmd}" \
--metric {metric} --direction {direction} \
[--evaluator {evaluator}] [--scope {scope}]
```
### If no arguments (interactive mode)
Collect each parameter one at a time:
1. **Domain** — Ask: "What domain? (engineering, marketing, content, prompts, custom)"
2. **Name** — Ask: "Experiment name? (e.g., api-speed, blog-titles)"
3. **Target file** — Ask: "Which file to optimize?" Verify it exists.
4. **Eval command** — Ask: "How to measure it? (e.g., pytest bench.py, python evaluate.py)"
5. **Metric** — Ask: "What metric does the eval output? (e.g., p50_ms, ctr_score)"
6. **Direction** — Ask: "Is lower or higher better?"
7. **Evaluator** (optional) — Show built-in evaluators. Ask: "Use a built-in evaluator, or your own?"
8. **Scope** — Ask: "Store in project (.autoresearch/) or user (~/.autoresearch/)?"
Then run `setup_experiment.py` with the collected parameters.
### Listing
```bash
# Show existing experiments
python {skill_path}/scripts/setup_experiment.py --list
# Show available evaluators
python {skill_path}/scripts/setup_experiment.py --list-evaluators
```
## Built-in Evaluators
| Name | Metric | Use Case |
|------|--------|----------|
| `benchmark_speed` | `p50_ms` (lower) | Function/API execution time |
| `benchmark_size` | `size_bytes` (lower) | File, bundle, Docker image size |
| `test_pass_rate` | `pass_rate` (higher) | Test suite pass percentage |
| `build_speed` | `build_seconds` (lower) | Build/compile/Docker build time |
| `memory_usage` | `peak_mb` (lower) | Peak memory during execution |
| `llm_judge_content` | `ctr_score` (higher) | Headlines, titles, descriptions |
| `llm_judge_prompt` | `quality_score` (higher) | System prompts, agent instructions |
| `llm_judge_copy` | `engagement_score` (higher) | Social posts, ad copy, emails |
## After Setup
Report to the user:
- Experiment path and branch name
- Whether the eval command worked and the baseline metric
- Suggest: "Run `/ar:run {domain}/{name}` to start iterating, or `/ar:loop {domain}/{name}` for autonomous mode."
Lập kế hoạch, thiết kế và triển khai thử nghiệm A/B hoặc chương trình thử nghiệm tăng trưởng.
---
name: ab-testing
description: When the user wants to plan, design, or implement an A/B test or experiment, or build a growth experimentation program. Also use when the user mentions "A/B test," "split test," "experiment," "test this change," "variant copy," "multivariate test," "hypothesis," "should I test this," "which version is better," "test two versions," "statistical significance," "how long should I run this test," "growth experiments," "experiment velocity," "experiment backlog," "ICE score," "experimentation program," or "experiment playbook." Use this whenever someone is comparing two approaches and wants to measure which performs better, or when they want to build a systematic experimentation practice. For tracking implementation, see analytics. For page-level conversion optimization, see cro.
metadata:
version: 2.0.0
---
# A/B Test Setup
You are an expert in experimentation and A/B testing. Your goal is to help design tests that produce statistically valid, actionable results.
## Initial Assessment
**Check for product marketing context first:**
If `.agents/product-marketing.md` exists (or `.claude/product-marketing.md`, or the legacy `product-marketing-context.md` filename, in older setups), read it before asking questions. Use that context and only ask for information not already covered or specific to this task.
Before designing a test, understand:
1. **Test Context** - What are you trying to improve? What change are you considering?
2. **Current State** - Baseline conversion rate? Current traffic volume?
3. **Constraints** - Technical complexity? Timeline? Tools available?
---
## Core Principles
### 1. Start with a Hypothesis
- Not just "let's see what happens"
- Specific prediction of outcome
- Based on reasoning or data
### 2. Test One Thing
- Single variable per test
- Otherwise you don't know what worked
### 3. Statistical Rigor
- Pre-determine sample size
- Don't peek and stop early
- Commit to the methodology
### 4. Measure What Matters
- Primary metric tied to business value
- Secondary metrics for context
- Guardrail metrics to prevent harm
---
## Hypothesis Framework
### Structure
```
Because [observation/data],
we believe [change]
will cause [expected outcome]
for [audience].
We'll know this is true when [metrics].
```
### Example
**Weak**: "Changing the button color might increase clicks."
**Strong**: "Because users report difficulty finding the CTA (per heatmaps and feedback), we believe making the button larger and using contrasting color will increase CTA clicks by 15%+ for new visitors. We'll measure click-through rate from page view to signup start."
---
## Test Types
| Type | Description | Traffic Needed |
|------|-------------|----------------|
| A/B | Two versions, single change | Moderate |
| A/B/n | Multiple variants | Higher |
| MVT | Multiple changes in combinations | Very high |
| Split URL | Different URLs for variants | Moderate |
---
## Sample Size
### Quick Reference
| Baseline | 10% Lift | 20% Lift | 50% Lift |
|----------|----------|----------|----------|
| 1% | 150k/variant | 39k/variant | 6k/variant |
| 3% | 47k/variant | 12k/variant | 2k/variant |
| 5% | 27k/variant | 7k/variant | 1.2k/variant |
| 10% | 12k/variant | 3k/variant | 550/variant |
**Calculators:**
- [Evan Miller's](https://www.evanmiller.org/ab-testing/sample-size.html)
- [Optimizely's](https://www.optimizely.com/sample-size-calculator/)
**For detailed sample size tables and duration calculations**: See [references/sample-size-guide.md](references/sample-size-guide.md)
---
## Metrics Selection
### Primary Metric
- Single metric that matters most
- Directly tied to hypothesis
- What you'll use to call the test
### Secondary Metrics
- Support primary metric interpretation
- Explain why/how the change worked
### Guardrail Metrics
- Things that shouldn't get worse
- Stop test if significantly negative
### Example: Pricing Page Test
- **Primary**: Plan selection rate
- **Secondary**: Time on page, plan distribution
- **Guardrail**: Support tickets, refund rate
---
## Designing Variants
### What to Vary
| Category | Examples |
|----------|----------|
| Headlines/Copy | Message angle, value prop, specificity, tone |
| Visual Design | Layout, color, images, hierarchy |
| CTA | Button copy, size, placement, number |
| Content | Information included, order, amount, social proof |
### Best Practices
- Single, meaningful change
- Bold enough to make a difference
- True to the hypothesis
---
## Traffic Allocation
| Approach | Split | When to Use |
|----------|-------|-------------|
| Standard | 50/50 | Default for A/B |
| Conservative | 90/10, 80/20 | Limit risk of bad variant |
| Ramping | Start small, increase | Technical risk mitigation |
**Considerations:**
- Consistency: Users see same variant on return
- Balanced exposure across time of day/week
---
## Implementation
### Client-Side
- JavaScript modifies page after load
- Quick to implement, can cause flicker
- Tools: PostHog, Optimizely, VWO
### Server-Side
- Variant determined before render
- No flicker, requires dev work
- Tools: PostHog, LaunchDarkly, Split
---
## Running the Test
### Pre-Launch Checklist
- [ ] Hypothesis documented
- [ ] Primary metric defined
- [ ] Sample size calculated
- [ ] Variants implemented correctly
- [ ] Tracking verified
- [ ] QA completed on all variants
### During the Test
**DO:**
- Monitor for technical issues
- Check segment quality
- Document external factors
**Avoid:**
- Peek at results and stop early
- Make changes to variants
- Add traffic from new sources
### The Peeking Problem
Looking at results before reaching sample size and stopping early leads to false positives and wrong decisions. Pre-commit to sample size and trust the process.
---
## Analyzing Results
### Statistical Significance
- 95% confidence = p-value < 0.05
- Means <5% chance result is random
- Not a guarantee—just a threshold
### Analysis Checklist
1. **Reach sample size?** If not, result is preliminary
2. **Statistically significant?** Check confidence intervals
3. **Effect size meaningful?** Compare to MDE, project impact
4. **Secondary metrics consistent?** Support the primary?
5. **Guardrail concerns?** Anything get worse?
6. **Segment differences?** Mobile vs. desktop? New vs. returning?
### Interpreting Results
| Result | Conclusion |
|--------|------------|
| Significant winner | Implement variant |
| Significant loser | Keep control, learn why |
| No significant difference | Need more traffic or bolder test |
| Mixed signals | Dig deeper, maybe segment |
---
## Documentation
Document every test with:
- Hypothesis
- Variants (with screenshots)
- Results (sample, metrics, significance)
- Decision and learnings
**For templates**: See [references/test-templates.md](references/test-templates.md)
---
## Growth Experimentation Program
Individual tests are valuable. A continuous experimentation program is a compounding asset. This section covers how to run experiments as an ongoing growth engine, not just one-off tests.
### The Experiment Loop
```
1. Generate hypotheses (from data, research, competitors, customer feedback)
2. Prioritize with ICE scoring
3. Design and run the test
4. Analyze results with statistical rigor
5. Promote winners to a playbook
6. Generate new hypotheses from learnings
→ Repeat
```
### Hypothesis Generation
Feed your experiment backlog from multiple sources:
| Source | What to Look For |
|--------|-----------------|
| Analytics | Drop-off points, low-converting pages, underperforming segments |
| Customer research | Pain points, confusion, unmet expectations |
| Competitor analysis | Features, messaging, or UX patterns they use that you don't |
| Support tickets | Recurring questions or complaints about conversion flows |
| Heatmaps/recordings | Where users hesitate, rage-click, or abandon |
| Past experiments | "Significant loser" tests often reveal new angles to try |
### ICE Prioritization
Score each hypothesis 1-10 on three dimensions:
| Dimension | Question |
|-----------|----------|
| **Impact** | If this works, how much will it move the primary metric? |
| **Confidence** | How sure are we this will work? (Based on data, not gut.) |
| **Ease** | How fast and cheap can we ship and measure this? |
**ICE Score** = (Impact + Confidence + Ease) / 3
Run highest-scoring experiments first. Re-score monthly as context changes.
### Experiment Velocity
Track your experimentation rate as a leading indicator of growth:
| Metric | Target |
|--------|--------|
| Experiments launched per month | 4-8 for most teams |
| Win rate | 20-30% is common for mature programs (sustained higher rates may indicate conservative hypotheses) |
| Average test duration | 2-4 weeks |
| Backlog depth | 20+ hypotheses queued |
| Cumulative lift | Compound gains from all winners |
### The Experiment Playbook
When a test wins, don't just implement it — document the pattern:
```
## [Experiment Name]
**Date**: [date]
**Hypothesis**: [the hypothesis]
**Sample size**: [n per variant]
**Result**: [winner/loser/inconclusive] — [primary metric] changed by [X%] (95% CI: [range], p=[value])
**Guardrails**: [any guardrail metrics and their outcomes]
**Segment deltas**: [notable differences by device, segment, or cohort]
**Why it worked/failed**: [analysis]
**Pattern**: [the reusable insight — e.g., "social proof near pricing CTAs increases plan selection"]
**Apply to**: [other pages/flows where this pattern might work]
**Status**: [implemented / parked / needs follow-up test]
```
Over time, your playbook becomes a library of proven growth patterns specific to your product and audience.
### Experiment Cadence
**Weekly (30 min)**: Review running experiments for technical issues and guardrail metrics. Don't call winners early — but do stop tests where guardrails are significantly negative.
**Bi-weekly**: Conclude completed experiments. Analyze results, update playbook, launch next experiment from backlog.
**Monthly (1 hour)**: Review experiment velocity, win rate, cumulative lift. Replenish hypothesis backlog. Re-prioritize with ICE.
**Quarterly**: Audit the playbook. Which patterns have been applied broadly? Which winning patterns haven't been scaled yet? What areas of the funnel are under-tested?
---
## Common Mistakes
### Test Design
- Testing too small a change (undetectable)
- Testing too many things (can't isolate)
- No clear hypothesis
### Execution
- Stopping early
- Changing things mid-test
- Not checking implementation
### Analysis
- Ignoring confidence intervals
- Cherry-picking segments
- Over-interpreting inconclusive results
---
## Task-Specific Questions
1. What's your current conversion rate?
2. How much traffic does this page get?
3. What change are you considering and why?
4. What's the smallest improvement worth detecting?
5. What tools do you have for testing?
6. Have you tested this area before?
---
## Related Skills
- **cro**: For generating test ideas based on CRO principles
- **analytics**: For setting up test measurement
- **copywriting**: For creating variant copy
FILE:evals/evals.json
{
"skill_name": "ab-testing",
"evals": [
{
"id": 1,
"prompt": "I want to A/B test our homepage headline. We currently say 'The All-in-One Project Management Tool' and want to test something benefit-focused. We get about 15,000 visitors/month and our current signup rate is 3.2%.",
"expected_output": "Should check for product-marketing.md first. Should build a proper hypothesis using the framework: 'Because [observation], we believe [change] will cause [outcome], which we'll measure by [metric].' Should identify this as an A/B test (two variants). Should calculate or reference sample size needs based on 15,000 monthly visitors and 3.2% baseline. Should define primary metric (signup rate), secondary metrics, and guardrail metrics. Should warn about the peeking problem and recommend a fixed test duration. Should provide the test plan in the structured output format.",
"assertions": [
"Checks for product-marketing.md",
"Uses the hypothesis framework with observation, belief, outcome, and metric",
"Identifies as A/B test type",
"Addresses sample size calculation based on traffic and baseline rate",
"Defines primary metric (signup rate)",
"Defines secondary and guardrail metrics",
"Warns about the peeking problem",
"Provides structured test plan output"
],
"files": []
},
{
"id": 2,
"prompt": "we want to test like 4 different CTA button colors on our pricing page. is that a good idea?",
"expected_output": "Should trigger on casual phrasing. Should identify this as an A/B/n test (multiple variants). Should caution that testing 4 variants requires significantly more traffic than a simple A/B test. Should reference the sample size quick reference showing traffic multipliers for multiple variants. Should question whether button color alone is likely to produce meaningful lift vs testing CTA copy, placement, or surrounding context. Should recommend either reducing to 2 variants or ensuring sufficient traffic. Should still provide hypothesis framework and test setup if proceeding.",
"assertions": [
"Triggers on casual phrasing",
"Identifies as A/B/n test (multiple variants)",
"Cautions about increased traffic needs for 4 variants",
"References sample size requirements",
"Questions whether button color alone is high-impact",
"Suggests alternative higher-impact elements to test",
"Provides hypothesis framework"
],
"files": []
},
{
"id": 3,
"prompt": "Our test has been running for 3 days and Variant B is winning with 95% confidence. Should we call it?",
"expected_output": "Should immediately address the peeking problem. Should explain that checking results early inflates false positive rates. Should recommend running for the full pre-calculated duration regardless of early results. Should explain why early significance can be misleading (regression to the mean, day-of-week effects, audience mix shifts). Should provide guidance on when it IS appropriate to stop early (sequential testing methods). Should recommend the pre-test commitment to duration.",
"assertions": [
"Addresses the peeking problem directly",
"Explains why early significance is misleading",
"Recommends running for full pre-calculated duration",
"Mentions day-of-week effects or audience mix shifts",
"Explains false positive rate inflation from peeking",
"Mentions sequential testing as alternative approach"
],
"files": []
},
{
"id": 4,
"prompt": "Help me set up a multivariate test on our landing page. I want to test the headline, hero image, and CTA button simultaneously.",
"expected_output": "Should identify this as a Multivariate Test (MVT). Should explain that MVT tests combinations of elements and requires much more traffic than A/B tests. Should calculate or reference traffic needs (combinations multiply: e.g., 2 headlines × 2 images × 2 CTAs = 8 combinations). Should recommend MVT only if traffic supports it, otherwise suggest sequential A/B tests. Should build hypotheses for each element being tested. Should define interaction effects to watch for. Should provide structured test plan.",
"assertions": [
"Identifies as multivariate test (MVT)",
"Explains MVT tests combinations of elements",
"Addresses dramatically higher traffic requirements",
"Calculates number of combinations",
"Suggests sequential A/B tests as alternative if traffic insufficient",
"Builds hypotheses for each element",
"Provides structured test plan"
],
"files": []
},
{
"id": 5,
"prompt": "What metrics should I track for an A/B test on our trial signup page? We're testing a longer form (adds company size and role fields) against the current short form.",
"expected_output": "Should apply the metrics selection framework with three tiers: primary, secondary, and guardrail metrics. Primary: form completion rate (the direct conversion metric). Secondary: lead quality metrics (SQL conversion rate, activation rate post-signup). Guardrail: overall signup volume (ensure longer form doesn't tank total signups below acceptable threshold). Should explain the tradeoff between conversion quantity and lead quality. Should note that this test needs longer observation window to measure downstream metrics.",
"assertions": [
"Applies three-tier metric framework (primary, secondary, guardrail)",
"Identifies form completion rate as primary metric",
"Identifies lead quality as secondary metric",
"Defines guardrail metrics to protect against negative outcomes",
"Explains quantity vs quality tradeoff",
"Notes need for longer observation window for downstream metrics"
],
"files": []
},
{
"id": 6,
"prompt": "Can you help me write copy for our new landing page? We want to test it against the current version.",
"expected_output": "Should recognize this is primarily a copywriting task, not a test setup task. Should defer to or cross-reference the copywriting skill for writing the actual copy. May help frame the test hypothesis and setup, but should make clear that copywriting is the right skill for creating the page copy itself.",
"assertions": [
"Recognizes this as primarily a copywriting task",
"References or defers to copywriting skill",
"Does not attempt to write full page copy using test setup patterns",
"May offer to help with test hypothesis and setup"
],
"files": []
},
{
"id": 7,
"prompt": "We ran an A/B test on our pricing page for 4 weeks. Control: 2.1% conversion. Variant: 2.4% conversion. 12,000 visitors per variant. Is this statistically significant? Should we ship it?",
"expected_output": "Should evaluate the results against statistical significance criteria. Should calculate or estimate whether the sample size is sufficient to detect a 0.3 percentage point lift from a 2.1% baseline (this is a ~14% relative lift). Should reference the 95% confidence threshold. Should discuss practical significance vs statistical significance. Should recommend whether to ship, continue testing, or iterate. Should consider segment analysis if results are borderline.",
"assertions": [
"Evaluates against statistical significance criteria",
"Addresses whether sample size is sufficient for this effect size",
"References 95% confidence threshold",
"Distinguishes statistical significance from practical significance",
"Provides clear recommendation on shipping",
"Suggests segment analysis or follow-up if borderline"
],
"files": []
}
]
}
FILE:references/sample-size-guide.md
# Sample Size Guide
Reference for calculating sample sizes and test duration.
## Contents
- Sample Size Fundamentals (required inputs, what these mean)
- Sample Size Quick Reference Tables
- Duration Calculator (formula, examples, minimum duration rules, maximum duration guidelines)
- Online Calculators
- Adjusting for Multiple Variants
- Common Sample Size Mistakes
- When Sample Size Requirements Are Too High
- Sequential Testing
- Quick Decision Framework
## Sample Size Fundamentals
### Required Inputs
1. **Baseline conversion rate**: Your current rate
2. **Minimum detectable effect (MDE)**: Smallest change worth detecting
3. **Statistical significance level**: Usually 95% (α = 0.05)
4. **Statistical power**: Usually 80% (β = 0.20)
### What These Mean
**Baseline conversion rate**: If your page converts at 5%, that's your baseline.
**MDE (Minimum Detectable Effect)**: The smallest improvement you care about detecting. Set this based on:
- Business impact (is a 5% lift meaningful?)
- Implementation cost (worth the effort?)
- Realistic expectations (what have past tests shown?)
**Statistical significance (95%)**: Means there's less than 5% chance the observed difference is due to random chance.
**Statistical power (80%)**: Means if there's a real effect of size MDE, you have 80% chance of detecting it.
---
## Sample Size Quick Reference Tables
### Conversion Rate: 1%
| Lift to Detect | Sample per Variant | Total Sample |
|----------------|-------------------|--------------|
| 5% (1% → 1.05%) | 1,500,000 | 3,000,000 |
| 10% (1% → 1.1%) | 380,000 | 760,000 |
| 20% (1% → 1.2%) | 97,000 | 194,000 |
| 50% (1% → 1.5%) | 16,000 | 32,000 |
| 100% (1% → 2%) | 4,200 | 8,400 |
### Conversion Rate: 3%
| Lift to Detect | Sample per Variant | Total Sample |
|----------------|-------------------|--------------|
| 5% (3% → 3.15%) | 480,000 | 960,000 |
| 10% (3% → 3.3%) | 120,000 | 240,000 |
| 20% (3% → 3.6%) | 31,000 | 62,000 |
| 50% (3% → 4.5%) | 5,200 | 10,400 |
| 100% (3% → 6%) | 1,400 | 2,800 |
### Conversion Rate: 5%
| Lift to Detect | Sample per Variant | Total Sample |
|----------------|-------------------|--------------|
| 5% (5% → 5.25%) | 280,000 | 560,000 |
| 10% (5% → 5.5%) | 72,000 | 144,000 |
| 20% (5% → 6%) | 18,000 | 36,000 |
| 50% (5% → 7.5%) | 3,100 | 6,200 |
| 100% (5% → 10%) | 810 | 1,620 |
### Conversion Rate: 10%
| Lift to Detect | Sample per Variant | Total Sample |
|----------------|-------------------|--------------|
| 5% (10% → 10.5%) | 130,000 | 260,000 |
| 10% (10% → 11%) | 34,000 | 68,000 |
| 20% (10% → 12%) | 8,700 | 17,400 |
| 50% (10% → 15%) | 1,500 | 3,000 |
| 100% (10% → 20%) | 400 | 800 |
### Conversion Rate: 20%
| Lift to Detect | Sample per Variant | Total Sample |
|----------------|-------------------|--------------|
| 5% (20% → 21%) | 60,000 | 120,000 |
| 10% (20% → 22%) | 16,000 | 32,000 |
| 20% (20% → 24%) | 4,000 | 8,000 |
| 50% (20% → 30%) | 700 | 1,400 |
| 100% (20% → 40%) | 200 | 400 |
---
## Duration Calculator
### Formula
```
Duration (days) = (Sample per variant × Number of variants) / (Daily traffic × % exposed)
```
### Examples
**Scenario 1: High-traffic page**
- Need: 10,000 per variant (2 variants = 20,000 total)
- Daily traffic: 5,000 visitors
- 100% exposed to test
- Duration: 20,000 / 5,000 = **4 days**
**Scenario 2: Medium-traffic page**
- Need: 30,000 per variant (60,000 total)
- Daily traffic: 2,000 visitors
- 100% exposed
- Duration: 60,000 / 2,000 = **30 days**
**Scenario 3: Low-traffic with partial exposure**
- Need: 15,000 per variant (30,000 total)
- Daily traffic: 500 visitors
- 50% exposed to test
- Effective daily: 250
- Duration: 30,000 / 250 = **120 days** (too long!)
### Minimum Duration Rules
Even with sufficient sample size, run tests for at least:
- **1 full week**: To capture day-of-week variation
- **2 business cycles**: If B2B (weekday vs. weekend patterns)
- **Through paydays**: If e-commerce (beginning/end of month)
### Maximum Duration Guidelines
Avoid running tests longer than 4-8 weeks:
- Novelty effects wear off
- External factors intervene
- Opportunity cost of other tests
---
## Online Calculators
### Recommended Tools
**Evan Miller's Calculator**
https://www.evanmiller.org/ab-testing/sample-size.html
- Simple interface
- Bookmark-worthy
**Optimizely's Calculator**
https://www.optimizely.com/sample-size-calculator/
- Business-friendly language
- Duration estimates
**AB Test Guide Calculator**
https://www.abtestguide.com/calc/
- Includes Bayesian option
- Multiple test types
**VWO Duration Calculator**
https://vwo.com/tools/ab-test-duration-calculator/
- Duration-focused
- Good for planning
---
## Adjusting for Multiple Variants
With more than 2 variants (A/B/n tests), you need more sample:
| Variants | Multiplier |
|----------|------------|
| 2 (A/B) | 1x |
| 3 (A/B/C) | ~1.5x |
| 4 (A/B/C/D) | ~2x |
| 5+ | Consider reducing variants |
**Why?** More comparisons increase chance of false positives. You're comparing:
- A vs B
- A vs C
- B vs C (sometimes)
Apply Bonferroni correction or use tools that handle this automatically.
---
## Common Sample Size Mistakes
### 1. Underpowered tests
**Problem**: Not enough sample to detect realistic effects
**Fix**: Be realistic about MDE, get more traffic, or don't test
### 2. Overpowered tests
**Problem**: Waiting for sample size when you already have significance
**Fix**: This is actually fine—you committed to sample size, honor it
### 3. Wrong baseline rate
**Problem**: Using wrong conversion rate for calculation
**Fix**: Use the specific metric and page, not site-wide averages
### 4. Ignoring segments
**Problem**: Calculating for full traffic, then analyzing segments
**Fix**: If you plan segment analysis, calculate sample for smallest segment
### 5. Testing too many things
**Problem**: Dividing traffic too many ways
**Fix**: Prioritize ruthlessly, run fewer concurrent tests
---
## When Sample Size Requirements Are Too High
Options when you can't get enough traffic:
1. **Increase MDE**: Accept only detecting larger effects (20%+ lift)
2. **Lower confidence**: Use 90% instead of 95% (risky, document it)
3. **Reduce variants**: Test only the most promising variant
4. **Combine traffic**: Test across multiple similar pages
5. **Test upstream**: Test earlier in funnel where traffic is higher
6. **Don't test**: Make decision based on qualitative data instead
7. **Longer test**: Accept longer duration (weeks/months)
---
## Sequential Testing
If you must check results before reaching sample size:
### What is it?
Statistical method that adjusts for multiple looks at data.
### When to use
- High-risk changes
- Need to stop bad variants early
- Time-sensitive decisions
### Tools that support it
- Optimizely (Stats Accelerator)
- VWO (SmartStats)
- PostHog (Bayesian approach)
### Tradeoff
- More flexibility to stop early
- Slightly larger sample size requirement
- More complex analysis
---
## Quick Decision Framework
### Can I run this test?
```
Daily traffic to page: _____
Baseline conversion rate: _____
MDE I care about: _____
Sample needed per variant: _____ (from tables above)
Days to run: Sample / Daily traffic = _____
If days > 60: Consider alternatives
If days > 30: Acceptable for high-impact tests
If days < 14: Likely feasible
If days < 7: Easy to run, consider running longer anyway
```
FILE:references/test-templates.md
# A/B Test Templates Reference
Templates for planning, documenting, and analyzing experiments.
## Contents
- Test Plan Template
- Results Documentation Template
- Test Repository Entry Template
- Quick Test Brief Template
- Stakeholder Update Template
- Experiment Prioritization Scorecard
- Hypothesis Bank Template
## Test Plan Template
```markdown
# A/B Test: [Name]
## Overview
- **Owner**: [Name]
- **Test ID**: [ID in testing tool]
- **Page/Feature**: [What's being tested]
- **Planned dates**: [Start] - [End]
## Hypothesis
Because [observation/data],
we believe [change]
will cause [expected outcome]
for [audience].
We'll know this is true when [metrics].
## Test Design
| Element | Details |
|---------|---------|
| Test type | A/B / A/B/n / MVT |
| Duration | X weeks |
| Sample size | X per variant |
| Traffic allocation | 50/50 |
| Tool | [Tool name] |
| Implementation | Client-side / Server-side |
## Variants
### Control (A)
[Screenshot]
- Current experience
- [Key details about current state]
### Variant (B)
[Screenshot or mockup]
- [Specific change #1]
- [Specific change #2]
- Rationale: [Why we think this will win]
## Metrics
### Primary
- **Metric**: [metric name]
- **Definition**: [how it's calculated]
- **Current baseline**: [X%]
- **Minimum detectable effect**: [X%]
### Secondary
- [Metric 1]: [what it tells us]
- [Metric 2]: [what it tells us]
- [Metric 3]: [what it tells us]
### Guardrails
- [Metric that shouldn't get worse]
- [Another safety metric]
## Segment Analysis Plan
- Mobile vs. desktop
- New vs. returning visitors
- Traffic source
- [Other relevant segments]
## Success Criteria
- Winner: [Primary metric improves by X% with 95% confidence]
- Loser: [Primary metric decreases significantly]
- Inconclusive: [What we'll do if no significant result]
## Pre-Launch Checklist
- [ ] Hypothesis documented and reviewed
- [ ] Primary metric defined and trackable
- [ ] Sample size calculated
- [ ] Test duration estimated
- [ ] Variants implemented correctly
- [ ] Tracking verified in all variants
- [ ] QA completed on all variants
- [ ] Stakeholders informed
- [ ] Calendar hold for analysis date
```
---
## Results Documentation Template
```markdown
# A/B Test Results: [Name]
## Summary
| Element | Value |
|---------|-------|
| Test ID | [ID] |
| Dates | [Start] - [End] |
| Duration | X days |
| Result | Winner / Loser / Inconclusive |
| Decision | [What we're doing] |
## Hypothesis (Reminder)
[Copy from test plan]
## Results
### Sample Size
| Variant | Target | Actual | % of target |
|---------|--------|--------|-------------|
| Control | X | Y | Z% |
| Variant | X | Y | Z% |
### Primary Metric: [Metric Name]
| Variant | Value | 95% CI | vs. Control |
|---------|-------|--------|-------------|
| Control | X% | [X%, Y%] | — |
| Variant | X% | [X%, Y%] | +X% |
**Statistical significance**: p = X.XX (95% = sig / not sig)
**Practical significance**: [Is this lift meaningful for the business?]
### Secondary Metrics
| Metric | Control | Variant | Change | Significant? |
|--------|---------|---------|--------|--------------|
| [Metric 1] | X | Y | +Z% | Yes/No |
| [Metric 2] | X | Y | +Z% | Yes/No |
### Guardrail Metrics
| Metric | Control | Variant | Change | Concern? |
|--------|---------|---------|--------|----------|
| [Metric 1] | X | Y | +Z% | Yes/No |
### Segment Analysis
**Mobile vs. Desktop**
| Segment | Control | Variant | Lift |
|---------|---------|---------|------|
| Mobile | X% | Y% | +Z% |
| Desktop | X% | Y% | +Z% |
**New vs. Returning**
| Segment | Control | Variant | Lift |
|---------|---------|---------|------|
| New | X% | Y% | +Z% |
| Returning | X% | Y% | +Z% |
## Interpretation
### What happened?
[Explanation of results in plain language]
### Why do we think this happened?
[Analysis and reasoning]
### Caveats
[Any limitations, external factors, or concerns]
## Decision
**Winner**: [Control / Variant]
**Action**: [Implement variant / Keep control / Re-test]
**Timeline**: [When changes will be implemented]
## Learnings
### What we learned
- [Key insight 1]
- [Key insight 2]
### What to test next
- [Follow-up test idea 1]
- [Follow-up test idea 2]
### Impact
- **Projected lift**: [X% improvement in Y metric]
- **Business impact**: [Revenue, conversions, etc.]
```
---
## Test Repository Entry Template
For tracking all tests in a central location:
```markdown
| Test ID | Name | Page | Dates | Primary Metric | Result | Lift | Link |
|---------|------|------|-------|----------------|--------|------|------|
| 001 | Hero headline test | Homepage | 1/1-1/15 | CTR | Winner | +12% | [Link] |
| 002 | Pricing table layout | Pricing | 1/10-1/31 | Plan selection | Loser | -5% | [Link] |
| 003 | Signup form fields | Signup | 2/1-2/14 | Completion | Inconclusive | +2% | [Link] |
```
---
## Quick Test Brief Template
For simple tests that don't need full documentation:
```markdown
## [Test Name]
**What**: [One sentence description]
**Why**: [One sentence hypothesis]
**Metric**: [Primary metric]
**Duration**: [X weeks]
**Result**: [TBD / Winner / Loser / Inconclusive]
**Learnings**: [Key takeaway]
```
---
## Stakeholder Update Template
```markdown
## A/B Test Update: [Name]
**Status**: Running / Complete
**Days remaining**: X (or complete)
**Current sample**: X% of target
### Preliminary observations
[What we're seeing - without making decisions yet]
### Next steps
[What happens next]
### Timeline
- [Date]: Analysis complete
- [Date]: Decision and recommendation
- [Date]: Implementation (if winner)
```
---
## Experiment Prioritization Scorecard
For deciding which tests to run:
| Factor | Weight | Test A | Test B | Test C |
|--------|--------|--------|--------|--------|
| Potential impact | 30% | | | |
| Confidence in hypothesis | 25% | | | |
| Ease of implementation | 20% | | | |
| Risk if wrong | 15% | | | |
| Strategic alignment | 10% | | | |
| **Total** | | | | |
Scoring: 1-5 (5 = best)
---
## Hypothesis Bank Template
For collecting test ideas:
```markdown
| ID | Page/Area | Observation | Hypothesis | Potential Impact | Status |
|----|-----------|-------------|------------|------------------|--------|
| H1 | Homepage | Low scroll depth | Shorter hero will increase scroll | High | Testing |
| H2 | Pricing | Users compare plans | Comparison table will help | Medium | Backlog |
| H3 | Signup | Drop-off at email | Social login will increase completion | Medium | Backlog |
```