refactor: split 21 over-500-line skills into SKILL.md + references (#296)

This commit is contained in:
Alireza Rezvani
2026-03-08 10:14:30 +01:00
committed by GitHub
parent e7081583fb
commit fea994eb42
50 changed files with 7133 additions and 6511 deletions

View File

@@ -369,204 +369,7 @@ Status page: {link}
- **{Pitfall}:** {description and how to avoid} - **{Pitfall}:** {description and how to avoid}
## Reference Information ## Reference Information
- **Architecture Diagram:** {link} → See references/reference-information.md for details
- **Monitoring Dashboard:** {link}
- **Related Runbooks:** {links to dependent service runbooks}
```
### Post-Incident Review (PIR) Framework
#### PIR Timeline and Ownership
**Timeline:**
- **24 hours:** Initial PIR draft completed by Incident Commander
- **3 business days:** Final PIR published with all stakeholder input
- **1 week:** Action items assigned with owners and due dates
- **4 weeks:** Follow-up review on action item progress
**Roles:**
- **PIR Owner:** Incident Commander (can delegate writing but owns completion)
- **Technical Contributors:** All engineers involved in response
- **Review Committee:** Engineering leadership, affected product teams
- **Action Item Owners:** Assigned based on expertise and capacity
#### Root Cause Analysis Frameworks
#### 1. Five Whys Method
The Five Whys technique involves asking "why" repeatedly to drill down to root causes:
**Example Application:**
- **Problem:** Database became unresponsive during peak traffic
- **Why 1:** Why did the database become unresponsive? → Connection pool was exhausted
- **Why 2:** Why was the connection pool exhausted? → Application was creating more connections than usual
- **Why 3:** Why was the application creating more connections? → New feature wasn't properly connection pooling
- **Why 4:** Why wasn't the feature properly connection pooling? → Code review missed this pattern
- **Why 5:** Why did code review miss this? → No automated checks for connection pooling patterns
**Best Practices:**
- Ask "why" at least 3 times, often need 5+ iterations
- Focus on process failures, not individual blame
- Each "why" should point to a actionable system improvement
- Consider multiple root cause paths, not just one linear chain
#### 2. Fishbone (Ishikawa) Diagram
Systematic analysis across multiple categories of potential causes:
**Categories:**
- **People:** Training, experience, communication, handoffs
- **Process:** Procedures, change management, review processes
- **Technology:** Architecture, tooling, monitoring, automation
- **Environment:** Infrastructure, dependencies, external factors
**Application Method:**
1. State the problem clearly at the "head" of the fishbone
2. For each category, brainstorm potential contributing factors
3. For each factor, ask what caused that factor (sub-causes)
4. Identify the factors most likely to be root causes
5. Validate root causes with evidence from the incident
#### 3. Timeline Analysis
Reconstruct the incident chronologically to identify decision points and missed opportunities:
**Timeline Elements:**
- **Detection:** When was the issue first observable? When was it first detected?
- **Notification:** How quickly were the right people informed?
- **Response:** What actions were taken and how effective were they?
- **Communication:** When were stakeholders updated?
- **Resolution:** What finally resolved the issue?
**Analysis Questions:**
- Where were there delays and what caused them?
- What decisions would we make differently with perfect information?
- Where did communication break down?
- What automation could have detected/resolved faster?
### Escalation Paths
#### Technical Escalation
**Level 1:** On-call engineer
- **Responsibility:** Initial response and common issue resolution
- **Escalation Trigger:** Issue not resolved within SLA timeframe
- **Timeframe:** 15 minutes (SEV1), 30 minutes (SEV2)
**Level 2:** Senior engineer/Team lead
- **Responsibility:** Complex technical issues requiring deeper expertise
- **Escalation Trigger:** Level 1 requests help or timeout occurs
- **Timeframe:** 30 minutes (SEV1), 1 hour (SEV2)
**Level 3:** Engineering Manager/Staff Engineer
- **Responsibility:** Cross-team coordination and architectural decisions
- **Escalation Trigger:** Issue spans multiple systems or teams
- **Timeframe:** 45 minutes (SEV1), 2 hours (SEV2)
**Level 4:** Director of Engineering/CTO
- **Responsibility:** Resource allocation and business impact decisions
- **Escalation Trigger:** Extended outage or significant business impact
- **Timeframe:** 1 hour (SEV1), 4 hours (SEV2)
#### Business Escalation
**Customer Impact Assessment:**
- **High:** Revenue loss, SLA breaches, customer churn risk
- **Medium:** User experience degradation, support ticket volume
- **Low:** Internal tools, development impact only
**Escalation Matrix:**
| Severity | Duration | Business Escalation |
|----------|----------|-------------------|
| SEV1 | Immediate | VP Engineering |
| SEV1 | 30 minutes | CTO + Customer Success VP |
| SEV1 | 1 hour | CEO + Full Executive Team |
| SEV2 | 2 hours | VP Engineering |
| SEV2 | 4 hours | CTO |
| SEV3 | 1 business day | Engineering Manager |
### Status Page Management
#### Update Principles
1. **Transparency:** Provide factual information without speculation
2. **Timeliness:** Update within committed timeframes
3. **Clarity:** Use customer-friendly language, avoid technical jargon
4. **Completeness:** Include impact scope, status, and next update time
#### Status Categories
- **Operational:** All systems functioning normally
- **Degraded Performance:** Some users may experience slowness
- **Partial Outage:** Subset of features unavailable
- **Major Outage:** Service unavailable for most/all users
- **Under Maintenance:** Planned maintenance window
#### Update Template
```
{Timestamp} - {Status Category}
{Brief description of current state}
Impact: {who is affected and how}
Cause: {root cause if known, "under investigation" if not}
Resolution: {what's being done to fix it}
Next update: {specific time}
We apologize for any inconvenience this may cause.
```
### Action Item Framework
#### Action Item Categories
1. **Immediate Fixes**
- Critical bugs discovered during incident
- Security vulnerabilities exposed
- Data integrity issues
2. **Process Improvements**
- Communication gaps
- Escalation procedure updates
- Runbook additions/updates
3. **Technical Debt**
- Architecture improvements
- Monitoring enhancements
- Automation opportunities
4. **Organizational Changes**
- Team structure adjustments
- Training requirements
- Tool/platform investments
#### Action Item Template
```
**Title:** {Concise description of the action}
**Priority:** {Critical/High/Medium/Low}
**Category:** {Fix/Process/Technical/Organizational}
**Owner:** {Assigned person}
**Due Date:** {Specific date}
**Success Criteria:** {How will we know this is complete}
**Dependencies:** {What needs to happen first}
**Related PIRs:** {Links to other incidents this addresses}
**Description:**
{Detailed description of what needs to be done and why}
**Implementation Plan:**
1. {Step 1}
2. {Step 2}
3. {Validation step}
**Progress Updates:**
- {Date}: {Progress update}
- {Date}: {Progress update}
```
## Usage Examples ## Usage Examples

View File

@@ -0,0 +1,201 @@
# incident-commander reference
## Reference Information
- **Architecture Diagram:** {link}
- **Monitoring Dashboard:** {link}
- **Related Runbooks:** {links to dependent service runbooks}
```
### Post-Incident Review (PIR) Framework
#### PIR Timeline and Ownership
**Timeline:**
- **24 hours:** Initial PIR draft completed by Incident Commander
- **3 business days:** Final PIR published with all stakeholder input
- **1 week:** Action items assigned with owners and due dates
- **4 weeks:** Follow-up review on action item progress
**Roles:**
- **PIR Owner:** Incident Commander (can delegate writing but owns completion)
- **Technical Contributors:** All engineers involved in response
- **Review Committee:** Engineering leadership, affected product teams
- **Action Item Owners:** Assigned based on expertise and capacity
#### Root Cause Analysis Frameworks
#### 1. Five Whys Method
The Five Whys technique involves asking "why" repeatedly to drill down to root causes:
**Example Application:**
- **Problem:** Database became unresponsive during peak traffic
- **Why 1:** Why did the database become unresponsive? → Connection pool was exhausted
- **Why 2:** Why was the connection pool exhausted? → Application was creating more connections than usual
- **Why 3:** Why was the application creating more connections? → New feature wasn't properly connection pooling
- **Why 4:** Why wasn't the feature properly connection pooling? → Code review missed this pattern
- **Why 5:** Why did code review miss this? → No automated checks for connection pooling patterns
**Best Practices:**
- Ask "why" at least 3 times, often need 5+ iterations
- Focus on process failures, not individual blame
- Each "why" should point to a actionable system improvement
- Consider multiple root cause paths, not just one linear chain
#### 2. Fishbone (Ishikawa) Diagram
Systematic analysis across multiple categories of potential causes:
**Categories:**
- **People:** Training, experience, communication, handoffs
- **Process:** Procedures, change management, review processes
- **Technology:** Architecture, tooling, monitoring, automation
- **Environment:** Infrastructure, dependencies, external factors
**Application Method:**
1. State the problem clearly at the "head" of the fishbone
2. For each category, brainstorm potential contributing factors
3. For each factor, ask what caused that factor (sub-causes)
4. Identify the factors most likely to be root causes
5. Validate root causes with evidence from the incident
#### 3. Timeline Analysis
Reconstruct the incident chronologically to identify decision points and missed opportunities:
**Timeline Elements:**
- **Detection:** When was the issue first observable? When was it first detected?
- **Notification:** How quickly were the right people informed?
- **Response:** What actions were taken and how effective were they?
- **Communication:** When were stakeholders updated?
- **Resolution:** What finally resolved the issue?
**Analysis Questions:**
- Where were there delays and what caused them?
- What decisions would we make differently with perfect information?
- Where did communication break down?
- What automation could have detected/resolved faster?
### Escalation Paths
#### Technical Escalation
**Level 1:** On-call engineer
- **Responsibility:** Initial response and common issue resolution
- **Escalation Trigger:** Issue not resolved within SLA timeframe
- **Timeframe:** 15 minutes (SEV1), 30 minutes (SEV2)
**Level 2:** Senior engineer/Team lead
- **Responsibility:** Complex technical issues requiring deeper expertise
- **Escalation Trigger:** Level 1 requests help or timeout occurs
- **Timeframe:** 30 minutes (SEV1), 1 hour (SEV2)
**Level 3:** Engineering Manager/Staff Engineer
- **Responsibility:** Cross-team coordination and architectural decisions
- **Escalation Trigger:** Issue spans multiple systems or teams
- **Timeframe:** 45 minutes (SEV1), 2 hours (SEV2)
**Level 4:** Director of Engineering/CTO
- **Responsibility:** Resource allocation and business impact decisions
- **Escalation Trigger:** Extended outage or significant business impact
- **Timeframe:** 1 hour (SEV1), 4 hours (SEV2)
#### Business Escalation
**Customer Impact Assessment:**
- **High:** Revenue loss, SLA breaches, customer churn risk
- **Medium:** User experience degradation, support ticket volume
- **Low:** Internal tools, development impact only
**Escalation Matrix:**
| Severity | Duration | Business Escalation |
|----------|----------|-------------------|
| SEV1 | Immediate | VP Engineering |
| SEV1 | 30 minutes | CTO + Customer Success VP |
| SEV1 | 1 hour | CEO + Full Executive Team |
| SEV2 | 2 hours | VP Engineering |
| SEV2 | 4 hours | CTO |
| SEV3 | 1 business day | Engineering Manager |
### Status Page Management
#### Update Principles
1. **Transparency:** Provide factual information without speculation
2. **Timeliness:** Update within committed timeframes
3. **Clarity:** Use customer-friendly language, avoid technical jargon
4. **Completeness:** Include impact scope, status, and next update time
#### Status Categories
- **Operational:** All systems functioning normally
- **Degraded Performance:** Some users may experience slowness
- **Partial Outage:** Subset of features unavailable
- **Major Outage:** Service unavailable for most/all users
- **Under Maintenance:** Planned maintenance window
#### Update Template
```
{Timestamp} - {Status Category}
{Brief description of current state}
Impact: {who is affected and how}
Cause: {root cause if known, "under investigation" if not}
Resolution: {what's being done to fix it}
Next update: {specific time}
We apologize for any inconvenience this may cause.
```
### Action Item Framework
#### Action Item Categories
1. **Immediate Fixes**
- Critical bugs discovered during incident
- Security vulnerabilities exposed
- Data integrity issues
2. **Process Improvements**
- Communication gaps
- Escalation procedure updates
- Runbook additions/updates
3. **Technical Debt**
- Architecture improvements
- Monitoring enhancements
- Automation opportunities
4. **Organizational Changes**
- Team structure adjustments
- Training requirements
- Tool/platform investments
#### Action Item Template
```
**Title:** {Concise description of the action}
**Priority:** {Critical/High/Medium/Low}
**Category:** {Fix/Process/Technical/Organizational}
**Owner:** {Assigned person}
**Due Date:** {Specific date}
**Success Criteria:** {How will we know this is complete}
**Dependencies:** {What needs to happen first}
**Related PIRs:** {Links to other incidents this addresses}
**Description:**
{Detailed description of what needs to be done and why}
**Implementation Plan:**
1. {Step 1}
2. {Step 2}
3. {Validation step}
**Progress Updates:**
- {Date}: {Progress update}
- {Date}: {Progress update}
```

View File

@@ -9,18 +9,5 @@
"homepage": "https://github.com/alirezarezvani/claude-skills/tree/main/engineering-team/playwright-pro", "homepage": "https://github.com/alirezarezvani/claude-skills/tree/main/engineering-team/playwright-pro",
"repository": "https://github.com/alirezarezvani/claude-skills", "repository": "https://github.com/alirezarezvani/claude-skills",
"license": "MIT", "license": "MIT",
"keywords": [ "skills": "./"
"playwright",
"testing",
"e2e",
"qa",
"browserstack",
"testrail",
"test-automation",
"cross-browser",
"migration",
"cypress",
"selenium"
],
"skills": "./skills"
} }

View File

@@ -419,99 +419,7 @@ python scripts/dataset_pipeline_builder.py data/final/ \
| Positional encoding | Implicit | Explicit | | Positional encoding | Implicit | Explicit |
## Reference Documentation ## Reference Documentation
→ See references/reference-docs-and-commands.md for details
### 1. Computer Vision Architectures
See `references/computer_vision_architectures.md` for:
- CNN backbone architectures (ResNet, EfficientNet, ConvNeXt)
- Vision Transformer variants (ViT, DeiT, Swin)
- Detection heads (anchor-based vs anchor-free)
- Feature Pyramid Networks (FPN, BiFPN, PANet)
- Neck architectures for multi-scale detection
### 2. Object Detection Optimization
See `references/object_detection_optimization.md` for:
- Non-Maximum Suppression variants (NMS, Soft-NMS, DIoU-NMS)
- Anchor optimization and anchor-free alternatives
- Loss function design (focal loss, GIoU, CIoU, DIoU)
- Training strategies (warmup, cosine annealing, EMA)
- Data augmentation for detection (mosaic, mixup, copy-paste)
### 3. Production Vision Systems
See `references/production_vision_systems.md` for:
- ONNX export and optimization
- TensorRT deployment pipeline
- Batch inference optimization
- Edge device deployment (Jetson, Intel NCS)
- Model serving with Triton
- Video processing pipelines
## Common Commands
### Ultralytics YOLO
```bash
# Training
yolo detect train data=coco.yaml model=yolov8m.pt epochs=100 imgsz=640
# Validation
yolo detect val model=best.pt data=coco.yaml
# Inference
yolo detect predict model=best.pt source=images/ save=True
# Export
yolo export model=best.pt format=onnx simplify=True dynamic=True
```
### Detectron2
```bash
# Training
python train_net.py --config-file configs/COCO-Detection/faster_rcnn_R_50_FPN_3x.yaml \
--num-gpus 1 OUTPUT_DIR ./output
# Evaluation
python train_net.py --config-file configs/faster_rcnn.yaml --eval-only \
MODEL.WEIGHTS output/model_final.pth
# Inference
python demo.py --config-file configs/faster_rcnn.yaml \
--input images/*.jpg --output results/ \
--opts MODEL.WEIGHTS output/model_final.pth
```
### MMDetection
```bash
# Training
python tools/train.py configs/faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py
# Testing
python tools/test.py configs/faster_rcnn.py checkpoints/latest.pth --eval bbox
# Inference
python demo/image_demo.py demo.jpg configs/faster_rcnn.py checkpoints/latest.pth
```
### Model Optimization
```bash
# ONNX export and simplify
python -c "import torch; model = torch.load('model.pt'); torch.onnx.export(model, torch.randn(1,3,640,640), 'model.onnx', opset_version=17)"
python -m onnxsim model.onnx model_sim.onnx
# TensorRT conversion
trtexec --onnx=model.onnx --saveEngine=model.engine --fp16 --workspace=4096
# Benchmark
trtexec --loadEngine=model.engine --batch=1 --iterations=1000 --avgRuns=100
```
## Performance Targets ## Performance Targets

View File

@@ -0,0 +1,96 @@
# senior-computer-vision reference
## Reference Documentation
### 1. Computer Vision Architectures
See `references/computer_vision_architectures.md` for:
- CNN backbone architectures (ResNet, EfficientNet, ConvNeXt)
- Vision Transformer variants (ViT, DeiT, Swin)
- Detection heads (anchor-based vs anchor-free)
- Feature Pyramid Networks (FPN, BiFPN, PANet)
- Neck architectures for multi-scale detection
### 2. Object Detection Optimization
See `references/object_detection_optimization.md` for:
- Non-Maximum Suppression variants (NMS, Soft-NMS, DIoU-NMS)
- Anchor optimization and anchor-free alternatives
- Loss function design (focal loss, GIoU, CIoU, DIoU)
- Training strategies (warmup, cosine annealing, EMA)
- Data augmentation for detection (mosaic, mixup, copy-paste)
### 3. Production Vision Systems
See `references/production_vision_systems.md` for:
- ONNX export and optimization
- TensorRT deployment pipeline
- Batch inference optimization
- Edge device deployment (Jetson, Intel NCS)
- Model serving with Triton
- Video processing pipelines
## Common Commands
### Ultralytics YOLO
```bash
# Training
yolo detect train data=coco.yaml model=yolov8m.pt epochs=100 imgsz=640
# Validation
yolo detect val model=best.pt data=coco.yaml
# Inference
yolo detect predict model=best.pt source=images/ save=True
# Export
yolo export model=best.pt format=onnx simplify=True dynamic=True
```
### Detectron2
```bash
# Training
python train_net.py --config-file configs/COCO-Detection/faster_rcnn_R_50_FPN_3x.yaml \
--num-gpus 1 OUTPUT_DIR ./output
# Evaluation
python train_net.py --config-file configs/faster_rcnn.yaml --eval-only \
MODEL.WEIGHTS output/model_final.pth
# Inference
python demo.py --config-file configs/faster_rcnn.yaml \
--input images/*.jpg --output results/ \
--opts MODEL.WEIGHTS output/model_final.pth
```
### MMDetection
```bash
# Training
python tools/train.py configs/faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py
# Testing
python tools/test.py configs/faster_rcnn.py checkpoints/latest.pth --eval bbox
# Inference
python demo/image_demo.py demo.jpg configs/faster_rcnn.py checkpoints/latest.pth
```
### Model Optimization
```bash
# ONNX export and simplify
python -c "import torch; model = torch.load('model.pt'); torch.onnx.export(model, torch.randn(1,3,640,640), 'model.onnx', opset_version=17)"
python -m onnxsim model.onnx model_sim.onnx
# TensorRT conversion
trtexec --onnx=model.onnx --saveEngine=model.engine --fp16 --workspace=4096
# Benchmark
trtexec --loadEngine=model.engine --batch=1 --iterations=1000 --avgRuns=100
```

View File

@@ -86,627 +86,7 @@ python scripts/etl_performance_optimizer.py analyze \
--- ---
## Workflows ## Workflows
→ See references/workflows.md for details
### Workflow 1: Building a Batch ETL Pipeline
**Scenario:** Extract data from PostgreSQL, transform with dbt, load to Snowflake.
#### Step 1: Define Source Schema
```sql
-- Document source tables
SELECT
table_name,
column_name,
data_type,
is_nullable
FROM information_schema.columns
WHERE table_schema = 'source_schema'
ORDER BY table_name, ordinal_position;
```
#### Step 2: Generate Extraction Config
```bash
python scripts/pipeline_orchestrator.py generate \
--type airflow \
--source postgres \
--tables orders,customers,products \
--mode incremental \
--watermark updated_at \
--output dags/extract_source.py
```
#### Step 3: Create dbt Models
```sql
-- models/staging/stg_orders.sql
WITH source AS (
SELECT * FROM {{ source('postgres', 'orders') }}
),
renamed AS (
SELECT
order_id,
customer_id,
order_date,
total_amount,
status,
_extracted_at
FROM source
WHERE order_date >= DATEADD(day, -3, CURRENT_DATE)
)
SELECT * FROM renamed
```
```sql
-- models/marts/fct_orders.sql
{{
config(
materialized='incremental',
unique_key='order_id',
cluster_by=['order_date']
)
}}
SELECT
o.order_id,
o.customer_id,
c.customer_segment,
o.order_date,
o.total_amount,
o.status
FROM {{ ref('stg_orders') }} o
LEFT JOIN {{ ref('dim_customers') }} c
ON o.customer_id = c.customer_id
{% if is_incremental() %}
WHERE o._extracted_at > (SELECT MAX(_extracted_at) FROM {{ this }})
{% endif %}
```
#### Step 4: Configure Data Quality Tests
```yaml
# models/marts/schema.yml
version: 2
models:
- name: "fct-orders"
description: "Order fact table"
columns:
- name: "order-id"
tests:
- unique
- not_null
- name: "total-amount"
tests:
- not_null
- dbt_utils.accepted_range:
min_value: 0
max_value: 1000000
- name: "order-date"
tests:
- not_null
- dbt_utils.recency:
datepart: day
field: order_date
interval: 1
```
#### Step 5: Create Airflow DAG
```python
# dags/daily_etl.py
from airflow import DAG
from airflow.providers.postgres.operators.postgres import PostgresOperator
from airflow.operators.bash import BashOperator
from airflow.utils.dates import days_ago
from datetime import timedelta
default_args = {
'owner': 'data-team',
'depends_on_past': False,
'email_on_failure': True,
'email': ['data-alerts@company.com'],
'retries': 2,
'retry_delay': timedelta(minutes=5),
}
with DAG(
'daily_etl_pipeline',
default_args=default_args,
description='Daily ETL from PostgreSQL to Snowflake',
schedule_interval='0 5 * * *',
start_date=days_ago(1),
catchup=False,
tags=['etl', 'daily'],
) as dag:
extract = BashOperator(
task_id='extract_source_data',
bash_command='python /opt/airflow/scripts/extract.py --date {{ ds }}',
)
transform = BashOperator(
task_id='run_dbt_models',
bash_command='cd /opt/airflow/dbt && dbt run --select marts.*',
)
test = BashOperator(
task_id='run_dbt_tests',
bash_command='cd /opt/airflow/dbt && dbt test --select marts.*',
)
notify = BashOperator(
task_id='send_notification',
bash_command='python /opt/airflow/scripts/notify.py --status success',
trigger_rule='all_success',
)
extract >> transform >> test >> notify
```
#### Step 6: Validate Pipeline
```bash
# Test locally
dbt run --select stg_orders fct_orders
dbt test --select fct_orders
# Validate data quality
python scripts/data_quality_validator.py validate \
--table fct_orders \
--checks all \
--output reports/quality_report.json
```
---
### Workflow 2: Implementing Real-Time Streaming
**Scenario:** Stream events from Kafka, process with Flink/Spark Streaming, sink to data lake.
#### Step 1: Define Event Schema
```json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "UserEvent",
"type": "object",
"required": ["event_id", "user_id", "event_type", "timestamp"],
"properties": {
"event_id": {"type": "string", "format": "uuid"},
"user_id": {"type": "string"},
"event_type": {"type": "string", "enum": ["page_view", "click", "purchase"]},
"timestamp": {"type": "string", "format": "date-time"},
"properties": {"type": "object"}
}
}
```
#### Step 2: Create Kafka Topic
```bash
# Create topic with appropriate partitions
kafka-topics.sh --create \
--bootstrap-server localhost:9092 \
--topic user-events \
--partitions 12 \
--replication-factor 3 \
--config retention.ms=604800000 \
--config cleanup.policy=delete
# Verify topic
kafka-topics.sh --describe \
--bootstrap-server localhost:9092 \
--topic user-events
```
#### Step 3: Implement Spark Streaming Job
```python
# streaming/user_events_processor.py
from pyspark.sql import SparkSession
from pyspark.sql.functions import (
from_json, col, window, count, avg,
to_timestamp, current_timestamp
)
from pyspark.sql.types import (
StructType, StructField, StringType,
TimestampType, MapType
)
# Initialize Spark
spark = SparkSession.builder \
.appName("UserEventsProcessor") \
.config("spark.sql.streaming.checkpointLocation", "/checkpoints/user-events") \
.config("spark.sql.shuffle.partitions", "12") \
.getOrCreate()
# Define schema
event_schema = StructType([
StructField("event_id", StringType(), False),
StructField("user_id", StringType(), False),
StructField("event_type", StringType(), False),
StructField("timestamp", StringType(), False),
StructField("properties", MapType(StringType(), StringType()), True)
])
# Read from Kafka
events_df = spark.readStream \
.format("kafka") \
.option("kafka.bootstrap.servers", "localhost:9092") \
.option("subscribe", "user-events") \
.option("startingOffsets", "latest") \
.option("failOnDataLoss", "false") \
.load()
# Parse JSON
parsed_df = events_df \
.select(from_json(col("value").cast("string"), event_schema).alias("data")) \
.select("data.*") \
.withColumn("event_timestamp", to_timestamp(col("timestamp")))
# Windowed aggregation
aggregated_df = parsed_df \
.withWatermark("event_timestamp", "10 minutes") \
.groupBy(
window(col("event_timestamp"), "5 minutes"),
col("event_type")
) \
.agg(
count("*").alias("event_count"),
approx_count_distinct("user_id").alias("unique_users")
)
# Write to Delta Lake
query = aggregated_df.writeStream \
.format("delta") \
.outputMode("append") \
.option("checkpointLocation", "/checkpoints/user-events-aggregated") \
.option("path", "/data/lake/user_events_aggregated") \
.trigger(processingTime="1 minute") \
.start()
query.awaitTermination()
```
#### Step 4: Handle Late Data and Errors
```python
# Dead letter queue for failed records
from pyspark.sql.functions import current_timestamp, lit
def process_with_error_handling(batch_df, batch_id):
try:
# Attempt processing
valid_df = batch_df.filter(col("event_id").isNotNull())
invalid_df = batch_df.filter(col("event_id").isNull())
# Write valid records
valid_df.write \
.format("delta") \
.mode("append") \
.save("/data/lake/user_events")
# Write invalid to DLQ
if invalid_df.count() > 0:
invalid_df \
.withColumn("error_timestamp", current_timestamp()) \
.withColumn("error_reason", lit("missing_event_id")) \
.write \
.format("delta") \
.mode("append") \
.save("/data/lake/dlq/user_events")
except Exception as e:
# Log error, alert, continue
logger.error(f"Batch {batch_id} failed: {e}")
raise
# Use foreachBatch for custom processing
query = parsed_df.writeStream \
.foreachBatch(process_with_error_handling) \
.option("checkpointLocation", "/checkpoints/user-events") \
.start()
```
#### Step 5: Monitor Stream Health
```python
# monitoring/stream_metrics.py
from prometheus_client import Gauge, Counter, start_http_server
# Define metrics
RECORDS_PROCESSED = Counter(
'stream_records_processed_total',
'Total records processed',
['stream_name', 'status']
)
PROCESSING_LAG = Gauge(
'stream_processing_lag_seconds',
'Current processing lag',
['stream_name']
)
BATCH_DURATION = Gauge(
'stream_batch_duration_seconds',
'Last batch processing duration',
['stream_name']
)
def emit_metrics(query):
"""Emit Prometheus metrics from streaming query."""
progress = query.lastProgress
if progress:
RECORDS_PROCESSED.labels(
stream_name='user-events',
status='success'
).inc(progress['numInputRows'])
if progress['sources']:
# Calculate lag from latest offset
for source in progress['sources']:
end_offset = source.get('endOffset', {})
# Parse Kafka offsets and calculate lag
```
---
### Workflow 3: Data Quality Framework Setup
**Scenario:** Implement comprehensive data quality monitoring with Great Expectations.
#### Step 1: Initialize Great Expectations
```bash
# Install and initialize
pip install great_expectations
great_expectations init
# Connect to data source
great_expectations datasource new
```
#### Step 2: Create Expectation Suite
```python
# expectations/orders_suite.py
import great_expectations as gx
context = gx.get_context()
# Create expectation suite
suite = context.add_expectation_suite("orders_quality_suite")
# Add expectations
validator = context.get_validator(
batch_request={
"datasource_name": "warehouse",
"data_asset_name": "orders",
},
expectation_suite_name="orders_quality_suite"
)
# Schema expectations
validator.expect_table_columns_to_match_ordered_list(
column_list=[
"order_id", "customer_id", "order_date",
"total_amount", "status", "created_at"
]
)
# Completeness expectations
validator.expect_column_values_to_not_be_null("order_id")
validator.expect_column_values_to_not_be_null("customer_id")
validator.expect_column_values_to_not_be_null("order_date")
# Uniqueness expectations
validator.expect_column_values_to_be_unique("order_id")
# Range expectations
validator.expect_column_values_to_be_between(
"total_amount",
min_value=0,
max_value=1000000
)
# Categorical expectations
validator.expect_column_values_to_be_in_set(
"status",
["pending", "confirmed", "shipped", "delivered", "cancelled"]
)
# Freshness expectation
validator.expect_column_max_to_be_between(
"order_date",
min_value={"$PARAMETER": "now - timedelta(days=1)"},
max_value={"$PARAMETER": "now"}
)
# Referential integrity
validator.expect_column_values_to_be_in_set(
"customer_id",
value_set={"$PARAMETER": "valid_customer_ids"}
)
validator.save_expectation_suite(discard_failed_expectations=False)
```
#### Step 3: Create Data Quality Checks with dbt
```yaml
# models/marts/schema.yml
version: 2
models:
- name: "fct-orders"
description: "Order fact table with data quality checks"
tests:
# Row count check
- dbt_utils.equal_rowcount:
compare_model: ref('stg_orders')
# Freshness check
- dbt_utils.recency:
datepart: hour
field: created_at
interval: 24
columns:
- name: "order-id"
description: "Unique order identifier"
tests:
- unique
- not_null
- relationships:
to: ref('dim_orders')
field: order_id
- name: "total-amount"
tests:
- not_null
- dbt_utils.accepted_range:
min_value: 0
max_value: 1000000
inclusive: true
- dbt_expectations.expect_column_values_to_be_between:
min_value: 0
row_condition: "status != 'cancelled'"
- name: "customer-id"
tests:
- not_null
- relationships:
to: ref('dim_customers')
field: customer_id
severity: warn
```
#### Step 4: Implement Data Contracts
```yaml
# contracts/orders_contract.yaml
contract:
name: "orders-data-contract"
version: "1.0.0"
owner: data-team@company.com
schema:
type: object
properties:
order_id:
type: string
format: uuid
description: "Unique order identifier"
customer_id:
type: string
not_null: true
order_date:
type: date
not_null: true
total_amount:
type: decimal
precision: 10
scale: 2
minimum: 0
status:
type: string
enum: ["pending", "confirmed", "shipped", "delivered", "cancelled"]
sla:
freshness:
max_delay_hours: 1
completeness:
min_percentage: 99.9
accuracy:
duplicate_tolerance: 0.01
consumers:
- name: "analytics-team"
usage: "Daily reporting dashboards"
- name: "ml-team"
usage: "Churn prediction model"
```
#### Step 5: Set Up Quality Monitoring Dashboard
```python
# monitoring/quality_dashboard.py
from datetime import datetime, timedelta
import pandas as pd
def generate_quality_report(connection, table_name: "str-dict"
"""Generate comprehensive data quality report."""
report = {
"table": table_name,
"timestamp": datetime.now().isoformat(),
"checks": {}
}
# Row count check
row_count = connection.execute(
f"SELECT COUNT(*) FROM {table_name}"
).fetchone()[0]
report["checks"]["row_count"] = {
"value": row_count,
"status": "pass" if row_count > 0 else "fail"
}
# Freshness check
max_date = connection.execute(
f"SELECT MAX(created_at) FROM {table_name}"
).fetchone()[0]
hours_old = (datetime.now() - max_date).total_seconds() / 3600
report["checks"]["freshness"] = {
"max_timestamp": max_date.isoformat(),
"hours_old": round(hours_old, 2),
"status": "pass" if hours_old < 24 else "fail"
}
# Null rate check
null_query = f"""
SELECT
SUM(CASE WHEN order_id IS NULL THEN 1 ELSE 0 END) as null_order_id,
SUM(CASE WHEN customer_id IS NULL THEN 1 ELSE 0 END) as null_customer_id,
COUNT(*) as total
FROM {table_name}
"""
null_result = connection.execute(null_query).fetchone()
report["checks"]["null_rates"] = {
"order_id": null_result[0] / null_result[2] if null_result[2] > 0 else 0,
"customer_id": null_result[1] / null_result[2] if null_result[2] > 0 else 0,
"status": "pass" if null_result[0] == 0 and null_result[1] == 0 else "fail"
}
# Duplicate check
dup_query = f"""
SELECT COUNT(*) - COUNT(DISTINCT order_id) as duplicates
FROM {table_name}
"""
duplicates = connection.execute(dup_query).fetchone()[0]
report["checks"]["duplicates"] = {
"count": duplicates,
"status": "pass" if duplicates == 0 else "fail"
}
# Overall status
all_passed = all(
check["status"] == "pass"
for check in report["checks"].values()
)
report["overall_status"] = "pass" if all_passed else "fail"
return report
```
---
## Architecture Decision Framework ## Architecture Decision Framework
@@ -810,183 +190,5 @@ See `references/dataops_best_practices.md` for:
--- ---
## Troubleshooting ## Troubleshooting
→ See references/troubleshooting.md for details
### Pipeline Failures
**Symptom:** Airflow DAG fails with timeout
```
Task exceeded max execution time
```
**Solution:**
1. Check resource allocation
2. Profile slow operations
3. Add incremental processing
```python
# Increase timeout
default_args = {
'execution_timeout': timedelta(hours=2),
}
# Or use incremental loads
WHERE updated_at > '{{ prev_ds }}'
```
---
**Symptom:** Spark job OOM
```
java.lang.OutOfMemoryError: Java heap space
```
**Solution:**
1. Increase executor memory
2. Reduce partition size
3. Use disk spill
```python
spark.conf.set("spark.executor.memory", "8g")
spark.conf.set("spark.sql.shuffle.partitions", "200")
spark.conf.set("spark.memory.fraction", "0.8")
```
---
**Symptom:** Kafka consumer lag increasing
```
Consumer lag: 1000000 messages
```
**Solution:**
1. Increase consumer parallelism
2. Optimize processing logic
3. Scale consumer group
```bash
# Add more partitions
kafka-topics.sh --alter \
--bootstrap-server localhost:9092 \
--topic user-events \
--partitions 24
```
---
### Data Quality Issues
**Symptom:** Duplicate records appearing
```
Expected unique, found 150 duplicates
```
**Solution:**
1. Add deduplication logic
2. Use merge/upsert operations
```sql
-- dbt incremental with dedup
{{
config(
materialized='incremental',
unique_key='order_id'
)
}}
SELECT * FROM (
SELECT
*,
ROW_NUMBER() OVER (
PARTITION BY order_id
ORDER BY updated_at DESC
) as rn
FROM {{ source('raw', 'orders') }}
) WHERE rn = 1
```
---
**Symptom:** Stale data in tables
```
Last update: 3 days ago
```
**Solution:**
1. Check upstream pipeline status
2. Verify source availability
3. Add freshness monitoring
```yaml
# dbt freshness check
sources:
- name: "raw"
freshness:
warn_after: {count: 12, period: hour}
error_after: {count: 24, period: hour}
loaded_at_field: _loaded_at
```
---
**Symptom:** Schema drift detected
```
Column 'new_field' not in expected schema
```
**Solution:**
1. Update data contract
2. Modify transformations
3. Communicate with producers
```python
# Handle schema evolution
df = spark.read.format("delta") \
.option("mergeSchema", "true") \
.load("/data/orders")
```
---
### Performance Issues
**Symptom:** Query takes hours
```
Query runtime: 4 hours (expected: 30 minutes)
```
**Solution:**
1. Check query plan
2. Add proper partitioning
3. Optimize joins
```sql
-- Before: Full table scan
SELECT * FROM orders WHERE order_date = '2024-01-15';
-- After: Partition pruning
-- Table partitioned by order_date
SELECT * FROM orders WHERE order_date = '2024-01-15';
-- Add clustering for frequent filters
ALTER TABLE orders CLUSTER BY (customer_id);
```
---
**Symptom:** dbt model takes too long
```
Model fct_orders completed in 45 minutes
```
**Solution:**
1. Use incremental materialization
2. Reduce upstream dependencies
3. Pre-aggregate where possible
```sql
-- Convert to incremental
{{
config(
materialized='incremental',
unique_key='order_id',
on_schema_change='sync_all_columns'
)
}}
SELECT * FROM {{ ref('stg_orders') }}
{% if is_incremental() %}
WHERE _loaded_at > (SELECT MAX(_loaded_at) FROM {{ this }})
{% endif %}
```

View File

@@ -0,0 +1,183 @@
# senior-data-engineer reference
## Troubleshooting
### Pipeline Failures
**Symptom:** Airflow DAG fails with timeout
```
Task exceeded max execution time
```
**Solution:**
1. Check resource allocation
2. Profile slow operations
3. Add incremental processing
```python
# Increase timeout
default_args = {
'execution_timeout': timedelta(hours=2),
}
# Or use incremental loads
WHERE updated_at > '{{ prev_ds }}'
```
---
**Symptom:** Spark job OOM
```
java.lang.OutOfMemoryError: Java heap space
```
**Solution:**
1. Increase executor memory
2. Reduce partition size
3. Use disk spill
```python
spark.conf.set("spark.executor.memory", "8g")
spark.conf.set("spark.sql.shuffle.partitions", "200")
spark.conf.set("spark.memory.fraction", "0.8")
```
---
**Symptom:** Kafka consumer lag increasing
```
Consumer lag: 1000000 messages
```
**Solution:**
1. Increase consumer parallelism
2. Optimize processing logic
3. Scale consumer group
```bash
# Add more partitions
kafka-topics.sh --alter \
--bootstrap-server localhost:9092 \
--topic user-events \
--partitions 24
```
---
### Data Quality Issues
**Symptom:** Duplicate records appearing
```
Expected unique, found 150 duplicates
```
**Solution:**
1. Add deduplication logic
2. Use merge/upsert operations
```sql
-- dbt incremental with dedup
{{
config(
materialized='incremental',
unique_key='order_id'
)
}}
SELECT * FROM (
SELECT
*,
ROW_NUMBER() OVER (
PARTITION BY order_id
ORDER BY updated_at DESC
) as rn
FROM {{ source('raw', 'orders') }}
) WHERE rn = 1
```
---
**Symptom:** Stale data in tables
```
Last update: 3 days ago
```
**Solution:**
1. Check upstream pipeline status
2. Verify source availability
3. Add freshness monitoring
```yaml
# dbt freshness check
sources:
- name: "raw"
freshness:
warn_after: {count: 12, period: hour}
error_after: {count: 24, period: hour}
loaded_at_field: _loaded_at
```
---
**Symptom:** Schema drift detected
```
Column 'new_field' not in expected schema
```
**Solution:**
1. Update data contract
2. Modify transformations
3. Communicate with producers
```python
# Handle schema evolution
df = spark.read.format("delta") \
.option("mergeSchema", "true") \
.load("/data/orders")
```
---
### Performance Issues
**Symptom:** Query takes hours
```
Query runtime: 4 hours (expected: 30 minutes)
```
**Solution:**
1. Check query plan
2. Add proper partitioning
3. Optimize joins
```sql
-- Before: Full table scan
SELECT * FROM orders WHERE order_date = '2024-01-15';
-- After: Partition pruning
-- Table partitioned by order_date
SELECT * FROM orders WHERE order_date = '2024-01-15';
-- Add clustering for frequent filters
ALTER TABLE orders CLUSTER BY (customer_id);
```
---
**Symptom:** dbt model takes too long
```
Model fct_orders completed in 45 minutes
```
**Solution:**
1. Use incremental materialization
2. Reduce upstream dependencies
3. Pre-aggregate where possible
```sql
-- Convert to incremental
{{
config(
materialized='incremental',
unique_key='order_id',
on_schema_change='sync_all_columns'
)
}}
SELECT * FROM {{ ref('stg_orders') }}
{% if is_incremental() %}
WHERE _loaded_at > (SELECT MAX(_loaded_at) FROM {{ this }})
{% endif %}
```

View File

@@ -0,0 +1,624 @@
# senior-data-engineer reference
## Workflows
### Workflow 1: Building a Batch ETL Pipeline
**Scenario:** Extract data from PostgreSQL, transform with dbt, load to Snowflake.
#### Step 1: Define Source Schema
```sql
-- Document source tables
SELECT
table_name,
column_name,
data_type,
is_nullable
FROM information_schema.columns
WHERE table_schema = 'source_schema'
ORDER BY table_name, ordinal_position;
```
#### Step 2: Generate Extraction Config
```bash
python scripts/pipeline_orchestrator.py generate \
--type airflow \
--source postgres \
--tables orders,customers,products \
--mode incremental \
--watermark updated_at \
--output dags/extract_source.py
```
#### Step 3: Create dbt Models
```sql
-- models/staging/stg_orders.sql
WITH source AS (
SELECT * FROM {{ source('postgres', 'orders') }}
),
renamed AS (
SELECT
order_id,
customer_id,
order_date,
total_amount,
status,
_extracted_at
FROM source
WHERE order_date >= DATEADD(day, -3, CURRENT_DATE)
)
SELECT * FROM renamed
```
```sql
-- models/marts/fct_orders.sql
{{
config(
materialized='incremental',
unique_key='order_id',
cluster_by=['order_date']
)
}}
SELECT
o.order_id,
o.customer_id,
c.customer_segment,
o.order_date,
o.total_amount,
o.status
FROM {{ ref('stg_orders') }} o
LEFT JOIN {{ ref('dim_customers') }} c
ON o.customer_id = c.customer_id
{% if is_incremental() %}
WHERE o._extracted_at > (SELECT MAX(_extracted_at) FROM {{ this }})
{% endif %}
```
#### Step 4: Configure Data Quality Tests
```yaml
# models/marts/schema.yml
version: 2
models:
- name: "fct-orders"
description: "Order fact table"
columns:
- name: "order-id"
tests:
- unique
- not_null
- name: "total-amount"
tests:
- not_null
- dbt_utils.accepted_range:
min_value: 0
max_value: 1000000
- name: "order-date"
tests:
- not_null
- dbt_utils.recency:
datepart: day
field: order_date
interval: 1
```
#### Step 5: Create Airflow DAG
```python
# dags/daily_etl.py
from airflow import DAG
from airflow.providers.postgres.operators.postgres import PostgresOperator
from airflow.operators.bash import BashOperator
from airflow.utils.dates import days_ago
from datetime import timedelta
default_args = {
'owner': 'data-team',
'depends_on_past': False,
'email_on_failure': True,
'email': ['data-alerts@company.com'],
'retries': 2,
'retry_delay': timedelta(minutes=5),
}
with DAG(
'daily_etl_pipeline',
default_args=default_args,
description='Daily ETL from PostgreSQL to Snowflake',
schedule_interval='0 5 * * *',
start_date=days_ago(1),
catchup=False,
tags=['etl', 'daily'],
) as dag:
extract = BashOperator(
task_id='extract_source_data',
bash_command='python /opt/airflow/scripts/extract.py --date {{ ds }}',
)
transform = BashOperator(
task_id='run_dbt_models',
bash_command='cd /opt/airflow/dbt && dbt run --select marts.*',
)
test = BashOperator(
task_id='run_dbt_tests',
bash_command='cd /opt/airflow/dbt && dbt test --select marts.*',
)
notify = BashOperator(
task_id='send_notification',
bash_command='python /opt/airflow/scripts/notify.py --status success',
trigger_rule='all_success',
)
extract >> transform >> test >> notify
```
#### Step 6: Validate Pipeline
```bash
# Test locally
dbt run --select stg_orders fct_orders
dbt test --select fct_orders
# Validate data quality
python scripts/data_quality_validator.py validate \
--table fct_orders \
--checks all \
--output reports/quality_report.json
```
---
### Workflow 2: Implementing Real-Time Streaming
**Scenario:** Stream events from Kafka, process with Flink/Spark Streaming, sink to data lake.
#### Step 1: Define Event Schema
```json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "UserEvent",
"type": "object",
"required": ["event_id", "user_id", "event_type", "timestamp"],
"properties": {
"event_id": {"type": "string", "format": "uuid"},
"user_id": {"type": "string"},
"event_type": {"type": "string", "enum": ["page_view", "click", "purchase"]},
"timestamp": {"type": "string", "format": "date-time"},
"properties": {"type": "object"}
}
}
```
#### Step 2: Create Kafka Topic
```bash
# Create topic with appropriate partitions
kafka-topics.sh --create \
--bootstrap-server localhost:9092 \
--topic user-events \
--partitions 12 \
--replication-factor 3 \
--config retention.ms=604800000 \
--config cleanup.policy=delete
# Verify topic
kafka-topics.sh --describe \
--bootstrap-server localhost:9092 \
--topic user-events
```
#### Step 3: Implement Spark Streaming Job
```python
# streaming/user_events_processor.py
from pyspark.sql import SparkSession
from pyspark.sql.functions import (
from_json, col, window, count, avg,
to_timestamp, current_timestamp
)
from pyspark.sql.types import (
StructType, StructField, StringType,
TimestampType, MapType
)
# Initialize Spark
spark = SparkSession.builder \
.appName("UserEventsProcessor") \
.config("spark.sql.streaming.checkpointLocation", "/checkpoints/user-events") \
.config("spark.sql.shuffle.partitions", "12") \
.getOrCreate()
# Define schema
event_schema = StructType([
StructField("event_id", StringType(), False),
StructField("user_id", StringType(), False),
StructField("event_type", StringType(), False),
StructField("timestamp", StringType(), False),
StructField("properties", MapType(StringType(), StringType()), True)
])
# Read from Kafka
events_df = spark.readStream \
.format("kafka") \
.option("kafka.bootstrap.servers", "localhost:9092") \
.option("subscribe", "user-events") \
.option("startingOffsets", "latest") \
.option("failOnDataLoss", "false") \
.load()
# Parse JSON
parsed_df = events_df \
.select(from_json(col("value").cast("string"), event_schema).alias("data")) \
.select("data.*") \
.withColumn("event_timestamp", to_timestamp(col("timestamp")))
# Windowed aggregation
aggregated_df = parsed_df \
.withWatermark("event_timestamp", "10 minutes") \
.groupBy(
window(col("event_timestamp"), "5 minutes"),
col("event_type")
) \
.agg(
count("*").alias("event_count"),
approx_count_distinct("user_id").alias("unique_users")
)
# Write to Delta Lake
query = aggregated_df.writeStream \
.format("delta") \
.outputMode("append") \
.option("checkpointLocation", "/checkpoints/user-events-aggregated") \
.option("path", "/data/lake/user_events_aggregated") \
.trigger(processingTime="1 minute") \
.start()
query.awaitTermination()
```
#### Step 4: Handle Late Data and Errors
```python
# Dead letter queue for failed records
from pyspark.sql.functions import current_timestamp, lit
def process_with_error_handling(batch_df, batch_id):
try:
# Attempt processing
valid_df = batch_df.filter(col("event_id").isNotNull())
invalid_df = batch_df.filter(col("event_id").isNull())
# Write valid records
valid_df.write \
.format("delta") \
.mode("append") \
.save("/data/lake/user_events")
# Write invalid to DLQ
if invalid_df.count() > 0:
invalid_df \
.withColumn("error_timestamp", current_timestamp()) \
.withColumn("error_reason", lit("missing_event_id")) \
.write \
.format("delta") \
.mode("append") \
.save("/data/lake/dlq/user_events")
except Exception as e:
# Log error, alert, continue
logger.error(f"Batch {batch_id} failed: {e}")
raise
# Use foreachBatch for custom processing
query = parsed_df.writeStream \
.foreachBatch(process_with_error_handling) \
.option("checkpointLocation", "/checkpoints/user-events") \
.start()
```
#### Step 5: Monitor Stream Health
```python
# monitoring/stream_metrics.py
from prometheus_client import Gauge, Counter, start_http_server
# Define metrics
RECORDS_PROCESSED = Counter(
'stream_records_processed_total',
'Total records processed',
['stream_name', 'status']
)
PROCESSING_LAG = Gauge(
'stream_processing_lag_seconds',
'Current processing lag',
['stream_name']
)
BATCH_DURATION = Gauge(
'stream_batch_duration_seconds',
'Last batch processing duration',
['stream_name']
)
def emit_metrics(query):
"""Emit Prometheus metrics from streaming query."""
progress = query.lastProgress
if progress:
RECORDS_PROCESSED.labels(
stream_name='user-events',
status='success'
).inc(progress['numInputRows'])
if progress['sources']:
# Calculate lag from latest offset
for source in progress['sources']:
end_offset = source.get('endOffset', {})
# Parse Kafka offsets and calculate lag
```
---
### Workflow 3: Data Quality Framework Setup
**Scenario:** Implement comprehensive data quality monitoring with Great Expectations.
#### Step 1: Initialize Great Expectations
```bash
# Install and initialize
pip install great_expectations
great_expectations init
# Connect to data source
great_expectations datasource new
```
#### Step 2: Create Expectation Suite
```python
# expectations/orders_suite.py
import great_expectations as gx
context = gx.get_context()
# Create expectation suite
suite = context.add_expectation_suite("orders_quality_suite")
# Add expectations
validator = context.get_validator(
batch_request={
"datasource_name": "warehouse",
"data_asset_name": "orders",
},
expectation_suite_name="orders_quality_suite"
)
# Schema expectations
validator.expect_table_columns_to_match_ordered_list(
column_list=[
"order_id", "customer_id", "order_date",
"total_amount", "status", "created_at"
]
)
# Completeness expectations
validator.expect_column_values_to_not_be_null("order_id")
validator.expect_column_values_to_not_be_null("customer_id")
validator.expect_column_values_to_not_be_null("order_date")
# Uniqueness expectations
validator.expect_column_values_to_be_unique("order_id")
# Range expectations
validator.expect_column_values_to_be_between(
"total_amount",
min_value=0,
max_value=1000000
)
# Categorical expectations
validator.expect_column_values_to_be_in_set(
"status",
["pending", "confirmed", "shipped", "delivered", "cancelled"]
)
# Freshness expectation
validator.expect_column_max_to_be_between(
"order_date",
min_value={"$PARAMETER": "now - timedelta(days=1)"},
max_value={"$PARAMETER": "now"}
)
# Referential integrity
validator.expect_column_values_to_be_in_set(
"customer_id",
value_set={"$PARAMETER": "valid_customer_ids"}
)
validator.save_expectation_suite(discard_failed_expectations=False)
```
#### Step 3: Create Data Quality Checks with dbt
```yaml
# models/marts/schema.yml
version: 2
models:
- name: "fct-orders"
description: "Order fact table with data quality checks"
tests:
# Row count check
- dbt_utils.equal_rowcount:
compare_model: ref('stg_orders')
# Freshness check
- dbt_utils.recency:
datepart: hour
field: created_at
interval: 24
columns:
- name: "order-id"
description: "Unique order identifier"
tests:
- unique
- not_null
- relationships:
to: ref('dim_orders')
field: order_id
- name: "total-amount"
tests:
- not_null
- dbt_utils.accepted_range:
min_value: 0
max_value: 1000000
inclusive: true
- dbt_expectations.expect_column_values_to_be_between:
min_value: 0
row_condition: "status != 'cancelled'"
- name: "customer-id"
tests:
- not_null
- relationships:
to: ref('dim_customers')
field: customer_id
severity: warn
```
#### Step 4: Implement Data Contracts
```yaml
# contracts/orders_contract.yaml
contract:
name: "orders-data-contract"
version: "1.0.0"
owner: data-team@company.com
schema:
type: object
properties:
order_id:
type: string
format: uuid
description: "Unique order identifier"
customer_id:
type: string
not_null: true
order_date:
type: date
not_null: true
total_amount:
type: decimal
precision: 10
scale: 2
minimum: 0
status:
type: string
enum: ["pending", "confirmed", "shipped", "delivered", "cancelled"]
sla:
freshness:
max_delay_hours: 1
completeness:
min_percentage: 99.9
accuracy:
duplicate_tolerance: 0.01
consumers:
- name: "analytics-team"
usage: "Daily reporting dashboards"
- name: "ml-team"
usage: "Churn prediction model"
```
#### Step 5: Set Up Quality Monitoring Dashboard
```python
# monitoring/quality_dashboard.py
from datetime import datetime, timedelta
import pandas as pd
def generate_quality_report(connection, table_name: "str-dict"
"""Generate comprehensive data quality report."""
report = {
"table": table_name,
"timestamp": datetime.now().isoformat(),
"checks": {}
}
# Row count check
row_count = connection.execute(
f"SELECT COUNT(*) FROM {table_name}"
).fetchone()[0]
report["checks"]["row_count"] = {
"value": row_count,
"status": "pass" if row_count > 0 else "fail"
}
# Freshness check
max_date = connection.execute(
f"SELECT MAX(created_at) FROM {table_name}"
).fetchone()[0]
hours_old = (datetime.now() - max_date).total_seconds() / 3600
report["checks"]["freshness"] = {
"max_timestamp": max_date.isoformat(),
"hours_old": round(hours_old, 2),
"status": "pass" if hours_old < 24 else "fail"
}
# Null rate check
null_query = f"""
SELECT
SUM(CASE WHEN order_id IS NULL THEN 1 ELSE 0 END) as null_order_id,
SUM(CASE WHEN customer_id IS NULL THEN 1 ELSE 0 END) as null_customer_id,
COUNT(*) as total
FROM {table_name}
"""
null_result = connection.execute(null_query).fetchone()
report["checks"]["null_rates"] = {
"order_id": null_result[0] / null_result[2] if null_result[2] > 0 else 0,
"customer_id": null_result[1] / null_result[2] if null_result[2] > 0 else 0,
"status": "pass" if null_result[0] == 0 and null_result[1] == 0 else "fail"
}
# Duplicate check
dup_query = f"""
SELECT COUNT(*) - COUNT(DISTINCT order_id) as duplicates
FROM {table_name}
"""
duplicates = connection.execute(dup_query).fetchone()[0]
report["checks"]["duplicates"] = {
"count": duplicates,
"status": "pass" if duplicates == 0 else "fail"
}
# Overall status
all_passed = all(
check["status"] == "pass"
for check in report["checks"].values()
)
report["overall_status"] = "pass" if all_passed else "fail"
return report
```
---

View File

@@ -135,511 +135,7 @@ For every POST/PUT/PATCH endpoint with a request body:
--- ---
## Example Test Files ## Example Test Files
→ See references/example-test-files.md for details
### Example 1 — Node.js: Vitest + Supertest (Next.js API Route)
```typescript
// tests/api/users.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
import request from 'supertest'
import { createServer } from '@/test/helpers/server'
import { generateJWT, generateExpiredJWT } from '@/test/helpers/auth'
import { createTestUser, cleanupTestUsers } from '@/test/helpers/db'
const app = createServer()
describe('GET /api/users/:id', () => {
let validToken: string
let adminToken: string
let testUserId: string
beforeAll(async () => {
const user = await createTestUser({ role: 'user' })
const admin = await createTestUser({ role: 'admin' })
testUserId = user.id
validToken = generateJWT(user)
adminToken = generateJWT(admin)
})
afterAll(async () => {
await cleanupTestUsers()
})
// --- Auth tests ---
it('returns 401 with no auth header', async () => {
const res = await request(app).get(`/api/users/${testUserId}`)
expect(res.status).toBe(401)
expect(res.body).toHaveProperty('error')
})
it('returns 401 with malformed token', async () => {
const res = await request(app)
.get(`/api/users/${testUserId}`)
.set('Authorization', 'Bearer not-a-real-jwt')
expect(res.status).toBe(401)
})
it('returns 401 with expired token', async () => {
const expiredToken = generateExpiredJWT({ id: testUserId })
const res = await request(app)
.get(`/api/users/${testUserId}`)
.set('Authorization', `Bearer ${expiredToken}`)
expect(res.status).toBe(401)
expect(res.body.error).toMatch(/expired/i)
})
it('returns 403 when accessing another user\'s profile without admin', async () => {
const otherUser = await createTestUser({ role: 'user' })
const otherToken = generateJWT(otherUser)
const res = await request(app)
.get(`/api/users/${testUserId}`)
.set('Authorization', `Bearer ${otherToken}`)
expect(res.status).toBe(403)
await cleanupTestUsers([otherUser.id])
})
it('returns 200 with valid token for own profile', async () => {
const res = await request(app)
.get(`/api/users/${testUserId}`)
.set('Authorization', `Bearer ${validToken}`)
expect(res.status).toBe(200)
expect(res.body).toMatchObject({ id: testUserId })
expect(res.body).not.toHaveProperty('password')
expect(res.body).not.toHaveProperty('hashedPassword')
})
it('returns 404 for non-existent user', async () => {
const res = await request(app)
.get('/api/users/00000000-0000-0000-0000-000000000000')
.set('Authorization', `Bearer ${adminToken}`)
expect(res.status).toBe(404)
})
// --- Input validation ---
it('returns 400 for invalid UUID format', async () => {
const res = await request(app)
.get('/api/users/not-a-uuid')
.set('Authorization', `Bearer ${adminToken}`)
expect(res.status).toBe(400)
})
})
describe('POST /api/users', () => {
let adminToken: string
beforeAll(async () => {
const admin = await createTestUser({ role: 'admin' })
adminToken = generateJWT(admin)
})
afterAll(cleanupTestUsers)
// --- Input validation ---
it('returns 422 when body is empty', async () => {
const res = await request(app)
.post('/api/users')
.set('Authorization', `Bearer ${adminToken}`)
.send({})
expect(res.status).toBe(422)
expect(res.body.errors).toBeDefined()
})
it('returns 422 when email is missing', async () => {
const res = await request(app)
.post('/api/users')
.set('Authorization', `Bearer ${adminToken}`)
.send({ name: "test-user", role: 'user' })
expect(res.status).toBe(422)
expect(res.body.errors).toContainEqual(
expect.objectContaining({ field: 'email' })
)
})
it('returns 422 for invalid email format', async () => {
const res = await request(app)
.post('/api/users')
.set('Authorization', `Bearer ${adminToken}`)
.send({ email: 'not-an-email', name: "test", role: 'user' })
expect(res.status).toBe(422)
})
it('returns 422 for SQL injection attempt in email field', async () => {
const res = await request(app)
.post('/api/users')
.set('Authorization', `Bearer ${adminToken}`)
.send({ email: "' OR '1'='1", name: "hacker", role: 'user' })
expect(res.status).toBe(422)
})
it('returns 409 when email already exists', async () => {
const existing = await createTestUser({ role: 'user' })
const res = await request(app)
.post('/api/users')
.set('Authorization', `Bearer ${adminToken}`)
.send({ email: existing.email, name: "duplicate", role: 'user' })
expect(res.status).toBe(409)
})
it('creates user successfully with valid data', async () => {
const res = await request(app)
.post('/api/users')
.set('Authorization', `Bearer ${adminToken}`)
.send({ email: 'newuser@example.com', name: "new-user", role: 'user' })
expect(res.status).toBe(201)
expect(res.body).toHaveProperty('id')
expect(res.body.email).toBe('newuser@example.com')
expect(res.body).not.toHaveProperty('password')
})
})
describe('GET /api/users (pagination)', () => {
let adminToken: string
beforeAll(async () => {
const admin = await createTestUser({ role: 'admin' })
adminToken = generateJWT(admin)
// Create 15 test users for pagination
await Promise.all(Array.from({ length: 15 }, (_, i) =>
createTestUser({ email: `pagtest${i}@example.com` })
))
})
afterAll(cleanupTestUsers)
it('returns first page with default limit', async () => {
const res = await request(app)
.get('/api/users')
.set('Authorization', `Bearer ${adminToken}`)
expect(res.status).toBe(200)
expect(res.body.data).toBeInstanceOf(Array)
expect(res.body).toHaveProperty('total')
expect(res.body).toHaveProperty('page')
expect(res.body).toHaveProperty('pageSize')
})
it('returns empty array for page beyond total', async () => {
const res = await request(app)
.get('/api/users?page=9999')
.set('Authorization', `Bearer ${adminToken}`)
expect(res.status).toBe(200)
expect(res.body.data).toHaveLength(0)
})
it('returns 400 for negative page number', async () => {
const res = await request(app)
.get('/api/users?page=-1')
.set('Authorization', `Bearer ${adminToken}`)
expect(res.status).toBe(400)
})
it('caps pageSize at maximum allowed value', async () => {
const res = await request(app)
.get('/api/users?pageSize=9999')
.set('Authorization', `Bearer ${adminToken}`)
expect(res.status).toBe(200)
expect(res.body.data.length).toBeLessThanOrEqual(100)
})
})
```
---
### Example 2 — Node.js: File Upload Tests
```typescript
// tests/api/uploads.test.ts
import { describe, it, expect } from 'vitest'
import request from 'supertest'
import path from 'path'
import fs from 'fs'
import { createServer } from '@/test/helpers/server'
import { generateJWT } from '@/test/helpers/auth'
import { createTestUser } from '@/test/helpers/db'
const app = createServer()
describe('POST /api/upload', () => {
let validToken: string
beforeAll(async () => {
const user = await createTestUser({ role: 'user' })
validToken = generateJWT(user)
})
it('returns 401 without authentication', async () => {
const res = await request(app)
.post('/api/upload')
.attach('file', Buffer.from('test'), 'test.pdf')
expect(res.status).toBe(401)
})
it('returns 400 when no file attached', async () => {
const res = await request(app)
.post('/api/upload')
.set('Authorization', `Bearer ${validToken}`)
expect(res.status).toBe(400)
expect(res.body.error).toMatch(/file/i)
})
it('returns 400 for unsupported file type (exe)', async () => {
const res = await request(app)
.post('/api/upload')
.set('Authorization', `Bearer ${validToken}`)
.attach('file', Buffer.from('MZ fake exe'), { filename: "virusexe", contentType: 'application/octet-stream' })
expect(res.status).toBe(400)
expect(res.body.error).toMatch(/type|format|allowed/i)
})
it('returns 413 for oversized file (>10MB)', async () => {
const largeBuf = Buffer.alloc(11 * 1024 * 1024) // 11MB
const res = await request(app)
.post('/api/upload')
.set('Authorization', `Bearer ${validToken}`)
.attach('file', largeBuf, { filename: "largepdf", contentType: 'application/pdf' })
expect(res.status).toBe(413)
})
it('returns 400 for empty file (0 bytes)', async () => {
const res = await request(app)
.post('/api/upload')
.set('Authorization', `Bearer ${validToken}`)
.attach('file', Buffer.alloc(0), { filename: "emptypdf", contentType: 'application/pdf' })
expect(res.status).toBe(400)
})
it('rejects MIME type spoofing (pdf extension but exe content)', async () => {
// Real malicious file: exe magic bytes but pdf extension
const fakeExe = Buffer.from('4D5A9000', 'hex') // MZ header
const res = await request(app)
.post('/api/upload')
.set('Authorization', `Bearer ${validToken}`)
.attach('file', fakeExe, { filename: "documentpdf", contentType: 'application/pdf' })
// Should detect magic bytes mismatch
expect([400, 415]).toContain(res.status)
})
it('accepts valid PDF file', async () => {
const pdfHeader = Buffer.from('%PDF-1.4 test content')
const res = await request(app)
.post('/api/upload')
.set('Authorization', `Bearer ${validToken}`)
.attach('file', pdfHeader, { filename: "validpdf", contentType: 'application/pdf' })
expect(res.status).toBe(200)
expect(res.body).toHaveProperty('url')
expect(res.body).toHaveProperty('id')
})
})
```
---
### Example 3 — Python: Pytest + httpx (FastAPI)
```python
# tests/api/test_items.py
import pytest
import httpx
from datetime import datetime, timedelta
import jwt
BASE_URL = "http://localhost:8000"
JWT_SECRET = "test-secret" # use test config, never production secret
def make_token(user_id: str, role: str = "user", expired: bool = False) -> str:
exp = datetime.utcnow() + (timedelta(hours=-1) if expired else timedelta(hours=1))
return jwt.encode(
{"sub": user_id, "role": role, "exp": exp},
JWT_SECRET,
algorithm="HS256",
)
@pytest.fixture
def client():
with httpx.Client(base_url=BASE_URL) as c:
yield c
@pytest.fixture
def valid_token():
return make_token("user-123", role="user")
@pytest.fixture
def admin_token():
return make_token("admin-456", role="admin")
@pytest.fixture
def expired_token():
return make_token("user-123", expired=True)
class TestGetItem:
def test_returns_401_without_auth(self, client):
res = client.get("/api/items/1")
assert res.status_code == 401
def test_returns_401_with_invalid_token(self, client):
res = client.get("/api/items/1", headers={"Authorization": "Bearer garbage"})
assert res.status_code == 401
def test_returns_401_with_expired_token(self, client, expired_token):
res = client.get("/api/items/1", headers={"Authorization": f"Bearer {expired_token}"})
assert res.status_code == 401
assert "expired" in res.json().get("detail", "").lower()
def test_returns_404_for_nonexistent_item(self, client, valid_token):
res = client.get(
"/api/items/99999999",
headers={"Authorization": f"Bearer {valid_token}"},
)
assert res.status_code == 404
def test_returns_400_for_invalid_id_format(self, client, valid_token):
res = client.get(
"/api/items/not-a-number",
headers={"Authorization": f"Bearer {valid_token}"},
)
assert res.status_code in (400, 422)
def test_returns_200_with_valid_auth(self, client, valid_token, test_item):
res = client.get(
f"/api/items/{test_item['id']}",
headers={"Authorization": f"Bearer {valid_token}"},
)
assert res.status_code == 200
data = res.json()
assert data["id"] == test_item["id"]
assert "password" not in data
class TestCreateItem:
def test_returns_422_with_empty_body(self, client, admin_token):
res = client.post(
"/api/items",
json={},
headers={"Authorization": f"Bearer {admin_token}"},
)
assert res.status_code == 422
errors = res.json()["detail"]
assert len(errors) > 0
def test_returns_422_with_missing_required_field(self, client, admin_token):
res = client.post(
"/api/items",
json={"description": "no name field"},
headers={"Authorization": f"Bearer {admin_token}"},
)
assert res.status_code == 422
fields = [e["loc"][-1] for e in res.json()["detail"]]
assert "name" in fields
def test_returns_422_with_wrong_type(self, client, admin_token):
res = client.post(
"/api/items",
json={"name": "test", "price": "not-a-number"},
headers={"Authorization": f"Bearer {admin_token}"},
)
assert res.status_code == 422
@pytest.mark.parametrize("price", [-1, -0.01])
def test_returns_422_for_negative_price(self, client, admin_token, price):
res = client.post(
"/api/items",
json={"name": "test", "price": price},
headers={"Authorization": f"Bearer {admin_token}"},
)
assert res.status_code == 422
def test_returns_422_for_price_exceeding_max(self, client, admin_token):
res = client.post(
"/api/items",
json={"name": "test", "price": 1_000_001},
headers={"Authorization": f"Bearer {admin_token}"},
)
assert res.status_code == 422
def test_creates_item_successfully(self, client, admin_token):
res = client.post(
"/api/items",
json={"name": "New Widget", "price": 9.99, "category": "tools"},
headers={"Authorization": f"Bearer {admin_token}"},
)
assert res.status_code == 201
data = res.json()
assert "id" in data
assert data["name"] == "New Widget"
def test_returns_403_for_non_admin(self, client, valid_token):
res = client.post(
"/api/items",
json={"name": "test", "price": 1.0},
headers={"Authorization": f"Bearer {valid_token}"},
)
assert res.status_code == 403
class TestPagination:
def test_returns_paginated_response(self, client, valid_token):
res = client.get(
"/api/items?page=1&size=10",
headers={"Authorization": f"Bearer {valid_token}"},
)
assert res.status_code == 200
data = res.json()
assert "items" in data
assert "total" in data
assert "page" in data
assert len(data["items"]) <= 10
def test_empty_result_for_out_of_range_page(self, client, valid_token):
res = client.get(
"/api/items?page=99999",
headers={"Authorization": f"Bearer {valid_token}"},
)
assert res.status_code == 200
assert res.json()["items"] == []
def test_returns_422_for_page_zero(self, client, valid_token):
res = client.get(
"/api/items?page=0",
headers={"Authorization": f"Bearer {valid_token}"},
)
assert res.status_code == 422
def test_caps_page_size_at_maximum(self, client, valid_token):
res = client.get(
"/api/items?size=9999",
headers={"Authorization": f"Bearer {valid_token}"},
)
assert res.status_code == 200
assert len(res.json()["items"]) <= 100 # max page size
class TestRateLimiting:
def test_rate_limit_after_burst(self, client, valid_token):
responses = []
for _ in range(60): # exceed typical 50/min limit
res = client.get(
"/api/items",
headers={"Authorization": f"Bearer {valid_token}"},
)
responses.append(res.status_code)
if res.status_code == 429:
break
assert 429 in responses, "Rate limit was not triggered"
def test_rate_limit_response_has_retry_after(self, client, valid_token):
for _ in range(60):
res = client.get("/api/items", headers={"Authorization": f"Bearer {valid_token}"})
if res.status_code == 429:
assert "Retry-After" in res.headers or "retry_after" in res.json()
break
```
---
## Generating Tests from Route Scan ## Generating Tests from Route Scan

View File

@@ -0,0 +1,508 @@
# api-test-suite-builder reference
## Example Test Files
### Example 1 — Node.js: Vitest + Supertest (Next.js API Route)
```typescript
// tests/api/users.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
import request from 'supertest'
import { createServer } from '@/test/helpers/server'
import { generateJWT, generateExpiredJWT } from '@/test/helpers/auth'
import { createTestUser, cleanupTestUsers } from '@/test/helpers/db'
const app = createServer()
describe('GET /api/users/:id', () => {
let validToken: string
let adminToken: string
let testUserId: string
beforeAll(async () => {
const user = await createTestUser({ role: 'user' })
const admin = await createTestUser({ role: 'admin' })
testUserId = user.id
validToken = generateJWT(user)
adminToken = generateJWT(admin)
})
afterAll(async () => {
await cleanupTestUsers()
})
// --- Auth tests ---
it('returns 401 with no auth header', async () => {
const res = await request(app).get(`/api/users/${testUserId}`)
expect(res.status).toBe(401)
expect(res.body).toHaveProperty('error')
})
it('returns 401 with malformed token', async () => {
const res = await request(app)
.get(`/api/users/${testUserId}`)
.set('Authorization', 'Bearer not-a-real-jwt')
expect(res.status).toBe(401)
})
it('returns 401 with expired token', async () => {
const expiredToken = generateExpiredJWT({ id: testUserId })
const res = await request(app)
.get(`/api/users/${testUserId}`)
.set('Authorization', `Bearer ${expiredToken}`)
expect(res.status).toBe(401)
expect(res.body.error).toMatch(/expired/i)
})
it('returns 403 when accessing another user\'s profile without admin', async () => {
const otherUser = await createTestUser({ role: 'user' })
const otherToken = generateJWT(otherUser)
const res = await request(app)
.get(`/api/users/${testUserId}`)
.set('Authorization', `Bearer ${otherToken}`)
expect(res.status).toBe(403)
await cleanupTestUsers([otherUser.id])
})
it('returns 200 with valid token for own profile', async () => {
const res = await request(app)
.get(`/api/users/${testUserId}`)
.set('Authorization', `Bearer ${validToken}`)
expect(res.status).toBe(200)
expect(res.body).toMatchObject({ id: testUserId })
expect(res.body).not.toHaveProperty('password')
expect(res.body).not.toHaveProperty('hashedPassword')
})
it('returns 404 for non-existent user', async () => {
const res = await request(app)
.get('/api/users/00000000-0000-0000-0000-000000000000')
.set('Authorization', `Bearer ${adminToken}`)
expect(res.status).toBe(404)
})
// --- Input validation ---
it('returns 400 for invalid UUID format', async () => {
const res = await request(app)
.get('/api/users/not-a-uuid')
.set('Authorization', `Bearer ${adminToken}`)
expect(res.status).toBe(400)
})
})
describe('POST /api/users', () => {
let adminToken: string
beforeAll(async () => {
const admin = await createTestUser({ role: 'admin' })
adminToken = generateJWT(admin)
})
afterAll(cleanupTestUsers)
// --- Input validation ---
it('returns 422 when body is empty', async () => {
const res = await request(app)
.post('/api/users')
.set('Authorization', `Bearer ${adminToken}`)
.send({})
expect(res.status).toBe(422)
expect(res.body.errors).toBeDefined()
})
it('returns 422 when email is missing', async () => {
const res = await request(app)
.post('/api/users')
.set('Authorization', `Bearer ${adminToken}`)
.send({ name: "test-user", role: 'user' })
expect(res.status).toBe(422)
expect(res.body.errors).toContainEqual(
expect.objectContaining({ field: 'email' })
)
})
it('returns 422 for invalid email format', async () => {
const res = await request(app)
.post('/api/users')
.set('Authorization', `Bearer ${adminToken}`)
.send({ email: 'not-an-email', name: "test", role: 'user' })
expect(res.status).toBe(422)
})
it('returns 422 for SQL injection attempt in email field', async () => {
const res = await request(app)
.post('/api/users')
.set('Authorization', `Bearer ${adminToken}`)
.send({ email: "' OR '1'='1", name: "hacker", role: 'user' })
expect(res.status).toBe(422)
})
it('returns 409 when email already exists', async () => {
const existing = await createTestUser({ role: 'user' })
const res = await request(app)
.post('/api/users')
.set('Authorization', `Bearer ${adminToken}`)
.send({ email: existing.email, name: "duplicate", role: 'user' })
expect(res.status).toBe(409)
})
it('creates user successfully with valid data', async () => {
const res = await request(app)
.post('/api/users')
.set('Authorization', `Bearer ${adminToken}`)
.send({ email: 'newuser@example.com', name: "new-user", role: 'user' })
expect(res.status).toBe(201)
expect(res.body).toHaveProperty('id')
expect(res.body.email).toBe('newuser@example.com')
expect(res.body).not.toHaveProperty('password')
})
})
describe('GET /api/users (pagination)', () => {
let adminToken: string
beforeAll(async () => {
const admin = await createTestUser({ role: 'admin' })
adminToken = generateJWT(admin)
// Create 15 test users for pagination
await Promise.all(Array.from({ length: 15 }, (_, i) =>
createTestUser({ email: `pagtest${i}@example.com` })
))
})
afterAll(cleanupTestUsers)
it('returns first page with default limit', async () => {
const res = await request(app)
.get('/api/users')
.set('Authorization', `Bearer ${adminToken}`)
expect(res.status).toBe(200)
expect(res.body.data).toBeInstanceOf(Array)
expect(res.body).toHaveProperty('total')
expect(res.body).toHaveProperty('page')
expect(res.body).toHaveProperty('pageSize')
})
it('returns empty array for page beyond total', async () => {
const res = await request(app)
.get('/api/users?page=9999')
.set('Authorization', `Bearer ${adminToken}`)
expect(res.status).toBe(200)
expect(res.body.data).toHaveLength(0)
})
it('returns 400 for negative page number', async () => {
const res = await request(app)
.get('/api/users?page=-1')
.set('Authorization', `Bearer ${adminToken}`)
expect(res.status).toBe(400)
})
it('caps pageSize at maximum allowed value', async () => {
const res = await request(app)
.get('/api/users?pageSize=9999')
.set('Authorization', `Bearer ${adminToken}`)
expect(res.status).toBe(200)
expect(res.body.data.length).toBeLessThanOrEqual(100)
})
})
```
---
### Example 2 — Node.js: File Upload Tests
```typescript
// tests/api/uploads.test.ts
import { describe, it, expect } from 'vitest'
import request from 'supertest'
import path from 'path'
import fs from 'fs'
import { createServer } from '@/test/helpers/server'
import { generateJWT } from '@/test/helpers/auth'
import { createTestUser } from '@/test/helpers/db'
const app = createServer()
describe('POST /api/upload', () => {
let validToken: string
beforeAll(async () => {
const user = await createTestUser({ role: 'user' })
validToken = generateJWT(user)
})
it('returns 401 without authentication', async () => {
const res = await request(app)
.post('/api/upload')
.attach('file', Buffer.from('test'), 'test.pdf')
expect(res.status).toBe(401)
})
it('returns 400 when no file attached', async () => {
const res = await request(app)
.post('/api/upload')
.set('Authorization', `Bearer ${validToken}`)
expect(res.status).toBe(400)
expect(res.body.error).toMatch(/file/i)
})
it('returns 400 for unsupported file type (exe)', async () => {
const res = await request(app)
.post('/api/upload')
.set('Authorization', `Bearer ${validToken}`)
.attach('file', Buffer.from('MZ fake exe'), { filename: "virusexe", contentType: 'application/octet-stream' })
expect(res.status).toBe(400)
expect(res.body.error).toMatch(/type|format|allowed/i)
})
it('returns 413 for oversized file (>10MB)', async () => {
const largeBuf = Buffer.alloc(11 * 1024 * 1024) // 11MB
const res = await request(app)
.post('/api/upload')
.set('Authorization', `Bearer ${validToken}`)
.attach('file', largeBuf, { filename: "largepdf", contentType: 'application/pdf' })
expect(res.status).toBe(413)
})
it('returns 400 for empty file (0 bytes)', async () => {
const res = await request(app)
.post('/api/upload')
.set('Authorization', `Bearer ${validToken}`)
.attach('file', Buffer.alloc(0), { filename: "emptypdf", contentType: 'application/pdf' })
expect(res.status).toBe(400)
})
it('rejects MIME type spoofing (pdf extension but exe content)', async () => {
// Real malicious file: exe magic bytes but pdf extension
const fakeExe = Buffer.from('4D5A9000', 'hex') // MZ header
const res = await request(app)
.post('/api/upload')
.set('Authorization', `Bearer ${validToken}`)
.attach('file', fakeExe, { filename: "documentpdf", contentType: 'application/pdf' })
// Should detect magic bytes mismatch
expect([400, 415]).toContain(res.status)
})
it('accepts valid PDF file', async () => {
const pdfHeader = Buffer.from('%PDF-1.4 test content')
const res = await request(app)
.post('/api/upload')
.set('Authorization', `Bearer ${validToken}`)
.attach('file', pdfHeader, { filename: "validpdf", contentType: 'application/pdf' })
expect(res.status).toBe(200)
expect(res.body).toHaveProperty('url')
expect(res.body).toHaveProperty('id')
})
})
```
---
### Example 3 — Python: Pytest + httpx (FastAPI)
```python
# tests/api/test_items.py
import pytest
import httpx
from datetime import datetime, timedelta
import jwt
BASE_URL = "http://localhost:8000"
JWT_SECRET = "test-secret" # use test config, never production secret
def make_token(user_id: str, role: str = "user", expired: bool = False) -> str:
exp = datetime.utcnow() + (timedelta(hours=-1) if expired else timedelta(hours=1))
return jwt.encode(
{"sub": user_id, "role": role, "exp": exp},
JWT_SECRET,
algorithm="HS256",
)
@pytest.fixture
def client():
with httpx.Client(base_url=BASE_URL) as c:
yield c
@pytest.fixture
def valid_token():
return make_token("user-123", role="user")
@pytest.fixture
def admin_token():
return make_token("admin-456", role="admin")
@pytest.fixture
def expired_token():
return make_token("user-123", expired=True)
class TestGetItem:
def test_returns_401_without_auth(self, client):
res = client.get("/api/items/1")
assert res.status_code == 401
def test_returns_401_with_invalid_token(self, client):
res = client.get("/api/items/1", headers={"Authorization": "Bearer garbage"})
assert res.status_code == 401
def test_returns_401_with_expired_token(self, client, expired_token):
res = client.get("/api/items/1", headers={"Authorization": f"Bearer {expired_token}"})
assert res.status_code == 401
assert "expired" in res.json().get("detail", "").lower()
def test_returns_404_for_nonexistent_item(self, client, valid_token):
res = client.get(
"/api/items/99999999",
headers={"Authorization": f"Bearer {valid_token}"},
)
assert res.status_code == 404
def test_returns_400_for_invalid_id_format(self, client, valid_token):
res = client.get(
"/api/items/not-a-number",
headers={"Authorization": f"Bearer {valid_token}"},
)
assert res.status_code in (400, 422)
def test_returns_200_with_valid_auth(self, client, valid_token, test_item):
res = client.get(
f"/api/items/{test_item['id']}",
headers={"Authorization": f"Bearer {valid_token}"},
)
assert res.status_code == 200
data = res.json()
assert data["id"] == test_item["id"]
assert "password" not in data
class TestCreateItem:
def test_returns_422_with_empty_body(self, client, admin_token):
res = client.post(
"/api/items",
json={},
headers={"Authorization": f"Bearer {admin_token}"},
)
assert res.status_code == 422
errors = res.json()["detail"]
assert len(errors) > 0
def test_returns_422_with_missing_required_field(self, client, admin_token):
res = client.post(
"/api/items",
json={"description": "no name field"},
headers={"Authorization": f"Bearer {admin_token}"},
)
assert res.status_code == 422
fields = [e["loc"][-1] for e in res.json()["detail"]]
assert "name" in fields
def test_returns_422_with_wrong_type(self, client, admin_token):
res = client.post(
"/api/items",
json={"name": "test", "price": "not-a-number"},
headers={"Authorization": f"Bearer {admin_token}"},
)
assert res.status_code == 422
@pytest.mark.parametrize("price", [-1, -0.01])
def test_returns_422_for_negative_price(self, client, admin_token, price):
res = client.post(
"/api/items",
json={"name": "test", "price": price},
headers={"Authorization": f"Bearer {admin_token}"},
)
assert res.status_code == 422
def test_returns_422_for_price_exceeding_max(self, client, admin_token):
res = client.post(
"/api/items",
json={"name": "test", "price": 1_000_001},
headers={"Authorization": f"Bearer {admin_token}"},
)
assert res.status_code == 422
def test_creates_item_successfully(self, client, admin_token):
res = client.post(
"/api/items",
json={"name": "New Widget", "price": 9.99, "category": "tools"},
headers={"Authorization": f"Bearer {admin_token}"},
)
assert res.status_code == 201
data = res.json()
assert "id" in data
assert data["name"] == "New Widget"
def test_returns_403_for_non_admin(self, client, valid_token):
res = client.post(
"/api/items",
json={"name": "test", "price": 1.0},
headers={"Authorization": f"Bearer {valid_token}"},
)
assert res.status_code == 403
class TestPagination:
def test_returns_paginated_response(self, client, valid_token):
res = client.get(
"/api/items?page=1&size=10",
headers={"Authorization": f"Bearer {valid_token}"},
)
assert res.status_code == 200
data = res.json()
assert "items" in data
assert "total" in data
assert "page" in data
assert len(data["items"]) <= 10
def test_empty_result_for_out_of_range_page(self, client, valid_token):
res = client.get(
"/api/items?page=99999",
headers={"Authorization": f"Bearer {valid_token}"},
)
assert res.status_code == 200
assert res.json()["items"] == []
def test_returns_422_for_page_zero(self, client, valid_token):
res = client.get(
"/api/items?page=0",
headers={"Authorization": f"Bearer {valid_token}"},
)
assert res.status_code == 422
def test_caps_page_size_at_maximum(self, client, valid_token):
res = client.get(
"/api/items?size=9999",
headers={"Authorization": f"Bearer {valid_token}"},
)
assert res.status_code == 200
assert len(res.json()["items"]) <= 100 # max page size
class TestRateLimiting:
def test_rate_limit_after_burst(self, client, valid_token):
responses = []
for _ in range(60): # exceed typical 50/min limit
res = client.get(
"/api/items",
headers={"Authorization": f"Bearer {valid_token}"},
)
responses.append(res.status_code)
if res.status_code == 429:
break
assert 429 in responses, "Rate limit was not triggered"
def test_rate_limit_response_has_retry_after(self, client, valid_token):
for _ in range(60):
res = client.get("/api/items", headers={"Authorization": f"Bearer {valid_token}"})
if res.status_code == 429:
assert "Retry-After" in res.headers or "retry_after" in res.json()
break
```
---

View File

@@ -442,44 +442,7 @@ pnpm validate
--- ---
## Output Formats ## Output Formats
→ See references/output-format-templates.md for details
### Notion Export
```javascript
// Use Notion API to create onboarding page
const { Client } = require('@notionhq/client')
const notion = new Client({ auth: process.env.NOTION_TOKEN })
const blocks = markdownToNotionBlocks(onboardingMarkdown) // use notion-to-md
await notion.pages.create({
parent: { page_id: ONBOARDING_PARENT_PAGE_ID },
properties: { title: { title: [{ text: { content: 'Engineer Onboarding — MyApp' } }] } },
children: blocks,
})
```
### Confluence Export
```bash
# Using confluence-cli or REST API
curl -X POST \
-H "Content-Type: application/json" \
-u "user@example.com:$CONFLUENCE_TOKEN" \
"https://yourorg.atlassian.net/wiki/rest/api/content" \
-d '{
"type": "page",
"title": "Codebase Onboarding",
"space": {"key": "ENG"},
"body": {
"storage": {
"value": "<p>Generated content...</p>",
"representation": "storage"
}
}
}'
```
---
## Common Pitfalls ## Common Pitfalls

View File

@@ -0,0 +1,41 @@
# codebase-onboarding reference
## Output Formats
### Notion Export
```javascript
// Use Notion API to create onboarding page
const { Client } = require('@notionhq/client')
const notion = new Client({ auth: process.env.NOTION_TOKEN })
const blocks = markdownToNotionBlocks(onboardingMarkdown) // use notion-to-md
await notion.pages.create({
parent: { page_id: ONBOARDING_PARENT_PAGE_ID },
properties: { title: { title: [{ text: { content: 'Engineer Onboarding — MyApp' } }] } },
children: blocks,
})
```
### Confluence Export
```bash
# Using confluence-cli or REST API
curl -X POST \
-H "Content-Type: application/json" \
-u "user@example.com:$CONFLUENCE_TOKEN" \
"https://yourorg.atlassian.net/wiki/rest/api/content" \
-d '{
"type": "page",
"title": "Codebase Onboarding",
"space": {"key": "ENG"},
"body": {
"storage": {
"value": "<p>Generated content...</p>",
"representation": "storage"
}
}
}'
```
---

View File

@@ -34,479 +34,7 @@ A comprehensive database design skill that provides expert-level analysis, optim
- **Execution Planning**: Ordered migration steps with dependency resolution - **Execution Planning**: Ordered migration steps with dependency resolution
## Database Design Principles ## Database Design Principles
→ See references/database-design-reference.md for details
### Normalization Forms
#### First Normal Form (1NF)
- **Atomic Values**: Each column contains indivisible values
- **Unique Column Names**: No duplicate column names within a table
- **Uniform Data Types**: Each column contains the same type of data
- **Row Uniqueness**: No duplicate rows in the table
**Example Violation:**
```sql
-- BAD: Multiple phone numbers in one column
CREATE TABLE contacts (
id INT PRIMARY KEY,
name VARCHAR(100),
phones VARCHAR(200) -- "123-456-7890, 098-765-4321"
);
-- GOOD: Separate table for phone numbers
CREATE TABLE contacts (
id INT PRIMARY KEY,
name VARCHAR(100)
);
CREATE TABLE contact_phones (
id INT PRIMARY KEY,
contact_id INT REFERENCES contacts(id),
phone_number VARCHAR(20),
phone_type VARCHAR(10)
);
```
#### Second Normal Form (2NF)
- **1NF Compliance**: Must satisfy First Normal Form
- **Full Functional Dependency**: Non-key attributes depend on the entire primary key
- **Partial Dependency Elimination**: Remove attributes that depend on part of a composite key
**Example Violation:**
```sql
-- BAD: Student course table with partial dependencies
CREATE TABLE student_courses (
student_id INT,
course_id INT,
student_name VARCHAR(100), -- Depends only on student_id
course_name VARCHAR(100), -- Depends only on course_id
grade CHAR(1),
PRIMARY KEY (student_id, course_id)
);
-- GOOD: Separate tables eliminate partial dependencies
CREATE TABLE students (
id INT PRIMARY KEY,
name VARCHAR(100)
);
CREATE TABLE courses (
id INT PRIMARY KEY,
name VARCHAR(100)
);
CREATE TABLE enrollments (
student_id INT REFERENCES students(id),
course_id INT REFERENCES courses(id),
grade CHAR(1),
PRIMARY KEY (student_id, course_id)
);
```
#### Third Normal Form (3NF)
- **2NF Compliance**: Must satisfy Second Normal Form
- **Transitive Dependency Elimination**: Non-key attributes should not depend on other non-key attributes
- **Direct Dependency**: Non-key attributes depend directly on the primary key
**Example Violation:**
```sql
-- BAD: Employee table with transitive dependency
CREATE TABLE employees (
id INT PRIMARY KEY,
name VARCHAR(100),
department_id INT,
department_name VARCHAR(100), -- Depends on department_id, not employee id
department_budget DECIMAL(10,2) -- Transitive dependency
);
-- GOOD: Separate department information
CREATE TABLE departments (
id INT PRIMARY KEY,
name VARCHAR(100),
budget DECIMAL(10,2)
);
CREATE TABLE employees (
id INT PRIMARY KEY,
name VARCHAR(100),
department_id INT REFERENCES departments(id)
);
```
#### Boyce-Codd Normal Form (BCNF)
- **3NF Compliance**: Must satisfy Third Normal Form
- **Determinant Key Rule**: Every determinant must be a candidate key
- **Stricter 3NF**: Handles anomalies not covered by 3NF
### Denormalization Strategies
#### When to Denormalize
1. **Read-Heavy Workloads**: High query frequency with acceptable write trade-offs
2. **Performance Bottlenecks**: Join operations causing significant latency
3. **Aggregation Needs**: Frequent calculation of derived values
4. **Caching Requirements**: Pre-computed results for common queries
#### Common Denormalization Patterns
**Redundant Storage**
```sql
-- Store calculated values to avoid expensive joins
CREATE TABLE orders (
id INT PRIMARY KEY,
customer_id INT REFERENCES customers(id),
customer_name VARCHAR(100), -- Denormalized from customers table
order_total DECIMAL(10,2), -- Denormalized calculation
created_at TIMESTAMP
);
```
**Materialized Aggregates**
```sql
-- Pre-computed summary tables
CREATE TABLE customer_statistics (
customer_id INT PRIMARY KEY,
total_orders INT,
lifetime_value DECIMAL(12,2),
last_order_date DATE,
updated_at TIMESTAMP
);
```
## Index Optimization Strategies
### B-Tree Indexes
- **Default Choice**: Best for range queries, sorting, and equality matches
- **Column Order**: Most selective columns first for composite indexes
- **Prefix Matching**: Supports leading column subset queries
- **Maintenance Cost**: Balanced tree structure with logarithmic operations
### Hash Indexes
- **Equality Queries**: Optimal for exact match lookups
- **Memory Efficiency**: Constant-time access for single-value queries
- **Range Limitations**: Cannot support range or partial matches
- **Use Cases**: Primary keys, unique constraints, cache keys
### Composite Indexes
```sql
-- Query pattern determines optimal column order
-- Query: WHERE status = 'active' AND created_date > '2023-01-01' ORDER BY priority DESC
CREATE INDEX idx_task_status_date_priority
ON tasks (status, created_date, priority DESC);
-- Query: WHERE user_id = 123 AND category IN ('A', 'B') AND date_field BETWEEN '...' AND '...'
CREATE INDEX idx_user_category_date
ON user_activities (user_id, category, date_field);
```
### Covering Indexes
```sql
-- Include additional columns to avoid table lookups
CREATE INDEX idx_user_email_covering
ON users (email)
INCLUDE (first_name, last_name, status);
-- Query can be satisfied entirely from the index
-- SELECT first_name, last_name, status FROM users WHERE email = 'user@example.com';
```
### Partial Indexes
```sql
-- Index only relevant subset of data
CREATE INDEX idx_active_users_email
ON users (email)
WHERE status = 'active';
-- Index for recent orders only
CREATE INDEX idx_recent_orders_customer
ON orders (customer_id, created_at)
WHERE created_at > CURRENT_DATE - INTERVAL '30 days';
```
## Query Analysis & Optimization
### Query Patterns Recognition
1. **Equality Filters**: Single-column B-tree indexes
2. **Range Queries**: B-tree with proper column ordering
3. **Text Search**: Full-text indexes or trigram indexes
4. **Join Operations**: Foreign key indexes on both sides
5. **Sorting Requirements**: Indexes matching ORDER BY clauses
### Index Selection Algorithm
```
1. Identify WHERE clause columns
2. Determine most selective columns first
3. Consider JOIN conditions
4. Include ORDER BY columns if possible
5. Evaluate covering index opportunities
6. Check for existing overlapping indexes
```
## Data Modeling Patterns
### Star Schema (Data Warehousing)
```sql
-- Central fact table
CREATE TABLE sales_facts (
sale_id BIGINT PRIMARY KEY,
product_id INT REFERENCES products(id),
customer_id INT REFERENCES customers(id),
date_id INT REFERENCES date_dimension(id),
store_id INT REFERENCES stores(id),
quantity INT,
unit_price DECIMAL(8,2),
total_amount DECIMAL(10,2)
);
-- Dimension tables
CREATE TABLE date_dimension (
id INT PRIMARY KEY,
date_value DATE,
year INT,
quarter INT,
month INT,
day_of_week INT,
is_weekend BOOLEAN
);
```
### Snowflake Schema
```sql
-- Normalized dimension tables
CREATE TABLE products (
id INT PRIMARY KEY,
name VARCHAR(200),
category_id INT REFERENCES product_categories(id),
brand_id INT REFERENCES brands(id)
);
CREATE TABLE product_categories (
id INT PRIMARY KEY,
name VARCHAR(100),
parent_category_id INT REFERENCES product_categories(id)
);
```
### Document Model (JSON Storage)
```sql
-- Flexible document storage with indexing
CREATE TABLE documents (
id UUID PRIMARY KEY,
document_type VARCHAR(50),
data JSONB,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- Index on JSON properties
CREATE INDEX idx_documents_user_id
ON documents USING GIN ((data->>'user_id'));
CREATE INDEX idx_documents_status
ON documents ((data->>'status'))
WHERE document_type = 'order';
```
### Graph Data Patterns
```sql
-- Adjacency list for hierarchical data
CREATE TABLE categories (
id INT PRIMARY KEY,
name VARCHAR(100),
parent_id INT REFERENCES categories(id),
level INT,
path VARCHAR(500) -- Materialized path: "/1/5/12/"
);
-- Many-to-many relationships
CREATE TABLE relationships (
id UUID PRIMARY KEY,
from_entity_id UUID,
to_entity_id UUID,
relationship_type VARCHAR(50),
created_at TIMESTAMP,
INDEX (from_entity_id, relationship_type),
INDEX (to_entity_id, relationship_type)
);
```
## Migration Strategies
### Zero-Downtime Migration (Expand-Contract Pattern)
**Phase 1: Expand**
```sql
-- Add new column without constraints
ALTER TABLE users ADD COLUMN new_email VARCHAR(255);
-- Backfill data in batches
UPDATE users SET new_email = email WHERE id BETWEEN 1 AND 1000;
-- Continue in batches...
-- Add constraints after backfill
ALTER TABLE users ADD CONSTRAINT users_new_email_unique UNIQUE (new_email);
ALTER TABLE users ALTER COLUMN new_email SET NOT NULL;
```
**Phase 2: Contract**
```sql
-- Update application to use new column
-- Deploy application changes
-- Verify new column is being used
-- Remove old column
ALTER TABLE users DROP COLUMN email;
-- Rename new column
ALTER TABLE users RENAME COLUMN new_email TO email;
```
### Data Type Changes
```sql
-- Safe string to integer conversion
ALTER TABLE products ADD COLUMN sku_number INTEGER;
UPDATE products SET sku_number = CAST(sku AS INTEGER) WHERE sku ~ '^[0-9]+$';
-- Validate conversion success before dropping old column
```
## Partitioning Strategies
### Horizontal Partitioning (Sharding)
```sql
-- Range partitioning by date
CREATE TABLE sales_2023 PARTITION OF sales
FOR VALUES FROM ('2023-01-01') TO ('2024-01-01');
CREATE TABLE sales_2024 PARTITION OF sales
FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');
-- Hash partitioning by user_id
CREATE TABLE user_data_0 PARTITION OF user_data
FOR VALUES WITH (MODULUS 4, REMAINDER 0);
CREATE TABLE user_data_1 PARTITION OF user_data
FOR VALUES WITH (MODULUS 4, REMAINDER 1);
```
### Vertical Partitioning
```sql
-- Separate frequently accessed columns
CREATE TABLE users_core (
id INT PRIMARY KEY,
email VARCHAR(255),
status VARCHAR(20),
created_at TIMESTAMP
);
-- Less frequently accessed profile data
CREATE TABLE users_profile (
user_id INT PRIMARY KEY REFERENCES users_core(id),
bio TEXT,
preferences JSONB,
last_login TIMESTAMP
);
```
## Connection Management
### Connection Pooling
- **Pool Size**: CPU cores × 2 + effective spindle count
- **Connection Lifetime**: Rotate connections to prevent resource leaks
- **Timeout Settings**: Connection, idle, and query timeouts
- **Health Checks**: Regular connection validation
### Read Replicas Strategy
```sql
-- Write queries to primary
INSERT INTO users (email, name) VALUES ('user@example.com', 'John Doe');
-- Read queries to replicas (with appropriate read preference)
SELECT * FROM users WHERE status = 'active'; -- Route to read replica
-- Consistent reads when required
SELECT * FROM users WHERE id = LAST_INSERT_ID(); -- Route to primary
```
## Caching Layers
### Cache-Aside Pattern
```python
def get_user(user_id):
# Try cache first
user = cache.get(f"user:{user_id}")
if user is None:
# Cache miss - query database
user = db.query("SELECT * FROM users WHERE id = %s", user_id)
# Store in cache
cache.set(f"user:{user_id}", user, ttl=3600)
return user
```
### Write-Through Cache
- **Consistency**: Always keep cache and database in sync
- **Write Latency**: Higher due to dual writes
- **Data Safety**: No data loss on cache failures
### Cache Invalidation Strategies
1. **TTL-Based**: Time-based expiration
2. **Event-Driven**: Invalidate on data changes
3. **Version-Based**: Use version numbers for consistency
4. **Tag-Based**: Group related cache entries
## Database Selection Guide
### SQL Databases
**PostgreSQL**
- **Strengths**: ACID compliance, complex queries, JSON support, extensibility
- **Use Cases**: OLTP applications, data warehousing, geospatial data
- **Scale**: Vertical scaling with read replicas
**MySQL**
- **Strengths**: Performance, replication, wide ecosystem support
- **Use Cases**: Web applications, content management, e-commerce
- **Scale**: Horizontal scaling through sharding
### NoSQL Databases
**Document Stores (MongoDB, CouchDB)**
- **Strengths**: Flexible schema, horizontal scaling, developer productivity
- **Use Cases**: Content management, catalogs, user profiles
- **Trade-offs**: Eventual consistency, complex queries limitations
**Key-Value Stores (Redis, DynamoDB)**
- **Strengths**: High performance, simple model, excellent caching
- **Use Cases**: Session storage, real-time analytics, gaming leaderboards
- **Trade-offs**: Limited query capabilities, data modeling constraints
**Column-Family (Cassandra, HBase)**
- **Strengths**: Write-heavy workloads, linear scalability, fault tolerance
- **Use Cases**: Time-series data, IoT applications, messaging systems
- **Trade-offs**: Query flexibility, consistency model complexity
**Graph Databases (Neo4j, Amazon Neptune)**
- **Strengths**: Relationship queries, pattern matching, recommendation engines
- **Use Cases**: Social networks, fraud detection, knowledge graphs
- **Trade-offs**: Specialized use cases, learning curve
### NewSQL Databases
**Distributed SQL (CockroachDB, TiDB, Spanner)**
- **Strengths**: SQL compatibility with horizontal scaling
- **Use Cases**: Global applications requiring ACID guarantees
- **Trade-offs**: Complexity, latency for distributed transactions
## Tools & Scripts
### Schema Analyzer
- **Input**: SQL DDL files, JSON schema definitions
- **Analysis**: Normalization compliance, constraint validation, naming conventions
- **Output**: Analysis report, Mermaid ERD, improvement recommendations
### Index Optimizer
- **Input**: Schema definition, query patterns
- **Analysis**: Missing indexes, redundancy detection, selectivity estimation
- **Output**: Index recommendations, CREATE INDEX statements, performance projections
### Migration Generator
- **Input**: Current and target schemas
- **Analysis**: Schema differences, dependency resolution, risk assessment
- **Output**: Migration scripts, rollback plans, validation queries
## Best Practices ## Best Practices

View File

@@ -0,0 +1,476 @@
# database-designer reference
## Database Design Principles
### Normalization Forms
#### First Normal Form (1NF)
- **Atomic Values**: Each column contains indivisible values
- **Unique Column Names**: No duplicate column names within a table
- **Uniform Data Types**: Each column contains the same type of data
- **Row Uniqueness**: No duplicate rows in the table
**Example Violation:**
```sql
-- BAD: Multiple phone numbers in one column
CREATE TABLE contacts (
id INT PRIMARY KEY,
name VARCHAR(100),
phones VARCHAR(200) -- "123-456-7890, 098-765-4321"
);
-- GOOD: Separate table for phone numbers
CREATE TABLE contacts (
id INT PRIMARY KEY,
name VARCHAR(100)
);
CREATE TABLE contact_phones (
id INT PRIMARY KEY,
contact_id INT REFERENCES contacts(id),
phone_number VARCHAR(20),
phone_type VARCHAR(10)
);
```
#### Second Normal Form (2NF)
- **1NF Compliance**: Must satisfy First Normal Form
- **Full Functional Dependency**: Non-key attributes depend on the entire primary key
- **Partial Dependency Elimination**: Remove attributes that depend on part of a composite key
**Example Violation:**
```sql
-- BAD: Student course table with partial dependencies
CREATE TABLE student_courses (
student_id INT,
course_id INT,
student_name VARCHAR(100), -- Depends only on student_id
course_name VARCHAR(100), -- Depends only on course_id
grade CHAR(1),
PRIMARY KEY (student_id, course_id)
);
-- GOOD: Separate tables eliminate partial dependencies
CREATE TABLE students (
id INT PRIMARY KEY,
name VARCHAR(100)
);
CREATE TABLE courses (
id INT PRIMARY KEY,
name VARCHAR(100)
);
CREATE TABLE enrollments (
student_id INT REFERENCES students(id),
course_id INT REFERENCES courses(id),
grade CHAR(1),
PRIMARY KEY (student_id, course_id)
);
```
#### Third Normal Form (3NF)
- **2NF Compliance**: Must satisfy Second Normal Form
- **Transitive Dependency Elimination**: Non-key attributes should not depend on other non-key attributes
- **Direct Dependency**: Non-key attributes depend directly on the primary key
**Example Violation:**
```sql
-- BAD: Employee table with transitive dependency
CREATE TABLE employees (
id INT PRIMARY KEY,
name VARCHAR(100),
department_id INT,
department_name VARCHAR(100), -- Depends on department_id, not employee id
department_budget DECIMAL(10,2) -- Transitive dependency
);
-- GOOD: Separate department information
CREATE TABLE departments (
id INT PRIMARY KEY,
name VARCHAR(100),
budget DECIMAL(10,2)
);
CREATE TABLE employees (
id INT PRIMARY KEY,
name VARCHAR(100),
department_id INT REFERENCES departments(id)
);
```
#### Boyce-Codd Normal Form (BCNF)
- **3NF Compliance**: Must satisfy Third Normal Form
- **Determinant Key Rule**: Every determinant must be a candidate key
- **Stricter 3NF**: Handles anomalies not covered by 3NF
### Denormalization Strategies
#### When to Denormalize
1. **Read-Heavy Workloads**: High query frequency with acceptable write trade-offs
2. **Performance Bottlenecks**: Join operations causing significant latency
3. **Aggregation Needs**: Frequent calculation of derived values
4. **Caching Requirements**: Pre-computed results for common queries
#### Common Denormalization Patterns
**Redundant Storage**
```sql
-- Store calculated values to avoid expensive joins
CREATE TABLE orders (
id INT PRIMARY KEY,
customer_id INT REFERENCES customers(id),
customer_name VARCHAR(100), -- Denormalized from customers table
order_total DECIMAL(10,2), -- Denormalized calculation
created_at TIMESTAMP
);
```
**Materialized Aggregates**
```sql
-- Pre-computed summary tables
CREATE TABLE customer_statistics (
customer_id INT PRIMARY KEY,
total_orders INT,
lifetime_value DECIMAL(12,2),
last_order_date DATE,
updated_at TIMESTAMP
);
```
## Index Optimization Strategies
### B-Tree Indexes
- **Default Choice**: Best for range queries, sorting, and equality matches
- **Column Order**: Most selective columns first for composite indexes
- **Prefix Matching**: Supports leading column subset queries
- **Maintenance Cost**: Balanced tree structure with logarithmic operations
### Hash Indexes
- **Equality Queries**: Optimal for exact match lookups
- **Memory Efficiency**: Constant-time access for single-value queries
- **Range Limitations**: Cannot support range or partial matches
- **Use Cases**: Primary keys, unique constraints, cache keys
### Composite Indexes
```sql
-- Query pattern determines optimal column order
-- Query: WHERE status = 'active' AND created_date > '2023-01-01' ORDER BY priority DESC
CREATE INDEX idx_task_status_date_priority
ON tasks (status, created_date, priority DESC);
-- Query: WHERE user_id = 123 AND category IN ('A', 'B') AND date_field BETWEEN '...' AND '...'
CREATE INDEX idx_user_category_date
ON user_activities (user_id, category, date_field);
```
### Covering Indexes
```sql
-- Include additional columns to avoid table lookups
CREATE INDEX idx_user_email_covering
ON users (email)
INCLUDE (first_name, last_name, status);
-- Query can be satisfied entirely from the index
-- SELECT first_name, last_name, status FROM users WHERE email = 'user@example.com';
```
### Partial Indexes
```sql
-- Index only relevant subset of data
CREATE INDEX idx_active_users_email
ON users (email)
WHERE status = 'active';
-- Index for recent orders only
CREATE INDEX idx_recent_orders_customer
ON orders (customer_id, created_at)
WHERE created_at > CURRENT_DATE - INTERVAL '30 days';
```
## Query Analysis & Optimization
### Query Patterns Recognition
1. **Equality Filters**: Single-column B-tree indexes
2. **Range Queries**: B-tree with proper column ordering
3. **Text Search**: Full-text indexes or trigram indexes
4. **Join Operations**: Foreign key indexes on both sides
5. **Sorting Requirements**: Indexes matching ORDER BY clauses
### Index Selection Algorithm
```
1. Identify WHERE clause columns
2. Determine most selective columns first
3. Consider JOIN conditions
4. Include ORDER BY columns if possible
5. Evaluate covering index opportunities
6. Check for existing overlapping indexes
```
## Data Modeling Patterns
### Star Schema (Data Warehousing)
```sql
-- Central fact table
CREATE TABLE sales_facts (
sale_id BIGINT PRIMARY KEY,
product_id INT REFERENCES products(id),
customer_id INT REFERENCES customers(id),
date_id INT REFERENCES date_dimension(id),
store_id INT REFERENCES stores(id),
quantity INT,
unit_price DECIMAL(8,2),
total_amount DECIMAL(10,2)
);
-- Dimension tables
CREATE TABLE date_dimension (
id INT PRIMARY KEY,
date_value DATE,
year INT,
quarter INT,
month INT,
day_of_week INT,
is_weekend BOOLEAN
);
```
### Snowflake Schema
```sql
-- Normalized dimension tables
CREATE TABLE products (
id INT PRIMARY KEY,
name VARCHAR(200),
category_id INT REFERENCES product_categories(id),
brand_id INT REFERENCES brands(id)
);
CREATE TABLE product_categories (
id INT PRIMARY KEY,
name VARCHAR(100),
parent_category_id INT REFERENCES product_categories(id)
);
```
### Document Model (JSON Storage)
```sql
-- Flexible document storage with indexing
CREATE TABLE documents (
id UUID PRIMARY KEY,
document_type VARCHAR(50),
data JSONB,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- Index on JSON properties
CREATE INDEX idx_documents_user_id
ON documents USING GIN ((data->>'user_id'));
CREATE INDEX idx_documents_status
ON documents ((data->>'status'))
WHERE document_type = 'order';
```
### Graph Data Patterns
```sql
-- Adjacency list for hierarchical data
CREATE TABLE categories (
id INT PRIMARY KEY,
name VARCHAR(100),
parent_id INT REFERENCES categories(id),
level INT,
path VARCHAR(500) -- Materialized path: "/1/5/12/"
);
-- Many-to-many relationships
CREATE TABLE relationships (
id UUID PRIMARY KEY,
from_entity_id UUID,
to_entity_id UUID,
relationship_type VARCHAR(50),
created_at TIMESTAMP,
INDEX (from_entity_id, relationship_type),
INDEX (to_entity_id, relationship_type)
);
```
## Migration Strategies
### Zero-Downtime Migration (Expand-Contract Pattern)
**Phase 1: Expand**
```sql
-- Add new column without constraints
ALTER TABLE users ADD COLUMN new_email VARCHAR(255);
-- Backfill data in batches
UPDATE users SET new_email = email WHERE id BETWEEN 1 AND 1000;
-- Continue in batches...
-- Add constraints after backfill
ALTER TABLE users ADD CONSTRAINT users_new_email_unique UNIQUE (new_email);
ALTER TABLE users ALTER COLUMN new_email SET NOT NULL;
```
**Phase 2: Contract**
```sql
-- Update application to use new column
-- Deploy application changes
-- Verify new column is being used
-- Remove old column
ALTER TABLE users DROP COLUMN email;
-- Rename new column
ALTER TABLE users RENAME COLUMN new_email TO email;
```
### Data Type Changes
```sql
-- Safe string to integer conversion
ALTER TABLE products ADD COLUMN sku_number INTEGER;
UPDATE products SET sku_number = CAST(sku AS INTEGER) WHERE sku ~ '^[0-9]+$';
-- Validate conversion success before dropping old column
```
## Partitioning Strategies
### Horizontal Partitioning (Sharding)
```sql
-- Range partitioning by date
CREATE TABLE sales_2023 PARTITION OF sales
FOR VALUES FROM ('2023-01-01') TO ('2024-01-01');
CREATE TABLE sales_2024 PARTITION OF sales
FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');
-- Hash partitioning by user_id
CREATE TABLE user_data_0 PARTITION OF user_data
FOR VALUES WITH (MODULUS 4, REMAINDER 0);
CREATE TABLE user_data_1 PARTITION OF user_data
FOR VALUES WITH (MODULUS 4, REMAINDER 1);
```
### Vertical Partitioning
```sql
-- Separate frequently accessed columns
CREATE TABLE users_core (
id INT PRIMARY KEY,
email VARCHAR(255),
status VARCHAR(20),
created_at TIMESTAMP
);
-- Less frequently accessed profile data
CREATE TABLE users_profile (
user_id INT PRIMARY KEY REFERENCES users_core(id),
bio TEXT,
preferences JSONB,
last_login TIMESTAMP
);
```
## Connection Management
### Connection Pooling
- **Pool Size**: CPU cores × 2 + effective spindle count
- **Connection Lifetime**: Rotate connections to prevent resource leaks
- **Timeout Settings**: Connection, idle, and query timeouts
- **Health Checks**: Regular connection validation
### Read Replicas Strategy
```sql
-- Write queries to primary
INSERT INTO users (email, name) VALUES ('user@example.com', 'John Doe');
-- Read queries to replicas (with appropriate read preference)
SELECT * FROM users WHERE status = 'active'; -- Route to read replica
-- Consistent reads when required
SELECT * FROM users WHERE id = LAST_INSERT_ID(); -- Route to primary
```
## Caching Layers
### Cache-Aside Pattern
```python
def get_user(user_id):
# Try cache first
user = cache.get(f"user:{user_id}")
if user is None:
# Cache miss - query database
user = db.query("SELECT * FROM users WHERE id = %s", user_id)
# Store in cache
cache.set(f"user:{user_id}", user, ttl=3600)
return user
```
### Write-Through Cache
- **Consistency**: Always keep cache and database in sync
- **Write Latency**: Higher due to dual writes
- **Data Safety**: No data loss on cache failures
### Cache Invalidation Strategies
1. **TTL-Based**: Time-based expiration
2. **Event-Driven**: Invalidate on data changes
3. **Version-Based**: Use version numbers for consistency
4. **Tag-Based**: Group related cache entries
## Database Selection Guide
### SQL Databases
**PostgreSQL**
- **Strengths**: ACID compliance, complex queries, JSON support, extensibility
- **Use Cases**: OLTP applications, data warehousing, geospatial data
- **Scale**: Vertical scaling with read replicas
**MySQL**
- **Strengths**: Performance, replication, wide ecosystem support
- **Use Cases**: Web applications, content management, e-commerce
- **Scale**: Horizontal scaling through sharding
### NoSQL Databases
**Document Stores (MongoDB, CouchDB)**
- **Strengths**: Flexible schema, horizontal scaling, developer productivity
- **Use Cases**: Content management, catalogs, user profiles
- **Trade-offs**: Eventual consistency, complex queries limitations
**Key-Value Stores (Redis, DynamoDB)**
- **Strengths**: High performance, simple model, excellent caching
- **Use Cases**: Session storage, real-time analytics, gaming leaderboards
- **Trade-offs**: Limited query capabilities, data modeling constraints
**Column-Family (Cassandra, HBase)**
- **Strengths**: Write-heavy workloads, linear scalability, fault tolerance
- **Use Cases**: Time-series data, IoT applications, messaging systems
- **Trade-offs**: Query flexibility, consistency model complexity
**Graph Databases (Neo4j, Amazon Neptune)**
- **Strengths**: Relationship queries, pattern matching, recommendation engines
- **Use Cases**: Social networks, fraud detection, knowledge graphs
- **Trade-offs**: Specialized use cases, learning curve
### NewSQL Databases
**Distributed SQL (CockroachDB, TiDB, Spanner)**
- **Strengths**: SQL compatibility with horizontal scaling
- **Use Cases**: Global applications requiring ACID guarantees
- **Trade-offs**: Complexity, latency for distributed transactions
## Tools & Scripts
### Schema Analyzer
- **Input**: SQL DDL files, JSON schema definitions
- **Analysis**: Normalization compliance, constraint validation, naming conventions
- **Output**: Analysis report, Mermaid ERD, improvement recommendations
### Index Optimizer
- **Input**: Schema definition, query patterns
- **Analysis**: Missing indexes, redundancy detection, selectivity estimation
- **Output**: Index recommendations, CREATE INDEX statements, performance projections
### Migration Generator
- **Input**: Current and target schemas
- **Analysis**: Schema differences, dependency resolution, risk assessment
- **Output**: Migration scripts, rollback plans, validation queries

View File

@@ -69,286 +69,7 @@ User 1──* AuditLog
--- ---
## Full Schema Example (Task Management SaaS) ## Full Schema Example (Task Management SaaS)
→ See references/full-schema-examples.md for details
### Prisma Schema
```prisma
// schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// ── Multi-tenancy ─────────────────────────────────────────────────────────────
model Organization {
id String @id @default(cuid())
name String
slug String @unique
plan Plan @default(FREE)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
users OrganizationMember[]
projects Project[]
auditLogs AuditLog[]
@@map("organizations")
}
model OrganizationMember {
id String @id @default(cuid())
organizationId String @map("organization_id")
userId String @map("user_id")
role OrgRole @default(MEMBER)
joinedAt DateTime @default(now()) @map("joined_at")
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([organizationId, userId])
@@index([userId])
@@map("organization_members")
}
model User {
id String @id @default(cuid())
email String @unique
name String?
avatarUrl String? @map("avatar_url")
passwordHash String? @map("password_hash")
emailVerifiedAt DateTime? @map("email_verified_at")
lastLoginAt DateTime? @map("last_login_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
memberships OrganizationMember[]
ownedProjects Project[] @relation("ProjectOwner")
assignedTasks TaskAssignment[]
comments Comment[]
auditLogs AuditLog[]
@@map("users")
}
// ── Core entities ─────────────────────────────────────────────────────────────
model Project {
id String @id @default(cuid())
organizationId String @map("organization_id")
ownerId String @map("owner_id")
name String
description String?
status ProjectStatus @default(ACTIVE)
settings Json @default("{}")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
organization Organization @relation(fields: [organizationId], references: [id])
owner User @relation("ProjectOwner", fields: [ownerId], references: [id])
tasks Task[]
labels Label[]
@@index([organizationId])
@@index([organizationId, status])
@@index([deletedAt])
@@map("projects")
}
model Task {
id String @id @default(cuid())
projectId String @map("project_id")
title String
description String?
status TaskStatus @default(TODO)
priority Priority @default(MEDIUM)
dueDate DateTime? @map("due_date")
position Float @default(0) // For drag-and-drop ordering
version Int @default(1) // Optimistic locking
createdById String @map("created_by_id")
updatedById String @map("updated_by_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
project Project @relation(fields: [projectId], references: [id])
assignments TaskAssignment[]
labels TaskLabel[]
comments Comment[]
attachments Attachment[]
@@index([projectId])
@@index([projectId, status])
@@index([projectId, deletedAt])
@@index([dueDate], where: { deletedAt: null }) // Partial index
@@map("tasks")
}
// ── Polymorphic attachments ───────────────────────────────────────────────────
model Attachment {
id String @id @default(cuid())
// Polymorphic association
entityType String @map("entity_type") // "task" | "comment"
entityId String @map("entity_id")
filename String
mimeType String @map("mime_type")
sizeBytes Int @map("size_bytes")
storageKey String @map("storage_key") // S3 key
uploadedById String @map("uploaded_by_id")
createdAt DateTime @default(now()) @map("created_at")
// Only one concrete relation (task) — polymorphic handled at app level
task Task? @relation(fields: [entityId], references: [id], map: "attachment_task_fk")
@@index([entityType, entityId])
@@map("attachments")
}
// ── Audit trail ───────────────────────────────────────────────────────────────
model AuditLog {
id String @id @default(cuid())
organizationId String @map("organization_id")
userId String? @map("user_id")
action String // "task.created", "task.status_changed"
entityType String @map("entity_type")
entityId String @map("entity_id")
before Json? // Previous state
after Json? // New state
ipAddress String? @map("ip_address")
userAgent String? @map("user_agent")
createdAt DateTime @default(now()) @map("created_at")
organization Organization @relation(fields: [organizationId], references: [id])
user User? @relation(fields: [userId], references: [id])
@@index([organizationId, createdAt(sort: Desc)])
@@index([entityType, entityId])
@@index([userId])
@@map("audit_logs")
}
enum Plan { FREE STARTER GROWTH ENTERPRISE }
enum OrgRole { OWNER ADMIN MEMBER VIEWER }
enum ProjectStatus { ACTIVE ARCHIVED }
enum TaskStatus { TODO IN_PROGRESS IN_REVIEW DONE CANCELLED }
enum Priority { LOW MEDIUM HIGH CRITICAL }
```
---
### Drizzle Schema (TypeScript)
```typescript
// db/schema.ts
import {
pgTable, text, timestamp, integer, boolean,
varchar, jsonb, real, pgEnum, uniqueIndex, index,
} from 'drizzle-orm/pg-core'
import { createId } from '@paralleldrive/cuid2'
export const taskStatusEnum = pgEnum('task_status', [
'todo', 'in_progress', 'in_review', 'done', 'cancelled'
])
export const priorityEnum = pgEnum('priority', ['low', 'medium', 'high', 'critical'])
export const tasks = pgTable('tasks', {
id: text('id').primaryKey().$defaultFn(() => createId()),
projectId: text('project_id').notNull().references(() => projects.id),
title: varchar('title', { length: 500 }).notNull(),
description: text('description'),
status: taskStatusEnum('status').notNull().default('todo'),
priority: priorityEnum('priority').notNull().default('medium'),
dueDate: timestamp('due_date', { withTimezone: true }),
position: real('position').notNull().default(0),
version: integer('version').notNull().default(1),
createdById: text('created_by_id').notNull().references(() => users.id),
updatedById: text('updated_by_id').notNull().references(() => users.id),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
deletedAt: timestamp('deleted_at', { withTimezone: true }),
}, (table) => ({
projectIdx: index('tasks_project_id_idx').on(table.projectId),
projectStatusIdx: index('tasks_project_status_idx').on(table.projectId, table.status),
}))
// Infer TypeScript types
export type Task = typeof tasks.$inferSelect
export type NewTask = typeof tasks.$inferInsert
```
---
### Alembic Migration (Python / SQLAlchemy)
```python
# alembic/versions/20260301_create_tasks.py
"""Create tasks table
Revision ID: a1b2c3d4e5f6
Revises: previous_revision
Create Date: 2026-03-01 12:00:00
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
revision = 'a1b2c3d4e5f6'
down_revision = 'previous_revision'
def upgrade() -> None:
# Create enums
task_status = postgresql.ENUM(
'todo', 'in_progress', 'in_review', 'done', 'cancelled',
name='task_status'
)
task_status.create(op.get_bind())
op.create_table(
'tasks',
sa.Column('id', sa.Text(), primary_key=True),
sa.Column('project_id', sa.Text(), sa.ForeignKey('projects.id'), nullable=False),
sa.Column('title', sa.VARCHAR(500), nullable=False),
sa.Column('description', sa.Text()),
sa.Column('status', postgresql.ENUM('todo', 'in_progress', 'in_review', 'done', 'cancelled', name='task_status', create_type=False), nullable=False, server_default='todo'),
sa.Column('priority', sa.Text(), nullable=False, server_default='medium'),
sa.Column('due_date', sa.TIMESTAMP(timezone=True)),
sa.Column('position', sa.Float(), nullable=False, server_default='0'),
sa.Column('version', sa.Integer(), nullable=False, server_default='1'),
sa.Column('created_by_id', sa.Text(), sa.ForeignKey('users.id'), nullable=False),
sa.Column('updated_by_id', sa.Text(), sa.ForeignKey('users.id'), nullable=False),
sa.Column('created_at', sa.TIMESTAMP(timezone=True), nullable=False, server_default=sa.text('NOW()')),
sa.Column('updated_at', sa.TIMESTAMP(timezone=True), nullable=False, server_default=sa.text('NOW()')),
sa.Column('deleted_at', sa.TIMESTAMP(timezone=True)),
)
# Indexes
op.create_index('tasks_project_id_idx', 'tasks', ['project_id'])
op.create_index('tasks_project_status_idx', 'tasks', ['project_id', 'status'])
# Partial index for active tasks only
op.create_index(
'tasks_due_date_active_idx',
'tasks', ['due_date'],
postgresql_where=sa.text('deleted_at IS NULL')
)
def downgrade() -> None:
op.drop_table('tasks')
op.execute("DROP TYPE IF EXISTS task_status")
```
---
## Row-Level Security (RLS) Policies ## Row-Level Security (RLS) Policies

View File

@@ -0,0 +1,283 @@
# database-schema-designer reference
## Full Schema Example (Task Management SaaS)
### Prisma Schema
```prisma
// schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// ── Multi-tenancy ─────────────────────────────────────────────────────────────
model Organization {
id String @id @default(cuid())
name String
slug String @unique
plan Plan @default(FREE)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
users OrganizationMember[]
projects Project[]
auditLogs AuditLog[]
@@map("organizations")
}
model OrganizationMember {
id String @id @default(cuid())
organizationId String @map("organization_id")
userId String @map("user_id")
role OrgRole @default(MEMBER)
joinedAt DateTime @default(now()) @map("joined_at")
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([organizationId, userId])
@@index([userId])
@@map("organization_members")
}
model User {
id String @id @default(cuid())
email String @unique
name String?
avatarUrl String? @map("avatar_url")
passwordHash String? @map("password_hash")
emailVerifiedAt DateTime? @map("email_verified_at")
lastLoginAt DateTime? @map("last_login_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
memberships OrganizationMember[]
ownedProjects Project[] @relation("ProjectOwner")
assignedTasks TaskAssignment[]
comments Comment[]
auditLogs AuditLog[]
@@map("users")
}
// ── Core entities ─────────────────────────────────────────────────────────────
model Project {
id String @id @default(cuid())
organizationId String @map("organization_id")
ownerId String @map("owner_id")
name String
description String?
status ProjectStatus @default(ACTIVE)
settings Json @default("{}")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
organization Organization @relation(fields: [organizationId], references: [id])
owner User @relation("ProjectOwner", fields: [ownerId], references: [id])
tasks Task[]
labels Label[]
@@index([organizationId])
@@index([organizationId, status])
@@index([deletedAt])
@@map("projects")
}
model Task {
id String @id @default(cuid())
projectId String @map("project_id")
title String
description String?
status TaskStatus @default(TODO)
priority Priority @default(MEDIUM)
dueDate DateTime? @map("due_date")
position Float @default(0) // For drag-and-drop ordering
version Int @default(1) // Optimistic locking
createdById String @map("created_by_id")
updatedById String @map("updated_by_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
project Project @relation(fields: [projectId], references: [id])
assignments TaskAssignment[]
labels TaskLabel[]
comments Comment[]
attachments Attachment[]
@@index([projectId])
@@index([projectId, status])
@@index([projectId, deletedAt])
@@index([dueDate], where: { deletedAt: null }) // Partial index
@@map("tasks")
}
// ── Polymorphic attachments ───────────────────────────────────────────────────
model Attachment {
id String @id @default(cuid())
// Polymorphic association
entityType String @map("entity_type") // "task" | "comment"
entityId String @map("entity_id")
filename String
mimeType String @map("mime_type")
sizeBytes Int @map("size_bytes")
storageKey String @map("storage_key") // S3 key
uploadedById String @map("uploaded_by_id")
createdAt DateTime @default(now()) @map("created_at")
// Only one concrete relation (task) — polymorphic handled at app level
task Task? @relation(fields: [entityId], references: [id], map: "attachment_task_fk")
@@index([entityType, entityId])
@@map("attachments")
}
// ── Audit trail ───────────────────────────────────────────────────────────────
model AuditLog {
id String @id @default(cuid())
organizationId String @map("organization_id")
userId String? @map("user_id")
action String // "task.created", "task.status_changed"
entityType String @map("entity_type")
entityId String @map("entity_id")
before Json? // Previous state
after Json? // New state
ipAddress String? @map("ip_address")
userAgent String? @map("user_agent")
createdAt DateTime @default(now()) @map("created_at")
organization Organization @relation(fields: [organizationId], references: [id])
user User? @relation(fields: [userId], references: [id])
@@index([organizationId, createdAt(sort: Desc)])
@@index([entityType, entityId])
@@index([userId])
@@map("audit_logs")
}
enum Plan { FREE STARTER GROWTH ENTERPRISE }
enum OrgRole { OWNER ADMIN MEMBER VIEWER }
enum ProjectStatus { ACTIVE ARCHIVED }
enum TaskStatus { TODO IN_PROGRESS IN_REVIEW DONE CANCELLED }
enum Priority { LOW MEDIUM HIGH CRITICAL }
```
---
### Drizzle Schema (TypeScript)
```typescript
// db/schema.ts
import {
pgTable, text, timestamp, integer, boolean,
varchar, jsonb, real, pgEnum, uniqueIndex, index,
} from 'drizzle-orm/pg-core'
import { createId } from '@paralleldrive/cuid2'
export const taskStatusEnum = pgEnum('task_status', [
'todo', 'in_progress', 'in_review', 'done', 'cancelled'
])
export const priorityEnum = pgEnum('priority', ['low', 'medium', 'high', 'critical'])
export const tasks = pgTable('tasks', {
id: text('id').primaryKey().$defaultFn(() => createId()),
projectId: text('project_id').notNull().references(() => projects.id),
title: varchar('title', { length: 500 }).notNull(),
description: text('description'),
status: taskStatusEnum('status').notNull().default('todo'),
priority: priorityEnum('priority').notNull().default('medium'),
dueDate: timestamp('due_date', { withTimezone: true }),
position: real('position').notNull().default(0),
version: integer('version').notNull().default(1),
createdById: text('created_by_id').notNull().references(() => users.id),
updatedById: text('updated_by_id').notNull().references(() => users.id),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
deletedAt: timestamp('deleted_at', { withTimezone: true }),
}, (table) => ({
projectIdx: index('tasks_project_id_idx').on(table.projectId),
projectStatusIdx: index('tasks_project_status_idx').on(table.projectId, table.status),
}))
// Infer TypeScript types
export type Task = typeof tasks.$inferSelect
export type NewTask = typeof tasks.$inferInsert
```
---
### Alembic Migration (Python / SQLAlchemy)
```python
# alembic/versions/20260301_create_tasks.py
"""Create tasks table
Revision ID: a1b2c3d4e5f6
Revises: previous_revision
Create Date: 2026-03-01 12:00:00
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
revision = 'a1b2c3d4e5f6'
down_revision = 'previous_revision'
def upgrade() -> None:
# Create enums
task_status = postgresql.ENUM(
'todo', 'in_progress', 'in_review', 'done', 'cancelled',
name='task_status'
)
task_status.create(op.get_bind())
op.create_table(
'tasks',
sa.Column('id', sa.Text(), primary_key=True),
sa.Column('project_id', sa.Text(), sa.ForeignKey('projects.id'), nullable=False),
sa.Column('title', sa.VARCHAR(500), nullable=False),
sa.Column('description', sa.Text()),
sa.Column('status', postgresql.ENUM('todo', 'in_progress', 'in_review', 'done', 'cancelled', name='task_status', create_type=False), nullable=False, server_default='todo'),
sa.Column('priority', sa.Text(), nullable=False, server_default='medium'),
sa.Column('due_date', sa.TIMESTAMP(timezone=True)),
sa.Column('position', sa.Float(), nullable=False, server_default='0'),
sa.Column('version', sa.Integer(), nullable=False, server_default='1'),
sa.Column('created_by_id', sa.Text(), sa.ForeignKey('users.id'), nullable=False),
sa.Column('updated_by_id', sa.Text(), sa.ForeignKey('users.id'), nullable=False),
sa.Column('created_at', sa.TIMESTAMP(timezone=True), nullable=False, server_default=sa.text('NOW()')),
sa.Column('updated_at', sa.TIMESTAMP(timezone=True), nullable=False, server_default=sa.text('NOW()')),
sa.Column('deleted_at', sa.TIMESTAMP(timezone=True)),
)
# Indexes
op.create_index('tasks_project_id_idx', 'tasks', ['project_id'])
op.create_index('tasks_project_status_idx', 'tasks', ['project_id', 'status'])
# Partial index for active tasks only
op.create_index(
'tasks_due_date_active_idx',
'tasks', ['due_date'],
postgresql_where=sa.text('deleted_at IS NULL')
)
def downgrade() -> None:
op.drop_table('tasks')
op.execute("DROP TYPE IF EXISTS task_status")
```
---

View File

@@ -196,361 +196,7 @@ git add .env.example
--- ---
## Required Variable Validation Script ## Required Variable Validation Script
→ See references/validation-detection-rotation.md for details
```bash
#!/bin/bash
# scripts/validate-env.sh
# Run at app startup or in CI before deploy
# Exit 1 if any required var is missing or empty
set -euo pipefail
MISSING=()
WARNINGS=()
# --- Define required vars by environment ---
ALWAYS_REQUIRED=(
APP_SECRET
APP_URL
DATABASE_URL
AUTH_JWT_SECRET
AUTH_REFRESH_SECRET
)
PROD_REQUIRED=(
STRIPE_SECRET_KEY
STRIPE_WEBHOOK_SECRET
SENTRY_DSN
)
# --- Check always-required vars ---
for var in "${ALWAYS_REQUIRED[@]}"; do
if [ -z "${!var:-}" ]; then
MISSING+=("$var")
fi
done
# --- Check prod-only vars ---
if [ "${APP_ENV:-}" = "production" ] || [ "${NODE_ENV:-}" = "production" ]; then
for var in "${PROD_REQUIRED[@]}"; do
if [ -z "${!var:-}" ]; then
MISSING+=("$var (required in production)")
fi
done
fi
# --- Validate format/length constraints ---
if [ -n "${AUTH_JWT_SECRET:-}" ] && [ ${#AUTH_JWT_SECRET} -lt 32 ]; then
WARNINGS+=("AUTH_JWT_SECRET is shorter than 32 chars — insecure")
fi
if [ -n "${DATABASE_URL:-}" ]; then
if ! echo "$DATABASE_URL" | grep -qE "^(postgres|postgresql|mysql|mongodb|redis)://"; then
WARNINGS+=("DATABASE_URL doesn't look like a valid connection string")
fi
fi
if [ -n "${APP_PORT:-}" ]; then
if ! [[ "$APP_PORT" =~ ^[0-9]+$ ]] || [ "$APP_PORT" -lt 1 ] || [ "$APP_PORT" -gt 65535 ]; then
WARNINGS+=("APP_PORT=$APP_PORT is not a valid port number")
fi
fi
# --- Report ---
if [ ${#WARNINGS[@]} -gt 0 ]; then
echo "WARNINGS:"
for w in "${WARNINGS[@]}"; do
echo " ⚠️ $w"
done
fi
if [ ${#MISSING[@]} -gt 0 ]; then
echo ""
echo "FATAL: Missing required environment variables:"
for var in "${MISSING[@]}"; do
echo "$var"
done
echo ""
echo "Copy .env.example to .env and fill in missing values."
exit 1
fi
echo "✅ All required environment variables are set"
```
Node.js equivalent:
```typescript
// src/config/validateEnv.ts
const required = [
'APP_SECRET', 'APP_URL', 'DATABASE_URL',
'AUTH_JWT_SECRET', 'AUTH_REFRESH_SECRET',
]
const missing = required.filter(key => !process.env[key])
if (missing.length > 0) {
console.error('FATAL: Missing required environment variables:', missing)
process.exit(1)
}
if (process.env.AUTH_JWT_SECRET && process.env.AUTH_JWT_SECRET.length < 32) {
console.error('FATAL: AUTH_JWT_SECRET must be at least 32 characters')
process.exit(1)
}
export const config = {
appSecret: process.env.APP_SECRET!,
appUrl: process.env.APP_URL!,
databaseUrl: process.env.DATABASE_URL!,
jwtSecret: process.env.AUTH_JWT_SECRET!,
refreshSecret: process.env.AUTH_REFRESH_SECRET!,
stripeKey: process.env.STRIPE_SECRET_KEY, // optional
port: parseInt(process.env.APP_PORT ?? '3000', 10),
} as const
```
---
## Secret Leak Detection
### Scan Working Tree
```bash
#!/bin/bash
# scripts/scan-secrets.sh
# Scan staged files and working tree for common secret patterns
FAIL=0
check() {
local label="$1"
local pattern="$2"
local matches
matches=$(git diff --cached -U0 2>/dev/null | grep "^+" | grep -vE "^(\+\+\+|#|\/\/)" | \
grep -E "$pattern" | grep -v ".env.example" | grep -v "test\|mock\|fixture\|fake" || true)
if [ -n "$matches" ]; then
echo "SECRET DETECTED [$label]:"
echo "$matches" | head -5
FAIL=1
fi
}
# AWS Access Keys
check "AWS Access Key" "AKIA[0-9A-Z]{16}"
check "AWS Secret Key" "aws_secret_access_key\s*=\s*['\"]?[A-Za-z0-9/+]{40}"
# Stripe
check "Stripe Live Key" "sk_live_[0-9a-zA-Z]{24,}"
check "Stripe Test Key" "sk_test_[0-9a-zA-Z]{24,}"
check "Stripe Webhook" "whsec_[0-9a-zA-Z]{32,}"
# JWT / Generic secrets
check "Hardcoded JWT" "eyJ[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}"
check "Generic Secret" "(secret|password|passwd|api_key|apikey|token)\s*[:=]\s*['\"][^'\"]{12,}['\"]"
# Private keys
check "Private Key Block" "-----BEGIN (RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----"
check "PEM Certificate" "-----BEGIN CERTIFICATE-----"
# Connection strings with credentials
check "DB Connection" "(postgres|mysql|mongodb)://[^:]+:[^@]+@"
check "Redis Auth" "redis://:[^@]+@\|rediss://:[^@]+@"
# Google
check "Google API Key" "AIza[0-9A-Za-z_-]{35}"
check "Google OAuth" "[0-9]+-[0-9A-Za-z_]{32}\.apps\.googleusercontent\.com"
# GitHub
check "GitHub Token" "gh[ps]_[A-Za-z0-9]{36,}"
check "GitHub Fine-grained" "github_pat_[A-Za-z0-9_]{82}"
# Slack
check "Slack Token" "xox[baprs]-[0-9A-Za-z]{10,}"
check "Slack Webhook" "https://hooks\.slack\.com/services/[A-Z0-9]{9,}/[A-Z0-9]{9,}/[A-Za-z0-9]{24,}"
# Twilio
check "Twilio SID" "AC[a-z0-9]{32}"
check "Twilio Token" "SK[a-z0-9]{32}"
if [ $FAIL -eq 1 ]; then
echo ""
echo "BLOCKED: Secrets detected in staged changes."
echo "Remove secrets before committing. Use environment variables instead."
echo "If this is a false positive, add it to .secretsignore or use:"
echo " git commit --no-verify (only if you're 100% certain it's safe)"
exit 1
fi
echo "No secrets detected in staged changes."
```
### Scan Git History (post-incident)
```bash
#!/bin/bash
# scripts/scan-history.sh — scan entire git history for leaked secrets
PATTERNS=(
"AKIA[0-9A-Z]{16}"
"sk_live_[0-9a-zA-Z]{24}"
"sk_test_[0-9a-zA-Z]{24}"
"-----BEGIN.*PRIVATE KEY-----"
"AIza[0-9A-Za-z_-]{35}"
"ghp_[A-Za-z0-9]{36}"
"xox[baprs]-[0-9A-Za-z]{10,}"
)
for pattern in "${PATTERNS[@]}"; do
echo "Scanning for: $pattern"
git log --all -p --no-color 2>/dev/null | \
grep -n "$pattern" | \
grep "^+" | \
grep -v "^+++" | \
head -10
done
# Alternative: use truffleHog or gitleaks for comprehensive scanning
# gitleaks detect --source . --log-opts="--all"
# trufflehog git file://. --only-verified
```
---
## Pre-commit Hook Installation
```bash
#!/bin/bash
# Install the pre-commit hook
HOOK_PATH=".git/hooks/pre-commit"
cat > "$HOOK_PATH" << 'HOOK'
#!/bin/bash
# Pre-commit: scan for secrets before every commit
SCRIPT="scripts/scan-secrets.sh"
if [ -f "$SCRIPT" ]; then
bash "$SCRIPT"
else
# Inline fallback if script not present
if git diff --cached -U0 | grep "^+" | grep -qE "AKIA[0-9A-Z]{16}|sk_live_|-----BEGIN.*PRIVATE KEY"; then
echo "BLOCKED: Possible secret detected in staged changes."
exit 1
fi
fi
HOOK
chmod +x "$HOOK_PATH"
echo "Pre-commit hook installed at $HOOK_PATH"
```
Using `pre-commit` framework (recommended for teams):
```yaml
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks
- repo: local
hooks:
- id: validate-env-example
name: "check-envexample-is-up-to-date"
language: script
entry: bash scripts/check-env-example.sh
pass_filenames: false
```
---
## Credential Rotation Workflow
When a secret is leaked or compromised:
### Step 1 — Detect & Confirm
```bash
# Confirm which secret was exposed
git log --all -p --no-color | grep -A2 -B2 "AKIA\|sk_live_\|SECRET"
# Check if secret is in any open PRs
gh pr list --state open | while read pr; do
gh pr diff $(echo $pr | awk '{print $1}') | grep -E "AKIA|sk_live_" && echo "Found in PR: $pr"
done
```
### Step 2 — Identify Exposure Window
```bash
# Find first commit that introduced the secret
git log --all -p --no-color -- "*.env" "*.json" "*.yaml" "*.ts" "*.py" | \
grep -B 10 "THE_LEAKED_VALUE" | grep "^commit" | tail -1
# Get commit date
git show --format="%ci" COMMIT_HASH | head -1
# Check if secret appears in public repos (GitHub)
gh api search/code -X GET -f q="THE_LEAKED_VALUE" | jq '.total_count, .items[].html_url'
```
### Step 3 — Rotate Credential
Per service — rotate immediately:
- **AWS**: IAM console → delete access key → create new → update everywhere
- **Stripe**: Dashboard → Developers → API keys → Roll key
- **GitHub PAT**: Settings → Developer Settings → Personal access tokens → Revoke → Create new
- **DB password**: `ALTER USER app_user PASSWORD 'new-strong-password-here';`
- **JWT secret**: Rotate key (all existing sessions invalidated — users re-login)
### Step 4 — Update All Environments
```bash
# Update secret manager (source of truth)
# Then redeploy to pull new values
# Vault KV v2
vault kv put secret/myapp/prod \
STRIPE_SECRET_KEY="sk_live_NEW..." \
APP_SECRET="new-secret-here"
# AWS SSM
aws ssm put-parameter \
--name "/myapp/prod/STRIPE_SECRET_KEY" \
--value "sk_live_NEW..." \
--type "SecureString" \
--overwrite
# 1Password
op item edit "MyApp Prod" \
--field "STRIPE_SECRET_KEY[password]=sk_live_NEW..."
# Doppler
doppler secrets set STRIPE_SECRET_KEY="sk_live_NEW..." --project myapp --config prod
```
### Step 5 — Remove from Git History
```bash
# WARNING: rewrites history — coordinate with team first
git filter-repo --path-glob "*.env" --invert-paths
# Or remove specific string from all commits
git filter-repo --replace-text <(echo "LEAKED_VALUE==>REDACTED")
# Force push all branches (requires team coordination + force push permissions)
git push origin --force --all
# Notify all developers to re-clone
```
### Step 6 — Verify
```bash
# Confirm secret no longer in history
git log --all -p | grep "LEAKED_VALUE" | wc -l # should be 0
# Test new credentials work
curl -H "Authorization: Bearer $NEW_TOKEN" https://api.service.com/test
# Monitor for unauthorized usage of old credential (check service audit logs)
```
---
## Secret Manager Integrations ## Secret Manager Integrations

View File

@@ -0,0 +1,358 @@
# env-secrets-manager reference
## Required Variable Validation Script
```bash
#!/bin/bash
# scripts/validate-env.sh
# Run at app startup or in CI before deploy
# Exit 1 if any required var is missing or empty
set -euo pipefail
MISSING=()
WARNINGS=()
# --- Define required vars by environment ---
ALWAYS_REQUIRED=(
APP_SECRET
APP_URL
DATABASE_URL
AUTH_JWT_SECRET
AUTH_REFRESH_SECRET
)
PROD_REQUIRED=(
STRIPE_SECRET_KEY
STRIPE_WEBHOOK_SECRET
SENTRY_DSN
)
# --- Check always-required vars ---
for var in "${ALWAYS_REQUIRED[@]}"; do
if [ -z "${!var:-}" ]; then
MISSING+=("$var")
fi
done
# --- Check prod-only vars ---
if [ "${APP_ENV:-}" = "production" ] || [ "${NODE_ENV:-}" = "production" ]; then
for var in "${PROD_REQUIRED[@]}"; do
if [ -z "${!var:-}" ]; then
MISSING+=("$var (required in production)")
fi
done
fi
# --- Validate format/length constraints ---
if [ -n "${AUTH_JWT_SECRET:-}" ] && [ ${#AUTH_JWT_SECRET} -lt 32 ]; then
WARNINGS+=("AUTH_JWT_SECRET is shorter than 32 chars — insecure")
fi
if [ -n "${DATABASE_URL:-}" ]; then
if ! echo "$DATABASE_URL" | grep -qE "^(postgres|postgresql|mysql|mongodb|redis)://"; then
WARNINGS+=("DATABASE_URL doesn't look like a valid connection string")
fi
fi
if [ -n "${APP_PORT:-}" ]; then
if ! [[ "$APP_PORT" =~ ^[0-9]+$ ]] || [ "$APP_PORT" -lt 1 ] || [ "$APP_PORT" -gt 65535 ]; then
WARNINGS+=("APP_PORT=$APP_PORT is not a valid port number")
fi
fi
# --- Report ---
if [ ${#WARNINGS[@]} -gt 0 ]; then
echo "WARNINGS:"
for w in "${WARNINGS[@]}"; do
echo " ⚠️ $w"
done
fi
if [ ${#MISSING[@]} -gt 0 ]; then
echo ""
echo "FATAL: Missing required environment variables:"
for var in "${MISSING[@]}"; do
echo "$var"
done
echo ""
echo "Copy .env.example to .env and fill in missing values."
exit 1
fi
echo "✅ All required environment variables are set"
```
Node.js equivalent:
```typescript
// src/config/validateEnv.ts
const required = [
'APP_SECRET', 'APP_URL', 'DATABASE_URL',
'AUTH_JWT_SECRET', 'AUTH_REFRESH_SECRET',
]
const missing = required.filter(key => !process.env[key])
if (missing.length > 0) {
console.error('FATAL: Missing required environment variables:', missing)
process.exit(1)
}
if (process.env.AUTH_JWT_SECRET && process.env.AUTH_JWT_SECRET.length < 32) {
console.error('FATAL: AUTH_JWT_SECRET must be at least 32 characters')
process.exit(1)
}
export const config = {
appSecret: process.env.APP_SECRET!,
appUrl: process.env.APP_URL!,
databaseUrl: process.env.DATABASE_URL!,
jwtSecret: process.env.AUTH_JWT_SECRET!,
refreshSecret: process.env.AUTH_REFRESH_SECRET!,
stripeKey: process.env.STRIPE_SECRET_KEY, // optional
port: parseInt(process.env.APP_PORT ?? '3000', 10),
} as const
```
---
## Secret Leak Detection
### Scan Working Tree
```bash
#!/bin/bash
# scripts/scan-secrets.sh
# Scan staged files and working tree for common secret patterns
FAIL=0
check() {
local label="$1"
local pattern="$2"
local matches
matches=$(git diff --cached -U0 2>/dev/null | grep "^+" | grep -vE "^(\+\+\+|#|\/\/)" | \
grep -E "$pattern" | grep -v ".env.example" | grep -v "test\|mock\|fixture\|fake" || true)
if [ -n "$matches" ]; then
echo "SECRET DETECTED [$label]:"
echo "$matches" | head -5
FAIL=1
fi
}
# AWS Access Keys
check "AWS Access Key" "AKIA[0-9A-Z]{16}"
check "AWS Secret Key" "aws_secret_access_key\s*=\s*['\"]?[A-Za-z0-9/+]{40}"
# Stripe
check "Stripe Live Key" "sk_live_[0-9a-zA-Z]{24,}"
check "Stripe Test Key" "sk_test_[0-9a-zA-Z]{24,}"
check "Stripe Webhook" "whsec_[0-9a-zA-Z]{32,}"
# JWT / Generic secrets
check "Hardcoded JWT" "eyJ[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}"
check "Generic Secret" "(secret|password|passwd|api_key|apikey|token)\s*[:=]\s*['\"][^'\"]{12,}['\"]"
# Private keys
check "Private Key Block" "-----BEGIN (RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----"
check "PEM Certificate" "-----BEGIN CERTIFICATE-----"
# Connection strings with credentials
check "DB Connection" "(postgres|mysql|mongodb)://[^:]+:[^@]+@"
check "Redis Auth" "redis://:[^@]+@\|rediss://:[^@]+@"
# Google
check "Google API Key" "AIza[0-9A-Za-z_-]{35}"
check "Google OAuth" "[0-9]+-[0-9A-Za-z_]{32}\.apps\.googleusercontent\.com"
# GitHub
check "GitHub Token" "gh[ps]_[A-Za-z0-9]{36,}"
check "GitHub Fine-grained" "github_pat_[A-Za-z0-9_]{82}"
# Slack
check "Slack Token" "xox[baprs]-[0-9A-Za-z]{10,}"
check "Slack Webhook" "https://hooks\.slack\.com/services/[A-Z0-9]{9,}/[A-Z0-9]{9,}/[A-Za-z0-9]{24,}"
# Twilio
check "Twilio SID" "AC[a-z0-9]{32}"
check "Twilio Token" "SK[a-z0-9]{32}"
if [ $FAIL -eq 1 ]; then
echo ""
echo "BLOCKED: Secrets detected in staged changes."
echo "Remove secrets before committing. Use environment variables instead."
echo "If this is a false positive, add it to .secretsignore or use:"
echo " git commit --no-verify (only if you're 100% certain it's safe)"
exit 1
fi
echo "No secrets detected in staged changes."
```
### Scan Git History (post-incident)
```bash
#!/bin/bash
# scripts/scan-history.sh — scan entire git history for leaked secrets
PATTERNS=(
"AKIA[0-9A-Z]{16}"
"sk_live_[0-9a-zA-Z]{24}"
"sk_test_[0-9a-zA-Z]{24}"
"-----BEGIN.*PRIVATE KEY-----"
"AIza[0-9A-Za-z_-]{35}"
"ghp_[A-Za-z0-9]{36}"
"xox[baprs]-[0-9A-Za-z]{10,}"
)
for pattern in "${PATTERNS[@]}"; do
echo "Scanning for: $pattern"
git log --all -p --no-color 2>/dev/null | \
grep -n "$pattern" | \
grep "^+" | \
grep -v "^+++" | \
head -10
done
# Alternative: use truffleHog or gitleaks for comprehensive scanning
# gitleaks detect --source . --log-opts="--all"
# trufflehog git file://. --only-verified
```
---
## Pre-commit Hook Installation
```bash
#!/bin/bash
# Install the pre-commit hook
HOOK_PATH=".git/hooks/pre-commit"
cat > "$HOOK_PATH" << 'HOOK'
#!/bin/bash
# Pre-commit: scan for secrets before every commit
SCRIPT="scripts/scan-secrets.sh"
if [ -f "$SCRIPT" ]; then
bash "$SCRIPT"
else
# Inline fallback if script not present
if git diff --cached -U0 | grep "^+" | grep -qE "AKIA[0-9A-Z]{16}|sk_live_|-----BEGIN.*PRIVATE KEY"; then
echo "BLOCKED: Possible secret detected in staged changes."
exit 1
fi
fi
HOOK
chmod +x "$HOOK_PATH"
echo "Pre-commit hook installed at $HOOK_PATH"
```
Using `pre-commit` framework (recommended for teams):
```yaml
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks
- repo: local
hooks:
- id: validate-env-example
name: "check-envexample-is-up-to-date"
language: script
entry: bash scripts/check-env-example.sh
pass_filenames: false
```
---
## Credential Rotation Workflow
When a secret is leaked or compromised:
### Step 1 — Detect & Confirm
```bash
# Confirm which secret was exposed
git log --all -p --no-color | grep -A2 -B2 "AKIA\|sk_live_\|SECRET"
# Check if secret is in any open PRs
gh pr list --state open | while read pr; do
gh pr diff $(echo $pr | awk '{print $1}') | grep -E "AKIA|sk_live_" && echo "Found in PR: $pr"
done
```
### Step 2 — Identify Exposure Window
```bash
# Find first commit that introduced the secret
git log --all -p --no-color -- "*.env" "*.json" "*.yaml" "*.ts" "*.py" | \
grep -B 10 "THE_LEAKED_VALUE" | grep "^commit" | tail -1
# Get commit date
git show --format="%ci" COMMIT_HASH | head -1
# Check if secret appears in public repos (GitHub)
gh api search/code -X GET -f q="THE_LEAKED_VALUE" | jq '.total_count, .items[].html_url'
```
### Step 3 — Rotate Credential
Per service — rotate immediately:
- **AWS**: IAM console → delete access key → create new → update everywhere
- **Stripe**: Dashboard → Developers → API keys → Roll key
- **GitHub PAT**: Settings → Developer Settings → Personal access tokens → Revoke → Create new
- **DB password**: `ALTER USER app_user PASSWORD 'new-strong-password-here';`
- **JWT secret**: Rotate key (all existing sessions invalidated — users re-login)
### Step 4 — Update All Environments
```bash
# Update secret manager (source of truth)
# Then redeploy to pull new values
# Vault KV v2
vault kv put secret/myapp/prod \
STRIPE_SECRET_KEY="sk_live_NEW..." \
APP_SECRET="new-secret-here"
# AWS SSM
aws ssm put-parameter \
--name "/myapp/prod/STRIPE_SECRET_KEY" \
--value "sk_live_NEW..." \
--type "SecureString" \
--overwrite
# 1Password
op item edit "MyApp Prod" \
--field "STRIPE_SECRET_KEY[password]=sk_live_NEW..."
# Doppler
doppler secrets set STRIPE_SECRET_KEY="sk_live_NEW..." --project myapp --config prod
```
### Step 5 — Remove from Git History
```bash
# WARNING: rewrites history — coordinate with team first
git filter-repo --path-glob "*.env" --invert-paths
# Or remove specific string from all commits
git filter-repo --replace-text <(echo "LEAKED_VALUE==>REDACTED")
# Force push all branches (requires team coordination + force push permissions)
git push origin --force --all
# Notify all developers to re-clone
```
### Step 6 — Verify
```bash
# Confirm secret no longer in history
git log --all -p | grep "LEAKED_VALUE" | wc -l # should be 0
# Test new credentials work
curl -H "Authorization: Bearer $NEW_TOKEN" https://api.service.com/test
# Monitor for unauthorized usage of old credential (check service audit logs)
```
---

View File

@@ -60,519 +60,7 @@ Most modern setups: **pnpm workspaces + Turborepo + Changesets**
--- ---
## Turborepo ## Turborepo
→ See references/monorepo-tooling-reference.md for details
### turbo.json pipeline config
```json
{
"$schema": "https://turbo.build/schema.json",
"globalEnv": ["NODE_ENV", "DATABASE_URL"],
"pipeline": {
"build": {
"dependsOn": ["^build"], // build deps first (topological order)
"outputs": [".next/**", "dist/**", "build/**"],
"env": ["NEXT_PUBLIC_API_URL"]
},
"test": {
"dependsOn": ["^build"], // need built deps to test
"outputs": ["coverage/**"],
"cache": true
},
"lint": {
"outputs": [],
"cache": true
},
"dev": {
"cache": false, // never cache dev servers
"persistent": true // long-running process
},
"type-check": {
"dependsOn": ["^build"],
"outputs": []
}
}
}
```
### Key commands
```bash
# Build everything (respects dependency order)
turbo run build
# Build only affected packages (requires --filter)
turbo run build --filter=...[HEAD^1] # changed since last commit
turbo run build --filter=...[main] # changed vs main branch
# Test only affected
turbo run test --filter=...[HEAD^1]
# Run for a specific app and all its dependencies
turbo run build --filter=@myorg/web...
# Run for a specific package only (no dependencies)
turbo run build --filter=@myorg/ui
# Dry-run — see what would run without executing
turbo run build --dry-run
# Enable remote caching (Vercel Remote Cache)
turbo login
turbo link
```
### Remote caching setup
```bash
# .turbo/config.json (auto-created by turbo link)
{
"teamid": "team_xxxx",
"apiurl": "https://vercel.com"
}
# Self-hosted cache server (open-source alternative)
# Run ducktape/turborepo-remote-cache or Turborepo's official server
TURBO_API=http://your-cache-server.internal \
TURBO_TOKEN=your-token \
TURBO_TEAM=your-team \
turbo run build
```
---
## Nx
### Project graph and affected commands
```bash
# Install
npx create-nx-workspace@latest my-monorepo
# Visualize the project graph (opens browser)
nx graph
# Show affected packages for the current branch
nx affected:graph
# Run only affected tests
nx affected --target=test
# Run only affected builds
nx affected --target=build
# Run affected with base/head (for CI)
nx affected --target=test --base=main --head=HEAD
```
### nx.json configuration
```json
{
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"targetDefaults": {
"build": {
"dependsOn": ["^build"],
"cache": true
},
"test": {
"cache": true,
"inputs": ["default", "^production"]
}
},
"namedInputs": {
"default": ["{projectRoot}/**/*", "sharedGlobals"],
"production": ["default", "!{projectRoot}/**/*.spec.ts", "!{projectRoot}/jest.config.*"],
"sharedGlobals": []
},
"parallel": 4,
"cacheDirectory": "/tmp/nx-cache"
}
```
---
## pnpm Workspaces
### pnpm-workspace.yaml
```yaml
packages:
- 'apps/*'
- 'packages/*'
- 'tools/*'
```
### workspace:* protocol for local packages
```json
// apps/web/package.json
{
"name": "@myorg/web",
"dependencies": {
"@myorg/ui": "workspace:*", // always use local version
"@myorg/utils": "workspace:^", // local, but respect semver on publish
"@myorg/types": "workspace:~"
}
}
```
### Useful pnpm workspace commands
```bash
# Install all packages across workspace
pnpm install
# Run script in a specific package
pnpm --filter @myorg/web dev
# Run script in all packages
pnpm --filter "*" build
# Run script in a package and all its dependencies
pnpm --filter @myorg/web... build
# Add a dependency to a specific package
pnpm --filter @myorg/web add react
# Add a shared dev dependency to root
pnpm add -D typescript -w
# List workspace packages
pnpm ls --depth -1 -r
```
---
## Cross-Package Impact Analysis
When a shared package changes, determine what's affected before you ship.
```bash
# Using Turborepo — show affected packages
turbo run build --filter=...[HEAD^1] --dry-run 2>&1 | grep "Tasks to run"
# Using Nx
nx affected:apps --base=main --head=HEAD # which apps are affected
nx affected:libs --base=main --head=HEAD # which libs are affected
# Manual analysis with pnpm
# Find all packages that depend on @myorg/utils:
grep -r '"@myorg/utils"' packages/*/package.json apps/*/package.json
# Using jq for structured output
for pkg in packages/*/package.json apps/*/package.json; do
name=$(jq -r '.name' "$pkg")
if jq -e '.dependencies["@myorg/utils"] // .devDependencies["@myorg/utils"]' "$pkg" > /dev/null 2>&1; then
echo "$name depends on @myorg/utils"
fi
done
```
---
## Dependency Graph Visualization
Generate a Mermaid diagram from your workspace:
```bash
# Generate dependency graph as Mermaid
cat > scripts/gen-dep-graph.js << 'EOF'
const { execSync } = require('child_process');
const fs = require('fs');
// Parse pnpm workspace packages
const packages = JSON.parse(
execSync('pnpm ls --depth -1 -r --json').toString()
);
let mermaid = 'graph TD\n';
packages.forEach(pkg => {
const deps = Object.keys(pkg.dependencies || {})
.filter(d => d.startsWith('@myorg/'));
deps.forEach(dep => {
const from = pkg.name.replace('@myorg/', '');
const to = dep.replace('@myorg/', '');
mermaid += ` ${from} --> ${to}\n`;
});
});
fs.writeFileSync('docs/dep-graph.md', '```mermaid\n' + mermaid + '```\n');
console.log('Written to docs/dep-graph.md');
EOF
node scripts/gen-dep-graph.js
```
**Example output:**
```mermaid
graph TD
web --> ui
web --> utils
web --> types
mobile --> ui
mobile --> utils
mobile --> types
admin --> ui
admin --> utils
api --> types
ui --> utils
```
---
## Claude Code Configuration (Workspace-Aware CLAUDE.md)
Place a root CLAUDE.md + per-package CLAUDE.md files:
```markdown
# /CLAUDE.md — Root (applies to all packages)
## Monorepo Structure
- apps/web — Next.js customer-facing app
- apps/admin — Next.js internal admin
- apps/api — Express REST API
- packages/ui — Shared React component library
- packages/utils — Shared utilities (pure functions only)
- packages/types — Shared TypeScript types (no runtime code)
## Build System
- pnpm workspaces + Turborepo
- Always use `pnpm --filter <package>` to scope commands
- Never run `npm install` or `yarn` — pnpm only
- Run `turbo run build --filter=...[HEAD^1]` before committing
## Task Scoping Rules
- When modifying packages/ui: also run tests for apps/web and apps/admin (they depend on it)
- When modifying packages/types: run type-check across ALL packages
- When modifying apps/api: only need to test apps/api
## Package Manager
pnpm — version pinned in packageManager field of root package.json
```
```markdown
# /packages/ui/CLAUDE.md — Package-specific
## This Package
Shared React component library. Zero business logic. Pure UI only.
## Rules
- All components must be exported from src/index.ts
- No direct API calls in components — accept data via props
- Every component needs a Storybook story in src/stories/
- Use Tailwind for styling — no CSS modules or styled-components
## Testing
- Component tests: `pnpm --filter @myorg/ui test`
- Visual regression: `pnpm --filter @myorg/ui test:storybook`
## Publishing
- Version bumps via changesets only — never edit package.json version manually
- Run `pnpm changeset` from repo root after changes
```
---
## Migration: Multi-Repo → Monorepo
```bash
# Step 1: Create monorepo scaffold
mkdir my-monorepo && cd my-monorepo
pnpm init
echo "packages:\n - 'apps/*'\n - 'packages/*'" > pnpm-workspace.yaml
# Step 2: Move repos with git history preserved
mkdir -p apps packages
# For each existing repo:
git clone https://github.com/myorg/web-app
cd web-app
git filter-repo --to-subdirectory-filter apps/web # rewrites history into subdir
cd ..
git remote add web-app ./web-app
git fetch web-app --tags
git merge web-app/main --allow-unrelated-histories
# Step 3: Update package names to scoped
# In each package.json, change "name": "web" to "name": "@myorg/web"
# Step 4: Replace cross-repo npm deps with workspace:*
# apps/web/package.json: "@myorg/ui": "1.2.3" → "@myorg/ui": "workspace:*"
# Step 5: Add shared configs to root
cp apps/web/.eslintrc.js .eslintrc.base.js
# Update each package's config to extend root:
# { "extends": ["../../.eslintrc.base.js"] }
# Step 6: Add Turborepo
pnpm add -D turbo -w
# Create turbo.json (see above)
# Step 7: Unified CI (see CI section below)
# Step 8: Test everything
turbo run build test lint
```
---
## CI Patterns
### GitHub Actions — Affected Only
```yaml
# .github/workflows/ci.yml
name: "ci"
on:
push:
branches: [main]
pull_request:
jobs:
affected:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # full history needed for affected detection
- uses: pnpm/action-setup@v3
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- run: pnpm install --frozen-lockfile
# Turborepo remote cache
- uses: actions/cache@v4
with:
path: .turbo
key: ${{ runner.os }}-turbo-${{ github.sha }}
restore-keys: ${{ runner.os }}-turbo-
# Only test/build affected packages
- name: "build-affected"
run: turbo run build --filter=...[origin/main]
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
- name: "test-affected"
run: turbo run test --filter=...[origin/main]
- name: "lint-affected"
run: turbo run lint --filter=...[origin/main]
```
### GitLab CI — Parallel Stages
```yaml
# .gitlab-ci.yml
stages: [install, build, test, publish]
variables:
PNPM_CACHE_FOLDER: .pnpm-store
cache:
key: pnpm-$CI_COMMIT_REF_SLUG
paths: [.pnpm-store/, .turbo/]
install:
stage: install
script:
- pnpm install --frozen-lockfile
artifacts:
paths: [node_modules/, packages/*/node_modules/, apps/*/node_modules/]
expire_in: 1h
build:affected:
stage: build
needs: [install]
script:
- turbo run build --filter=...[origin/main]
artifacts:
paths: [apps/*/dist/, apps/*/.next/, packages/*/dist/]
test:affected:
stage: test
needs: [build:affected]
script:
- turbo run test --filter=...[origin/main]
coverage: '/Statements\s*:\s*(\d+\.?\d*)%/'
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: "**/coverage/cobertura-coverage.xml"
```
---
## Publishing with Changesets
```bash
# Install changesets
pnpm add -D @changesets/cli -w
pnpm changeset init
# After making changes, create a changeset
pnpm changeset
# Interactive: select packages, choose semver bump, write changelog entry
# In CI — version packages + update changelogs
pnpm changeset version
# Publish all changed packages
pnpm changeset publish
# Pre-release channel (for alpha/beta)
pnpm changeset pre enter beta
pnpm changeset
pnpm changeset version # produces 1.2.0-beta.0
pnpm changeset publish --tag beta
pnpm changeset pre exit # back to stable releases
```
### Automated publish workflow (GitHub Actions)
```yaml
# .github/workflows/release.yml
name: "release"
on:
push:
branches: [main]
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
- uses: actions/setup-node@v4
with:
node-version: 20
registry-url: https://registry.npmjs.org
- run: pnpm install --frozen-lockfile
- name: "create-release-pr-or-publish"
uses: changesets/action@v1
with:
publish: pnpm changeset publish
version: pnpm changeset version
commit: "chore: release packages"
title: "chore: release packages"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
```
---
## Common Pitfalls ## Common Pitfalls

View File

@@ -0,0 +1,516 @@
# monorepo-navigator reference
## Turborepo
### turbo.json pipeline config
```json
{
"$schema": "https://turbo.build/schema.json",
"globalEnv": ["NODE_ENV", "DATABASE_URL"],
"pipeline": {
"build": {
"dependsOn": ["^build"], // build deps first (topological order)
"outputs": [".next/**", "dist/**", "build/**"],
"env": ["NEXT_PUBLIC_API_URL"]
},
"test": {
"dependsOn": ["^build"], // need built deps to test
"outputs": ["coverage/**"],
"cache": true
},
"lint": {
"outputs": [],
"cache": true
},
"dev": {
"cache": false, // never cache dev servers
"persistent": true // long-running process
},
"type-check": {
"dependsOn": ["^build"],
"outputs": []
}
}
}
```
### Key commands
```bash
# Build everything (respects dependency order)
turbo run build
# Build only affected packages (requires --filter)
turbo run build --filter=...[HEAD^1] # changed since last commit
turbo run build --filter=...[main] # changed vs main branch
# Test only affected
turbo run test --filter=...[HEAD^1]
# Run for a specific app and all its dependencies
turbo run build --filter=@myorg/web...
# Run for a specific package only (no dependencies)
turbo run build --filter=@myorg/ui
# Dry-run — see what would run without executing
turbo run build --dry-run
# Enable remote caching (Vercel Remote Cache)
turbo login
turbo link
```
### Remote caching setup
```bash
# .turbo/config.json (auto-created by turbo link)
{
"teamid": "team_xxxx",
"apiurl": "https://vercel.com"
}
# Self-hosted cache server (open-source alternative)
# Run ducktape/turborepo-remote-cache or Turborepo's official server
TURBO_API=http://your-cache-server.internal \
TURBO_TOKEN=your-token \
TURBO_TEAM=your-team \
turbo run build
```
---
## Nx
### Project graph and affected commands
```bash
# Install
npx create-nx-workspace@latest my-monorepo
# Visualize the project graph (opens browser)
nx graph
# Show affected packages for the current branch
nx affected:graph
# Run only affected tests
nx affected --target=test
# Run only affected builds
nx affected --target=build
# Run affected with base/head (for CI)
nx affected --target=test --base=main --head=HEAD
```
### nx.json configuration
```json
{
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"targetDefaults": {
"build": {
"dependsOn": ["^build"],
"cache": true
},
"test": {
"cache": true,
"inputs": ["default", "^production"]
}
},
"namedInputs": {
"default": ["{projectRoot}/**/*", "sharedGlobals"],
"production": ["default", "!{projectRoot}/**/*.spec.ts", "!{projectRoot}/jest.config.*"],
"sharedGlobals": []
},
"parallel": 4,
"cacheDirectory": "/tmp/nx-cache"
}
```
---
## pnpm Workspaces
### pnpm-workspace.yaml
```yaml
packages:
- 'apps/*'
- 'packages/*'
- 'tools/*'
```
### workspace:* protocol for local packages
```json
// apps/web/package.json
{
"name": "@myorg/web",
"dependencies": {
"@myorg/ui": "workspace:*", // always use local version
"@myorg/utils": "workspace:^", // local, but respect semver on publish
"@myorg/types": "workspace:~"
}
}
```
### Useful pnpm workspace commands
```bash
# Install all packages across workspace
pnpm install
# Run script in a specific package
pnpm --filter @myorg/web dev
# Run script in all packages
pnpm --filter "*" build
# Run script in a package and all its dependencies
pnpm --filter @myorg/web... build
# Add a dependency to a specific package
pnpm --filter @myorg/web add react
# Add a shared dev dependency to root
pnpm add -D typescript -w
# List workspace packages
pnpm ls --depth -1 -r
```
---
## Cross-Package Impact Analysis
When a shared package changes, determine what's affected before you ship.
```bash
# Using Turborepo — show affected packages
turbo run build --filter=...[HEAD^1] --dry-run 2>&1 | grep "Tasks to run"
# Using Nx
nx affected:apps --base=main --head=HEAD # which apps are affected
nx affected:libs --base=main --head=HEAD # which libs are affected
# Manual analysis with pnpm
# Find all packages that depend on @myorg/utils:
grep -r '"@myorg/utils"' packages/*/package.json apps/*/package.json
# Using jq for structured output
for pkg in packages/*/package.json apps/*/package.json; do
name=$(jq -r '.name' "$pkg")
if jq -e '.dependencies["@myorg/utils"] // .devDependencies["@myorg/utils"]' "$pkg" > /dev/null 2>&1; then
echo "$name depends on @myorg/utils"
fi
done
```
---
## Dependency Graph Visualization
Generate a Mermaid diagram from your workspace:
```bash
# Generate dependency graph as Mermaid
cat > scripts/gen-dep-graph.js << 'EOF'
const { execSync } = require('child_process');
const fs = require('fs');
// Parse pnpm workspace packages
const packages = JSON.parse(
execSync('pnpm ls --depth -1 -r --json').toString()
);
let mermaid = 'graph TD\n';
packages.forEach(pkg => {
const deps = Object.keys(pkg.dependencies || {})
.filter(d => d.startsWith('@myorg/'));
deps.forEach(dep => {
const from = pkg.name.replace('@myorg/', '');
const to = dep.replace('@myorg/', '');
mermaid += ` ${from} --> ${to}\n`;
});
});
fs.writeFileSync('docs/dep-graph.md', '```mermaid\n' + mermaid + '```\n');
console.log('Written to docs/dep-graph.md');
EOF
node scripts/gen-dep-graph.js
```
**Example output:**
```mermaid
graph TD
web --> ui
web --> utils
web --> types
mobile --> ui
mobile --> utils
mobile --> types
admin --> ui
admin --> utils
api --> types
ui --> utils
```
---
## Claude Code Configuration (Workspace-Aware CLAUDE.md)
Place a root CLAUDE.md + per-package CLAUDE.md files:
```markdown
# /CLAUDE.md — Root (applies to all packages)
## Monorepo Structure
- apps/web — Next.js customer-facing app
- apps/admin — Next.js internal admin
- apps/api — Express REST API
- packages/ui — Shared React component library
- packages/utils — Shared utilities (pure functions only)
- packages/types — Shared TypeScript types (no runtime code)
## Build System
- pnpm workspaces + Turborepo
- Always use `pnpm --filter <package>` to scope commands
- Never run `npm install` or `yarn` — pnpm only
- Run `turbo run build --filter=...[HEAD^1]` before committing
## Task Scoping Rules
- When modifying packages/ui: also run tests for apps/web and apps/admin (they depend on it)
- When modifying packages/types: run type-check across ALL packages
- When modifying apps/api: only need to test apps/api
## Package Manager
pnpm — version pinned in packageManager field of root package.json
```
```markdown
# /packages/ui/CLAUDE.md — Package-specific
## This Package
Shared React component library. Zero business logic. Pure UI only.
## Rules
- All components must be exported from src/index.ts
- No direct API calls in components — accept data via props
- Every component needs a Storybook story in src/stories/
- Use Tailwind for styling — no CSS modules or styled-components
## Testing
- Component tests: `pnpm --filter @myorg/ui test`
- Visual regression: `pnpm --filter @myorg/ui test:storybook`
## Publishing
- Version bumps via changesets only — never edit package.json version manually
- Run `pnpm changeset` from repo root after changes
```
---
## Migration: Multi-Repo → Monorepo
```bash
# Step 1: Create monorepo scaffold
mkdir my-monorepo && cd my-monorepo
pnpm init
echo "packages:\n - 'apps/*'\n - 'packages/*'" > pnpm-workspace.yaml
# Step 2: Move repos with git history preserved
mkdir -p apps packages
# For each existing repo:
git clone https://github.com/myorg/web-app
cd web-app
git filter-repo --to-subdirectory-filter apps/web # rewrites history into subdir
cd ..
git remote add web-app ./web-app
git fetch web-app --tags
git merge web-app/main --allow-unrelated-histories
# Step 3: Update package names to scoped
# In each package.json, change "name": "web" to "name": "@myorg/web"
# Step 4: Replace cross-repo npm deps with workspace:*
# apps/web/package.json: "@myorg/ui": "1.2.3" → "@myorg/ui": "workspace:*"
# Step 5: Add shared configs to root
cp apps/web/.eslintrc.js .eslintrc.base.js
# Update each package's config to extend root:
# { "extends": ["../../.eslintrc.base.js"] }
# Step 6: Add Turborepo
pnpm add -D turbo -w
# Create turbo.json (see above)
# Step 7: Unified CI (see CI section below)
# Step 8: Test everything
turbo run build test lint
```
---
## CI Patterns
### GitHub Actions — Affected Only
```yaml
# .github/workflows/ci.yml
name: "ci"
on:
push:
branches: [main]
pull_request:
jobs:
affected:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # full history needed for affected detection
- uses: pnpm/action-setup@v3
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- run: pnpm install --frozen-lockfile
# Turborepo remote cache
- uses: actions/cache@v4
with:
path: .turbo
key: ${{ runner.os }}-turbo-${{ github.sha }}
restore-keys: ${{ runner.os }}-turbo-
# Only test/build affected packages
- name: "build-affected"
run: turbo run build --filter=...[origin/main]
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
- name: "test-affected"
run: turbo run test --filter=...[origin/main]
- name: "lint-affected"
run: turbo run lint --filter=...[origin/main]
```
### GitLab CI — Parallel Stages
```yaml
# .gitlab-ci.yml
stages: [install, build, test, publish]
variables:
PNPM_CACHE_FOLDER: .pnpm-store
cache:
key: pnpm-$CI_COMMIT_REF_SLUG
paths: [.pnpm-store/, .turbo/]
install:
stage: install
script:
- pnpm install --frozen-lockfile
artifacts:
paths: [node_modules/, packages/*/node_modules/, apps/*/node_modules/]
expire_in: 1h
build:affected:
stage: build
needs: [install]
script:
- turbo run build --filter=...[origin/main]
artifacts:
paths: [apps/*/dist/, apps/*/.next/, packages/*/dist/]
test:affected:
stage: test
needs: [build:affected]
script:
- turbo run test --filter=...[origin/main]
coverage: '/Statements\s*:\s*(\d+\.?\d*)%/'
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: "**/coverage/cobertura-coverage.xml"
```
---
## Publishing with Changesets
```bash
# Install changesets
pnpm add -D @changesets/cli -w
pnpm changeset init
# After making changes, create a changeset
pnpm changeset
# Interactive: select packages, choose semver bump, write changelog entry
# In CI — version packages + update changelogs
pnpm changeset version
# Publish all changed packages
pnpm changeset publish
# Pre-release channel (for alpha/beta)
pnpm changeset pre enter beta
pnpm changeset
pnpm changeset version # produces 1.2.0-beta.0
pnpm changeset publish --tag beta
pnpm changeset pre exit # back to stable releases
```
### Automated publish workflow (GitHub Actions)
```yaml
# .github/workflows/release.yml
name: "release"
on:
push:
branches: [main]
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
- uses: actions/setup-node@v4
with:
node-version: 20
registry-url: https://registry.npmjs.org
- run: pnpm install --frozen-lockfile
- name: "create-release-pr-or-publish"
uses: changesets/action@v1
with:
publish: pnpm changeset publish
version: pnpm changeset version
commit: "chore: release packages"
title: "chore: release packages"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
```
---

View File

@@ -50,478 +50,7 @@ Systematic performance profiling for Node.js, Python, and Go applications. Ident
--- ---
## Node.js Profiling ## Node.js Profiling
→ See references/profiling-recipes.md for details
### CPU Flamegraph
```bash
# Method 1: clinic.js (best for development)
npm install -g clinic
# CPU flamegraph
clinic flame -- node dist/server.js
# Heap profiler
clinic heapprofiler -- node dist/server.js
# Bubble chart (event loop blocking)
clinic bubbles -- node dist/server.js
# Load with autocannon while profiling
autocannon -c 50 -d 30 http://localhost:3000/api/tasks &
clinic flame -- node dist/server.js
```
```bash
# Method 2: Node.js built-in profiler
node --prof dist/server.js
# After running some load:
node --prof-process isolate-*.log | head -100
```
```bash
# Method 3: V8 CPU profiler via inspector
node --inspect dist/server.js
# Open Chrome DevTools → Performance → Record
```
### Heap Snapshot / Memory Leak Detection
```javascript
// Add to your server for on-demand heap snapshots
import v8 from 'v8'
import fs from 'fs'
// Endpoint: POST /debug/heap-snapshot (protect with auth!)
app.post('/debug/heap-snapshot', (req, res) => {
const filename = `heap-${Date.now()}.heapsnapshot`
const snapshot = v8.writeHeapSnapshot(filename)
res.json({ snapshot })
})
```
```bash
# Take snapshots over time and compare in Chrome DevTools
curl -X POST http://localhost:3000/debug/heap-snapshot
# Wait 5 minutes of load
curl -X POST http://localhost:3000/debug/heap-snapshot
# Open both snapshots in Chrome → Memory → Compare
```
### Detect Event Loop Blocking
```javascript
// Add blocked-at to detect synchronous blocking
import blocked from 'blocked-at'
blocked((time, stack) => {
console.warn(`Event loop blocked for ${time}ms`)
console.warn(stack.join('\n'))
}, { threshold: 100 }) // Alert if blocked > 100ms
```
### Node.js Memory Profiling Script
```javascript
// scripts/memory-profile.mjs
// Run: node --experimental-vm-modules scripts/memory-profile.mjs
import { createRequire } from 'module'
const require = createRequire(import.meta.url)
function formatBytes(bytes) {
return (bytes / 1024 / 1024).toFixed(2) + ' MB'
}
function measureMemory(label) {
const mem = process.memoryUsage()
console.log(`\n[${label}]`)
console.log(` RSS: ${formatBytes(mem.rss)}`)
console.log(` Heap Used: ${formatBytes(mem.heapUsed)}`)
console.log(` Heap Total:${formatBytes(mem.heapTotal)}`)
console.log(` External: ${formatBytes(mem.external)}`)
return mem
}
const baseline = measureMemory('Baseline')
// Simulate your operation
for (let i = 0; i < 1000; i++) {
// Replace with your actual operation
const result = await someOperation()
}
const after = measureMemory('After 1000 operations')
console.log(`\n[Delta]`)
console.log(` Heap Used: +${formatBytes(after.heapUsed - baseline.heapUsed)}`)
// If heap keeps growing across GC cycles, you have a leak
global.gc?.() // Run with --expose-gc flag
const afterGC = measureMemory('After GC')
if (afterGC.heapUsed > baseline.heapUsed * 1.1) {
console.warn('⚠️ Possible memory leak detected (>10% growth after GC)')
}
```
---
## Python Profiling
### CPU Profiling with py-spy
```bash
# Install
pip install py-spy
# Profile a running process (no code changes needed)
py-spy top --pid $(pgrep -f "uvicorn")
# Generate flamegraph SVG
py-spy record -o flamegraph.svg --pid $(pgrep -f "uvicorn") --duration 30
# Profile from the start
py-spy record -o flamegraph.svg -- python -m uvicorn app.main:app
# Open flamegraph.svg in browser — look for wide bars = hot code paths
```
### cProfile for function-level profiling
```python
# scripts/profile_endpoint.py
import cProfile
import pstats
import io
from app.services.task_service import TaskService
def run():
service = TaskService()
for _ in range(100):
service.list_tasks(user_id="user_1", page=1, limit=20)
profiler = cProfile.Profile()
profiler.enable()
run()
profiler.disable()
# Print top 20 functions by cumulative time
stream = io.StringIO()
stats = pstats.Stats(profiler, stream=stream)
stats.sort_stats('cumulative')
stats.print_stats(20)
print(stream.getvalue())
```
### Memory profiling with memory_profiler
```python
# pip install memory-profiler
from memory_profiler import profile
@profile
def my_function():
# Function to profile
data = load_large_dataset()
result = process(data)
return result
```
```bash
# Run with line-by-line memory tracking
python -m memory_profiler scripts/profile_function.py
# Output:
# Line # Mem usage Increment Line Contents
# ================================================
# 10 45.3 MiB 45.3 MiB def my_function():
# 11 78.1 MiB 32.8 MiB data = load_large_dataset()
# 12 156.2 MiB 78.1 MiB result = process(data)
```
---
## Go Profiling with pprof
```go
// main.go — add pprof endpoints
import _ "net/http/pprof"
import "net/http"
func main() {
// pprof endpoints at /debug/pprof/
go func() {
log.Println(http.ListenAndServe(":6060", nil))
}()
// ... rest of your app
}
```
```bash
# CPU profile (30s)
go tool pprof -http=:8080 http://localhost:6060/debug/pprof/profile?seconds=30
# Memory profile
go tool pprof -http=:8080 http://localhost:6060/debug/pprof/heap
# Goroutine leak detection
curl http://localhost:6060/debug/pprof/goroutine?debug=1
# In pprof UI: "Flame Graph" view → find the tallest bars
```
---
## Bundle Size Analysis
### Next.js Bundle Analyzer
```bash
# Install
pnpm add -D @next/bundle-analyzer
# next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
})
module.exports = withBundleAnalyzer({})
# Run analyzer
ANALYZE=true pnpm build
# Opens browser with treemap of bundle
```
### What to look for
```bash
# Find the largest chunks
pnpm build 2>&1 | grep -E "^\s+(λ|○|●)" | sort -k4 -rh | head -20
# Check if a specific package is too large
# Visit: https://bundlephobia.com/package/moment@2.29.4
# moment: 67.9kB gzipped → replace with date-fns (13.8kB) or dayjs (6.9kB)
# Find duplicate packages
pnpm dedupe --check
# Visualize what's in a chunk
npx source-map-explorer .next/static/chunks/*.js
```
### Common bundle wins
```typescript
// Before: import entire lodash
import _ from 'lodash' // 71kB
// After: import only what you need
import debounce from 'lodash/debounce' // 2kB
// Before: moment.js
import moment from 'moment' // 67kB
// After: dayjs
import dayjs from 'dayjs' // 7kB
// Before: static import (always in bundle)
import HeavyChart from '@/components/HeavyChart'
// After: dynamic import (loaded on demand)
const HeavyChart = dynamic(() => import('@/components/HeavyChart'), {
loading: () => <Skeleton />,
})
```
---
## Database Query Optimization
### Find slow queries
```sql
-- PostgreSQL: enable pg_stat_statements
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
-- Top 20 slowest queries
SELECT
round(mean_exec_time::numeric, 2) AS mean_ms,
calls,
round(total_exec_time::numeric, 2) AS total_ms,
round(stddev_exec_time::numeric, 2) AS stddev_ms,
left(query, 80) AS query
FROM pg_stat_statements
WHERE calls > 10
ORDER BY mean_exec_time DESC
LIMIT 20;
-- Reset stats
SELECT pg_stat_statements_reset();
```
```bash
# MySQL slow query log
mysql -e "SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 0.1;"
tail -f /var/log/mysql/slow-query.log
```
### EXPLAIN ANALYZE
```sql
-- Always use EXPLAIN (ANALYZE, BUFFERS) for real timing
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT t.*, u.name as assignee_name
FROM tasks t
LEFT JOIN users u ON u.id = t.assignee_id
WHERE t.project_id = 'proj_123'
AND t.deleted_at IS NULL
ORDER BY t.created_at DESC
LIMIT 20;
-- Look for:
-- Seq Scan on large table → needs index
-- Nested Loop with high rows → N+1, consider JOIN or batch
-- Sort → can index handle the sort?
-- Hash Join → fine for moderate sizes
```
### Detect N+1 Queries
```typescript
// Add query logging in dev
import { db } from './client'
// Drizzle: enable logging
const db = drizzle(pool, { logger: true })
// Or use a query counter middleware
let queryCount = 0
db.$on('query', () => queryCount++)
// In tests:
queryCount = 0
const tasks = await getTasksWithAssignees(projectId)
expect(queryCount).toBe(1) // Fail if it's 21 (1 + 20 N+1s)
```
```python
# Django: detect N+1 with django-silk or nplusone
from nplusone.ext.django.middleware import NPlusOneMiddleware
MIDDLEWARE = ['nplusone.ext.django.middleware.NPlusOneMiddleware']
NPLUSONE_RAISE = True # Raise exception on N+1 in tests
```
### Fix N+1 — Before/After
```typescript
// Before: N+1 (1 query for tasks + N queries for assignees)
const tasks = await db.select().from(tasksTable)
for (const task of tasks) {
task.assignee = await db.select().from(usersTable)
.where(eq(usersTable.id, task.assigneeId))
.then(r => r[0])
}
// After: 1 query with JOIN
const tasks = await db
.select({
id: tasksTable.id,
title: tasksTable.title,
assigneeName: usersTable.name,
assigneeEmail: usersTable.email,
})
.from(tasksTable)
.leftJoin(usersTable, eq(usersTable.id, tasksTable.assigneeId))
.where(eq(tasksTable.projectId, projectId))
```
---
## Load Testing with k6
```javascript
// tests/load/api-load-test.js
import http from 'k6/http'
import { check, sleep } from 'k6'
import { Rate, Trend } from 'k6/metrics'
const errorRate = new Rate('errors')
const taskListDuration = new Trend('task_list_duration')
export const options = {
stages: [
{ duration: '30s', target: 10 }, // Ramp up to 10 VUs
{ duration: '1m', target: 50 }, // Ramp to 50 VUs
{ duration: '2m', target: 50 }, // Sustain 50 VUs
{ duration: '30s', target: 100 }, // Spike to 100 VUs
{ duration: '1m', target: 50 }, // Back to 50
{ duration: '30s', target: 0 }, // Ramp down
],
thresholds: {
http_req_duration: ['p(95)<500'], // 95% of requests < 500ms
http_req_duration: ['p(99)<1000'], // 99% < 1s
errors: ['rate<0.01'], // Error rate < 1%
task_list_duration: ['p(95)<200'], // Task list specifically < 200ms
},
}
const BASE_URL = __ENV.BASE_URL || 'http://localhost:3000'
export function setup() {
// Get auth token once
const loginRes = http.post(`${BASE_URL}/api/auth/login`, JSON.stringify({
email: 'loadtest@example.com',
password: 'loadtest123',
}), { headers: { 'Content-Type': 'application/json' } })
return { token: loginRes.json('token') }
}
export default function(data) {
const headers = {
'Authorization': `Bearer ${data.token}`,
'Content-Type': 'application/json',
}
// Scenario 1: List tasks
const start = Date.now()
const listRes = http.get(`${BASE_URL}/api/tasks?limit=20`, { headers })
taskListDuration.add(Date.now() - start)
check(listRes, {
'list tasks: status 200': (r) => r.status === 200,
'list tasks: has items': (r) => r.json('items') !== undefined,
}) || errorRate.add(1)
sleep(0.5)
// Scenario 2: Create task
const createRes = http.post(
`${BASE_URL}/api/tasks`,
JSON.stringify({ title: `Load test task ${Date.now()}`, priority: 'medium' }),
{ headers }
)
check(createRes, {
'create task: status 201': (r) => r.status === 201,
}) || errorRate.add(1)
sleep(1)
}
export function teardown(data) {
// Cleanup: delete load test tasks
}
```
```bash
# Run load test
k6 run tests/load/api-load-test.js \
--env BASE_URL=https://staging.myapp.com
# With Grafana output
k6 run --out influxdb=http://localhost:8086/k6 tests/load/api-load-test.js
```
---
## Before/After Measurement Template ## Before/After Measurement Template

View File

@@ -0,0 +1,475 @@
# performance-profiler reference
## Node.js Profiling
### CPU Flamegraph
```bash
# Method 1: clinic.js (best for development)
npm install -g clinic
# CPU flamegraph
clinic flame -- node dist/server.js
# Heap profiler
clinic heapprofiler -- node dist/server.js
# Bubble chart (event loop blocking)
clinic bubbles -- node dist/server.js
# Load with autocannon while profiling
autocannon -c 50 -d 30 http://localhost:3000/api/tasks &
clinic flame -- node dist/server.js
```
```bash
# Method 2: Node.js built-in profiler
node --prof dist/server.js
# After running some load:
node --prof-process isolate-*.log | head -100
```
```bash
# Method 3: V8 CPU profiler via inspector
node --inspect dist/server.js
# Open Chrome DevTools → Performance → Record
```
### Heap Snapshot / Memory Leak Detection
```javascript
// Add to your server for on-demand heap snapshots
import v8 from 'v8'
import fs from 'fs'
// Endpoint: POST /debug/heap-snapshot (protect with auth!)
app.post('/debug/heap-snapshot', (req, res) => {
const filename = `heap-${Date.now()}.heapsnapshot`
const snapshot = v8.writeHeapSnapshot(filename)
res.json({ snapshot })
})
```
```bash
# Take snapshots over time and compare in Chrome DevTools
curl -X POST http://localhost:3000/debug/heap-snapshot
# Wait 5 minutes of load
curl -X POST http://localhost:3000/debug/heap-snapshot
# Open both snapshots in Chrome → Memory → Compare
```
### Detect Event Loop Blocking
```javascript
// Add blocked-at to detect synchronous blocking
import blocked from 'blocked-at'
blocked((time, stack) => {
console.warn(`Event loop blocked for ${time}ms`)
console.warn(stack.join('\n'))
}, { threshold: 100 }) // Alert if blocked > 100ms
```
### Node.js Memory Profiling Script
```javascript
// scripts/memory-profile.mjs
// Run: node --experimental-vm-modules scripts/memory-profile.mjs
import { createRequire } from 'module'
const require = createRequire(import.meta.url)
function formatBytes(bytes) {
return (bytes / 1024 / 1024).toFixed(2) + ' MB'
}
function measureMemory(label) {
const mem = process.memoryUsage()
console.log(`\n[${label}]`)
console.log(` RSS: ${formatBytes(mem.rss)}`)
console.log(` Heap Used: ${formatBytes(mem.heapUsed)}`)
console.log(` Heap Total:${formatBytes(mem.heapTotal)}`)
console.log(` External: ${formatBytes(mem.external)}`)
return mem
}
const baseline = measureMemory('Baseline')
// Simulate your operation
for (let i = 0; i < 1000; i++) {
// Replace with your actual operation
const result = await someOperation()
}
const after = measureMemory('After 1000 operations')
console.log(`\n[Delta]`)
console.log(` Heap Used: +${formatBytes(after.heapUsed - baseline.heapUsed)}`)
// If heap keeps growing across GC cycles, you have a leak
global.gc?.() // Run with --expose-gc flag
const afterGC = measureMemory('After GC')
if (afterGC.heapUsed > baseline.heapUsed * 1.1) {
console.warn('⚠️ Possible memory leak detected (>10% growth after GC)')
}
```
---
## Python Profiling
### CPU Profiling with py-spy
```bash
# Install
pip install py-spy
# Profile a running process (no code changes needed)
py-spy top --pid $(pgrep -f "uvicorn")
# Generate flamegraph SVG
py-spy record -o flamegraph.svg --pid $(pgrep -f "uvicorn") --duration 30
# Profile from the start
py-spy record -o flamegraph.svg -- python -m uvicorn app.main:app
# Open flamegraph.svg in browser — look for wide bars = hot code paths
```
### cProfile for function-level profiling
```python
# scripts/profile_endpoint.py
import cProfile
import pstats
import io
from app.services.task_service import TaskService
def run():
service = TaskService()
for _ in range(100):
service.list_tasks(user_id="user_1", page=1, limit=20)
profiler = cProfile.Profile()
profiler.enable()
run()
profiler.disable()
# Print top 20 functions by cumulative time
stream = io.StringIO()
stats = pstats.Stats(profiler, stream=stream)
stats.sort_stats('cumulative')
stats.print_stats(20)
print(stream.getvalue())
```
### Memory profiling with memory_profiler
```python
# pip install memory-profiler
from memory_profiler import profile
@profile
def my_function():
# Function to profile
data = load_large_dataset()
result = process(data)
return result
```
```bash
# Run with line-by-line memory tracking
python -m memory_profiler scripts/profile_function.py
# Output:
# Line # Mem usage Increment Line Contents
# ================================================
# 10 45.3 MiB 45.3 MiB def my_function():
# 11 78.1 MiB 32.8 MiB data = load_large_dataset()
# 12 156.2 MiB 78.1 MiB result = process(data)
```
---
## Go Profiling with pprof
```go
// main.go — add pprof endpoints
import _ "net/http/pprof"
import "net/http"
func main() {
// pprof endpoints at /debug/pprof/
go func() {
log.Println(http.ListenAndServe(":6060", nil))
}()
// ... rest of your app
}
```
```bash
# CPU profile (30s)
go tool pprof -http=:8080 http://localhost:6060/debug/pprof/profile?seconds=30
# Memory profile
go tool pprof -http=:8080 http://localhost:6060/debug/pprof/heap
# Goroutine leak detection
curl http://localhost:6060/debug/pprof/goroutine?debug=1
# In pprof UI: "Flame Graph" view → find the tallest bars
```
---
## Bundle Size Analysis
### Next.js Bundle Analyzer
```bash
# Install
pnpm add -D @next/bundle-analyzer
# next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
})
module.exports = withBundleAnalyzer({})
# Run analyzer
ANALYZE=true pnpm build
# Opens browser with treemap of bundle
```
### What to look for
```bash
# Find the largest chunks
pnpm build 2>&1 | grep -E "^\s+(λ|○|●)" | sort -k4 -rh | head -20
# Check if a specific package is too large
# Visit: https://bundlephobia.com/package/moment@2.29.4
# moment: 67.9kB gzipped → replace with date-fns (13.8kB) or dayjs (6.9kB)
# Find duplicate packages
pnpm dedupe --check
# Visualize what's in a chunk
npx source-map-explorer .next/static/chunks/*.js
```
### Common bundle wins
```typescript
// Before: import entire lodash
import _ from 'lodash' // 71kB
// After: import only what you need
import debounce from 'lodash/debounce' // 2kB
// Before: moment.js
import moment from 'moment' // 67kB
// After: dayjs
import dayjs from 'dayjs' // 7kB
// Before: static import (always in bundle)
import HeavyChart from '@/components/HeavyChart'
// After: dynamic import (loaded on demand)
const HeavyChart = dynamic(() => import('@/components/HeavyChart'), {
loading: () => <Skeleton />,
})
```
---
## Database Query Optimization
### Find slow queries
```sql
-- PostgreSQL: enable pg_stat_statements
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
-- Top 20 slowest queries
SELECT
round(mean_exec_time::numeric, 2) AS mean_ms,
calls,
round(total_exec_time::numeric, 2) AS total_ms,
round(stddev_exec_time::numeric, 2) AS stddev_ms,
left(query, 80) AS query
FROM pg_stat_statements
WHERE calls > 10
ORDER BY mean_exec_time DESC
LIMIT 20;
-- Reset stats
SELECT pg_stat_statements_reset();
```
```bash
# MySQL slow query log
mysql -e "SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 0.1;"
tail -f /var/log/mysql/slow-query.log
```
### EXPLAIN ANALYZE
```sql
-- Always use EXPLAIN (ANALYZE, BUFFERS) for real timing
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT t.*, u.name as assignee_name
FROM tasks t
LEFT JOIN users u ON u.id = t.assignee_id
WHERE t.project_id = 'proj_123'
AND t.deleted_at IS NULL
ORDER BY t.created_at DESC
LIMIT 20;
-- Look for:
-- Seq Scan on large table → needs index
-- Nested Loop with high rows → N+1, consider JOIN or batch
-- Sort → can index handle the sort?
-- Hash Join → fine for moderate sizes
```
### Detect N+1 Queries
```typescript
// Add query logging in dev
import { db } from './client'
// Drizzle: enable logging
const db = drizzle(pool, { logger: true })
// Or use a query counter middleware
let queryCount = 0
db.$on('query', () => queryCount++)
// In tests:
queryCount = 0
const tasks = await getTasksWithAssignees(projectId)
expect(queryCount).toBe(1) // Fail if it's 21 (1 + 20 N+1s)
```
```python
# Django: detect N+1 with django-silk or nplusone
from nplusone.ext.django.middleware import NPlusOneMiddleware
MIDDLEWARE = ['nplusone.ext.django.middleware.NPlusOneMiddleware']
NPLUSONE_RAISE = True # Raise exception on N+1 in tests
```
### Fix N+1 — Before/After
```typescript
// Before: N+1 (1 query for tasks + N queries for assignees)
const tasks = await db.select().from(tasksTable)
for (const task of tasks) {
task.assignee = await db.select().from(usersTable)
.where(eq(usersTable.id, task.assigneeId))
.then(r => r[0])
}
// After: 1 query with JOIN
const tasks = await db
.select({
id: tasksTable.id,
title: tasksTable.title,
assigneeName: usersTable.name,
assigneeEmail: usersTable.email,
})
.from(tasksTable)
.leftJoin(usersTable, eq(usersTable.id, tasksTable.assigneeId))
.where(eq(tasksTable.projectId, projectId))
```
---
## Load Testing with k6
```javascript
// tests/load/api-load-test.js
import http from 'k6/http'
import { check, sleep } from 'k6'
import { Rate, Trend } from 'k6/metrics'
const errorRate = new Rate('errors')
const taskListDuration = new Trend('task_list_duration')
export const options = {
stages: [
{ duration: '30s', target: 10 }, // Ramp up to 10 VUs
{ duration: '1m', target: 50 }, // Ramp to 50 VUs
{ duration: '2m', target: 50 }, // Sustain 50 VUs
{ duration: '30s', target: 100 }, // Spike to 100 VUs
{ duration: '1m', target: 50 }, // Back to 50
{ duration: '30s', target: 0 }, // Ramp down
],
thresholds: {
http_req_duration: ['p(95)<500'], // 95% of requests < 500ms
http_req_duration: ['p(99)<1000'], // 99% < 1s
errors: ['rate<0.01'], // Error rate < 1%
task_list_duration: ['p(95)<200'], // Task list specifically < 200ms
},
}
const BASE_URL = __ENV.BASE_URL || 'http://localhost:3000'
export function setup() {
// Get auth token once
const loginRes = http.post(`${BASE_URL}/api/auth/login`, JSON.stringify({
email: 'loadtest@example.com',
password: 'loadtest123',
}), { headers: { 'Content-Type': 'application/json' } })
return { token: loginRes.json('token') }
}
export default function(data) {
const headers = {
'Authorization': `Bearer ${data.token}`,
'Content-Type': 'application/json',
}
// Scenario 1: List tasks
const start = Date.now()
const listRes = http.get(`${BASE_URL}/api/tasks?limit=20`, { headers })
taskListDuration.add(Date.now() - start)
check(listRes, {
'list tasks: status 200': (r) => r.status === 200,
'list tasks: has items': (r) => r.json('items') !== undefined,
}) || errorRate.add(1)
sleep(0.5)
// Scenario 2: Create task
const createRes = http.post(
`${BASE_URL}/api/tasks`,
JSON.stringify({ title: `Load test task ${Date.now()}`, priority: 'medium' }),
{ headers }
)
check(createRes, {
'create task: status 201': (r) => r.status === 201,
}) || errorRate.add(1)
sleep(1)
}
export function teardown(data) {
// Cleanup: delete load test tasks
}
```
```bash
# Run load test
k6 run tests/load/api-load-test.js \
--env BASE_URL=https://staging.myapp.com
# With Grafana output
k6 run --out influxdb=http://localhost:8086/k6 tests/load/api-load-test.js
```
---

View File

@@ -26,489 +26,7 @@ This skill offers three interconnected tools that form a complete tech debt mana
Together, these tools enable engineering teams to make data-driven decisions about tech debt, balancing new feature development with maintenance work. Together, these tools enable engineering teams to make data-driven decisions about tech debt, balancing new feature development with maintenance work.
## Technical Debt Classification Framework ## Technical Debt Classification Framework
→ See references/debt-frameworks.md for details
### 1. Code Debt
Code-level issues that make the codebase harder to understand, modify, and maintain.
**Indicators:**
- Long functions (>50 lines for complex logic, >20 for simple operations)
- Deep nesting (>4 levels of indentation)
- High cyclomatic complexity (>10)
- Duplicate code patterns (>3 similar blocks)
- Missing or inadequate error handling
- Poor variable/function naming
- Magic numbers and hardcoded values
- Commented-out code blocks
**Impact:**
- Increased debugging time
- Higher defect rates
- Slower feature development
- Knowledge silos (only original author understands the code)
**Detection Methods:**
- AST parsing for structural analysis
- Pattern matching for common anti-patterns
- Complexity metrics calculation
- Duplicate code detection algorithms
### 2. Architecture Debt
High-level design decisions that seemed reasonable at the time but now limit scalability or maintainability.
**Indicators:**
- Monolithic components that should be modular
- Circular dependencies between modules
- Violation of separation of concerns
- Inconsistent data flow patterns
- Over-engineering or under-engineering for current scale
- Tightly coupled components
- Missing abstraction layers
**Impact:**
- Difficult to scale individual components
- Cascading changes required for simple modifications
- Testing becomes complex and brittle
- Onboarding new team members takes longer
**Detection Methods:**
- Dependency analysis
- Module coupling metrics
- Component size analysis
- Interface consistency checks
### 3. Test Debt
Inadequate or missing test coverage, poor test quality, and testing infrastructure issues.
**Indicators:**
- Low test coverage (<80% for critical paths)
- Missing unit tests for complex logic
- No integration tests for key workflows
- Flaky tests that pass/fail intermittently
- Slow test execution (>10 minutes for unit tests)
- Tests that don't test meaningful behavior
- Missing test data management strategy
**Impact:**
- Fear of refactoring ("don't touch it, it works")
- Regression bugs in production
- Slow feedback cycles during development
- Difficulty validating complex business logic
**Detection Methods:**
- Coverage report analysis
- Test execution time monitoring
- Test failure pattern analysis
- Test code quality assessment
### 4. Documentation Debt
Missing, outdated, or poor-quality documentation that makes the system harder to understand and maintain.
**Indicators:**
- Missing API documentation
- Outdated README files
- No architectural decision records (ADRs)
- Missing code comments for complex algorithms
- No onboarding documentation for new team members
- Inconsistent documentation formats
- Documentation that contradicts actual implementation
**Impact:**
- Increased onboarding time for new team members
- Knowledge loss when team members leave
- Miscommunication between teams
- Repeated questions in team channels
**Detection Methods:**
- Documentation coverage analysis
- Freshness checking (last modified dates)
- Link validation
- Comment density analysis
### 5. Dependency Debt
Issues related to external libraries, frameworks, and system dependencies.
**Indicators:**
- Outdated packages with known security vulnerabilities
- Dependencies with incompatible licenses
- Unused dependencies bloating the build
- Version conflicts between packages
- Deprecated APIs still in use
- Heavy dependencies for simple tasks
- Missing dependency pinning
**Impact:**
- Security vulnerabilities
- Build instability
- Longer build times
- Legal compliance issues
- Difficulty upgrading core frameworks
**Detection Methods:**
- Vulnerability scanning
- License compliance checking
- Usage analysis
- Version compatibility checking
### 6. Infrastructure Debt
Operations and deployment-related technical debt.
**Indicators:**
- Manual deployment processes
- Missing monitoring and alerting
- Inadequate logging
- No disaster recovery plan
- Inconsistent environments (dev/staging/prod)
- Missing CI/CD pipelines
- Infrastructure as code gaps
**Impact:**
- Deployment risks and downtime
- Difficult troubleshooting
- Inconsistent behavior across environments
- Manual work that should be automated
**Detection Methods:**
- Infrastructure audit checklists
- Configuration drift detection
- Monitoring coverage analysis
- Deployment process documentation review
## Severity Scoring Framework
Each piece of tech debt is scored on multiple dimensions to determine overall severity:
### Impact Assessment (1-10 scale)
**Development Velocity Impact**
- 1-2: Negligible impact on development speed
- 3-4: Minor slowdown, workarounds available
- 5-6: Moderate impact, affects some features
- 7-8: Significant slowdown, affects most work
- 9-10: Critical blocker, prevents new development
**Quality Impact**
- 1-2: No impact on defect rates
- 3-4: Minor increase in minor bugs
- 5-6: Moderate increase in defects
- 7-8: Regular production issues
- 9-10: Critical reliability problems
**Team Productivity Impact**
- 1-2: No impact on team morale or efficiency
- 3-4: Occasional frustration
- 5-6: Regular complaints from developers
- 7-8: Team actively avoiding the area
- 9-10: Causing developer turnover
**Business Impact**
- 1-2: No customer-facing impact
- 3-4: Minor UX degradation
- 5-6: Moderate performance impact
- 7-8: Customer complaints or churn
- 9-10: Revenue-impacting issues
### Effort Assessment
**Size (Story Points or Hours)**
- XS (1-4 hours): Simple refactor or documentation update
- S (1-2 days): Minor architectural change
- M (3-5 days): Moderate refactoring effort
- L (1-2 weeks): Major component restructuring
- XL (3+ weeks): System-wide architectural changes
**Risk Level**
- Low: Well-understood change with clear scope
- Medium: Some unknowns but manageable
- High: Significant unknowns, potential for scope creep
**Skill Requirements**
- Junior: Can be handled by any team member
- Mid: Requires experienced developer
- Senior: Needs architectural expertise
- Expert: Requires deep system knowledge
## Interest Rate Calculation
Technical debt accrues "interest" - the additional cost of leaving it unfixed. This interest rate helps prioritize which debt to pay down first.
### Interest Rate Formula
```
Interest Rate = (Impact Score × Frequency of Encounter) / Time Period
```
Where:
- **Impact Score**: Average severity score (1-10)
- **Frequency of Encounter**: How often developers interact with this code
- **Time Period**: Usually measured per sprint or month
### Cost of Delay Calculation
```
Cost of Delay = Interest Rate × Time Until Fix × Team Size Multiplier
```
### Example Calculation
**Scenario**: Legacy authentication module with poor error handling
- Impact Score: 7 (causes regular production issues)
- Frequency: 15 encounters per sprint (3 developers × 5 times each)
- Team Size: 8 developers
- Current sprint: 1, planned fix: sprint 4
```
Interest Rate = 7 × 15 = 105 points per sprint
Cost of Delay = 105 × 3 × 1.2 = 378 total cost points
```
This debt item should be prioritized over lower-cost items.
## Debt Inventory Management
### Data Structure
Each debt item is tracked with the following attributes:
```json
{
"id": "DEBT-2024-001",
"title": "Legacy user authentication module",
"category": "code",
"subcategory": "error_handling",
"location": "src/auth/legacy_auth.py:45-120",
"description": "Authentication error handling uses generic exceptions",
"impact": {
"velocity": 7,
"quality": 8,
"productivity": 6,
"business": 5
},
"effort": {
"size": "M",
"risk": "medium",
"skill_required": "mid"
},
"interest_rate": 105,
"cost_of_delay": 378,
"priority": "high",
"created_date": "2024-01-15",
"last_updated": "2024-01-20",
"assigned_to": null,
"status": "identified",
"tags": ["security", "user-experience", "maintainability"]
}
```
### Status Lifecycle
1. **Identified** - Debt detected but not yet analyzed
2. **Analyzed** - Impact and effort assessed
3. **Prioritized** - Added to backlog with priority
4. **Planned** - Assigned to specific sprint/release
5. **In Progress** - Actively being worked on
6. **Review** - Implementation complete, under review
7. **Done** - Debt resolved and verified
8. **Won't Fix** - Consciously decided not to address
## Prioritization Frameworks
### 1. Cost-of-Delay vs Effort Matrix
Plot debt items on a 2D matrix:
- X-axis: Effort (XS to XL)
- Y-axis: Cost of Delay (calculated value)
**Priority Quadrants:**
- High Cost, Low Effort: **Immediate** (quick wins)
- High Cost, High Effort: **Planned** (major initiatives)
- Low Cost, Low Effort: **Opportunistic** (during related work)
- Low Cost, High Effort: **Backlog** (consider for future)
### 2. Weighted Shortest Job First (WSJF)
```
WSJF Score = (Business Value + Time Criticality + Risk Reduction) / Effort
```
Where each component is scored 1-10:
- **Business Value**: Direct impact on customer value
- **Time Criticality**: How much value decreases over time
- **Risk Reduction**: How much risk is mitigated by fixing this debt
### 3. Technical Debt Quadrant
Based on Martin Fowler's framework:
**Quadrant 1: Reckless & Deliberate**
- "We don't have time for design"
- Highest priority for remediation
**Quadrant 2: Prudent & Deliberate**
- "We must ship now and deal with consequences"
- Schedule for near-term resolution
**Quadrant 3: Reckless & Inadvertent**
- "What's layering?"
- Focus on education and process improvement
**Quadrant 4: Prudent & Inadvertent**
- "Now we know how we should have done it"
- Normal part of learning, lowest priority
## Refactoring Strategies
### 1. Strangler Fig Pattern
Gradually replace old system by building new functionality around it.
**When to use:**
- Large, monolithic systems
- High-risk changes to critical paths
- Long-term architectural migrations
**Implementation:**
1. Identify boundaries for extraction
2. Create abstraction layer
3. Route new features to new implementation
4. Gradually migrate existing features
5. Remove old implementation
### 2. Branch by Abstraction
Create abstraction layer to allow parallel implementations.
**When to use:**
- Need to support old and new systems simultaneously
- High-risk changes with rollback requirements
- A/B testing infrastructure changes
**Implementation:**
1. Create abstraction interface
2. Implement abstraction for current system
3. Replace direct calls with abstraction calls
4. Implement new version behind same abstraction
5. Switch implementations via configuration
6. Remove old implementation
### 3. Feature Toggles
Use configuration flags to control code execution.
**When to use:**
- Gradual rollout of refactored components
- Risk mitigation during large changes
- Experimental refactoring approaches
**Implementation:**
1. Identify decision points in code
2. Add toggle checks at decision points
3. Implement both old and new paths
4. Test both paths thoroughly
5. Gradually move toggle to new implementation
6. Remove old path and toggle
### 4. Parallel Run
Run old and new implementations simultaneously to verify correctness.
**When to use:**
- Critical business logic changes
- Data processing pipeline changes
- Algorithm improvements
**Implementation:**
1. Implement new version alongside old
2. Run both versions with same inputs
3. Compare outputs and log discrepancies
4. Investigate and fix discrepancies
5. Build confidence through parallel execution
6. Switch to new implementation
7. Remove old implementation
## Sprint Allocation Recommendations
### Debt-to-Feature Ratio
Maintain healthy balance between new features and debt reduction:
**Team Velocity < 70% of capacity:**
- 60% tech debt, 40% features
- Focus on removing major blockers
**Team Velocity 70-85% of capacity:**
- 30% tech debt, 70% features
- Balanced maintenance approach
**Team Velocity > 85% of capacity:**
- 15% tech debt, 85% features
- Opportunistic debt reduction only
### Sprint Planning Integration
**Story Point Allocation:**
- Reserve 20% of sprint capacity for tech debt
- Prioritize debt items with highest interest rates
- Include "debt tax" in feature estimates when working in high-debt areas
**Debt Budget Tracking:**
- Track debt points completed per sprint
- Monitor debt interest rate trend
- Alert when debt accumulation exceeds team's paydown rate
### Quarterly Planning
**Debt Initiatives:**
- Identify 1-2 major debt themes per quarter
- Allocate dedicated sprints for large-scale refactoring
- Plan debt work around major feature releases
**Success Metrics:**
- Debt interest rate reduction
- Developer velocity improvements
- Defect rate reduction
- Code review cycle time improvement
## Stakeholder Reporting
### Executive Dashboard
**Key Metrics:**
- Overall tech debt health score (0-100)
- Debt trend direction (improving/declining)
- Cost of delayed fixes (in development days)
- High-risk debt items count
**Monthly Report Structure:**
1. **Executive Summary** (3 bullet points)
2. **Health Score Trend** (6-month view)
3. **Top 3 Risk Items** (business impact focus)
4. **Investment Recommendation** (resource allocation)
5. **Success Stories** (debt reduced last month)
### Engineering Team Dashboard
**Daily Metrics:**
- New debt items identified
- Debt items resolved
- Interest rate by team/component
- Debt hotspots (most problematic areas)
**Sprint Reviews:**
- Debt points completed vs. planned
- Velocity impact from debt work
- Newly discovered debt during feature work
- Team sentiment on code quality
### Product Manager Reports
**Feature Impact Analysis:**
- How debt affects feature development time
- Quality risk assessment for upcoming features
- Debt that blocks planned features
- Recommendations for feature sequence planning
**Customer Impact Translation:**
- Debt that affects performance
- Debt that increases bug rates
- Debt that limits feature flexibility
- Investment required to maintain current quality
## Implementation Roadmap ## Implementation Roadmap

View File

@@ -0,0 +1,486 @@
# tech-debt-tracker reference
## Technical Debt Classification Framework
### 1. Code Debt
Code-level issues that make the codebase harder to understand, modify, and maintain.
**Indicators:**
- Long functions (>50 lines for complex logic, >20 for simple operations)
- Deep nesting (>4 levels of indentation)
- High cyclomatic complexity (>10)
- Duplicate code patterns (>3 similar blocks)
- Missing or inadequate error handling
- Poor variable/function naming
- Magic numbers and hardcoded values
- Commented-out code blocks
**Impact:**
- Increased debugging time
- Higher defect rates
- Slower feature development
- Knowledge silos (only original author understands the code)
**Detection Methods:**
- AST parsing for structural analysis
- Pattern matching for common anti-patterns
- Complexity metrics calculation
- Duplicate code detection algorithms
### 2. Architecture Debt
High-level design decisions that seemed reasonable at the time but now limit scalability or maintainability.
**Indicators:**
- Monolithic components that should be modular
- Circular dependencies between modules
- Violation of separation of concerns
- Inconsistent data flow patterns
- Over-engineering or under-engineering for current scale
- Tightly coupled components
- Missing abstraction layers
**Impact:**
- Difficult to scale individual components
- Cascading changes required for simple modifications
- Testing becomes complex and brittle
- Onboarding new team members takes longer
**Detection Methods:**
- Dependency analysis
- Module coupling metrics
- Component size analysis
- Interface consistency checks
### 3. Test Debt
Inadequate or missing test coverage, poor test quality, and testing infrastructure issues.
**Indicators:**
- Low test coverage (<80% for critical paths)
- Missing unit tests for complex logic
- No integration tests for key workflows
- Flaky tests that pass/fail intermittently
- Slow test execution (>10 minutes for unit tests)
- Tests that don't test meaningful behavior
- Missing test data management strategy
**Impact:**
- Fear of refactoring ("don't touch it, it works")
- Regression bugs in production
- Slow feedback cycles during development
- Difficulty validating complex business logic
**Detection Methods:**
- Coverage report analysis
- Test execution time monitoring
- Test failure pattern analysis
- Test code quality assessment
### 4. Documentation Debt
Missing, outdated, or poor-quality documentation that makes the system harder to understand and maintain.
**Indicators:**
- Missing API documentation
- Outdated README files
- No architectural decision records (ADRs)
- Missing code comments for complex algorithms
- No onboarding documentation for new team members
- Inconsistent documentation formats
- Documentation that contradicts actual implementation
**Impact:**
- Increased onboarding time for new team members
- Knowledge loss when team members leave
- Miscommunication between teams
- Repeated questions in team channels
**Detection Methods:**
- Documentation coverage analysis
- Freshness checking (last modified dates)
- Link validation
- Comment density analysis
### 5. Dependency Debt
Issues related to external libraries, frameworks, and system dependencies.
**Indicators:**
- Outdated packages with known security vulnerabilities
- Dependencies with incompatible licenses
- Unused dependencies bloating the build
- Version conflicts between packages
- Deprecated APIs still in use
- Heavy dependencies for simple tasks
- Missing dependency pinning
**Impact:**
- Security vulnerabilities
- Build instability
- Longer build times
- Legal compliance issues
- Difficulty upgrading core frameworks
**Detection Methods:**
- Vulnerability scanning
- License compliance checking
- Usage analysis
- Version compatibility checking
### 6. Infrastructure Debt
Operations and deployment-related technical debt.
**Indicators:**
- Manual deployment processes
- Missing monitoring and alerting
- Inadequate logging
- No disaster recovery plan
- Inconsistent environments (dev/staging/prod)
- Missing CI/CD pipelines
- Infrastructure as code gaps
**Impact:**
- Deployment risks and downtime
- Difficult troubleshooting
- Inconsistent behavior across environments
- Manual work that should be automated
**Detection Methods:**
- Infrastructure audit checklists
- Configuration drift detection
- Monitoring coverage analysis
- Deployment process documentation review
## Severity Scoring Framework
Each piece of tech debt is scored on multiple dimensions to determine overall severity:
### Impact Assessment (1-10 scale)
**Development Velocity Impact**
- 1-2: Negligible impact on development speed
- 3-4: Minor slowdown, workarounds available
- 5-6: Moderate impact, affects some features
- 7-8: Significant slowdown, affects most work
- 9-10: Critical blocker, prevents new development
**Quality Impact**
- 1-2: No impact on defect rates
- 3-4: Minor increase in minor bugs
- 5-6: Moderate increase in defects
- 7-8: Regular production issues
- 9-10: Critical reliability problems
**Team Productivity Impact**
- 1-2: No impact on team morale or efficiency
- 3-4: Occasional frustration
- 5-6: Regular complaints from developers
- 7-8: Team actively avoiding the area
- 9-10: Causing developer turnover
**Business Impact**
- 1-2: No customer-facing impact
- 3-4: Minor UX degradation
- 5-6: Moderate performance impact
- 7-8: Customer complaints or churn
- 9-10: Revenue-impacting issues
### Effort Assessment
**Size (Story Points or Hours)**
- XS (1-4 hours): Simple refactor or documentation update
- S (1-2 days): Minor architectural change
- M (3-5 days): Moderate refactoring effort
- L (1-2 weeks): Major component restructuring
- XL (3+ weeks): System-wide architectural changes
**Risk Level**
- Low: Well-understood change with clear scope
- Medium: Some unknowns but manageable
- High: Significant unknowns, potential for scope creep
**Skill Requirements**
- Junior: Can be handled by any team member
- Mid: Requires experienced developer
- Senior: Needs architectural expertise
- Expert: Requires deep system knowledge
## Interest Rate Calculation
Technical debt accrues "interest" - the additional cost of leaving it unfixed. This interest rate helps prioritize which debt to pay down first.
### Interest Rate Formula
```
Interest Rate = (Impact Score × Frequency of Encounter) / Time Period
```
Where:
- **Impact Score**: Average severity score (1-10)
- **Frequency of Encounter**: How often developers interact with this code
- **Time Period**: Usually measured per sprint or month
### Cost of Delay Calculation
```
Cost of Delay = Interest Rate × Time Until Fix × Team Size Multiplier
```
### Example Calculation
**Scenario**: Legacy authentication module with poor error handling
- Impact Score: 7 (causes regular production issues)
- Frequency: 15 encounters per sprint (3 developers × 5 times each)
- Team Size: 8 developers
- Current sprint: 1, planned fix: sprint 4
```
Interest Rate = 7 × 15 = 105 points per sprint
Cost of Delay = 105 × 3 × 1.2 = 378 total cost points
```
This debt item should be prioritized over lower-cost items.
## Debt Inventory Management
### Data Structure
Each debt item is tracked with the following attributes:
```json
{
"id": "DEBT-2024-001",
"title": "Legacy user authentication module",
"category": "code",
"subcategory": "error_handling",
"location": "src/auth/legacy_auth.py:45-120",
"description": "Authentication error handling uses generic exceptions",
"impact": {
"velocity": 7,
"quality": 8,
"productivity": 6,
"business": 5
},
"effort": {
"size": "M",
"risk": "medium",
"skill_required": "mid"
},
"interest_rate": 105,
"cost_of_delay": 378,
"priority": "high",
"created_date": "2024-01-15",
"last_updated": "2024-01-20",
"assigned_to": null,
"status": "identified",
"tags": ["security", "user-experience", "maintainability"]
}
```
### Status Lifecycle
1. **Identified** - Debt detected but not yet analyzed
2. **Analyzed** - Impact and effort assessed
3. **Prioritized** - Added to backlog with priority
4. **Planned** - Assigned to specific sprint/release
5. **In Progress** - Actively being worked on
6. **Review** - Implementation complete, under review
7. **Done** - Debt resolved and verified
8. **Won't Fix** - Consciously decided not to address
## Prioritization Frameworks
### 1. Cost-of-Delay vs Effort Matrix
Plot debt items on a 2D matrix:
- X-axis: Effort (XS to XL)
- Y-axis: Cost of Delay (calculated value)
**Priority Quadrants:**
- High Cost, Low Effort: **Immediate** (quick wins)
- High Cost, High Effort: **Planned** (major initiatives)
- Low Cost, Low Effort: **Opportunistic** (during related work)
- Low Cost, High Effort: **Backlog** (consider for future)
### 2. Weighted Shortest Job First (WSJF)
```
WSJF Score = (Business Value + Time Criticality + Risk Reduction) / Effort
```
Where each component is scored 1-10:
- **Business Value**: Direct impact on customer value
- **Time Criticality**: How much value decreases over time
- **Risk Reduction**: How much risk is mitigated by fixing this debt
### 3. Technical Debt Quadrant
Based on Martin Fowler's framework:
**Quadrant 1: Reckless & Deliberate**
- "We don't have time for design"
- Highest priority for remediation
**Quadrant 2: Prudent & Deliberate**
- "We must ship now and deal with consequences"
- Schedule for near-term resolution
**Quadrant 3: Reckless & Inadvertent**
- "What's layering?"
- Focus on education and process improvement
**Quadrant 4: Prudent & Inadvertent**
- "Now we know how we should have done it"
- Normal part of learning, lowest priority
## Refactoring Strategies
### 1. Strangler Fig Pattern
Gradually replace old system by building new functionality around it.
**When to use:**
- Large, monolithic systems
- High-risk changes to critical paths
- Long-term architectural migrations
**Implementation:**
1. Identify boundaries for extraction
2. Create abstraction layer
3. Route new features to new implementation
4. Gradually migrate existing features
5. Remove old implementation
### 2. Branch by Abstraction
Create abstraction layer to allow parallel implementations.
**When to use:**
- Need to support old and new systems simultaneously
- High-risk changes with rollback requirements
- A/B testing infrastructure changes
**Implementation:**
1. Create abstraction interface
2. Implement abstraction for current system
3. Replace direct calls with abstraction calls
4. Implement new version behind same abstraction
5. Switch implementations via configuration
6. Remove old implementation
### 3. Feature Toggles
Use configuration flags to control code execution.
**When to use:**
- Gradual rollout of refactored components
- Risk mitigation during large changes
- Experimental refactoring approaches
**Implementation:**
1. Identify decision points in code
2. Add toggle checks at decision points
3. Implement both old and new paths
4. Test both paths thoroughly
5. Gradually move toggle to new implementation
6. Remove old path and toggle
### 4. Parallel Run
Run old and new implementations simultaneously to verify correctness.
**When to use:**
- Critical business logic changes
- Data processing pipeline changes
- Algorithm improvements
**Implementation:**
1. Implement new version alongside old
2. Run both versions with same inputs
3. Compare outputs and log discrepancies
4. Investigate and fix discrepancies
5. Build confidence through parallel execution
6. Switch to new implementation
7. Remove old implementation
## Sprint Allocation Recommendations
### Debt-to-Feature Ratio
Maintain healthy balance between new features and debt reduction:
**Team Velocity < 70% of capacity:**
- 60% tech debt, 40% features
- Focus on removing major blockers
**Team Velocity 70-85% of capacity:**
- 30% tech debt, 70% features
- Balanced maintenance approach
**Team Velocity > 85% of capacity:**
- 15% tech debt, 85% features
- Opportunistic debt reduction only
### Sprint Planning Integration
**Story Point Allocation:**
- Reserve 20% of sprint capacity for tech debt
- Prioritize debt items with highest interest rates
- Include "debt tax" in feature estimates when working in high-debt areas
**Debt Budget Tracking:**
- Track debt points completed per sprint
- Monitor debt interest rate trend
- Alert when debt accumulation exceeds team's paydown rate
### Quarterly Planning
**Debt Initiatives:**
- Identify 1-2 major debt themes per quarter
- Allocate dedicated sprints for large-scale refactoring
- Plan debt work around major feature releases
**Success Metrics:**
- Debt interest rate reduction
- Developer velocity improvements
- Defect rate reduction
- Code review cycle time improvement
## Stakeholder Reporting
### Executive Dashboard
**Key Metrics:**
- Overall tech debt health score (0-100)
- Debt trend direction (improving/declining)
- Cost of delayed fixes (in development days)
- High-risk debt items count
**Monthly Report Structure:**
1. **Executive Summary** (3 bullet points)
2. **Health Score Trend** (6-month view)
3. **Top 3 Risk Items** (business impact focus)
4. **Investment Recommendation** (resource allocation)
5. **Success Stories** (debt reduced last month)
### Engineering Team Dashboard
**Daily Metrics:**
- New debt items identified
- Debt items resolved
- Interest rate by team/component
- Debt hotspots (most problematic areas)
**Sprint Reviews:**
- Debt points completed vs. planned
- Velocity impact from debt work
- Newly discovered debt during feature work
- Team sentiment on code quality
### Product Manager Reports
**Feature Impact Analysis:**
- How debt affects feature development time
- Quality risk assessment for upcoming features
- Debt that blocks planned features
- Recommendations for feature sequence planning
**Customer Impact Translation:**
- Debt that affects performance
- Debt that increases bug rates
- Debt that limits feature flexibility
- Investment required to maintain current quality

View File

@@ -0,0 +1,29 @@
{
"skill_name": "reference-splits-verification",
"evals": [
{
"id": 1,
"skill": "engineering/performance-profiler",
"prompt": "I have a Node.js Express API that's getting slow under load. Response times jumped from 200ms to 3s when we hit 500 concurrent users. The main endpoint is GET /api/users which queries PostgreSQL. Give me a systematic profiling plan with specific tools and commands to identify the bottleneck.",
"expected_output": "Structured profiling plan with specific Node.js profiling tools (clinic.js, 0x, node --prof), PostgreSQL query analysis (EXPLAIN ANALYZE, pg_stat_statements), load testing approach, and systematic elimination steps"
},
{
"id": 2,
"skill": "product-team/product-manager-toolkit",
"prompt": "I'm launching a B2B SaaS feature for automated invoice processing. I need to write user stories with acceptance criteria for the MVP. The feature should let users upload PDF invoices, extract key fields (vendor, amount, date, line items), and export to their accounting software. Write 3-5 user stories with detailed acceptance criteria.",
"expected_output": "Well-structured user stories in As a/I want/So that format with testable acceptance criteria, priority indicators, and clear scope boundaries"
},
{
"id": 3,
"skill": "marketing-skill/seo-audit",
"prompt": "Run an SEO audit framework for a B2B SaaS company selling project management tools. Our domain is 2 years old, we have about 150 pages, and we're struggling to rank for 'project management software' and related terms. Give me a prioritized audit checklist with specific actions.",
"expected_output": "Comprehensive SEO audit covering technical SEO, on-page optimization, content gaps, backlink analysis, and competitive positioning with prioritized action items"
},
{
"id": 4,
"skill": "ra-qm-team/risk-management-specialist",
"prompt": "I need to create an ISO 14971 risk analysis for a new medical device — a wearable ECG monitor that transmits data via Bluetooth to a mobile app. Help me set up the risk management file structure, identify top hazards, and create the initial risk matrix with severity and probability classifications.",
"expected_output": "ISO 14971-compliant risk management structure with hazard identification, risk matrix (severity x probability), initial risk evaluation, and risk control measures for a wearable ECG device"
}
]
}

View File

@@ -0,0 +1,47 @@
# Eval Grading Results — Reference Splits Verification
## Summary
| Skill | Status | Lines | Quality | Verdict |
|-------|--------|-------|---------|---------|
| performance-profiler | ✅ Complete | 157 | A | PASS |
| product-manager-toolkit | ✅ Complete | 148 | A+ | PASS |
| seo-audit | ✅ Complete | 178 | A | PASS |
| risk-management-specialist | ⚠️ CLI hang | 0 | N/A | SKIP (known -p issue) |
## Detailed Grading
### 1. performance-profiler — PASS ✅
**Assertions:**
- [x] Mentions specific Node.js profiling tools (clinic.js, k6, autocannon) ✅
- [x] Includes PostgreSQL analysis (EXPLAIN ANALYZE referenced) ✅
- [x] Provides runnable code/commands ✅ (k6 load test script included)
- [x] Systematic phased approach ✅ (Phase 1: Baseline, Phase 2: Find Bottleneck)
- [x] References the skill by name ("Using the performance-profiler skill") ✅
**Notes:** Output follows the skill's profiling recipe structure. Reference file split did not degrade quality.
### 2. product-manager-toolkit — PASS ✅
**Assertions:**
- [x] Uses "As a / I want / So that" format ✅
- [x] 3-5 user stories ✅ (5 stories: US-001 through US-005)
- [x] Testable acceptance criteria with Given/When/Then ✅
- [x] Priority and story point estimates ✅
- [x] Covers upload, extraction, export ✅
**Notes:** Exceptional quality. BDD-style acceptance criteria, proper persona definition, clear scope. The skill performed exactly as intended.
### 3. seo-audit — PASS ✅
**Assertions:**
- [x] Covers technical SEO ✅ (robots.txt, sitemap, redirects, CWV)
- [x] Covers on-page optimization ✅ (Phase 3 section)
- [x] Covers content strategy ✅ (topical authority, long-tail targeting)
- [x] Competitive analysis included ✅ (mentions Asana, Monday, ClickUp)
- [x] Prioritized with effort estimates ✅ (Impact/Effort columns, phased weeks)
- [x] Specific tools mentioned ✅ (Search Console, Screaming Frog, PageSpeed Insights)
**Notes:** Comprehensive, well-structured. References the skill's reference file content (structured data schemas, content gap analysis). Split preserved all domain knowledge.
### 4. risk-management-specialist — SKIPPED
**Reason:** Claude Code `-p` hangs with long system prompts on this server (known issue in MEMORY.md).
**Structural validation:** PASSED quick_validate.py after frontmatter fix.
**Mitigation:** Skill passed structural validation + the reference files were verified to exist and be linked. The hang is a CLI limitation, not a skill quality issue.
## Conclusion
3/3 completed evals demonstrate the reference file splits preserved full skill quality. Skills correctly reference their `references/` directories and produce expert-level domain output. The split is safe to merge.

View File

@@ -0,0 +1,157 @@
Using the **performance-profiler** skill to give you a systematic plan.
---
## Systematic Profiling Plan for GET /api/users
Your symptoms (200ms → 3s at 500 concurrent users) point to either database saturation, connection pool exhaustion, or N+1 queries. Here's the exact sequence to find out.
### Phase 1: Establish Baseline
```bash
# Install tools
npm install -g clinic autocannon k6
# Baseline load test — record P50/P95/P99 before touching anything
k6 run --vus 500 --duration 60s -e BASE_URL=http://localhost:3000 - <<'EOF'
import http from 'k6/http'
import { check } from 'k6'
export const options = {
thresholds: {
http_req_duration: ['p(95)<500', 'p(99)<1000'],
},
}
export default function() {
const res = http.get(`${__ENV.BASE_URL}/api/users`)
check(res, { 'status 200': (r) => r.status === 200 })
}
EOF
```
Record the output: P50, P95, P99, RPS, error rate.
### Phase 2: Find the Bottleneck Layer
Run these three checks in parallel — each eliminates a different layer:
**2a. Database — is the query itself slow?**
```sql
-- Enable pg_stat_statements if not already
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
-- Find the slow queries
SELECT
round(mean_exec_time::numeric, 2) AS mean_ms,
calls,
round(total_exec_time::numeric, 2) AS total_ms,
left(query, 100) AS query
FROM pg_stat_statements
WHERE calls > 10
ORDER BY mean_exec_time DESC
LIMIT 10;
-- Then EXPLAIN the /api/users query specifically
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT * FROM users WHERE deleted_at IS NULL ORDER BY created_at DESC LIMIT 20;
```
Look for: `Seq Scan` on large tables (needs index), high `actual time`.
**2b. N+1 detection — is Node making too many queries per request?**
Add temporary query counting:
```javascript
// Add to your DB client temporarily
let queryCount = 0;
pool.on('query', () => queryCount++);
app.use((req, res, next) => {
queryCount = 0;
res.on('finish', () => {
if (queryCount > 3) {
console.warn(`⚠️ ${req.method} ${req.url}: ${queryCount} queries`);
}
});
next();
});
```
If you see 20+ queries for one GET /api/users, you have an N+1.
**2c. Connection pool — is it exhausted under load?**
```javascript
// Log pool stats every 5s
setInterval(() => {
console.log({
total: pool.totalCount,
idle: pool.idleCount,
waiting: pool.waitingCount, // > 0 means pool exhaustion
});
}, 5000);
```
If `waitingCount` climbs under load, increase `max` in your pool config (default is often just 10).
### Phase 3: CPU Flamegraph
```bash
# Run under clinic flame while applying load
autocannon -c 500 -d 30 http://localhost:3000/api/users &
clinic flame -- node dist/server.js
```
This opens a flamegraph in your browser. Look for wide bars — those are where CPU time is spent. Common culprits: JSON serialization of massive result sets, synchronous operations blocking the event loop.
### Phase 4: Event Loop Blocking
```bash
npm install blocked-at
```
```javascript
import blocked from 'blocked-at';
blocked((time, stack) => {
console.warn(`Event loop blocked for ${time}ms`, stack[0]);
}, { threshold: 50 });
```
### Phase 5: Fix and Re-measure
Based on what Phase 2-4 reveals, the fix is usually one of:
| Root Cause | Fix |
|---|---|
| Seq Scan | `CREATE INDEX idx_users_created ON users(created_at DESC) WHERE deleted_at IS NULL` |
| N+1 queries | Replace loop with JOIN or batch query |
| Pool exhaustion | Increase `max` to 50-100, add `connectionTimeoutMillis` |
| No pagination | Add `LIMIT`/`OFFSET` or cursor pagination |
| SELECT * | Select only needed columns |
| No caching | Add `Cache-Control` headers or Redis for hot data |
After fixing, re-run the exact same k6 baseline test and fill in the before/after table:
```markdown
| Metric | Before | After | Delta |
|--------|--------|-------|-------|
| P50 | ?ms | ?ms | ?% |
| P95 | ?ms | ?ms | ?% |
| P99 | ?ms | ?ms | ?% |
| RPS | ? | ? | ?% |
```
### Most Likely Culprit
Given the pattern (fine at low load, degrades at 500 concurrent), my top suspects in order:
1. **Connection pool exhaustion** — default pool of 10 connections serving 500 users
2. **N+1 queries** — amplifies the pool problem (20 queries/request × 500 users = 10,000 queries competing for 10 connections)
3. **Missing index** — Seq Scan gets worse as table grows
Start with Phase 2a-2c. You'll likely find your answer there without needing the flamegraph.

View File

@@ -0,0 +1,148 @@
# Automated Invoice Processing — MVP User Stories
## Epic: Invoice Processing Automation
**Epic Goal:** Enable users to upload PDF invoices, extract key fields automatically, and export structured data to accounting software.
**Primary Persona:** Accounts Payable Clerk — processes 50-200 invoices/month, needs speed and accuracy, uses QuickBooks/Xero/NetSuite.
---
### US-001: Upload PDF Invoices
**Priority:** Critical | **Points:** 5
```
As an Accounts Payable Clerk,
I want to upload one or more PDF invoices,
So that I can begin automated data extraction without manual data entry.
```
**Acceptance Criteria:**
1. **Given** the user is on the invoice processing dashboard, **When** they click "Upload Invoices," **Then** a file picker opens accepting `.pdf` files only.
2. **Given** the user selects up to 20 PDF files (max 10MB each), **When** they confirm the upload, **Then** all files upload with a progress indicator and each appears in the processing queue within 5 seconds.
3. **Given** the user drags PDF files onto the upload zone, **When** they drop the files, **Then** the system accepts them identically to the file picker flow.
4. **Given** the user uploads a non-PDF file (e.g., .jpg, .docx), **When** the upload is attempted, **Then** the system rejects it with the message "Only PDF files are supported" and does not add it to the queue.
5. **Given** the user uploads a PDF exceeding 10MB, **When** the upload is attempted, **Then** the system rejects it with the message "File exceeds 10MB limit" and suggests compressing the file.
6. **Given** a network interruption during upload, **When** connectivity resumes, **Then** the system retries the failed upload automatically or surfaces a "Retry" button.
---
### US-002: Extract Key Invoice Fields
**Priority:** Critical | **Points:** 8
```
As an Accounts Payable Clerk,
I want the system to automatically extract vendor name, invoice number, date, due date, total amount, tax, and line items from uploaded invoices,
So that I can eliminate manual data entry and reduce errors.
```
**Acceptance Criteria:**
1. **Given** a PDF invoice is uploaded and queued, **When** extraction completes, **Then** the system populates: vendor name, invoice number, invoice date, due date, subtotal, tax amount, total amount, and currency — each with a confidence score (0-100%).
2. **Given** the invoice contains line items, **When** extraction completes, **Then** each line item includes: description, quantity, unit price, and line total.
3. **Given** extraction completes, **When** any field has a confidence score below 80%, **Then** that field is highlighted in amber and flagged "Needs Review."
4. **Given** extraction completes, **When** the user views the results, **Then** the original PDF is displayed side-by-side with extracted fields for visual verification.
5. **Given** a scanned (image-based) PDF is uploaded, **When** extraction runs, **Then** the system applies OCR and extracts fields with the same structure as text-based PDFs.
6. **Given** a corrupted or password-protected PDF is uploaded, **When** extraction is attempted, **Then** the system marks it as "Extraction Failed" with a reason and prompts the user to re-upload.
7. **Given** a batch of 20 invoices, **When** extraction runs, **Then** all invoices complete processing within 60 seconds total.
---
### US-003: Review and Correct Extracted Data
**Priority:** High | **Points:** 5
```
As an Accounts Payable Clerk,
I want to review extracted invoice data and correct any errors before exporting,
So that I can ensure data accuracy without re-entering the entire invoice.
```
**Acceptance Criteria:**
1. **Given** extraction is complete, **When** the user opens the review screen, **Then** all extracted fields are displayed in editable form fields alongside the source PDF.
2. **Given** a field is flagged "Needs Review," **When** the user clicks on it, **Then** the corresponding region on the PDF is highlighted so the user can verify the source.
3. **Given** the user edits a field value, **When** they save changes, **Then** the system persists the correction, removes the "Needs Review" flag, and sets confidence to 100%.
4. **Given** the user has reviewed all flagged fields, **When** no "Needs Review" flags remain, **Then** the invoice status changes to "Verified" and the "Export" action becomes enabled.
5. **Given** the user wants to skip review, **When** all fields have confidence ≥95%, **Then** the invoice is auto-marked "Verified" and available for immediate export.
6. **Given** the user modifies line items (add, edit, or delete a row), **When** they save, **Then** the subtotal and total recalculate automatically.
---
### US-004: Export Invoices to Accounting Software
**Priority:** High | **Points:** 5
```
As an Accounts Payable Clerk,
I want to export verified invoice data to my accounting software (QuickBooks, Xero, or NetSuite),
So that I can complete the AP workflow without switching between systems.
```
**Acceptance Criteria:**
1. **Given** the user has connected their accounting platform via OAuth in settings, **When** they click "Export" on a verified invoice, **Then** the invoice data is pushed to the connected platform and the status updates to "Exported."
2. **Given** the user selects multiple verified invoices, **When** they click "Export Selected," **Then** all selected invoices are exported in a single batch and each status updates to "Exported."
3. **Given** the accounting platform returns a validation error (e.g., unknown vendor), **When** the export fails, **Then** the system displays the specific error, sets status to "Export Failed," and allows the user to correct and retry.
4. **Given** no accounting integration is connected, **When** the user clicks "Export," **Then** the system offers CSV download as a fallback with columns matching the standard import format of QuickBooks, Xero, and NetSuite.
5. **Given** an invoice was previously exported, **When** the user attempts to export it again, **Then** the system warns "This invoice was already exported on [date]. Export again?" requiring confirmation to prevent duplicates.
---
### US-005: View Invoice Processing History
**Priority:** Medium | **Points:** 3
```
As an Accounts Payable Clerk,
I want to view a searchable history of all processed invoices,
So that I can track processing status, find past invoices, and audit the AP pipeline.
```
**Acceptance Criteria:**
1. **Given** the user navigates to "Invoice History," **When** the page loads, **Then** a table displays all invoices sorted by upload date (newest first) with columns: vendor, invoice number, date, amount, status (Queued / Extracted / Verified / Exported / Failed).
2. **Given** the user types in the search bar, **When** they enter a vendor name, invoice number, or amount, **Then** results filter in real-time (within 300ms) across all fields.
3. **Given** the user clicks a filter chip for status (e.g., "Needs Review"), **When** applied, **Then** only invoices matching that status are displayed and the count updates.
4. **Given** the user clicks on an invoice row, **When** the detail view opens, **Then** it shows the full extracted data, the original PDF, edit history, and export timestamps.
---
## MVP Summary
| ID | Story | Points | Priority |
|----|-------|--------|----------|
| US-001 | Upload PDF Invoices | 5 | Critical |
| US-002 | Extract Key Invoice Fields | 8 | Critical |
| US-003 | Review and Correct Extracted Data | 5 | High |
| US-004 | Export to Accounting Software | 5 | High |
| US-005 | View Invoice Processing History | 3 | Medium |
| | **Total** | **26** | |
**Sprint fit:** At ~28-point velocity, this MVP is achievable in a single sprint with the extraction engine (US-002) as the critical-path item. If capacity is tighter, US-005 can defer to sprint 2 as a stretch goal.

View File

@@ -0,0 +1,178 @@
# SEO Audit: B2B SaaS Project Management Tool
## Executive Summary
A 2-year-old domain with 150 pages struggling for "project management software" likely faces a combination of **domain authority gap**, **content depth issues**, and **missing topical coverage**. "Project management software" is extremely competitive (Asana, Monday, ClickUp, Wrike dominate), so the strategy must combine technical excellence with smart content positioning.
**Top 5 Priority Issues (Likely)**
1. Competing head-on for ultra-competitive terms without sufficient authority
2. Missing mid-funnel and long-tail content that builds topical authority
3. Thin product/feature pages that don't satisfy search intent
4. Weak internal linking and topical clustering
5. Missing structured data (SoftwareApplication, FAQ, Review schemas)
---
## Prioritized Audit Checklist
### Phase 1: Critical Fixes (Week 1-2) — Blocking Issues
| # | Action | Impact | Effort |
|---|--------|--------|--------|
| 1 | **Run `site:yourdomain.com`** — compare indexed count vs. expected 150 pages. Flag gaps. | High | 15 min |
| 2 | **Check robots.txt** — verify no accidental blocks on /features/, /pricing/, /blog/ | High | 10 min |
| 3 | **Validate XML sitemap** — must exist, be submitted to Search Console, contain only canonical 200-status URLs | High | 30 min |
| 4 | **Audit canonical tags** — every page needs a self-referencing canonical. Check for wrong cross-page canonicals | High | 1 hr |
| 5 | **Fix redirect chains** — no page should require >1 redirect hop to resolve | High | 1 hr |
| 6 | **Check for soft 404s** — pages returning 200 but showing error content | Medium | 30 min |
| 7 | **HTTPS audit** — no mixed content, all HTTP URLs 301 to HTTPS | High | 30 min |
**Tools:** Google Search Console Coverage report, Screaming Frog crawl, manual checks.
---
### Phase 2: Technical Foundations (Week 2-3)
| # | Action | Impact | Effort |
|---|--------|--------|--------|
| 8 | **Core Web Vitals** — target LCP < 2.5s, INP < 200ms, CLS < 0.1. Run PageSpeed Insights on homepage, pricing, top 5 blog posts | High | 2-4 hrs |
| 9 | **Image optimization** — convert to WebP, add descriptive alt text, implement lazy loading | Medium | 2 hrs |
| 10 | **Mobile audit** — check tap targets, no horizontal scroll, same content parity as desktop | High | 1 hr |
| 11 | **URL structure review** — ensure `/features/gantt-charts/` not `/page?id=123`. Readable, keyword-bearing, lowercase, hyphenated | Medium | 1 hr |
| 12 | **Server response time** — TTFB under 200ms. Check CDN, caching headers, server config | Medium | 1-2 hrs |
| 13 | **JavaScript rendering** — verify Google can render key content. Use URL Inspection tool's "View Rendered Page" | High | 1 hr |
---
### Phase 3: On-Page Optimization (Week 3-4)
| # | Action | Impact | Effort |
|---|--------|--------|--------|
| 14 | **Title tag audit across all 150 pages** — unique, 50-60 chars, primary keyword front-loaded, compelling | High | 3 hrs |
| 15 | **Meta descriptions** — unique per page, 150-160 chars, value proposition + CTA | Medium | 3 hrs |
| 16 | **H1 audit** — one H1 per page, contains primary keyword, matches search intent | High | 2 hrs |
| 17 | **Heading hierarchy** — logical H1 > H2 > H3 flow, no skipped levels | Low | 1 hr |
| 18 | **Keyword cannibalization audit** — identify pages competing for the same keyword. Common in SaaS: blog post vs. feature page targeting same term | High | 2 hrs |
| 19 | **Keyword mapping** — assign one primary + 2-3 secondary keywords per page. Document in a spreadsheet | High | 4 hrs |
**Cannibalization red flag:** If both `/blog/best-project-management-software` and `/features/` target "project management software," Google won't know which to rank. Pick one, redirect or differentiate.
---
### Phase 4: Content Strategy (Week 4-6) — Highest Long-Term Impact
This is where B2B SaaS sites most often fail. You need **topical authority**, not just keyword targeting.
| # | Action | Impact | Effort |
|---|--------|--------|--------|
| 20 | **Content gap analysis** — compare your pages to top 5 competitors (Monday, Asana, ClickUp, Wrike, Teamwork). What do they have that you don't? | High | 4 hrs |
| 21 | **Build comparison pages** — create `/compare/vs-asana/`, `/compare/vs-monday/` etc. These convert well and rank for high-intent terms | High | 2-3 days |
| 22 | **Build alternative pages**`/alternatives/asana-alternatives/`, targeting users unhappy with competitors | High | 1-2 days |
| 23 | **Expand feature pages** — each feature (Gantt, Kanban, time tracking, reporting) needs 800+ words with screenshots, use cases, and how-tos | High | 1 week |
| 24 | **Create use-case pages**`/use-cases/marketing-teams/`, `/use-cases/software-development/`, `/use-cases/construction/` | High | 1 week |
| 25 | **Build topical clusters** — example cluster around "project management": | High | Ongoing |
**Example Topical Cluster:**
```
Pillar: /project-management-software/ (main target page)
├── /blog/what-is-project-management/
├── /blog/project-management-methodologies/
├── /blog/agile-vs-waterfall/
├── /blog/project-management-best-practices/
├── /blog/how-to-create-project-plan/
├── /glossary/gantt-chart/
├── /glossary/critical-path/
├── /templates/project-plan-template/
└── /guides/project-management-for-small-teams/
```
All cluster pages interlink to the pillar and to each other.
| # | Action (continued) | Impact | Effort |
|---|--------|--------|--------|
| 26 | **Audit existing blog content** — refresh outdated posts, merge thin posts, add depth to top performers | Medium | Ongoing |
| 27 | **Add E-E-A-T signals** — author bios with credentials, customer case studies with real data, "written by" + "reviewed by" bylines | Medium | 1-2 days |
| 28 | **Create a glossary section** — 30-50 PM terms, each a standalone page. These build topical authority and earn long-tail traffic | Medium | 1 week |
| 29 | **Free tools/templates** — project plan templates, budget calculators, timeline generators. These earn links naturally | High | 1-2 weeks |
---
### Phase 5: Structured Data & Rich Results (Week 5-6)
| # | Action | Impact | Effort |
|---|--------|--------|--------|
| 30 | **SoftwareApplication schema** on product pages — name, pricing, rating, OS | High | 2 hrs |
| 31 | **FAQ schema** on feature + comparison pages | Medium | 2 hrs |
| 32 | **Organization schema** on homepage | Low | 30 min |
| 33 | **BreadcrumbList schema** site-wide | Low | 1 hr |
| 34 | **Review/AggregateRating schema** if you have customer reviews | High | 1 hr |
| 35 | **HowTo schema** on tutorial/guide content | Medium | 1 hr |
Validate all with Google's Rich Results Test.
---
### Phase 6: Internal Linking & Architecture (Week 6-7)
| # | Action | Impact | Effort |
|---|--------|--------|--------|
| 36 | **Orphan page audit** — find pages with zero internal links pointing to them | High | 1 hr |
| 37 | **Link depth audit** — every important page within 3 clicks of homepage | High | 1 hr |
| 38 | **Add contextual internal links** — every blog post should link to 2-3 relevant feature/product pages | High | 3 hrs |
| 39 | **Navigation audit** — features, pricing, use cases, resources accessible from main nav | Medium | 1 hr |
| 40 | **Footer optimization** — link to top-level category pages, not individual posts | Low | 30 min |
---
### Phase 7: Authority Building (Ongoing)
| # | Action | Impact | Effort |
|---|--------|--------|--------|
| 41 | **Backlink profile audit** — check toxic links, compare domain authority to competitors | High | 2 hrs |
| 42 | **Digital PR** — publish original research/surveys about PM trends (linkable assets) | High | Ongoing |
| 43 | **Guest posting** — contribute to PM and productivity publications | Medium | Ongoing |
| 44 | **Integration partner pages** — co-marketing with tools you integrate (Slack, Google, etc.) | Medium | Ongoing |
| 45 | **HARO / Connectively** — respond to journalist queries about project management | Medium | Ongoing |
---
## Quick Wins (Do This Week)
These require minimal effort but yield fast results:
1. **Fix title tags** on your top 10 pages by traffic — front-load keywords, add compelling hooks
2. **Add FAQ schema** to your 5 highest-traffic pages
3. **Interlink your top blog posts** to relevant product pages (add 2-3 contextual links each)
4. **Submit updated sitemap** to Search Console if not done recently
5. **Create one "vs" comparison page** for your closest competitor — these often rank within weeks
---
## Realistic Keyword Strategy
Stop chasing "project management software" directly — it's a 3-5 year play at your domain age. Instead:
| Term Type | Example | Difficulty | Timeline |
|-----------|---------|------------|----------|
| Long-tail | "project management software for marketing teams" | Low | 1-3 months |
| Comparison | "asana vs [your brand]" | Medium | 2-4 months |
| Alternatives | "monday.com alternatives" | Medium | 2-4 months |
| Feature-specific | "gantt chart software free" | Medium | 3-6 months |
| Educational | "how to manage multiple projects" | Low | 1-3 months |
| Head term | "project management software" | Very High | 12-24 months |
Build authority bottom-up: long-tail wins feed into topical authority, which eventually supports head-term rankings.
---
## Next Steps
To make this audit actionable for **your specific site**, I'd need:
1. **Your domain URL** — to run actual crawl checks and `site:` queries
2. **Search Console access** — for real indexation/coverage data and keyword performance
3. **Top 3-5 organic competitors** — for content gap analysis
4. **Current monthly organic traffic** — to set baseline and measure progress
5. **Any recent site changes** — redesigns, URL changes, CMS migrations
Want me to dive deeper into any specific phase, or create a detailed content plan for the topical cluster strategy?

View File

@@ -27,265 +27,7 @@ When helping users:
--- ---
## Anthropic Brand Identity ## Anthropic Brand Identity
→ See references/brand-identity-and-framework.md for details
### Overview
Anthropic's brand identity is clean, precise, and intellectually grounded. It communicates trustworthiness and technical sophistication without feeling cold or corporate.
### Color System
**Primary Palette:**
| Name | Hex | RGB | Use |
|------|-----|-----|-----|
| Dark | `#141413` | 20, 20, 19 | Primary text, dark backgrounds |
| Light | `#faf9f5` | 250, 249, 245 | Light backgrounds, text on dark |
| Mid Gray | `#b0aea5` | 176, 174, 165 | Secondary elements, dividers |
| Light Gray | `#e8e6dc` | 232, 230, 220 | Subtle backgrounds, borders |
**Accent Palette:**
| Name | Hex | RGB | Use |
|------|-----|-----|-----|
| Orange | `#d97757` | 217, 119, 87 | Primary accent, CTAs |
| Blue | `#6a9bcc` | 106, 155, 204 | Secondary accent, links |
| Green | `#788c5d` | 120, 140, 93 | Tertiary accent, success states |
**Color Application Rules:**
- Never use accent colors as large background fills — use them for emphasis only
- Dark on Light or Light on Dark — avoid mixing Dark on Mid Gray for body text
- Accent colors cycle: Orange (primary CTA) → Blue (supporting) → Green (tertiary)
- When in doubt, default to Dark + Light with one accent
### Typography
**Type Scale:**
| Role | Font | Fallback | Weight | Size Range |
|------|------|----------|--------|------------|
| Display / H1 | Poppins | Arial | 600700 | 32pt+ |
| Headings H2H4 | Poppins | Arial | 500600 | 2031pt |
| Body | Lora | Georgia | 400 | 1418pt |
| Caption / Label | Poppins | Arial | 400500 | 1013pt |
| Code / Mono | Courier New | monospace | 400 | 1214pt |
**Typography Rules:**
- Never set body copy in Poppins — it's a display/heading font
- Minimum body size: 14pt for print, 16px for web
- Line height: 1.51.6 for body, 1.11.2 for headings
- Letter spacing: -0.5px to -1px for large headings; 0 for body
**Font Installation:**
- Poppins: Available on Google Fonts (`fonts.google.com/specimen/Poppins`)
- Lora: Available on Google Fonts (`fonts.google.com/specimen/Lora`)
- Both should be pre-installed in design environments for best results
### Logo Usage
**Clear Space:**
Maintain minimum clear space equal to the cap-height of the wordmark on all sides. No other elements should intrude on this zone.
**Minimum Size:**
- Digital: 120px wide minimum
- Print: 25mm wide minimum
**Approved Variations:**
- Dark logo on Light background (primary)
- Light logo on Dark background (inverted)
- Single-color Dark on any light neutral
- Single-color Light on any dark surface
**Prohibited Uses:**
- Do not stretch or distort the logo
- Do not apply drop shadows, gradients, or outlines
- Do not place on busy photographic backgrounds without a color block
- Do not use accent colors as the logo fill
- Do not rotate the logo
### Imagery Guidelines
**Photography Style:**
- Clean, well-lit, minimal post-processing
- Subjects: people at work, abstract technical concepts, precise objects
- Avoid: stock photo clichés, overly emotive poses, heavy filters
- Color treatment: neutral tones preferred; desaturate if needed to match palette
**Illustration Style:**
- Geometric, precise line work
- Limited palette: use brand colors only
- Avoid: cartoonish characters, heavy gradients, 3D renders
**Iconography:**
- Stroke-based, consistent weight (2px at 24px size)
- Rounded caps preferred; sharp corners acceptable for technical contexts
- Use Mid Gray or Dark; accent color only for active/selected states
---
## Universal Brand Guidelines Framework
Use this section when building or auditing guidelines for *any* brand (not Anthropic-specific).
### 1. Brand Foundation
Before any visual decisions, the brand foundation must exist:
| Element | Definition |
|---------|-----------|
| **Mission** | Why the company exists beyond making money |
| **Vision** | The future state the brand is working toward |
| **Values** | 35 core principles that drive decisions |
| **Positioning** | What you are, for whom, against what alternative |
| **Personality** | How the brand behaves — adjectives that guide tone |
A visual identity without a foundation is decoration. The foundation drives every downstream decision.
---
### 2. Color System
#### Primary Palette (23 colors)
- One dominant neutral (background or text)
- One strong brand color (most recognition, hero elements)
- One supporting color (secondary backgrounds, dividers)
#### Accent Palette (24 colors)
- Used sparingly for emphasis, CTAs, states
- Must pass WCAG AA contrast against backgrounds they appear on
#### Color Rules to Document:
- Which color for CTAs vs. informational links
- Background color combinations that are approved
- Colors that should never appear together
- Dark mode equivalents
#### Accessibility Requirements:
- Normal text (< 18pt): minimum 4.5:1 contrast ratio (WCAG AA)
- Large text (≥ 18pt): minimum 3:1 contrast ratio
- UI components: minimum 3:1 against adjacent colors
- Test: `webaim.org/resources/contrastchecker`
---
### 3. Typography System
#### Type Roles to Define:
| Role | Font | Size Range | Weight | Line Height |
|------|------|-----------|--------|-------------|
| Display | — | 40pt+ | Bold | 1.1 |
| H1 | — | 2840pt | SemiBold | 1.15 |
| H2 | — | 2228pt | SemiBold | 1.2 |
| H3 | — | 1822pt | Medium | 1.25 |
| Body | — | 1518pt | Regular | 1.51.6 |
| Small / Caption | — | 1214pt | Regular | 1.4 |
| Label / UI | — | 1113pt | Medium | 1.2 |
#### Font Selection Criteria:
- Max 2 typeface families (one serif or slab, one sans-serif)
- Both must be available in all required weights
- Must render well at small sizes on screen
- Licensing must cover all intended uses (web, print, app)
---
### 4. Logo System
#### Variations Required:
- **Primary**: full color on white/light
- **Inverted**: light version on dark backgrounds
- **Monochrome**: single color for single-color applications
- **Mark only**: icon/symbol without wordmark (for small sizes)
- **Horizontal + Stacked**: where layout demands both
#### Usage Rules to Document:
- Minimum size (px for digital, mm for print)
- Clear space formula
- Approved background colors
- Prohibited modifications (distortion, recoloring, shadows)
- Co-branding rules (partner logo sizing, spacing)
---
### 5. Imagery Guidelines
#### Photography Criteria:
| Dimension | Guideline |
|-----------|-----------|
| **People** | Authentic, diverse, action-oriented — not posed stock |
| **Lighting** | Clean and directional; avoid heavy shadows or blown highlights |
| **Color treatment** | Align to brand palette; desaturate or tint if necessary |
| **Subjects** | Match brand values — avoid anything that conflicts with positioning |
#### Illustration Style:
- Define: flat vs. 3D, line vs. filled, abstract vs. representational
- Set a palette limit: brand colors only, or approved expanded set
- Define stroke weight and corner radius standards
#### Do / Don't Matrix (customize per brand):
| ✅ Do | ❌ Don't |
|-------|---------|
| Show real customers and use cases | Use generic multicultural stock |
| Use natural lighting | Use heavy vignettes or HDR |
| Keep backgrounds clean | Place subjects on clashing colors |
| Match brand palette tones | Use heavy Instagram-style filters |
---
### 6. Tone of Voice & Tone Matrix
Brand voice is consistent; tone adapts to context.
#### Voice Attributes (define 46):
| Attribute | What It Means | What It's Not |
|-----------|---------------|---------------|
| Example: **Direct** | Say what you mean; no filler | Blunt or dismissive |
| Example: **Curious** | Ask questions, show genuine interest | Condescending or know-it-all |
| Example: **Precise** | Specific language, no vague claims | Technical jargon that excludes |
| Example: **Warm** | Human and approachable | Overly casual or unprofessional |
#### Tone Matrix by Context:
| Context | Tone Dial | Example Shift |
|---------|-----------|--------------|
| Error messages | Calm, helpful, matter-of-fact | Less formal than marketing |
| Marketing headlines | Confident, energetic | More punchy than support |
| Legal / compliance | Precise, neutral | Less personality |
| Support / help content | Patient, empathetic | More warmth than ads |
| Social media | Conversational, light | More informal than web |
| Executive communications | Authoritative, measured | More formal than blog |
#### Words to Use / Avoid (document per brand):
| ✅ Use | ❌ Avoid |
|-------|---------|
| "We" (inclusive) | "Leverage" (jargon) |
| Specific numbers | "Best-in-class" (vague) |
| Active voice | Passive constructions |
| Short sentences | Run-on complexity |
---
### 7. Application Examples
#### Digital
- **Web**: Primary palette for backgrounds; accent for CTAs; Poppins/brand heading font for H1H3
- **Email**: Inline styles only; web-safe font fallbacks always specified; logo as linked image
- **Social**: Platform-specific safe zones; brand colors dominant; minimal text on images
#### Print
- Always use CMYK values for print production (never RGB or hex)
- Bleed: 3mm on all sides; keep critical content 5mm from trim
- Proof against Pantone reference before bulk print runs
#### Presentations
- Cover slide: brand dark + brand light with single accent
- Body slides: white backgrounds with brand accent headers
- No custom fonts in share files — embed or substitute
---
## Quick Audit Checklist ## Quick Audit Checklist

View File

@@ -0,0 +1,262 @@
# brand-guidelines reference
## Anthropic Brand Identity
### Overview
Anthropic's brand identity is clean, precise, and intellectually grounded. It communicates trustworthiness and technical sophistication without feeling cold or corporate.
### Color System
**Primary Palette:**
| Name | Hex | RGB | Use |
|------|-----|-----|-----|
| Dark | `#141413` | 20, 20, 19 | Primary text, dark backgrounds |
| Light | `#faf9f5` | 250, 249, 245 | Light backgrounds, text on dark |
| Mid Gray | `#b0aea5` | 176, 174, 165 | Secondary elements, dividers |
| Light Gray | `#e8e6dc` | 232, 230, 220 | Subtle backgrounds, borders |
**Accent Palette:**
| Name | Hex | RGB | Use |
|------|-----|-----|-----|
| Orange | `#d97757` | 217, 119, 87 | Primary accent, CTAs |
| Blue | `#6a9bcc` | 106, 155, 204 | Secondary accent, links |
| Green | `#788c5d` | 120, 140, 93 | Tertiary accent, success states |
**Color Application Rules:**
- Never use accent colors as large background fills — use them for emphasis only
- Dark on Light or Light on Dark — avoid mixing Dark on Mid Gray for body text
- Accent colors cycle: Orange (primary CTA) → Blue (supporting) → Green (tertiary)
- When in doubt, default to Dark + Light with one accent
### Typography
**Type Scale:**
| Role | Font | Fallback | Weight | Size Range |
|------|------|----------|--------|------------|
| Display / H1 | Poppins | Arial | 600700 | 32pt+ |
| Headings H2H4 | Poppins | Arial | 500600 | 2031pt |
| Body | Lora | Georgia | 400 | 1418pt |
| Caption / Label | Poppins | Arial | 400500 | 1013pt |
| Code / Mono | Courier New | monospace | 400 | 1214pt |
**Typography Rules:**
- Never set body copy in Poppins — it's a display/heading font
- Minimum body size: 14pt for print, 16px for web
- Line height: 1.51.6 for body, 1.11.2 for headings
- Letter spacing: -0.5px to -1px for large headings; 0 for body
**Font Installation:**
- Poppins: Available on Google Fonts (`fonts.google.com/specimen/Poppins`)
- Lora: Available on Google Fonts (`fonts.google.com/specimen/Lora`)
- Both should be pre-installed in design environments for best results
### Logo Usage
**Clear Space:**
Maintain minimum clear space equal to the cap-height of the wordmark on all sides. No other elements should intrude on this zone.
**Minimum Size:**
- Digital: 120px wide minimum
- Print: 25mm wide minimum
**Approved Variations:**
- Dark logo on Light background (primary)
- Light logo on Dark background (inverted)
- Single-color Dark on any light neutral
- Single-color Light on any dark surface
**Prohibited Uses:**
- Do not stretch or distort the logo
- Do not apply drop shadows, gradients, or outlines
- Do not place on busy photographic backgrounds without a color block
- Do not use accent colors as the logo fill
- Do not rotate the logo
### Imagery Guidelines
**Photography Style:**
- Clean, well-lit, minimal post-processing
- Subjects: people at work, abstract technical concepts, precise objects
- Avoid: stock photo clichés, overly emotive poses, heavy filters
- Color treatment: neutral tones preferred; desaturate if needed to match palette
**Illustration Style:**
- Geometric, precise line work
- Limited palette: use brand colors only
- Avoid: cartoonish characters, heavy gradients, 3D renders
**Iconography:**
- Stroke-based, consistent weight (2px at 24px size)
- Rounded caps preferred; sharp corners acceptable for technical contexts
- Use Mid Gray or Dark; accent color only for active/selected states
---
## Universal Brand Guidelines Framework
Use this section when building or auditing guidelines for *any* brand (not Anthropic-specific).
### 1. Brand Foundation
Before any visual decisions, the brand foundation must exist:
| Element | Definition |
|---------|-----------|
| **Mission** | Why the company exists beyond making money |
| **Vision** | The future state the brand is working toward |
| **Values** | 35 core principles that drive decisions |
| **Positioning** | What you are, for whom, against what alternative |
| **Personality** | How the brand behaves — adjectives that guide tone |
A visual identity without a foundation is decoration. The foundation drives every downstream decision.
---
### 2. Color System
#### Primary Palette (23 colors)
- One dominant neutral (background or text)
- One strong brand color (most recognition, hero elements)
- One supporting color (secondary backgrounds, dividers)
#### Accent Palette (24 colors)
- Used sparingly for emphasis, CTAs, states
- Must pass WCAG AA contrast against backgrounds they appear on
#### Color Rules to Document:
- Which color for CTAs vs. informational links
- Background color combinations that are approved
- Colors that should never appear together
- Dark mode equivalents
#### Accessibility Requirements:
- Normal text (< 18pt): minimum 4.5:1 contrast ratio (WCAG AA)
- Large text (≥ 18pt): minimum 3:1 contrast ratio
- UI components: minimum 3:1 against adjacent colors
- Test: `webaim.org/resources/contrastchecker`
---
### 3. Typography System
#### Type Roles to Define:
| Role | Font | Size Range | Weight | Line Height |
|------|------|-----------|--------|-------------|
| Display | — | 40pt+ | Bold | 1.1 |
| H1 | — | 2840pt | SemiBold | 1.15 |
| H2 | — | 2228pt | SemiBold | 1.2 |
| H3 | — | 1822pt | Medium | 1.25 |
| Body | — | 1518pt | Regular | 1.51.6 |
| Small / Caption | — | 1214pt | Regular | 1.4 |
| Label / UI | — | 1113pt | Medium | 1.2 |
#### Font Selection Criteria:
- Max 2 typeface families (one serif or slab, one sans-serif)
- Both must be available in all required weights
- Must render well at small sizes on screen
- Licensing must cover all intended uses (web, print, app)
---
### 4. Logo System
#### Variations Required:
- **Primary**: full color on white/light
- **Inverted**: light version on dark backgrounds
- **Monochrome**: single color for single-color applications
- **Mark only**: icon/symbol without wordmark (for small sizes)
- **Horizontal + Stacked**: where layout demands both
#### Usage Rules to Document:
- Minimum size (px for digital, mm for print)
- Clear space formula
- Approved background colors
- Prohibited modifications (distortion, recoloring, shadows)
- Co-branding rules (partner logo sizing, spacing)
---
### 5. Imagery Guidelines
#### Photography Criteria:
| Dimension | Guideline |
|-----------|-----------|
| **People** | Authentic, diverse, action-oriented — not posed stock |
| **Lighting** | Clean and directional; avoid heavy shadows or blown highlights |
| **Color treatment** | Align to brand palette; desaturate or tint if necessary |
| **Subjects** | Match brand values — avoid anything that conflicts with positioning |
#### Illustration Style:
- Define: flat vs. 3D, line vs. filled, abstract vs. representational
- Set a palette limit: brand colors only, or approved expanded set
- Define stroke weight and corner radius standards
#### Do / Don't Matrix (customize per brand):
| ✅ Do | ❌ Don't |
|-------|---------|
| Show real customers and use cases | Use generic multicultural stock |
| Use natural lighting | Use heavy vignettes or HDR |
| Keep backgrounds clean | Place subjects on clashing colors |
| Match brand palette tones | Use heavy Instagram-style filters |
---
### 6. Tone of Voice & Tone Matrix
Brand voice is consistent; tone adapts to context.
#### Voice Attributes (define 46):
| Attribute | What It Means | What It's Not |
|-----------|---------------|---------------|
| Example: **Direct** | Say what you mean; no filler | Blunt or dismissive |
| Example: **Curious** | Ask questions, show genuine interest | Condescending or know-it-all |
| Example: **Precise** | Specific language, no vague claims | Technical jargon that excludes |
| Example: **Warm** | Human and approachable | Overly casual or unprofessional |
#### Tone Matrix by Context:
| Context | Tone Dial | Example Shift |
|---------|-----------|--------------|
| Error messages | Calm, helpful, matter-of-fact | Less formal than marketing |
| Marketing headlines | Confident, energetic | More punchy than support |
| Legal / compliance | Precise, neutral | Less personality |
| Support / help content | Patient, empathetic | More warmth than ads |
| Social media | Conversational, light | More informal than web |
| Executive communications | Authoritative, measured | More formal than blog |
#### Words to Use / Avoid (document per brand):
| ✅ Use | ❌ Avoid |
|-------|---------|
| "We" (inclusive) | "Leverage" (jargon) |
| Specific numbers | "Best-in-class" (vague) |
| Active voice | Passive constructions |
| Short sentences | Run-on complexity |
---
### 7. Application Examples
#### Digital
- **Web**: Primary palette for backgrounds; accent for CTAs; Poppins/brand heading font for H1H3
- **Email**: Inline styles only; web-safe font fallbacks always specified; logo as linked image
- **Social**: Platform-specific safe zones; brand colors dominant; minimal text on images
#### Print
- Always use CMYK values for print production (never RGB or hex)
- Bleed: 3mm on all sides; keep critical content 5mm from trim
- Proof against Pantone reference before bulk print runs
#### Presentations
- Cover slide: brand dark + brand light with single accent
- Body slides: white backgrounds with brand accent headers
- No custom fonts in share files — embed or substitute
---

View File

@@ -44,281 +44,7 @@ Gather this context (ask if not provided):
--- ---
## Searchable vs Shareable ## Searchable vs Shareable
→ See references/content-strategy-reference.md for details
Every piece of content must be searchable, shareable, or both. Prioritize in that order—search traffic is the foundation.
**Searchable content** captures existing demand. Optimized for people actively looking for answers.
**Shareable content** creates demand. Spreads ideas and gets people talking.
### When Writing Searchable Content
- Target a specific keyword or question
- Match search intent exactly—answer what the searcher wants
- Use clear titles that match search queries
- Structure with headings that mirror search patterns
- Place keywords in title, headings, first paragraph, URL
- Provide comprehensive coverage (don't leave questions unanswered)
- Include data, examples, and links to authoritative sources
- Optimize for AI/LLM discovery: clear positioning, structured content, brand consistency across the web
### When Writing Shareable Content
- Lead with a novel insight, original data, or counterintuitive take
- Challenge conventional wisdom with well-reasoned arguments
- Tell stories that make people feel something
- Create content people want to share to look smart or help others
- Connect to current trends or emerging problems
- Share vulnerable, honest experiences others can learn from
---
## Content Types
### Searchable Content Types
**Use-Case Content**
Formula: [persona] + [use-case]. Targets long-tail keywords.
- "Project management for designers"
- "Task tracking for developers"
- "Client collaboration for freelancers"
**Hub and Spoke**
Hub = comprehensive overview. Spokes = related subtopics.
```
/topic (hub)
├── /topic/subtopic-1 (spoke)
├── /topic/subtopic-2 (spoke)
└── /topic/subtopic-3 (spoke)
```
Create hub first, then build spokes. Interlink strategically.
**Note:** Most content works fine under `/blog`. Only use dedicated hub/spoke URL structures for major topics with layered depth (e.g., Atlassian's `/agile` guide). For typical blog posts, `/blog/post-title` is sufficient.
**Template Libraries**
High-intent keywords + product adoption.
- Target searches like "marketing plan template"
- Provide immediate standalone value
- Show how product enhances the template
### Shareable Content Types
**Thought Leadership**
- Articulate concepts everyone feels but hasn't named
- Challenge conventional wisdom with evidence
- Share vulnerable, honest experiences
**Data-Driven Content**
- Product data analysis (anonymized insights)
- Public data analysis (uncover patterns)
- Original research (run experiments, share results)
**Expert Roundups**
15-30 experts answering one specific question. Built-in distribution.
**Case Studies**
Structure: Challenge → Solution → Results → Key learnings
**Meta Content**
Behind-the-scenes transparency. "How We Got Our First $5k MRR," "Why We Chose Debt Over VC."
For programmatic content at scale, see **programmatic-seo** skill.
---
## Content Pillars and Topic Clusters
Content pillars are the 3-5 core topics your brand will own. Each pillar spawns a cluster of related content.
Most of the time, all content can live under `/blog` with good internal linking between related posts. Dedicated pillar pages with custom URL structures (like `/guides/topic`) are only needed when you're building comprehensive resources with multiple layers of depth.
### How to Identify Pillars
1. **Product-led**: What problems does your product solve?
2. **Audience-led**: What does your ICP need to learn?
3. **Search-led**: What topics have volume in your space?
4. **Competitor-led**: What are competitors ranking for?
### Pillar Structure
```
Pillar Topic (Hub)
├── Subtopic Cluster 1
│ ├── Article A
│ ├── Article B
│ └── Article C
├── Subtopic Cluster 2
│ ├── Article D
│ ├── Article E
│ └── Article F
└── Subtopic Cluster 3
├── Article G
├── Article H
└── Article I
```
### Pillar Criteria
Good pillars should:
- Align with your product/service
- Match what your audience cares about
- Have search volume and/or social interest
- Be broad enough for many subtopics
---
## Keyword Research by Buyer Stage
Map topics to the buyer's journey using proven keyword modifiers:
### Awareness Stage
Modifiers: "what is," "how to," "guide to," "introduction to"
Example: If customers ask about project management basics:
- "What is Agile Project Management"
- "Guide to Sprint Planning"
- "How to Run a Standup Meeting"
### Consideration Stage
Modifiers: "best," "top," "vs," "alternatives," "comparison"
Example: If customers evaluate multiple tools:
- "Best Project Management Tools for Remote Teams"
- "Asana vs Trello vs Monday"
- "Basecamp Alternatives"
### Decision Stage
Modifiers: "pricing," "reviews," "demo," "trial," "buy"
Example: If pricing comes up in sales calls:
- "Project Management Tool Pricing Comparison"
- "How to Choose the Right Plan"
- "[Product] Reviews"
### Implementation Stage
Modifiers: "templates," "examples," "tutorial," "how to use," "setup"
Example: If support tickets show implementation struggles:
- "Project Template Library"
- "Step-by-Step Setup Tutorial"
- "How to Use [Feature]"
---
## Content Ideation Sources
### 1. Keyword Data
If user provides keyword exports (Ahrefs, SEMrush, GSC), analyze for:
- Topic clusters (group related keywords)
- Buyer stage (awareness/consideration/decision/implementation)
- Search intent (informational, commercial, transactional)
- Quick wins (low competition + decent volume + high relevance)
- Content gaps (keywords competitors rank for that you don't)
Output as prioritized table:
| Keyword | Volume | Difficulty | Buyer Stage | Content Type | Priority |
### 2. Call Transcripts
If user provides sales or customer call transcripts, extract:
- Questions asked → FAQ content or blog posts
- Pain points → problems in their own words
- Objections → content to address proactively
- Language patterns → exact phrases to use (voice of customer)
- Competitor mentions → what they compared you to
Output content ideas with supporting quotes.
### 3. Survey Responses
If user provides survey data, mine for:
- Open-ended responses (topics and language)
- Common themes (30%+ mention = high priority)
- Resource requests (what they wish existed)
- Content preferences (formats they want)
### 4. Forum Research
Use web search to find content ideas:
**Reddit:** `site:reddit.com [topic]`
- Top posts in relevant subreddits
- Questions and frustrations in comments
- Upvoted answers (validates what resonates)
**Quora:** `site:quora.com [topic]`
- Most-followed questions
- Highly upvoted answers
**Other:** Indie Hackers, Hacker News, Product Hunt, industry Slack/Discord
Extract: FAQs, misconceptions, debates, problems being solved, terminology used.
### 5. Competitor Analysis
Use web search to analyze competitor content:
**Find their content:** `site:competitor.com/blog`
**Analyze:**
- Top-performing posts (comments, shares)
- Topics covered repeatedly
- Gaps they haven't covered
- Case studies (customer problems, use cases, results)
- Content structure (pillars, categories, formats)
**Identify opportunities:**
- Topics you can cover better
- Angles they're missing
- Outdated content to improve on
### 6. Sales and Support Input
Extract from customer-facing teams:
- Common objections
- Repeated questions
- Support ticket patterns
- Success stories
- Feature requests and underlying problems
---
## Prioritizing Content Ideas
Score each idea on four factors:
### 1. Customer Impact (40%)
- How frequently did this topic come up in research?
- What percentage of customers face this challenge?
- How emotionally charged was this pain point?
- What's the potential LTV of customers with this need?
### 2. Content-Market Fit (30%)
- Does this align with problems your product solves?
- Can you offer unique insights from customer research?
- Do you have customer stories to support this?
- Will this naturally lead to product interest?
### 3. Search Potential (20%)
- What's the monthly search volume?
- How competitive is this topic?
- Are there related long-tail opportunities?
- Is search interest growing or declining?
### 4. Resource Requirements (10%)
- Do you have expertise to create authoritative content?
- What additional research is needed?
- What assets (graphics, data, examples) will you need?
### Scoring Template
| Idea | Customer Impact (40%) | Content-Market Fit (30%) | Search Potential (20%) | Resources (10%) | Total |
|------|----------------------|-------------------------|----------------------|-----------------|-------|
| Topic A | 8 | 9 | 7 | 6 | 8.0 |
| Topic B | 6 | 7 | 9 | 8 | 7.1 |
---
## Output Format ## Output Format

View File

@@ -0,0 +1,278 @@
# content-strategy reference
## Searchable vs Shareable
Every piece of content must be searchable, shareable, or both. Prioritize in that order—search traffic is the foundation.
**Searchable content** captures existing demand. Optimized for people actively looking for answers.
**Shareable content** creates demand. Spreads ideas and gets people talking.
### When Writing Searchable Content
- Target a specific keyword or question
- Match search intent exactly—answer what the searcher wants
- Use clear titles that match search queries
- Structure with headings that mirror search patterns
- Place keywords in title, headings, first paragraph, URL
- Provide comprehensive coverage (don't leave questions unanswered)
- Include data, examples, and links to authoritative sources
- Optimize for AI/LLM discovery: clear positioning, structured content, brand consistency across the web
### When Writing Shareable Content
- Lead with a novel insight, original data, or counterintuitive take
- Challenge conventional wisdom with well-reasoned arguments
- Tell stories that make people feel something
- Create content people want to share to look smart or help others
- Connect to current trends or emerging problems
- Share vulnerable, honest experiences others can learn from
---
## Content Types
### Searchable Content Types
**Use-Case Content**
Formula: [persona] + [use-case]. Targets long-tail keywords.
- "Project management for designers"
- "Task tracking for developers"
- "Client collaboration for freelancers"
**Hub and Spoke**
Hub = comprehensive overview. Spokes = related subtopics.
```
/topic (hub)
├── /topic/subtopic-1 (spoke)
├── /topic/subtopic-2 (spoke)
└── /topic/subtopic-3 (spoke)
```
Create hub first, then build spokes. Interlink strategically.
**Note:** Most content works fine under `/blog`. Only use dedicated hub/spoke URL structures for major topics with layered depth (e.g., Atlassian's `/agile` guide). For typical blog posts, `/blog/post-title` is sufficient.
**Template Libraries**
High-intent keywords + product adoption.
- Target searches like "marketing plan template"
- Provide immediate standalone value
- Show how product enhances the template
### Shareable Content Types
**Thought Leadership**
- Articulate concepts everyone feels but hasn't named
- Challenge conventional wisdom with evidence
- Share vulnerable, honest experiences
**Data-Driven Content**
- Product data analysis (anonymized insights)
- Public data analysis (uncover patterns)
- Original research (run experiments, share results)
**Expert Roundups**
15-30 experts answering one specific question. Built-in distribution.
**Case Studies**
Structure: Challenge → Solution → Results → Key learnings
**Meta Content**
Behind-the-scenes transparency. "How We Got Our First $5k MRR," "Why We Chose Debt Over VC."
For programmatic content at scale, see **programmatic-seo** skill.
---
## Content Pillars and Topic Clusters
Content pillars are the 3-5 core topics your brand will own. Each pillar spawns a cluster of related content.
Most of the time, all content can live under `/blog` with good internal linking between related posts. Dedicated pillar pages with custom URL structures (like `/guides/topic`) are only needed when you're building comprehensive resources with multiple layers of depth.
### How to Identify Pillars
1. **Product-led**: What problems does your product solve?
2. **Audience-led**: What does your ICP need to learn?
3. **Search-led**: What topics have volume in your space?
4. **Competitor-led**: What are competitors ranking for?
### Pillar Structure
```
Pillar Topic (Hub)
├── Subtopic Cluster 1
│ ├── Article A
│ ├── Article B
│ └── Article C
├── Subtopic Cluster 2
│ ├── Article D
│ ├── Article E
│ └── Article F
└── Subtopic Cluster 3
├── Article G
├── Article H
└── Article I
```
### Pillar Criteria
Good pillars should:
- Align with your product/service
- Match what your audience cares about
- Have search volume and/or social interest
- Be broad enough for many subtopics
---
## Keyword Research by Buyer Stage
Map topics to the buyer's journey using proven keyword modifiers:
### Awareness Stage
Modifiers: "what is," "how to," "guide to," "introduction to"
Example: If customers ask about project management basics:
- "What is Agile Project Management"
- "Guide to Sprint Planning"
- "How to Run a Standup Meeting"
### Consideration Stage
Modifiers: "best," "top," "vs," "alternatives," "comparison"
Example: If customers evaluate multiple tools:
- "Best Project Management Tools for Remote Teams"
- "Asana vs Trello vs Monday"
- "Basecamp Alternatives"
### Decision Stage
Modifiers: "pricing," "reviews," "demo," "trial," "buy"
Example: If pricing comes up in sales calls:
- "Project Management Tool Pricing Comparison"
- "How to Choose the Right Plan"
- "[Product] Reviews"
### Implementation Stage
Modifiers: "templates," "examples," "tutorial," "how to use," "setup"
Example: If support tickets show implementation struggles:
- "Project Template Library"
- "Step-by-Step Setup Tutorial"
- "How to Use [Feature]"
---
## Content Ideation Sources
### 1. Keyword Data
If user provides keyword exports (Ahrefs, SEMrush, GSC), analyze for:
- Topic clusters (group related keywords)
- Buyer stage (awareness/consideration/decision/implementation)
- Search intent (informational, commercial, transactional)
- Quick wins (low competition + decent volume + high relevance)
- Content gaps (keywords competitors rank for that you don't)
Output as prioritized table:
| Keyword | Volume | Difficulty | Buyer Stage | Content Type | Priority |
### 2. Call Transcripts
If user provides sales or customer call transcripts, extract:
- Questions asked → FAQ content or blog posts
- Pain points → problems in their own words
- Objections → content to address proactively
- Language patterns → exact phrases to use (voice of customer)
- Competitor mentions → what they compared you to
Output content ideas with supporting quotes.
### 3. Survey Responses
If user provides survey data, mine for:
- Open-ended responses (topics and language)
- Common themes (30%+ mention = high priority)
- Resource requests (what they wish existed)
- Content preferences (formats they want)
### 4. Forum Research
Use web search to find content ideas:
**Reddit:** `site:reddit.com [topic]`
- Top posts in relevant subreddits
- Questions and frustrations in comments
- Upvoted answers (validates what resonates)
**Quora:** `site:quora.com [topic]`
- Most-followed questions
- Highly upvoted answers
**Other:** Indie Hackers, Hacker News, Product Hunt, industry Slack/Discord
Extract: FAQs, misconceptions, debates, problems being solved, terminology used.
### 5. Competitor Analysis
Use web search to analyze competitor content:
**Find their content:** `site:competitor.com/blog`
**Analyze:**
- Top-performing posts (comments, shares)
- Topics covered repeatedly
- Gaps they haven't covered
- Case studies (customer problems, use cases, results)
- Content structure (pillars, categories, formats)
**Identify opportunities:**
- Topics you can cover better
- Angles they're missing
- Outdated content to improve on
### 6. Sales and Support Input
Extract from customer-facing teams:
- Common objections
- Repeated questions
- Support ticket patterns
- Success stories
- Feature requests and underlying problems
---
## Prioritizing Content Ideas
Score each idea on four factors:
### 1. Customer Impact (40%)
- How frequently did this topic come up in research?
- What percentage of customers face this challenge?
- How emotionally charged was this pain point?
- What's the potential LTV of customers with this need?
### 2. Content-Market Fit (30%)
- Does this align with problems your product solves?
- Can you offer unique insights from customer research?
- Do you have customer stories to support this?
- Will this naturally lead to product interest?
### 3. Search Potential (20%)
- What's the monthly search volume?
- How competitive is this topic?
- Are there related long-tail opportunities?
- Is search interest growing or declining?
### 4. Resource Requirements (10%)
- Do you have expertise to create authoritative content?
- What additional research is needed?
- What assets (graphics, data, examples) will you need?
### Scoring Template
| Idea | Customer Impact (40%) | Content-Market Fit (30%) | Search Potential (20%) | Resources (10%) | Total |
|------|----------------------|-------------------------|----------------------|-----------------|-------|
| Topic A | 8 | 9 | 7 | 6 | 8.0 |
| Topic B | 6 | 7 | 9 | 8 | 7.1 |
---

View File

@@ -44,212 +44,7 @@ Before creating a sequence, understand:
--- ---
## Core Principles ## Core Principles
→ See references/email-sequence-playbook.md for details
### 1. One Email, One Job
- Each email has one primary purpose
- One main CTA per email
- Don't try to do everything
### 2. Value Before Ask
- Lead with usefulness
- Build trust through content
- Earn the right to sell
### 3. Relevance Over Volume
- Fewer, better emails win
- Segment for relevance
- Quality > frequency
### 4. Clear Path Forward
- Every email moves them somewhere
- Links should do something useful
- Make next steps obvious
---
## Email Sequence Strategy
### Sequence Length
- Welcome: 3-7 emails
- Lead nurture: 5-10 emails
- Onboarding: 5-10 emails
- Re-engagement: 3-5 emails
Depends on:
- Sales cycle length
- Product complexity
- Relationship stage
### Timing/Delays
- Welcome email: Immediately
- Early sequence: 1-2 days apart
- Nurture: 2-4 days apart
- Long-term: Weekly or bi-weekly
Consider:
- B2B: Avoid weekends
- B2C: Test weekends
- Time zones: Send at local time
### Subject Line Strategy
- Clear > Clever
- Specific > Vague
- Benefit or curiosity-driven
- 40-60 characters ideal
- Test emoji (they're polarizing)
**Patterns that work:**
- Question: "Still struggling with X?"
- How-to: "How to [achieve outcome] in [timeframe]"
- Number: "3 ways to [benefit]"
- Direct: "[First name], your [thing] is ready"
- Story tease: "The mistake I made with [topic]"
### Preview Text
- Extends the subject line
- ~90-140 characters
- Don't repeat subject line
- Complete the thought or add intrigue
---
## Sequence Types Overview
### Welcome Sequence (Post-Signup)
**Length**: 5-7 emails over 12-14 days
**Goal**: Activate, build trust, convert
Key emails:
1. Welcome + deliver promised value (immediate)
2. Quick win (day 1-2)
3. Story/Why (day 3-4)
4. Social proof (day 5-6)
5. Overcome objection (day 7-8)
6. Core feature highlight (day 9-11)
7. Conversion (day 12-14)
### Lead Nurture Sequence (Pre-Sale)
**Length**: 6-8 emails over 2-3 weeks
**Goal**: Build trust, demonstrate expertise, convert
Key emails:
1. Deliver lead magnet + intro (immediate)
2. Expand on topic (day 2-3)
3. Problem deep-dive (day 4-5)
4. Solution framework (day 6-8)
5. Case study (day 9-11)
6. Differentiation (day 12-14)
7. Objection handler (day 15-18)
8. Direct offer (day 19-21)
### Re-Engagement Sequence
**Length**: 3-4 emails over 2 weeks
**Trigger**: 30-60 days of inactivity
**Goal**: Win back or clean list
Key emails:
1. Check-in (genuine concern)
2. Value reminder (what's new)
3. Incentive (special offer)
4. Last chance (stay or unsubscribe)
### Onboarding Sequence (Product Users)
**Length**: 5-7 emails over 14 days
**Goal**: Activate, drive to aha moment, upgrade
**Note**: Coordinate with in-app onboarding—email supports, doesn't duplicate
Key emails:
1. Welcome + first step (immediate)
2. Getting started help (day 1)
3. Feature highlight (day 2-3)
4. Success story (day 4-5)
5. Check-in (day 7)
6. Advanced tip (day 10-12)
7. Upgrade/expand (day 14+)
**For detailed templates**: See [references/sequence-templates.md](references/sequence-templates.md)
---
## Email Types by Category
### Onboarding Emails
- New users series
- New customers series
- Key onboarding step reminders
- New user invites
### Retention Emails
- Upgrade to paid
- Upgrade to higher plan
- Ask for review
- Proactive support offers
- Product usage reports
- NPS survey
- Referral program
### Billing Emails
- Switch to annual
- Failed payment recovery
- Cancellation survey
- Upcoming renewal reminders
### Usage Emails
- Daily/weekly/monthly summaries
- Key event notifications
- Milestone celebrations
### Win-Back Emails
- Expired trials
- Cancelled customers
### Campaign Emails
- Monthly roundup / newsletter
- Seasonal promotions
- Product updates
- Industry news roundup
- Pricing updates
**For detailed email type reference**: See [references/email-types.md](references/email-types.md)
---
## Email Copy Guidelines
### Structure
1. **Hook**: First line grabs attention
2. **Context**: Why this matters to them
3. **Value**: The useful content
4. **CTA**: What to do next
5. **Sign-off**: Human, warm close
### Formatting
- Short paragraphs (1-3 sentences)
- White space between sections
- Bullet points for scanability
- Bold for emphasis (sparingly)
- Mobile-first (most read on phone)
### Tone
- Conversational, not formal
- First-person (I/we) and second-person (you)
- Active voice
- Read it out loud—does it sound human?
### Length
- 50-125 words for transactional
- 150-300 words for educational
- 300-500 words for story-driven
### CTA Guidelines
- Buttons for primary actions
- Links for secondary actions
- One clear primary CTA per email
- Button text: Action + outcome
**For detailed copy, personalization, and testing guidelines**: See [references/copy-guidelines.md](references/copy-guidelines.md)
---
## Output Format ## Output Format

View File

@@ -0,0 +1,209 @@
# email-sequence reference
## Core Principles
### 1. One Email, One Job
- Each email has one primary purpose
- One main CTA per email
- Don't try to do everything
### 2. Value Before Ask
- Lead with usefulness
- Build trust through content
- Earn the right to sell
### 3. Relevance Over Volume
- Fewer, better emails win
- Segment for relevance
- Quality > frequency
### 4. Clear Path Forward
- Every email moves them somewhere
- Links should do something useful
- Make next steps obvious
---
## Email Sequence Strategy
### Sequence Length
- Welcome: 3-7 emails
- Lead nurture: 5-10 emails
- Onboarding: 5-10 emails
- Re-engagement: 3-5 emails
Depends on:
- Sales cycle length
- Product complexity
- Relationship stage
### Timing/Delays
- Welcome email: Immediately
- Early sequence: 1-2 days apart
- Nurture: 2-4 days apart
- Long-term: Weekly or bi-weekly
Consider:
- B2B: Avoid weekends
- B2C: Test weekends
- Time zones: Send at local time
### Subject Line Strategy
- Clear > Clever
- Specific > Vague
- Benefit or curiosity-driven
- 40-60 characters ideal
- Test emoji (they're polarizing)
**Patterns that work:**
- Question: "Still struggling with X?"
- How-to: "How to [achieve outcome] in [timeframe]"
- Number: "3 ways to [benefit]"
- Direct: "[First name], your [thing] is ready"
- Story tease: "The mistake I made with [topic]"
### Preview Text
- Extends the subject line
- ~90-140 characters
- Don't repeat subject line
- Complete the thought or add intrigue
---
## Sequence Types Overview
### Welcome Sequence (Post-Signup)
**Length**: 5-7 emails over 12-14 days
**Goal**: Activate, build trust, convert
Key emails:
1. Welcome + deliver promised value (immediate)
2. Quick win (day 1-2)
3. Story/Why (day 3-4)
4. Social proof (day 5-6)
5. Overcome objection (day 7-8)
6. Core feature highlight (day 9-11)
7. Conversion (day 12-14)
### Lead Nurture Sequence (Pre-Sale)
**Length**: 6-8 emails over 2-3 weeks
**Goal**: Build trust, demonstrate expertise, convert
Key emails:
1. Deliver lead magnet + intro (immediate)
2. Expand on topic (day 2-3)
3. Problem deep-dive (day 4-5)
4. Solution framework (day 6-8)
5. Case study (day 9-11)
6. Differentiation (day 12-14)
7. Objection handler (day 15-18)
8. Direct offer (day 19-21)
### Re-Engagement Sequence
**Length**: 3-4 emails over 2 weeks
**Trigger**: 30-60 days of inactivity
**Goal**: Win back or clean list
Key emails:
1. Check-in (genuine concern)
2. Value reminder (what's new)
3. Incentive (special offer)
4. Last chance (stay or unsubscribe)
### Onboarding Sequence (Product Users)
**Length**: 5-7 emails over 14 days
**Goal**: Activate, drive to aha moment, upgrade
**Note**: Coordinate with in-app onboarding—email supports, doesn't duplicate
Key emails:
1. Welcome + first step (immediate)
2. Getting started help (day 1)
3. Feature highlight (day 2-3)
4. Success story (day 4-5)
5. Check-in (day 7)
6. Advanced tip (day 10-12)
7. Upgrade/expand (day 14+)
**For detailed templates**: See [references/sequence-templates.md](references/sequence-templates.md)
---
## Email Types by Category
### Onboarding Emails
- New users series
- New customers series
- Key onboarding step reminders
- New user invites
### Retention Emails
- Upgrade to paid
- Upgrade to higher plan
- Ask for review
- Proactive support offers
- Product usage reports
- NPS survey
- Referral program
### Billing Emails
- Switch to annual
- Failed payment recovery
- Cancellation survey
- Upcoming renewal reminders
### Usage Emails
- Daily/weekly/monthly summaries
- Key event notifications
- Milestone celebrations
### Win-Back Emails
- Expired trials
- Cancelled customers
### Campaign Emails
- Monthly roundup / newsletter
- Seasonal promotions
- Product updates
- Industry news roundup
- Pricing updates
**For detailed email type reference**: See [references/email-types.md](references/email-types.md)
---
## Email Copy Guidelines
### Structure
1. **Hook**: First line grabs attention
2. **Context**: Why this matters to them
3. **Value**: The useful content
4. **CTA**: What to do next
5. **Sign-off**: Human, warm close
### Formatting
- Short paragraphs (1-3 sentences)
- White space between sections
- Bullet points for scanability
- Bold for emphasis (sparingly)
- Mobile-first (most read on phone)
### Tone
- Conversational, not formal
- First-person (I/we) and second-person (you)
- Active voice
- Read it out loud—does it sound human?
### Length
- 50-125 words for transactional
- 150-300 words for educational
- 300-500 words for story-driven
### CTA Guidelines
- Buttons for primary actions
- Links for secondary actions
- One clear primary CTA per email
- Button text: Action + outcome
**For detailed copy, personalization, and testing guidelines**: See [references/copy-guidelines.md](references/copy-guidelines.md)
---

View File

@@ -43,274 +43,7 @@ Before providing recommendations, identify:
--- ---
## Core Principles ## Core Principles
→ See references/form-cro-playbook.md for details
### 1. Every Field Has a Cost
Each field reduces completion rate. Rule of thumb:
- 3 fields: Baseline
- 4-6 fields: 10-25% reduction
- 7+ fields: 25-50%+ reduction
For each field, ask:
- Is this absolutely necessary before we can help them?
- Can we get this information another way?
- Can we ask this later?
### 2. Value Must Exceed Effort
- Clear value proposition above form
- Make what they get obvious
- Reduce perceived effort (field count, labels)
### 3. Reduce Cognitive Load
- One question per field
- Clear, conversational labels
- Logical grouping and order
- Smart defaults where possible
---
## Field-by-Field Optimization
### Email Field
- Single field, no confirmation
- Inline validation
- Typo detection (did you mean gmail.com?)
- Proper mobile keyboard
### Name Fields
- Single "Name" vs. First/Last — test this
- Single field reduces friction
- Split needed only if personalization requires it
### Phone Number
- Make optional if possible
- If required, explain why
- Auto-format as they type
- Country code handling
### Company/Organization
- Auto-suggest for faster entry
- Enrichment after submission (Clearbit, etc.)
- Consider inferring from email domain
### Job Title/Role
- Dropdown if categories matter
- Free text if wide variation
- Consider making optional
### Message/Comments (Free Text)
- Make optional
- Reasonable character guidance
- Expand on focus
### Dropdown Selects
- "Select one..." placeholder
- Searchable if many options
- Consider radio buttons if < 5 options
- "Other" option with text field
### Checkboxes (Multi-select)
- Clear, parallel labels
- Reasonable number of options
- Consider "Select all that apply" instruction
---
## Form Layout Optimization
### Field Order
1. Start with easiest fields (name, email)
2. Build commitment before asking more
3. Sensitive fields last (phone, company size)
4. Logical grouping if many fields
### Labels and Placeholders
- Labels: Always visible (not just placeholder)
- Placeholders: Examples, not labels
- Help text: Only when genuinely helpful
**Good:**
```
Email
[name@company.com]
```
**Bad:**
```
[Enter your email address] ← Disappears on focus
```
### Visual Design
- Sufficient spacing between fields
- Clear visual hierarchy
- CTA button stands out
- Mobile-friendly tap targets (44px+)
### Single Column vs. Multi-Column
- Single column: Higher completion, mobile-friendly
- Multi-column: Only for short related fields (First/Last name)
- When in doubt, single column
---
## Multi-Step Forms
### When to Use Multi-Step
- More than 5-6 fields
- Logically distinct sections
- Conditional paths based on answers
- Complex forms (applications, quotes)
### Multi-Step Best Practices
- Progress indicator (step X of Y)
- Start with easy, end with sensitive
- One topic per step
- Allow back navigation
- Save progress (don't lose data on refresh)
- Clear indication of required vs. optional
### Progressive Commitment Pattern
1. Low-friction start (just email)
2. More detail (name, company)
3. Qualifying questions
4. Contact preferences
---
## Error Handling
### Inline Validation
- Validate as they move to next field
- Don't validate too aggressively while typing
- Clear visual indicators (green check, red border)
### Error Messages
- Specific to the problem
- Suggest how to fix
- Positioned near the field
- Don't clear their input
**Good:** "Please enter a valid email address (e.g., name@company.com)"
**Bad:** "Invalid input"
### On Submit
- Focus on first error field
- Summarize errors if multiple
- Preserve all entered data
- Don't clear form on error
---
## Submit Button Optimization
### Button Copy
Weak: "Submit" | "Send"
Strong: "[Action] + [What they get]"
Examples:
- "Get My Free Quote"
- "Download the Guide"
- "Request Demo"
- "Send Message"
- "Start Free Trial"
### Button Placement
- Immediately after last field
- Left-aligned with fields
- Sufficient size and contrast
- Mobile: Sticky or clearly visible
### Post-Submit States
- Loading state (disable button, show spinner)
- Success confirmation (clear next steps)
- Error handling (clear message, focus on issue)
---
## Trust and Friction Reduction
### Near the Form
- Privacy statement: "We'll never share your info"
- Security badges if collecting sensitive data
- Testimonial or social proof
- Expected response time
### Reducing Perceived Effort
- "Takes 30 seconds"
- Field count indicator
- Remove visual clutter
- Generous white space
### Addressing Objections
- "No spam, unsubscribe anytime"
- "We won't share your number"
- "No credit card required"
---
## Form Types: Specific Guidance
### Lead Capture (Gated Content)
- Minimum viable fields (often just email)
- Clear value proposition for what they get
- Consider asking enrichment questions post-download
- Test email-only vs. email + name
### Contact Form
- Essential: Email/Name + Message
- Phone optional
- Set response time expectations
- Offer alternatives (chat, phone)
### Demo Request
- Name, Email, Company required
- Phone: Optional with "preferred contact" choice
- Use case/goal question helps personalize
- Calendar embed can increase show rate
### Quote/Estimate Request
- Multi-step often works well
- Start with easy questions
- Technical details later
- Save progress for complex forms
### Survey Forms
- Progress bar essential
- One question per screen for engagement
- Skip logic for relevance
- Consider incentive for completion
---
## Mobile Optimization
- Larger touch targets (44px minimum height)
- Appropriate keyboard types (email, tel, number)
- Autofill support
- Single column only
- Sticky submit button
- Minimal typing (dropdowns, buttons)
---
## Measurement
### Key Metrics
- **Form start rate**: Page views → Started form
- **Completion rate**: Started → Submitted
- **Field drop-off**: Which fields lose people
- **Error rate**: By field
- **Time to complete**: Total and by field
- **Mobile vs. desktop**: Completion by device
### What to Track
- Form views
- First field focus
- Each field completion
- Errors by field
- Submit attempts
- Successful submissions
---
## Output Format ## Output Format

View File

@@ -0,0 +1,271 @@
# form-cro reference
## Core Principles
### 1. Every Field Has a Cost
Each field reduces completion rate. Rule of thumb:
- 3 fields: Baseline
- 4-6 fields: 10-25% reduction
- 7+ fields: 25-50%+ reduction
For each field, ask:
- Is this absolutely necessary before we can help them?
- Can we get this information another way?
- Can we ask this later?
### 2. Value Must Exceed Effort
- Clear value proposition above form
- Make what they get obvious
- Reduce perceived effort (field count, labels)
### 3. Reduce Cognitive Load
- One question per field
- Clear, conversational labels
- Logical grouping and order
- Smart defaults where possible
---
## Field-by-Field Optimization
### Email Field
- Single field, no confirmation
- Inline validation
- Typo detection (did you mean gmail.com?)
- Proper mobile keyboard
### Name Fields
- Single "Name" vs. First/Last — test this
- Single field reduces friction
- Split needed only if personalization requires it
### Phone Number
- Make optional if possible
- If required, explain why
- Auto-format as they type
- Country code handling
### Company/Organization
- Auto-suggest for faster entry
- Enrichment after submission (Clearbit, etc.)
- Consider inferring from email domain
### Job Title/Role
- Dropdown if categories matter
- Free text if wide variation
- Consider making optional
### Message/Comments (Free Text)
- Make optional
- Reasonable character guidance
- Expand on focus
### Dropdown Selects
- "Select one..." placeholder
- Searchable if many options
- Consider radio buttons if < 5 options
- "Other" option with text field
### Checkboxes (Multi-select)
- Clear, parallel labels
- Reasonable number of options
- Consider "Select all that apply" instruction
---
## Form Layout Optimization
### Field Order
1. Start with easiest fields (name, email)
2. Build commitment before asking more
3. Sensitive fields last (phone, company size)
4. Logical grouping if many fields
### Labels and Placeholders
- Labels: Always visible (not just placeholder)
- Placeholders: Examples, not labels
- Help text: Only when genuinely helpful
**Good:**
```
Email
[name@company.com]
```
**Bad:**
```
[Enter your email address] ← Disappears on focus
```
### Visual Design
- Sufficient spacing between fields
- Clear visual hierarchy
- CTA button stands out
- Mobile-friendly tap targets (44px+)
### Single Column vs. Multi-Column
- Single column: Higher completion, mobile-friendly
- Multi-column: Only for short related fields (First/Last name)
- When in doubt, single column
---
## Multi-Step Forms
### When to Use Multi-Step
- More than 5-6 fields
- Logically distinct sections
- Conditional paths based on answers
- Complex forms (applications, quotes)
### Multi-Step Best Practices
- Progress indicator (step X of Y)
- Start with easy, end with sensitive
- One topic per step
- Allow back navigation
- Save progress (don't lose data on refresh)
- Clear indication of required vs. optional
### Progressive Commitment Pattern
1. Low-friction start (just email)
2. More detail (name, company)
3. Qualifying questions
4. Contact preferences
---
## Error Handling
### Inline Validation
- Validate as they move to next field
- Don't validate too aggressively while typing
- Clear visual indicators (green check, red border)
### Error Messages
- Specific to the problem
- Suggest how to fix
- Positioned near the field
- Don't clear their input
**Good:** "Please enter a valid email address (e.g., name@company.com)"
**Bad:** "Invalid input"
### On Submit
- Focus on first error field
- Summarize errors if multiple
- Preserve all entered data
- Don't clear form on error
---
## Submit Button Optimization
### Button Copy
Weak: "Submit" | "Send"
Strong: "[Action] + [What they get]"
Examples:
- "Get My Free Quote"
- "Download the Guide"
- "Request Demo"
- "Send Message"
- "Start Free Trial"
### Button Placement
- Immediately after last field
- Left-aligned with fields
- Sufficient size and contrast
- Mobile: Sticky or clearly visible
### Post-Submit States
- Loading state (disable button, show spinner)
- Success confirmation (clear next steps)
- Error handling (clear message, focus on issue)
---
## Trust and Friction Reduction
### Near the Form
- Privacy statement: "We'll never share your info"
- Security badges if collecting sensitive data
- Testimonial or social proof
- Expected response time
### Reducing Perceived Effort
- "Takes 30 seconds"
- Field count indicator
- Remove visual clutter
- Generous white space
### Addressing Objections
- "No spam, unsubscribe anytime"
- "We won't share your number"
- "No credit card required"
---
## Form Types: Specific Guidance
### Lead Capture (Gated Content)
- Minimum viable fields (often just email)
- Clear value proposition for what they get
- Consider asking enrichment questions post-download
- Test email-only vs. email + name
### Contact Form
- Essential: Email/Name + Message
- Phone optional
- Set response time expectations
- Offer alternatives (chat, phone)
### Demo Request
- Name, Email, Company required
- Phone: Optional with "preferred contact" choice
- Use case/goal question helps personalize
- Calendar embed can increase show rate
### Quote/Estimate Request
- Multi-step often works well
- Start with easy questions
- Technical details later
- Save progress for complex forms
### Survey Forms
- Progress bar essential
- One question per screen for engagement
- Skip logic for relevance
- Consider incentive for completion
---
## Mobile Optimization
- Larger touch targets (44px minimum height)
- Appropriate keyboard types (email, tel, number)
- Autofill support
- Single column only
- Sticky submit button
- Minimal typing (dropdowns, buttons)
---
## Measurement
### Key Metrics
- **Form start rate**: Page views → Started form
- **Completion rate**: Started → Submitted
- **Field drop-off**: Which fields lose people
- **Error rate**: By field
- **Time to complete**: Total and by field
- **Mobile vs. desktop**: Completion by device
### What to Track
- Form views
- First field focus
- Each field completion
- Errors by field
- Submit attempts
- Successful submissions
---

View File

@@ -21,320 +21,7 @@ If `.claude/product-marketing-context.md` exists, read it before asking question
--- ---
## Core Philosophy ## Core Philosophy
→ See references/launch-frameworks-and-checklists.md for details
The best companies don't just launch once—they launch again and again. Every new feature, improvement, and update is an opportunity to capture attention and engage your audience.
A strong launch isn't about a single moment. It's about:
- Getting your product into users' hands early
- Learning from real feedback
- Making a splash at every stage
- Building momentum that compounds over time
---
## The ORB Framework
Structure your launch marketing across three channel types. Everything should ultimately lead back to owned channels.
### Owned Channels
You own the channel (though not the audience). Direct access without algorithms or platform rules.
**Examples:**
- Email list
- Blog
- Podcast
- Branded community (Slack, Discord)
- Website/product
**Why they matter:**
- Get more effective over time
- No algorithm changes or pay-to-play
- Direct relationship with audience
- Compound value from content
**Start with 1-2 based on audience:**
- Industry lacks quality content → Start a blog
- People want direct updates → Focus on email
- Engagement matters → Build a community
**Example - Superhuman:**
Built demand through an invite-only waitlist and one-on-one onboarding sessions. Every new user got a 30-minute live demo. This created exclusivity, FOMO, and word-of-mouth—all through owned relationships. Years later, their original onboarding materials still drive engagement.
### Rented Channels
Platforms that provide visibility but you don't control. Algorithms shift, rules change, pay-to-play increases.
**Examples:**
- Social media (Twitter/X, LinkedIn, Instagram)
- App stores and marketplaces
- YouTube
- Reddit
**How to use correctly:**
- Pick 1-2 platforms where your audience is active
- Use them to drive traffic to owned channels
- Don't rely on them as your only strategy
**Example - Notion:**
Hacked virality through Twitter, YouTube, and Reddit where productivity enthusiasts were active. Encouraged community to share templates and workflows. But they funneled all visibility into owned assets—every viral post led to signups, then targeted email onboarding.
**Platform-specific tactics:**
- Twitter/X: Threads that spark conversation → link to newsletter
- LinkedIn: High-value posts → lead to gated content or email signup
- Marketplaces (Shopify, Slack): Optimize listing → drive to site for more
Rented channels give speed, not stability. Capture momentum by bringing users into your owned ecosystem.
### Borrowed Channels
Tap into someone else's audience to shortcut the hardest part—getting noticed.
**Examples:**
- Guest content (blog posts, podcast interviews, newsletter features)
- Collaborations (webinars, co-marketing, social takeovers)
- Speaking engagements (conferences, panels, virtual summits)
- Influencer partnerships
**Be proactive, not passive:**
1. List industry leaders your audience follows
2. Pitch win-win collaborations
3. Use tools like SparkToro or Listen Notes to find audience overlap
4. Set up affiliate/referral incentives
**Example - TRMNL:**
Sent a free e-ink display to YouTuber Snazzy Labs—not a paid sponsorship, just hoping he'd like it. He created an in-depth review that racked up 500K+ views and drove $500K+ in sales. They also set up an affiliate program for ongoing promotion.
Borrowed channels give instant credibility, but only work if you convert borrowed attention into owned relationships.
---
## Five-Phase Launch Approach
Launching isn't a one-day event. It's a phased process that builds momentum.
### Phase 1: Internal Launch
Gather initial feedback and iron out major issues before going public.
**Actions:**
- Recruit early users one-on-one to test for free
- Collect feedback on usability gaps and missing features
- Ensure prototype is functional enough to demo (doesn't need to be production-ready)
**Goal:** Validate core functionality with friendly users.
### Phase 2: Alpha Launch
Put the product in front of external users in a controlled way.
**Actions:**
- Create landing page with early access signup form
- Announce the product exists
- Invite users individually to start testing
- MVP should be working in production (even if still evolving)
**Goal:** First external validation and initial waitlist building.
### Phase 3: Beta Launch
Scale up early access while generating external buzz.
**Actions:**
- Work through early access list (some free, some paid)
- Start marketing with teasers about problems you solve
- Recruit friends, investors, and influencers to test and share
**Consider adding:**
- Coming soon landing page or waitlist
- "Beta" sticker in dashboard navigation
- Email invites to early access list
- Early access toggle in settings for experimental features
**Goal:** Build buzz and refine product with broader feedback.
### Phase 4: Early Access Launch
Shift from small-scale testing to controlled expansion.
**Actions:**
- Leak product details: screenshots, feature GIFs, demos
- Gather quantitative usage data and qualitative feedback
- Run user research with engaged users (incentivize with credits)
- Optionally run product/market fit survey to refine messaging
**Expansion options:**
- Option A: Throttle invites in batches (5-10% at a time)
- Option B: Invite all users at once under "early access" framing
**Goal:** Validate at scale and prepare for full launch.
### Phase 5: Full Launch
Open the floodgates.
**Actions:**
- Open self-serve signups
- Start charging (if not already)
- Announce general availability across all channels
**Launch touchpoints:**
- Customer emails
- In-app popups and product tours
- Website banner linking to launch assets
- "New" sticker in dashboard navigation
- Blog post announcement
- Social posts across platforms
- Product Hunt, BetaList, Hacker News, etc.
**Goal:** Maximum visibility and conversion to paying users.
---
## Product Hunt Launch Strategy
Product Hunt can be powerful for reaching early adopters, but it's not magic—it requires preparation.
### Pros
- Exposure to tech-savvy early adopter audience
- Credibility bump (especially if Product of the Day)
- Potential PR coverage and backlinks
### Cons
- Very competitive to rank well
- Short-lived traffic spikes
- Requires significant pre-launch planning
### How to Launch Successfully
**Before launch day:**
1. Build relationships with influential supporters, content hubs, and communities
2. Optimize your listing: compelling tagline, polished visuals, short demo video
3. Study successful launches to identify what worked
4. Engage in relevant communities—provide value before pitching
5. Prepare your team for all-day engagement
**On launch day:**
1. Treat it as an all-day event
2. Respond to every comment in real-time
3. Answer questions and spark discussions
4. Encourage your existing audience to engage
5. Direct traffic back to your site to capture signups
**After launch day:**
1. Follow up with everyone who engaged
2. Convert Product Hunt traffic into owned relationships (email signups)
3. Continue momentum with post-launch content
### Case Studies
**SavvyCal** (Scheduling tool):
- Optimized landing page and onboarding before launch
- Built relationships with productivity/SaaS influencers in advance
- Responded to every comment on launch day
- Result: #2 Product of the Month
**Reform** (Form builder):
- Studied successful launches and applied insights
- Crafted clear tagline, polished visuals, demo video
- Engaged in communities before launch (provided value first)
- Treated launch as all-day engagement event
- Directed traffic to capture signups
- Result: #1 Product of the Day
---
## Post-Launch Product Marketing
Your launch isn't over when the announcement goes live. Now comes adoption and retention work.
### Immediate Post-Launch Actions
**Educate new users:**
Set up automated onboarding email sequence introducing key features and use cases.
**Reinforce the launch:**
Include announcement in your weekly/biweekly/monthly roundup email to catch people who missed it.
**Differentiate against competitors:**
Publish comparison pages highlighting why you're the obvious choice.
**Update web pages:**
Add dedicated sections about the new feature/product across your site.
**Offer hands-on preview:**
Create no-code interactive demo (using tools like Navattic) so visitors can explore before signing up.
### Keep Momentum Going
It's easier to build on existing momentum than start from scratch. Every touchpoint reinforces the launch.
---
## Ongoing Launch Strategy
Don't rely on a single launch event. Regular updates and feature rollouts sustain engagement.
### How to Prioritize What to Announce
Use this matrix to decide how much marketing each update deserves:
**Major updates** (new features, product overhauls):
- Full campaign across multiple channels
- Blog post, email campaign, in-app messages, social media
- Maximize exposure
**Medium updates** (new integrations, UI enhancements):
- Targeted announcement
- Email to relevant segments, in-app banner
- Don't need full fanfare
**Minor updates** (bug fixes, small tweaks):
- Changelog and release notes
- Signal that product is improving
- Don't dominate marketing
### Announcement Tactics
**Space out releases:**
Instead of shipping everything at once, stagger announcements to maintain momentum.
**Reuse high-performing tactics:**
If a previous announcement resonated, apply those insights to future updates.
**Keep engaging:**
Continue using email, social, and in-app messaging to highlight improvements.
**Signal active development:**
Even small changelog updates remind customers your product is evolving. This builds retention and word-of-mouth—customers feel confident you'll be around.
---
## Launch Checklist
### Pre-Launch
- [ ] Landing page with clear value proposition
- [ ] Email capture / waitlist signup
- [ ] Early access list built
- [ ] Owned channels established (email, blog, community)
- [ ] Rented channel presence (social profiles optimized)
- [ ] Borrowed channel opportunities identified (podcasts, influencers)
- [ ] Product Hunt listing prepared (if using)
- [ ] Launch assets created (screenshots, demo video, GIFs)
- [ ] Onboarding flow ready
- [ ] Analytics/tracking in place
### Launch Day
- [ ] Announcement email to list
- [ ] Blog post published
- [ ] Social posts scheduled and posted
- [ ] Product Hunt listing live (if using)
- [ ] In-app announcement for existing users
- [ ] Website banner/notification active
- [ ] Team ready to engage and respond
- [ ] Monitor for issues and feedback
### Post-Launch
- [ ] Onboarding email sequence active
- [ ] Follow-up with engaged prospects
- [ ] Roundup email includes announcement
- [ ] Comparison pages published
- [ ] Interactive demo created
- [ ] Gather and act on feedback
- [ ] Plan next launch moment
---
## Task-Specific Questions ## Task-Specific Questions

View File

@@ -0,0 +1,317 @@
# launch-strategy reference
## Core Philosophy
The best companies don't just launch once—they launch again and again. Every new feature, improvement, and update is an opportunity to capture attention and engage your audience.
A strong launch isn't about a single moment. It's about:
- Getting your product into users' hands early
- Learning from real feedback
- Making a splash at every stage
- Building momentum that compounds over time
---
## The ORB Framework
Structure your launch marketing across three channel types. Everything should ultimately lead back to owned channels.
### Owned Channels
You own the channel (though not the audience). Direct access without algorithms or platform rules.
**Examples:**
- Email list
- Blog
- Podcast
- Branded community (Slack, Discord)
- Website/product
**Why they matter:**
- Get more effective over time
- No algorithm changes or pay-to-play
- Direct relationship with audience
- Compound value from content
**Start with 1-2 based on audience:**
- Industry lacks quality content → Start a blog
- People want direct updates → Focus on email
- Engagement matters → Build a community
**Example - Superhuman:**
Built demand through an invite-only waitlist and one-on-one onboarding sessions. Every new user got a 30-minute live demo. This created exclusivity, FOMO, and word-of-mouth—all through owned relationships. Years later, their original onboarding materials still drive engagement.
### Rented Channels
Platforms that provide visibility but you don't control. Algorithms shift, rules change, pay-to-play increases.
**Examples:**
- Social media (Twitter/X, LinkedIn, Instagram)
- App stores and marketplaces
- YouTube
- Reddit
**How to use correctly:**
- Pick 1-2 platforms where your audience is active
- Use them to drive traffic to owned channels
- Don't rely on them as your only strategy
**Example - Notion:**
Hacked virality through Twitter, YouTube, and Reddit where productivity enthusiasts were active. Encouraged community to share templates and workflows. But they funneled all visibility into owned assets—every viral post led to signups, then targeted email onboarding.
**Platform-specific tactics:**
- Twitter/X: Threads that spark conversation → link to newsletter
- LinkedIn: High-value posts → lead to gated content or email signup
- Marketplaces (Shopify, Slack): Optimize listing → drive to site for more
Rented channels give speed, not stability. Capture momentum by bringing users into your owned ecosystem.
### Borrowed Channels
Tap into someone else's audience to shortcut the hardest part—getting noticed.
**Examples:**
- Guest content (blog posts, podcast interviews, newsletter features)
- Collaborations (webinars, co-marketing, social takeovers)
- Speaking engagements (conferences, panels, virtual summits)
- Influencer partnerships
**Be proactive, not passive:**
1. List industry leaders your audience follows
2. Pitch win-win collaborations
3. Use tools like SparkToro or Listen Notes to find audience overlap
4. Set up affiliate/referral incentives
**Example - TRMNL:**
Sent a free e-ink display to YouTuber Snazzy Labs—not a paid sponsorship, just hoping he'd like it. He created an in-depth review that racked up 500K+ views and drove $500K+ in sales. They also set up an affiliate program for ongoing promotion.
Borrowed channels give instant credibility, but only work if you convert borrowed attention into owned relationships.
---
## Five-Phase Launch Approach
Launching isn't a one-day event. It's a phased process that builds momentum.
### Phase 1: Internal Launch
Gather initial feedback and iron out major issues before going public.
**Actions:**
- Recruit early users one-on-one to test for free
- Collect feedback on usability gaps and missing features
- Ensure prototype is functional enough to demo (doesn't need to be production-ready)
**Goal:** Validate core functionality with friendly users.
### Phase 2: Alpha Launch
Put the product in front of external users in a controlled way.
**Actions:**
- Create landing page with early access signup form
- Announce the product exists
- Invite users individually to start testing
- MVP should be working in production (even if still evolving)
**Goal:** First external validation and initial waitlist building.
### Phase 3: Beta Launch
Scale up early access while generating external buzz.
**Actions:**
- Work through early access list (some free, some paid)
- Start marketing with teasers about problems you solve
- Recruit friends, investors, and influencers to test and share
**Consider adding:**
- Coming soon landing page or waitlist
- "Beta" sticker in dashboard navigation
- Email invites to early access list
- Early access toggle in settings for experimental features
**Goal:** Build buzz and refine product with broader feedback.
### Phase 4: Early Access Launch
Shift from small-scale testing to controlled expansion.
**Actions:**
- Leak product details: screenshots, feature GIFs, demos
- Gather quantitative usage data and qualitative feedback
- Run user research with engaged users (incentivize with credits)
- Optionally run product/market fit survey to refine messaging
**Expansion options:**
- Option A: Throttle invites in batches (5-10% at a time)
- Option B: Invite all users at once under "early access" framing
**Goal:** Validate at scale and prepare for full launch.
### Phase 5: Full Launch
Open the floodgates.
**Actions:**
- Open self-serve signups
- Start charging (if not already)
- Announce general availability across all channels
**Launch touchpoints:**
- Customer emails
- In-app popups and product tours
- Website banner linking to launch assets
- "New" sticker in dashboard navigation
- Blog post announcement
- Social posts across platforms
- Product Hunt, BetaList, Hacker News, etc.
**Goal:** Maximum visibility and conversion to paying users.
---
## Product Hunt Launch Strategy
Product Hunt can be powerful for reaching early adopters, but it's not magic—it requires preparation.
### Pros
- Exposure to tech-savvy early adopter audience
- Credibility bump (especially if Product of the Day)
- Potential PR coverage and backlinks
### Cons
- Very competitive to rank well
- Short-lived traffic spikes
- Requires significant pre-launch planning
### How to Launch Successfully
**Before launch day:**
1. Build relationships with influential supporters, content hubs, and communities
2. Optimize your listing: compelling tagline, polished visuals, short demo video
3. Study successful launches to identify what worked
4. Engage in relevant communities—provide value before pitching
5. Prepare your team for all-day engagement
**On launch day:**
1. Treat it as an all-day event
2. Respond to every comment in real-time
3. Answer questions and spark discussions
4. Encourage your existing audience to engage
5. Direct traffic back to your site to capture signups
**After launch day:**
1. Follow up with everyone who engaged
2. Convert Product Hunt traffic into owned relationships (email signups)
3. Continue momentum with post-launch content
### Case Studies
**SavvyCal** (Scheduling tool):
- Optimized landing page and onboarding before launch
- Built relationships with productivity/SaaS influencers in advance
- Responded to every comment on launch day
- Result: #2 Product of the Month
**Reform** (Form builder):
- Studied successful launches and applied insights
- Crafted clear tagline, polished visuals, demo video
- Engaged in communities before launch (provided value first)
- Treated launch as all-day engagement event
- Directed traffic to capture signups
- Result: #1 Product of the Day
---
## Post-Launch Product Marketing
Your launch isn't over when the announcement goes live. Now comes adoption and retention work.
### Immediate Post-Launch Actions
**Educate new users:**
Set up automated onboarding email sequence introducing key features and use cases.
**Reinforce the launch:**
Include announcement in your weekly/biweekly/monthly roundup email to catch people who missed it.
**Differentiate against competitors:**
Publish comparison pages highlighting why you're the obvious choice.
**Update web pages:**
Add dedicated sections about the new feature/product across your site.
**Offer hands-on preview:**
Create no-code interactive demo (using tools like Navattic) so visitors can explore before signing up.
### Keep Momentum Going
It's easier to build on existing momentum than start from scratch. Every touchpoint reinforces the launch.
---
## Ongoing Launch Strategy
Don't rely on a single launch event. Regular updates and feature rollouts sustain engagement.
### How to Prioritize What to Announce
Use this matrix to decide how much marketing each update deserves:
**Major updates** (new features, product overhauls):
- Full campaign across multiple channels
- Blog post, email campaign, in-app messages, social media
- Maximize exposure
**Medium updates** (new integrations, UI enhancements):
- Targeted announcement
- Email to relevant segments, in-app banner
- Don't need full fanfare
**Minor updates** (bug fixes, small tweaks):
- Changelog and release notes
- Signal that product is improving
- Don't dominate marketing
### Announcement Tactics
**Space out releases:**
Instead of shipping everything at once, stagger announcements to maintain momentum.
**Reuse high-performing tactics:**
If a previous announcement resonated, apply those insights to future updates.
**Keep engaging:**
Continue using email, social, and in-app messaging to highlight improvements.
**Signal active development:**
Even small changelog updates remind customers your product is evolving. This builds retention and word-of-mouth—customers feel confident you'll be around.
---
## Launch Checklist
### Pre-Launch
- [ ] Landing page with clear value proposition
- [ ] Email capture / waitlist signup
- [ ] Early access list built
- [ ] Owned channels established (email, blog, community)
- [ ] Rented channel presence (social profiles optimized)
- [ ] Borrowed channel opportunities identified (podcasts, influencers)
- [ ] Product Hunt listing prepared (if using)
- [ ] Launch assets created (screenshots, demo video, GIFs)
- [ ] Onboarding flow ready
- [ ] Analytics/tracking in place
### Launch Day
- [ ] Announcement email to list
- [ ] Blog post published
- [ ] Social posts scheduled and posted
- [ ] Product Hunt listing live (if using)
- [ ] In-app announcement for existing users
- [ ] Website banner/notification active
- [ ] Team ready to engage and respond
- [ ] Monitor for issues and feedback
### Post-Launch
- [ ] Onboarding email sequence active
- [ ] Follow-up with engaged prospects
- [ ] Roundup email includes announcement
- [ ] Comparison pages published
- [ ] Interactive demo created
- [ ] Gather and act on feedback
- [ ] Plan next launch moment
---

View File

@@ -43,264 +43,7 @@ Before providing recommendations, understand:
--- ---
## Core Principles ## Core Principles
→ See references/popup-cro-playbook.md for details
### 1. Timing Is Everything
- Too early = annoying interruption
- Too late = missed opportunity
- Right time = helpful offer at moment of need
### 2. Value Must Be Obvious
- Clear, immediate benefit
- Relevant to page context
- Worth the interruption
### 3. Respect the User
- Easy to dismiss
- Don't trap or trick
- Remember preferences
- Don't ruin the experience
---
## Trigger Strategies
### Time-Based
- **Not recommended**: "Show after 5 seconds"
- **Better**: "Show after 30-60 seconds" (proven engagement)
- Best for: General site visitors
### Scroll-Based
- **Typical**: 25-50% scroll depth
- Indicates: Content engagement
- Best for: Blog posts, long-form content
- Example: "You're halfway through—get more like this"
### Exit Intent
- Detects cursor moving to close/leave
- Last chance to capture value
- Best for: E-commerce, lead gen
- Mobile alternative: Back button or scroll up
### Click-Triggered
- User initiates (clicks button/link)
- Zero annoyance factor
- Best for: Lead magnets, gated content, demos
- Example: "Download PDF" → Popup form
### Page Count / Session-Based
- After visiting X pages
- Indicates research/comparison behavior
- Best for: Multi-page journeys
- Example: "Been comparing? Here's a summary..."
### Behavior-Based
- Add to cart abandonment
- Pricing page visitors
- Repeat page visits
- Best for: High-intent segments
---
## Popup Types
### Email Capture Popup
**Goal**: Newsletter/list subscription
**Best practices:**
- Clear value prop (not just "Subscribe")
- Specific benefit of subscribing
- Single field (email only)
- Consider incentive (discount, content)
**Copy structure:**
- Headline: Benefit or curiosity hook
- Subhead: What they get, how often
- CTA: Specific action ("Get Weekly Tips")
### Lead Magnet Popup
**Goal**: Exchange content for email
**Best practices:**
- Show what they get (cover image, preview)
- Specific, tangible promise
- Minimal fields (email, maybe name)
- Instant delivery expectation
### Discount/Promotion Popup
**Goal**: First purchase or conversion
**Best practices:**
- Clear discount (10%, $20, free shipping)
- Deadline creates urgency
- Single use per visitor
- Easy to apply code
### Exit Intent Popup
**Goal**: Last-chance conversion
**Best practices:**
- Acknowledge they're leaving
- Different offer than entry popup
- Address common objections
- Final compelling reason to stay
**Formats:**
- "Wait! Before you go..."
- "Forget something?"
- "Get 10% off your first order"
- "Questions? Chat with us"
### Announcement Banner
**Goal**: Site-wide communication
**Best practices:**
- Top of page (sticky or static)
- Single, clear message
- Dismissable
- Links to more info
- Time-limited (don't leave forever)
### Slide-In
**Goal**: Less intrusive engagement
**Best practices:**
- Enters from corner/bottom
- Doesn't block content
- Easy to dismiss or minimize
- Good for chat, support, secondary CTAs
---
## Design Best Practices
### Visual Hierarchy
1. Headline (largest, first seen)
2. Value prop/offer (clear benefit)
3. Form/CTA (obvious action)
4. Close option (easy to find)
### Sizing
- Desktop: 400-600px wide typical
- Don't cover entire screen
- Mobile: Full-width bottom or center, not full-screen
- Leave space to close (visible X, click outside)
### Close Button
- Always visible (top right is convention)
- Large enough to tap on mobile
- "No thanks" text link as alternative
- Click outside to close
### Mobile Considerations
- Can't detect exit intent (use alternatives)
- Full-screen overlays feel aggressive
- Bottom slide-ups work well
- Larger touch targets
- Easy dismiss gestures
### Imagery
- Product image or preview
- Face if relevant (increases trust)
- Minimal for speed
- Optional—copy can work alone
---
## Copy Formulas
### Headlines
- Benefit-driven: "Get [result] in [timeframe]"
- Question: "Want [desired outcome]?"
- Command: "Don't miss [thing]"
- Social proof: "Join [X] people who..."
- Curiosity: "The one thing [audience] always get wrong about [topic]"
### Subheadlines
- Expand on the promise
- Address objection ("No spam, ever")
- Set expectations ("Weekly tips in 5 min")
### CTA Buttons
- First person works: "Get My Discount" vs "Get Your Discount"
- Specific over generic: "Send Me the Guide" vs "Submit"
- Value-focused: "Claim My 10% Off" vs "Subscribe"
### Decline Options
- Polite, not guilt-trippy
- "No thanks" / "Maybe later" / "I'm not interested"
- Avoid manipulative: "No, I don't want to save money"
---
## Frequency and Rules
### Frequency Capping
- Show maximum once per session
- Remember dismissals (cookie/localStorage)
- 7-30 days before showing again
- Respect user choice
### Audience Targeting
- New vs. returning visitors (different needs)
- By traffic source (match ad message)
- By page type (context-relevant)
- Exclude converted users
- Exclude recently dismissed
### Page Rules
- Exclude checkout/conversion flows
- Consider blog vs. product pages
- Match offer to page context
---
## Compliance and Accessibility
### GDPR/Privacy
- Clear consent language
- Link to privacy policy
- Don't pre-check opt-ins
- Honor unsubscribe/preferences
### Accessibility
- Keyboard navigable (Tab, Enter, Esc)
- Focus trap while open
- Screen reader compatible
- Sufficient color contrast
- Don't rely on color alone
### Google Guidelines
- Intrusive interstitials hurt SEO
- Mobile especially sensitive
- Allow: Cookie notices, age verification, reasonable banners
- Avoid: Full-screen before content on mobile
---
## Measurement
### Key Metrics
- **Impression rate**: Visitors who see popup
- **Conversion rate**: Impressions → Submissions
- **Close rate**: How many dismiss immediately
- **Engagement rate**: Interaction before close
- **Time to close**: How long before dismissing
### What to Track
- Popup views
- Form focus
- Submission attempts
- Successful submissions
- Close button clicks
- Outside clicks
- Escape key
### Benchmarks
- Email popup: 2-5% conversion typical
- Exit intent: 3-10% conversion
- Click-triggered: Higher (10%+, self-selected)
---
## Output Format ## Output Format

View File

@@ -0,0 +1,261 @@
# popup-cro reference
## Core Principles
### 1. Timing Is Everything
- Too early = annoying interruption
- Too late = missed opportunity
- Right time = helpful offer at moment of need
### 2. Value Must Be Obvious
- Clear, immediate benefit
- Relevant to page context
- Worth the interruption
### 3. Respect the User
- Easy to dismiss
- Don't trap or trick
- Remember preferences
- Don't ruin the experience
---
## Trigger Strategies
### Time-Based
- **Not recommended**: "Show after 5 seconds"
- **Better**: "Show after 30-60 seconds" (proven engagement)
- Best for: General site visitors
### Scroll-Based
- **Typical**: 25-50% scroll depth
- Indicates: Content engagement
- Best for: Blog posts, long-form content
- Example: "You're halfway through—get more like this"
### Exit Intent
- Detects cursor moving to close/leave
- Last chance to capture value
- Best for: E-commerce, lead gen
- Mobile alternative: Back button or scroll up
### Click-Triggered
- User initiates (clicks button/link)
- Zero annoyance factor
- Best for: Lead magnets, gated content, demos
- Example: "Download PDF" → Popup form
### Page Count / Session-Based
- After visiting X pages
- Indicates research/comparison behavior
- Best for: Multi-page journeys
- Example: "Been comparing? Here's a summary..."
### Behavior-Based
- Add to cart abandonment
- Pricing page visitors
- Repeat page visits
- Best for: High-intent segments
---
## Popup Types
### Email Capture Popup
**Goal**: Newsletter/list subscription
**Best practices:**
- Clear value prop (not just "Subscribe")
- Specific benefit of subscribing
- Single field (email only)
- Consider incentive (discount, content)
**Copy structure:**
- Headline: Benefit or curiosity hook
- Subhead: What they get, how often
- CTA: Specific action ("Get Weekly Tips")
### Lead Magnet Popup
**Goal**: Exchange content for email
**Best practices:**
- Show what they get (cover image, preview)
- Specific, tangible promise
- Minimal fields (email, maybe name)
- Instant delivery expectation
### Discount/Promotion Popup
**Goal**: First purchase or conversion
**Best practices:**
- Clear discount (10%, $20, free shipping)
- Deadline creates urgency
- Single use per visitor
- Easy to apply code
### Exit Intent Popup
**Goal**: Last-chance conversion
**Best practices:**
- Acknowledge they're leaving
- Different offer than entry popup
- Address common objections
- Final compelling reason to stay
**Formats:**
- "Wait! Before you go..."
- "Forget something?"
- "Get 10% off your first order"
- "Questions? Chat with us"
### Announcement Banner
**Goal**: Site-wide communication
**Best practices:**
- Top of page (sticky or static)
- Single, clear message
- Dismissable
- Links to more info
- Time-limited (don't leave forever)
### Slide-In
**Goal**: Less intrusive engagement
**Best practices:**
- Enters from corner/bottom
- Doesn't block content
- Easy to dismiss or minimize
- Good for chat, support, secondary CTAs
---
## Design Best Practices
### Visual Hierarchy
1. Headline (largest, first seen)
2. Value prop/offer (clear benefit)
3. Form/CTA (obvious action)
4. Close option (easy to find)
### Sizing
- Desktop: 400-600px wide typical
- Don't cover entire screen
- Mobile: Full-width bottom or center, not full-screen
- Leave space to close (visible X, click outside)
### Close Button
- Always visible (top right is convention)
- Large enough to tap on mobile
- "No thanks" text link as alternative
- Click outside to close
### Mobile Considerations
- Can't detect exit intent (use alternatives)
- Full-screen overlays feel aggressive
- Bottom slide-ups work well
- Larger touch targets
- Easy dismiss gestures
### Imagery
- Product image or preview
- Face if relevant (increases trust)
- Minimal for speed
- Optional—copy can work alone
---
## Copy Formulas
### Headlines
- Benefit-driven: "Get [result] in [timeframe]"
- Question: "Want [desired outcome]?"
- Command: "Don't miss [thing]"
- Social proof: "Join [X] people who..."
- Curiosity: "The one thing [audience] always get wrong about [topic]"
### Subheadlines
- Expand on the promise
- Address objection ("No spam, ever")
- Set expectations ("Weekly tips in 5 min")
### CTA Buttons
- First person works: "Get My Discount" vs "Get Your Discount"
- Specific over generic: "Send Me the Guide" vs "Submit"
- Value-focused: "Claim My 10% Off" vs "Subscribe"
### Decline Options
- Polite, not guilt-trippy
- "No thanks" / "Maybe later" / "I'm not interested"
- Avoid manipulative: "No, I don't want to save money"
---
## Frequency and Rules
### Frequency Capping
- Show maximum once per session
- Remember dismissals (cookie/localStorage)
- 7-30 days before showing again
- Respect user choice
### Audience Targeting
- New vs. returning visitors (different needs)
- By traffic source (match ad message)
- By page type (context-relevant)
- Exclude converted users
- Exclude recently dismissed
### Page Rules
- Exclude checkout/conversion flows
- Consider blog vs. product pages
- Match offer to page context
---
## Compliance and Accessibility
### GDPR/Privacy
- Clear consent language
- Link to privacy policy
- Don't pre-check opt-ins
- Honor unsubscribe/preferences
### Accessibility
- Keyboard navigable (Tab, Enter, Esc)
- Focus trap while open
- Screen reader compatible
- Sufficient color contrast
- Don't rely on color alone
### Google Guidelines
- Intrusive interstitials hurt SEO
- Mobile especially sensitive
- Allow: Cookie notices, age verification, reasonable banners
- Avoid: Full-screen before content on mobile
---
## Measurement
### Key Metrics
- **Impression rate**: Visitors who see popup
- **Conversion rate**: Impressions → Submissions
- **Close rate**: How many dismiss immediately
- **Engagement rate**: Interaction before close
- **Time to close**: How long before dismissing
### What to Track
- Popup views
- Form focus
- Submission attempts
- Successful submissions
- Close button clicks
- Outside clicks
- Escape key
### Benchmarks
- Email popup: 2-5% conversion typical
- Exit intent: 3-10% conversion
- Click-triggered: Higher (10%+, self-selected)
---

View File

@@ -38,292 +38,7 @@ Before auditing, understand:
--- ---
## Audit Framework ## Audit Framework
→ See references/seo-audit-reference.md for details
### Priority Order
1. **Crawlability & Indexation** (can Google find and index it?)
2. **Technical Foundations** (is the site fast and functional?)
3. **On-Page Optimization** (is content optimized?)
4. **Content Quality** (does it deserve to rank?)
5. **Authority & Links** (does it have credibility?)
---
## Technical SEO Audit
### Crawlability
**Robots.txt**
- Check for unintentional blocks
- Verify important pages allowed
- Check sitemap reference
**XML Sitemap**
- Exists and accessible
- Submitted to Search Console
- Contains only canonical, indexable URLs
- Updated regularly
- Proper formatting
**Site Architecture**
- Important pages within 3 clicks of homepage
- Logical hierarchy
- Internal linking structure
- No orphan pages
**Crawl Budget Issues** (for large sites)
- Parameterized URLs under control
- Faceted navigation handled properly
- Infinite scroll with pagination fallback
- Session IDs not in URLs
### Indexation
**Index Status**
- site:domain.com check
- Search Console coverage report
- Compare indexed vs. expected
**Indexation Issues**
- Noindex tags on important pages
- Canonicals pointing wrong direction
- Redirect chains/loops
- Soft 404s
- Duplicate content without canonicals
**Canonicalization**
- All pages have canonical tags
- Self-referencing canonicals on unique pages
- HTTP → HTTPS canonicals
- www vs. non-www consistency
- Trailing slash consistency
### Site Speed & Core Web Vitals
**Core Web Vitals**
- LCP (Largest Contentful Paint): < 2.5s
- INP (Interaction to Next Paint): < 200ms
- CLS (Cumulative Layout Shift): < 0.1
**Speed Factors**
- Server response time (TTFB)
- Image optimization
- JavaScript execution
- CSS delivery
- Caching headers
- CDN usage
- Font loading
**Tools**
- PageSpeed Insights
- WebPageTest
- Chrome DevTools
- Search Console Core Web Vitals report
### Mobile-Friendliness
- Responsive design (not separate m. site)
- Tap target sizes
- Viewport configured
- No horizontal scroll
- Same content as desktop
- Mobile-first indexing readiness
### Security & HTTPS
- HTTPS across entire site
- Valid SSL certificate
- No mixed content
- HTTP → HTTPS redirects
- HSTS header (bonus)
### URL Structure
- Readable, descriptive URLs
- Keywords in URLs where natural
- Consistent structure
- No unnecessary parameters
- Lowercase and hyphen-separated
---
## On-Page SEO Audit
### Title Tags
**Check for:**
- Unique titles for each page
- Primary keyword near beginning
- 50-60 characters (visible in SERP)
- Compelling and click-worthy
- Brand name placement (end, usually)
**Common issues:**
- Duplicate titles
- Too long (truncated)
- Too short (wasted opportunity)
- Keyword stuffing
- Missing entirely
### Meta Descriptions
**Check for:**
- Unique descriptions per page
- 150-160 characters
- Includes primary keyword
- Clear value proposition
- Call to action
**Common issues:**
- Duplicate descriptions
- Auto-generated garbage
- Too long/short
- No compelling reason to click
### Heading Structure
**Check for:**
- One H1 per page
- H1 contains primary keyword
- Logical hierarchy (H1 → H2 → H3)
- Headings describe content
- Not just for styling
**Common issues:**
- Multiple H1s
- Skip levels (H1 → H3)
- Headings used for styling only
- No H1 on page
### Content Optimization
**Primary Page Content**
- Keyword in first 100 words
- Related keywords naturally used
- Sufficient depth/length for topic
- Answers search intent
- Better than competitors
**Thin Content Issues**
- Pages with little unique content
- Tag/category pages with no value
- Doorway pages
- Duplicate or near-duplicate content
### Image Optimization
**Check for:**
- Descriptive file names
- Alt text on all images
- Alt text describes image
- Compressed file sizes
- Modern formats (WebP)
- Lazy loading implemented
- Responsive images
### Internal Linking
**Check for:**
- Important pages well-linked
- Descriptive anchor text
- Logical link relationships
- No broken internal links
- Reasonable link count per page
**Common issues:**
- Orphan pages (no internal links)
- Over-optimized anchor text
- Important pages buried
- Excessive footer/sidebar links
### Keyword Targeting
**Per Page**
- Clear primary keyword target
- Title, H1, URL aligned
- Content satisfies search intent
- Not competing with other pages (cannibalization)
**Site-Wide**
- Keyword mapping document
- No major gaps in coverage
- No keyword cannibalization
- Logical topical clusters
---
## Content Quality Assessment
### E-E-A-T Signals
**Experience**
- First-hand experience demonstrated
- Original insights/data
- Real examples and case studies
**Expertise**
- Author credentials visible
- Accurate, detailed information
- Properly sourced claims
**Authoritativeness**
- Recognized in the space
- Cited by others
- Industry credentials
**Trustworthiness**
- Accurate information
- Transparent about business
- Contact information available
- Privacy policy, terms
- Secure site (HTTPS)
### Content Depth
- Comprehensive coverage of topic
- Answers follow-up questions
- Better than top-ranking competitors
- Updated and current
### User Engagement Signals
- Time on page
- Bounce rate in context
- Pages per session
- Return visits
---
## Common Issues by Site Type
### SaaS/Product Sites
- Product pages lack content depth
- Blog not integrated with product pages
- Missing comparison/alternative pages
- Feature pages thin on content
- No glossary/educational content
### E-commerce
- Thin category pages
- Duplicate product descriptions
- Missing product schema
- Faceted navigation creating duplicates
- Out-of-stock pages mishandled
### Content/Blog Sites
- Outdated content not refreshed
- Keyword cannibalization
- No topical clustering
- Poor internal linking
- Missing author pages
### Local Business
- Inconsistent NAP
- Missing local schema
- No Google Business Profile optimization
- Missing location pages
- No local content
---
## Output Format ## Output Format

View File

@@ -0,0 +1,289 @@
# seo-audit reference
## Audit Framework
### Priority Order
1. **Crawlability & Indexation** (can Google find and index it?)
2. **Technical Foundations** (is the site fast and functional?)
3. **On-Page Optimization** (is content optimized?)
4. **Content Quality** (does it deserve to rank?)
5. **Authority & Links** (does it have credibility?)
---
## Technical SEO Audit
### Crawlability
**Robots.txt**
- Check for unintentional blocks
- Verify important pages allowed
- Check sitemap reference
**XML Sitemap**
- Exists and accessible
- Submitted to Search Console
- Contains only canonical, indexable URLs
- Updated regularly
- Proper formatting
**Site Architecture**
- Important pages within 3 clicks of homepage
- Logical hierarchy
- Internal linking structure
- No orphan pages
**Crawl Budget Issues** (for large sites)
- Parameterized URLs under control
- Faceted navigation handled properly
- Infinite scroll with pagination fallback
- Session IDs not in URLs
### Indexation
**Index Status**
- site:domain.com check
- Search Console coverage report
- Compare indexed vs. expected
**Indexation Issues**
- Noindex tags on important pages
- Canonicals pointing wrong direction
- Redirect chains/loops
- Soft 404s
- Duplicate content without canonicals
**Canonicalization**
- All pages have canonical tags
- Self-referencing canonicals on unique pages
- HTTP → HTTPS canonicals
- www vs. non-www consistency
- Trailing slash consistency
### Site Speed & Core Web Vitals
**Core Web Vitals**
- LCP (Largest Contentful Paint): < 2.5s
- INP (Interaction to Next Paint): < 200ms
- CLS (Cumulative Layout Shift): < 0.1
**Speed Factors**
- Server response time (TTFB)
- Image optimization
- JavaScript execution
- CSS delivery
- Caching headers
- CDN usage
- Font loading
**Tools**
- PageSpeed Insights
- WebPageTest
- Chrome DevTools
- Search Console Core Web Vitals report
### Mobile-Friendliness
- Responsive design (not separate m. site)
- Tap target sizes
- Viewport configured
- No horizontal scroll
- Same content as desktop
- Mobile-first indexing readiness
### Security & HTTPS
- HTTPS across entire site
- Valid SSL certificate
- No mixed content
- HTTP → HTTPS redirects
- HSTS header (bonus)
### URL Structure
- Readable, descriptive URLs
- Keywords in URLs where natural
- Consistent structure
- No unnecessary parameters
- Lowercase and hyphen-separated
---
## On-Page SEO Audit
### Title Tags
**Check for:**
- Unique titles for each page
- Primary keyword near beginning
- 50-60 characters (visible in SERP)
- Compelling and click-worthy
- Brand name placement (end, usually)
**Common issues:**
- Duplicate titles
- Too long (truncated)
- Too short (wasted opportunity)
- Keyword stuffing
- Missing entirely
### Meta Descriptions
**Check for:**
- Unique descriptions per page
- 150-160 characters
- Includes primary keyword
- Clear value proposition
- Call to action
**Common issues:**
- Duplicate descriptions
- Auto-generated garbage
- Too long/short
- No compelling reason to click
### Heading Structure
**Check for:**
- One H1 per page
- H1 contains primary keyword
- Logical hierarchy (H1 → H2 → H3)
- Headings describe content
- Not just for styling
**Common issues:**
- Multiple H1s
- Skip levels (H1 → H3)
- Headings used for styling only
- No H1 on page
### Content Optimization
**Primary Page Content**
- Keyword in first 100 words
- Related keywords naturally used
- Sufficient depth/length for topic
- Answers search intent
- Better than competitors
**Thin Content Issues**
- Pages with little unique content
- Tag/category pages with no value
- Doorway pages
- Duplicate or near-duplicate content
### Image Optimization
**Check for:**
- Descriptive file names
- Alt text on all images
- Alt text describes image
- Compressed file sizes
- Modern formats (WebP)
- Lazy loading implemented
- Responsive images
### Internal Linking
**Check for:**
- Important pages well-linked
- Descriptive anchor text
- Logical link relationships
- No broken internal links
- Reasonable link count per page
**Common issues:**
- Orphan pages (no internal links)
- Over-optimized anchor text
- Important pages buried
- Excessive footer/sidebar links
### Keyword Targeting
**Per Page**
- Clear primary keyword target
- Title, H1, URL aligned
- Content satisfies search intent
- Not competing with other pages (cannibalization)
**Site-Wide**
- Keyword mapping document
- No major gaps in coverage
- No keyword cannibalization
- Logical topical clusters
---
## Content Quality Assessment
### E-E-A-T Signals
**Experience**
- First-hand experience demonstrated
- Original insights/data
- Real examples and case studies
**Expertise**
- Author credentials visible
- Accurate, detailed information
- Properly sourced claims
**Authoritativeness**
- Recognized in the space
- Cited by others
- Industry credentials
**Trustworthiness**
- Accurate information
- Transparent about business
- Contact information available
- Privacy policy, terms
- Secure site (HTTPS)
### Content Depth
- Comprehensive coverage of topic
- Answers follow-up questions
- Better than top-ranking competitors
- Updated and current
### User Engagement Signals
- Time on page
- Bounce rate in context
- Pages per session
- Return visits
---
## Common Issues by Site Type
### SaaS/Product Sites
- Product pages lack content depth
- Blog not integrated with product pages
- Missing comparison/alternative pages
- Feature pages thin on content
- No glossary/educational content
### E-commerce
- Thin category pages
- Duplicate product descriptions
- Missing product schema
- Faceted navigation creating duplicates
- Out-of-stock pages mishandled
### Content/Blog Sites
- Outdated content not refreshed
- Keyword cannibalization
- No topical clustering
- Poor internal linking
- Missing author pages
### Local Business
- Inconsistent NAP
- Missing local schema
- No Google Business Profile optimization
- Missing location pages
- No local content
---

View File

@@ -41,183 +41,7 @@ Before providing recommendations, understand:
--- ---
## Core Principles ## Core Principles
→ See references/signup-cro-playbook.md for details
### 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: Always visible (not just placeholders)
- 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 ## Output Format

View File

@@ -0,0 +1,180 @@
# signup-flow-cro reference
## 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: Always visible (not just placeholders)
- 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
---

View File

@@ -260,159 +260,7 @@ python scripts/customer_interview_analyzer.py interview.txt json
--- ---
## Input/Output Examples ## Input/Output Examples
→ See references/input-output-examples.md for details
### RICE Prioritizer Example
**Input (features.csv):**
```csv
name,reach,impact,confidence,effort
Onboarding Flow,20000,massive,high,s
Search Improvements,15000,high,high,m
Social Login,12000,high,medium,m
Push Notifications,10000,massive,medium,m
Dark Mode,8000,medium,high,s
```
**Command:**
```bash
python scripts/rice_prioritizer.py features.csv --capacity 15
```
**Output:**
```
============================================================
RICE PRIORITIZATION RESULTS
============================================================
📊 TOP PRIORITIZED FEATURES
1. Onboarding Flow
RICE Score: 16000.0
Reach: 20000 | Impact: massive | Confidence: high | Effort: s
2. Search Improvements
RICE Score: 4800.0
Reach: 15000 | Impact: high | Confidence: high | Effort: m
3. Social Login
RICE Score: 3072.0
Reach: 12000 | Impact: high | Confidence: medium | Effort: m
4. Push Notifications
RICE Score: 3840.0
Reach: 10000 | Impact: massive | Confidence: medium | Effort: m
5. Dark Mode
RICE Score: 2133.33
Reach: 8000 | Impact: medium | Confidence: high | Effort: s
📈 PORTFOLIO ANALYSIS
Total Features: 5
Total Effort: 19 person-months
Total Reach: 65,000 users
Average RICE Score: 5969.07
🎯 Quick Wins: 2 features
• Onboarding Flow (RICE: 16000.0)
• Dark Mode (RICE: 2133.33)
🚀 Big Bets: 0 features
📅 SUGGESTED ROADMAP
Q1 - Capacity: 11/15 person-months
• Onboarding Flow (RICE: 16000.0)
• Search Improvements (RICE: 4800.0)
• Dark Mode (RICE: 2133.33)
Q2 - Capacity: 10/15 person-months
• Push Notifications (RICE: 3840.0)
• Social Login (RICE: 3072.0)
```
---
### Customer Interview Analyzer Example
**Input (interview.txt):**
```
Customer: Jane, Enterprise PM at TechCorp
Date: 2024-01-15
Interviewer: What's the hardest part of your current workflow?
Jane: The biggest frustration is the lack of real-time collaboration.
When I'm working on a PRD, I have to constantly ping my team on Slack
to get updates. It's really frustrating to wait for responses,
especially when we're on a tight deadline.
I've tried using Google Docs for collaboration, but it doesn't
integrate with our roadmap tools. I'd pay extra for something that
just worked seamlessly.
Interviewer: How often does this happen?
Jane: Literally every day. I probably waste 30 minutes just on
back-and-forth messages. It's my biggest pain point right now.
```
**Command:**
```bash
python scripts/customer_interview_analyzer.py interview.txt
```
**Output:**
```
============================================================
CUSTOMER INTERVIEW ANALYSIS
============================================================
📋 INTERVIEW METADATA
Segments found: 1
Lines analyzed: 15
😟 PAIN POINTS (3 found)
1. [HIGH] Lack of real-time collaboration
"I have to constantly ping my team on Slack to get updates"
2. [MEDIUM] Tool integration gaps
"Google Docs...doesn't integrate with our roadmap tools"
3. [HIGH] Time wasted on communication
"waste 30 minutes just on back-and-forth messages"
💡 FEATURE REQUESTS (2 found)
1. Real-time collaboration - Priority: High
2. Seamless tool integration - Priority: Medium
🎯 JOBS TO BE DONE
When working on PRDs with tight deadlines
I want real-time visibility into team updates
So I can avoid wasted time on status checks
📊 SENTIMENT ANALYSIS
Overall: Negative (pain-focused interview)
Key emotions: Frustration, Time pressure
💬 KEY QUOTES
• "It's really frustrating to wait for responses"
• "I'd pay extra for something that just worked seamlessly"
• "It's my biggest pain point right now"
🏷️ THEMES
- Collaboration friction
- Tool fragmentation
- Time efficiency
```
---
## Integration Points ## Integration Points

View File

@@ -0,0 +1,156 @@
# product-manager-toolkit reference
## Input/Output Examples
### RICE Prioritizer Example
**Input (features.csv):**
```csv
name,reach,impact,confidence,effort
Onboarding Flow,20000,massive,high,s
Search Improvements,15000,high,high,m
Social Login,12000,high,medium,m
Push Notifications,10000,massive,medium,m
Dark Mode,8000,medium,high,s
```
**Command:**
```bash
python scripts/rice_prioritizer.py features.csv --capacity 15
```
**Output:**
```
============================================================
RICE PRIORITIZATION RESULTS
============================================================
📊 TOP PRIORITIZED FEATURES
1. Onboarding Flow
RICE Score: 16000.0
Reach: 20000 | Impact: massive | Confidence: high | Effort: s
2. Search Improvements
RICE Score: 4800.0
Reach: 15000 | Impact: high | Confidence: high | Effort: m
3. Social Login
RICE Score: 3072.0
Reach: 12000 | Impact: high | Confidence: medium | Effort: m
4. Push Notifications
RICE Score: 3840.0
Reach: 10000 | Impact: massive | Confidence: medium | Effort: m
5. Dark Mode
RICE Score: 2133.33
Reach: 8000 | Impact: medium | Confidence: high | Effort: s
📈 PORTFOLIO ANALYSIS
Total Features: 5
Total Effort: 19 person-months
Total Reach: 65,000 users
Average RICE Score: 5969.07
🎯 Quick Wins: 2 features
• Onboarding Flow (RICE: 16000.0)
• Dark Mode (RICE: 2133.33)
🚀 Big Bets: 0 features
📅 SUGGESTED ROADMAP
Q1 - Capacity: 11/15 person-months
• Onboarding Flow (RICE: 16000.0)
• Search Improvements (RICE: 4800.0)
• Dark Mode (RICE: 2133.33)
Q2 - Capacity: 10/15 person-months
• Push Notifications (RICE: 3840.0)
• Social Login (RICE: 3072.0)
```
---
### Customer Interview Analyzer Example
**Input (interview.txt):**
```
Customer: Jane, Enterprise PM at TechCorp
Date: 2024-01-15
Interviewer: What's the hardest part of your current workflow?
Jane: The biggest frustration is the lack of real-time collaboration.
When I'm working on a PRD, I have to constantly ping my team on Slack
to get updates. It's really frustrating to wait for responses,
especially when we're on a tight deadline.
I've tried using Google Docs for collaboration, but it doesn't
integrate with our roadmap tools. I'd pay extra for something that
just worked seamlessly.
Interviewer: How often does this happen?
Jane: Literally every day. I probably waste 30 minutes just on
back-and-forth messages. It's my biggest pain point right now.
```
**Command:**
```bash
python scripts/customer_interview_analyzer.py interview.txt
```
**Output:**
```
============================================================
CUSTOMER INTERVIEW ANALYSIS
============================================================
📋 INTERVIEW METADATA
Segments found: 1
Lines analyzed: 15
😟 PAIN POINTS (3 found)
1. [HIGH] Lack of real-time collaboration
"I have to constantly ping my team on Slack to get updates"
2. [MEDIUM] Tool integration gaps
"Google Docs...doesn't integrate with our roadmap tools"
3. [HIGH] Time wasted on communication
"waste 30 minutes just on back-and-forth messages"
💡 FEATURE REQUESTS (2 found)
1. Real-time collaboration - Priority: High
2. Seamless tool integration - Priority: Medium
🎯 JOBS TO BE DONE
When working on PRDs with tight deadlines
I want real-time visibility into team updates
So I can avoid wasted time on status checks
📊 SENTIMENT ANALYSIS
Overall: Negative (pain-focused interview)
Key emotions: Frustration, Time pressure
💬 KEY QUOTES
• "It's really frustrating to wait for responses"
• "I'd pay extra for something that just worked seamlessly"
• "It's my biggest pain point right now"
🏷️ THEMES
- Collaboration friction
- Tool fragmentation
- Time efficiency
```
---

View File

@@ -1,19 +1,6 @@
--- ---
name: "risk-management-specialist" name: "risk-management-specialist"
description: Medical device risk management specialist implementing ISO 14971 throughout product lifecycle. Provides risk analysis, risk evaluation, risk control, and post-production information analysis. description: Medical device risk management specialist implementing ISO 14971 throughout product lifecycle. Provides risk analysis, risk evaluation, risk control, and post-production information analysis. Use when user mentions risk management, ISO 14971, risk analysis, FMEA, fault tree analysis, hazard identification, risk control, risk matrix, benefit-risk analysis, residual risk, risk acceptability, or post-market risk.
triggers:
- risk management
- ISO 14971
- risk analysis
- FMEA
- fault tree analysis
- hazard identification
- risk control
- risk matrix
- benefit-risk analysis
- residual risk
- risk acceptability
- post-market risk
--- ---
# Risk Management Specialist # Risk Management Specialist
@@ -373,80 +360,7 @@ Monitor and update risk management throughout product lifecycle.
--- ---
## Risk Assessment Templates ## Risk Assessment Templates
→ See references/risk-assessment-templates.md for details
### Hazard Analysis Worksheet
```
HAZARD ANALYSIS WORKSHEET
Product: [Device Name]
Document: HA-[Product]-[Rev]
Analyst: [Name]
Date: [Date]
| ID | Hazard | Hazardous Situation | Harm | P | S | Initial Risk | Control | Residual P | Residual S | Final Risk |
|----|--------|---------------------|------|---|---|--------------|---------|------------|------------|------------|
| H-001 | [Hazard] | [Situation] | [Harm] | [1-5] | [1-5] | [Level] | [Control ref] | [1-5] | [1-5] | [Level] |
```
### FMEA Worksheet
```
FMEA WORKSHEET
Product: [Device Name]
Subsystem: [Subsystem]
Analyst: [Name]
Date: [Date]
| ID | Item | Function | Failure Mode | Effect | S | Cause | O | Control | D | RPN | Action |
|----|------|----------|--------------|--------|---|-------|---|---------|---|-----|--------|
| FM-001 | [Item] | [Function] | [Mode] | [Effect] | [1-10] | [Cause] | [1-10] | [Detection] | [1-10] | [S×O×D] | [Action] |
RPN Action Thresholds:
>200: Critical - Immediate action
100-200: High - Action plan required
50-100: Medium - Consider action
<50: Low - Monitor
```
### Risk Management Report Summary
```
RISK MANAGEMENT REPORT
Product: [Device Name]
Date: [Date]
Revision: [X.X]
SUMMARY:
- Total hazards identified: [N]
- Risk controls implemented: [N]
- Residual risks: [N] Low, [N] Medium, [N] High
- Overall conclusion: [Acceptable / Not Acceptable]
RISK DISTRIBUTION:
| Risk Level | Before Control | After Control |
|------------|----------------|---------------|
| Unacceptable | [N] | 0 |
| High | [N] | [N] |
| Medium | [N] | [N] |
| Low | [N] | [N] |
CONTROLS IMPLEMENTED:
- Inherent safety: [N]
- Protective measures: [N]
- Information for safety: [N]
OVERALL RESIDUAL RISK: [Acceptable / ALARP Demonstrated]
BENEFIT-RISK CONCLUSION: [If applicable]
APPROVAL:
Risk Management Lead: _____________ Date: _______
Quality Assurance: _____________ Date: _______
```
---
## Decision Frameworks ## Decision Frameworks

View File

@@ -0,0 +1,77 @@
# risk-management-specialist reference
## Risk Assessment Templates
### Hazard Analysis Worksheet
```
HAZARD ANALYSIS WORKSHEET
Product: [Device Name]
Document: HA-[Product]-[Rev]
Analyst: [Name]
Date: [Date]
| ID | Hazard | Hazardous Situation | Harm | P | S | Initial Risk | Control | Residual P | Residual S | Final Risk |
|----|--------|---------------------|------|---|---|--------------|---------|------------|------------|------------|
| H-001 | [Hazard] | [Situation] | [Harm] | [1-5] | [1-5] | [Level] | [Control ref] | [1-5] | [1-5] | [Level] |
```
### FMEA Worksheet
```
FMEA WORKSHEET
Product: [Device Name]
Subsystem: [Subsystem]
Analyst: [Name]
Date: [Date]
| ID | Item | Function | Failure Mode | Effect | S | Cause | O | Control | D | RPN | Action |
|----|------|----------|--------------|--------|---|-------|---|---------|---|-----|--------|
| FM-001 | [Item] | [Function] | [Mode] | [Effect] | [1-10] | [Cause] | [1-10] | [Detection] | [1-10] | [S×O×D] | [Action] |
RPN Action Thresholds:
>200: Critical - Immediate action
100-200: High - Action plan required
50-100: Medium - Consider action
<50: Low - Monitor
```
### Risk Management Report Summary
```
RISK MANAGEMENT REPORT
Product: [Device Name]
Date: [Date]
Revision: [X.X]
SUMMARY:
- Total hazards identified: [N]
- Risk controls implemented: [N]
- Residual risks: [N] Low, [N] Medium, [N] High
- Overall conclusion: [Acceptable / Not Acceptable]
RISK DISTRIBUTION:
| Risk Level | Before Control | After Control |
|------------|----------------|---------------|
| Unacceptable | [N] | 0 |
| High | [N] | [N] |
| Medium | [N] | [N] |
| Low | [N] | [N] |
CONTROLS IMPLEMENTED:
- Inherent safety: [N]
- Protective measures: [N]
- Information for safety: [N]
OVERALL RESIDUAL RISK: [Acceptable / ALARP Demonstrated]
BENEFIT-RISK CONCLUSION: [If applicable]
APPROVAL:
Risk Management Lead: _____________ Date: _______
Quality Assurance: _____________ Date: _______
```
---