brainstorming
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or planning anything new, in software or out of it (a talk, a business, a renovation).
framework-requirementsDefines, evaluates, and validates software framework requirements including non-functional criteria (performance, security, extensibility), weighted selection scoring matrices, proof-of-concept feasibility testing, and architecture decision records for production-grade applications.
Independent GitHub snapshot · Automated classification · Original documentation governs
name: framework-requirements description: Defines, evaluates, and validates software framework requirements including non-functional criteria (performance, security, extensibility), weighted selection scoring matrices, proof-of-concept feasibility testing, and architecture decision records for production-grade applications. license: MIT compatibility: opencode metadata: version: "1.0.0" domain: coding triggers: framework requirements, selecting libraries, tech stack selection, framework evaluation criteria, non-functional requirements, POC testing, integration feasibility, production readiness gate archetypes: - tactical - strategic anti_triggers: - brainstorming - vague ideation - code golf - over-engineering response_profile: verbosity: medium directive_strength: high abstraction_level: operational role: implementation scope: implementation output-format: code content-types: [code, guidance, do-dont, examples] related-skills: framework-evaluation-criteria, test-driven-development, modular-design, dependency-injection, framework-utilization
Defines and validates software framework requirements across functional, non-functional, operational, and strategic dimensions. When loaded, this skill makes the model act as a senior architect — eliciting structured requirements using formal catalogs (MoSCoW prioritization, NFR classification), building weighted evaluation scoring matrices with evidence-based criterion validation, designing proof-of-concept test harnesses for integration feasibility assessment, generating Architecture Decision Records documenting trade-offs, and enforcing production readiness gates before any framework is committed to code.
Use this skill when:
Avoid this skill for:
framework-utilization insteadpython vs node script.js would work equally well (overhead outweighs benefit)framework-evaluation-criteria for lighter comparisonsSystematically extract requirements from project context using the MoSCoW priority model. Each requirement receives exactly one priority label: Must (hard constraint — framework is disqualified if it fails), Should (important but has acceptable workarounds), Could (nice-to-have, scored but not required), or Won't (explicitly deferred). Use the Functional Requirements Catalog and NFR Catalog below to ensure coverage. Checkpoint: Every category must have at least one Must requirement written down. If a category genuinely has no requirements for this project, document why explicitly with a reason code.
Non-functional requirements are the most frequently overlooked dimension in framework selection. Go through each NFR category below and write specific, quantifiable targets — not vague aspirations. Performance must include numeric thresholds (e.g., "p95 response latency under 200ms under 1000 concurrent users"). Security must reference applicable standards (OWASP Top 10, SOC 2, GDPR). Observability must specify required integration hooks (OpenTelemetry, Prometheus metrics, structured JSON logging). Checkpoint: Every NFR category that applies must have at least one quantified target before proceeding.
Create a scoring matrix mapping candidates against criteria. Must requirements become knock-out criteria (binary pass/fail — weight = 0.0, failure means disqualification). Should/Could requirements become scored criteria with normalized weights summing to exactly 1.0. Use evidence for every score — "documentation quality: 8/10" is not sufficient; use "documentation quality: 8/10 — covers 16 of 20 features with runnable examples, verified via docs audit on [date]". Checkpoint: All candidate weights must sum to 1.0 (validate with assert total == 1.0). All knock-out failures must be explicitly listed before scoring is considered complete.
For the top two candidates, design proof-of-concept tests that exercise the three most critical integration points under realistic conditions — not toy examples with hardcoded data. Each POC test should measure actual performance metrics (startup time, memory footprint, request throughput) and verify concrete integration capability (database connection pooling, auth flow completion, webhook delivery). Checkpoint: Each POC must produce measurable output files (JSON benchmarks, coverage reports) that can be compared side by side. If a POC cannot demonstrate the critical integration point within 2 hours of implementation time, flag it as a risk.
Run each remaining candidate through the production readiness checklist before final selection. This covers deployment constraints (image size, startup time, memory baseline), dependency graph analysis (depth, known CVEs, license compatibility), CI/CD integration (test runner compatibility, build speed impact), and operational hooks (health checks, metrics endpoints, structured logging). Checkpoint: Any candidate failing a Must-level production readiness gate must be disqualified or require explicit stakeholder exception.
Produce a complete ADR documenting the decision context, weighted evaluation results, POC findings, and most critically — documented consequences in both directions (why selected AND why rejected). The ADR is not just justification; it is future-proofing that enables informed re-evaluation when requirements change. Checkpoint: Both positive and negative consequences must be present. Status should be proposed pending all stakeholder review before moving to accepted.
Every framework evaluation begins with functional requirements — what the framework must actually do for your application to work. Use this catalog to ensure completeness, then prioritize using MoSCoW.
The fundamental features your application needs. This is the most commonly evaluated dimension but also the most often done incompletely — teams compare feature lists without considering depth of capability or long-term roadmap alignment.
Requirements to elicit:
Priority guidance: Core capability Must requirements should cover every use case that, if missing, would force a complete architectural rewrite. Should requirements cover the next layer of capabilities that improve developer productivity but have documented workarounds.
How easy it is to write correct code with this framework. Bad API design creates bugs, slows onboarding, and increases maintenance burden over time.
Requirements to elicit:
Priority guidance: API design is typically a Should requirement — it affects velocity but rarely blocks correctness. However, for teams with high turnover or frequent onboarding, elevate to Must if the existing team lacks domain expertise.
How the framework interacts with data stores — ORM capability, query building, migration management, connection pooling, and transaction support.
Requirements to elicit:
Priority guidance: Data access is typically a Should or Could requirement. Most frameworks can work with external data libraries, but an integrated solution reduces operational surface area and simplifies the dependency graph.
Built-in security patterns for user identity management, role-based access control, session handling, and token-based authentication flows.
Requirements to elicit:
Priority guidance: Auth is a Must requirement for any user-facing application. If the framework does not support your required auth pattern natively, the integration cost must be factored into scoring or treated as a disqualification.
Non-functional requirements are quantifiable targets that define how well the system performs its functions. They are frequently the most critical selection differentiator but also the most frequently skipped. Go through every applicable category below.
Quantitative performance targets for the application under defined load conditions. Performance must be expressed in specific numeric terms — never "fast" or "good enough".
Required metrics to define:
Scoring evidence examples:
Security posture covering known vulnerability history, dependency supply chain risk, licensing compliance, and built-in security mechanisms. This dimension must not be an afterthought — security flaws in a foundational framework cascade into every module that uses it.
Required checks to perform:
pip audit, npm audit, or equivalent)Scoring evidence examples:
The ability to customize and extend framework behavior without forking or modifying core code. Frameworks that are not extensible become technical debt when requirements evolve.
Required checks to perform:
Priority guidance: Extensibility is typically a Should requirement. For platforms or product-as-a-service applications where customers may request custom behavior, elevate to Must.
Built-in support for monitoring, logging, and tracing that enables production debugging, performance analysis, and incident response. Frameworks without observability hooks turn every production issue into a forensic investigation.
Required checks to perform:
Priority guidance: Observability is a Must requirement for any production system. Frameworks that require significant custom instrumentation to achieve basic monitoring capability should be penalized heavily in scoring or treated as knock-out failures.
How easy the framework makes deployment, configuration management, and day-2 operations including scaling, upgrades, and incident recovery.
Required checks to perform:
Priority guidance: Operability is typically a Should requirement for internal services and a Must for customer-facing platforms where downtime carries direct revenue impact.
The ability to run on required platforms, integrate with existing infrastructure, and avoid vendor lock-in that makes migration prohibitively expensive.
Required checks to perform:
Priority guidance: Portability is typically a Should requirement but escalates to Must for platforms serving multi-tenant customers who may have infrastructure constraints.
Before any framework passes from evaluation to selection, it must clear these operational gates. Each gate is either binary (pass/fail) or has a defined threshold that can be measured quantitatively.
Run pipdeptree, npm list --depth=0, or the equivalent for your language ecosystem. The framework's dependency graph must meet these thresholds:
Measure these metrics for each remaining candidate framework using an identical test environment:
docker run to first successful HTTP response (target: < 5 seconds for containers)Verify the framework works correctly within your existing CI/CD pipeline:
Verify the framework can emit data to your operational monitoring stack:
A complete Python implementation of framework evaluation using quantified NFR thresholds. Each criterion has a measurable target, and scores are calculated by how close candidates come to meeting those targets — eliminating subjective judgment from scoring.
"""Framework requirements evaluation engine with quantified NFR thresholds.
This module implements evidence-based framework evaluation where every score is
derived from measurable metrics against defined thresholds. Scores are computed
by comparing candidate performance against target values, producing objective
comparisons that eliminate subjective scoring bias.
"""
from dataclasses import dataclass, field
from enum import IntEnum
from typing import Any
class Priority(IntEnum):
"""MoSCoW priority classification for requirements."""
MUST = 5 # Hard constraint — framework disqualified if fails
SHOULD = 4 # Important but has acceptable workarounds
COULD = 3 # Nice-to-have, scored but not required
WONT = 0 # Explicitly deferred
def label(self) -> str:
return {self.MUST: "MUST", self.SHOULD: "SHOULD", self.COULD: "COULD", self.WONT: "WONT"}[self]
@dataclass
class Requirement:
"""A single prioritized framework requirement.
Each requirement specifies what the framework must or should do,
with a quantified threshold where applicable and evidence for scoring.
"""
name: str
category: str # e.g., "performance", "security", "core_capability"
priority: Priority # MoSCoW classification
is_knockout: bool = False # Must requirements default to knockout
target_value: float | None = None # Quantified target (e.g., p95 latency < 200ms → 200.0)
unit: str = "" # Metric unit ("ms", "MB", "RPS", "%")
description: str = "" # Human-readable explanation of the requirement
evidence: str = "" # How the score was determined
def __post_init__(self) -> None:
if self.priority == Priority.MUST and not self.is_knockout:
# Must requirements can be non-knockout only when they have a quantified threshold
# that allows partial credit (e.g., MUST meet but scores vary above threshold)
pass
@dataclass
class NFRCriterion:
"""A non-functional requirement criterion with measurable thresholds.
Unlike standard requirements, NFRs are always scored on a 0-10 scale
based on how the candidate performs against quantified targets.
"""
name: str
nfr_category: str # performance, security, extensibility, observability, operability, portability
priority: Priority
target_value: float # The desired value (lower is better for latency, higher for throughput)
direction: str = "lower_is_better" # "lower_is_better" or "higher_is_better"
tolerance_range: float = 1.5 # Acceptable deviation from target before score drops
unit: str = ""
@property
def max_score(self) -> int:
return 10
def compute_score(self, actual_value: float) -> float:
"""Compute a 0-10 score based on how close the candidate is to the target.
Uses a linear falloff within tolerance range, then steep penalty outside it.
At target: score = 10. Within tolerance: linearly interpolated. Beyond tolerance: drops toward 0.
"""
if self.direction == "lower_is_better":
deviation = max(0.0, actual_value - self.target_value)
else:
deviation = max(0.0, self.target_value - actual_value)
# Within tolerance: perfect score
if deviation <= self.target_value * (self.tolerance_range - 1.0):
return float(self.max_score)
# Calculate falloff — linear within tolerance, steep outside
penalty_zone = self.target_value * self.tolerance_range
ratio = min(deviation / penalty_zone, 5.0) # Cap at 5x target for max penalty
score = max(0.0, self.max_score * (1.0 - ratio))
return round(score, 2)
@dataclass
class FrameworkCandidate:
"""A framework candidate being evaluated against all requirements."""
name: str
version: str
license_type: str
nfr_scores: dict[str, float] = field(default_factory=dict) # criterion_name -> score (0-10)
knockout_failures: list[dict[str, str]] = field(default_factory=list) # {requirement: reason}
poc_results: dict[str, Any] = field(default_factory=dict) # test_name -> metrics
production_gates: dict[str, bool] = field(default_factory=dict) # gate_name -> pass/fail
@property
def is_viable(self) -> bool:
"""Framework passes all knock-out criteria and production readiness gates."""
return len(self.knockout_failures) == 0
@property
def weighted_nfr_score(self) -> float:
"""Calculate average NFR score across all evaluated categories."""
if not self.nfr_scores:
return 0.0
return round(sum(self.nfr_scores.values()) / len(self.nfr_scores), 2)
@property
def nfr_categories_scored(self) -> list[str]:
"""List of NFR categories that have been scored for this candidate."""
return list(self.nfr_scores.keys())
def add_nfr_score(self, criterion: NFRCriterion, actual_value: float) -> None:
"""Score a candidate on an NFR criterion using measured performance data.
Args:
criterion: The NFR criterion definition with target and direction.
actual_value: The measured value for this candidate (e.g., p95 latency in ms).
"""
score = criterion.compute_score(actual_value)
self.nfr_scores[criterion.name] = score
def record_knockout_failure(self, requirement_name: str, reason: str) -> None:
"""Record a knock-out failure that disqualifies this candidate."""
self.knockout_failures.append({
"requirement": requirement_name,
"reason": reason,
})
def record_poc_result(self, test_name: str, metrics: dict[str, Any]) -> None:
"""Record proof-of-concept test results for this candidate."""
self.poc_results[test_name] = metrics
def pass_production_gate(self, gate_name: str, passed: bool) -> None:
"""Record a production readiness gate result.
Args:
gate_name: Name of the gate (e.g., "dependency_graph", "deployment_baseline").
passed: True if the candidate meets this gate's threshold.
"""
self.production_gates[gate_name] = passed
class RequirementsEvaluator:
"""Framework requirements evaluation engine.
Implements weighted scoring matrices with evidence-based NFR thresholds,
knock-out criteria enforcement, POC result aggregation, and production
readiness gate validation.
"""
# Predefined NFR catalog for consistent elicitation
DEFAULT_NFR_CRITERIA: list[dict[str, Any]] = [
{"name": "response_latency_p95", "nfr_category": "performance", "target_value": 200.0, "unit": "ms", "direction": "lower_is_better"},
{"name": "idle_memory_mb", "nfr_category": "operability", "target_value": 100.0, "unit": "MB", "direction": "lower_is_better"},
{"name": "cold_start_seconds", "nfr_category": "operability", "target_value": 5.0, "unit": "s", "direction": "lower_is_better"},
{"name": "docker_image_mb", "nfr_category": "operability", "target_value": 200.0, "unit": "MB", "direction": "lower_is_better"},
{"name": "dependency_count", "nfr_category": "portability", "target_value": 20, "unit": "deps", "direction": "lower_is_better"},
]
all_nfr_criteria: dict[str, NFRCriterion] = {}
@classmethod
def initialize_criteria(cls) -> None:
"""Initialize the default NFR criteria catalog."""
cls.all_nfr_criteria = {
c["name"]: NFRCriterion(
name=c["name"],
nfr_category=c["nfr_category"],
priority=Priority.SHOULD,
target_value=c["target_value"],
direction=c["direction"],
tolerance_range=1.5,
unit=c.get("unit", ""),
)
for c in cls.DEFAULT_NFR_CRITERIA
}
@classmethod
def evaluate_nfrs(
cls,
candidates: list[FrameworkCandidate],
measured_metrics: dict[str, dict[str, float]],
) -> None:
"""Score all candidates on NFR criteria using measured performance data.
Args:
candidates: List of framework candidates to evaluate.
measured_metrics: Mapping of candidate_name -> {metric_name: actual_value}.
Example: {"FastAPI": {"response_latency_p95": 45.0, "idle_memory_mb": 62.0}}
"""
for candidate in candidates:
candidate_metrics = measured_metrics.get(candidate.name, {})
for criterion in cls.all_nfr_criteria.values():
if criterion.name in candidate_metrics:
actual = candidate_metrics[criterion.name]
candidate.add_nfr_score(criterion, actual)
@classmethod
def calculate_final_scores(
cls,
candidates: list[FrameworkCandidate],
nfr_weights: dict[str, float] | None = None,
) -> list[dict]:
"""Calculate and rank final evaluation scores for all viable candidates.
Args:
candidates: Candidates that have been scored on NFR criteria.
nfr_weights: Optional per-category weights. If None, categories are weighted equally.
Returns:
List of result dicts sorted by score descending (excluding knock-out failures).
"""
if not nfr_weights:
# Equal weighting across all scored categories
scored_categories = set()
for c in candidates:
scored_categories.update(c.nfr_categories_scored)
nfr_weights = {cat: 1.0 / len(scored_categories) if scored_categories else 0.0 for cat in scored_categories}
results: list[dict] = []
for candidate in candidates:
if not candidate.is_viable:
continue # Knock-out failures excluded entirely
# Calculate weighted NFR score
weighted_total = sum(
candidate.nfr_scores.get(cat, 0.0) * weight
for cat, weight in nfr_weights.items()
)
total_weight = sum(nfr_weights.values()) or 1.0
normalized_score = round(weighted_total / total_weight * 10, 2)
# Check production gates — all must pass
all_gates_pass = all(candidate.production_gates.values()) if candidate.production_gates else True
results.append({
"name": candidate.name,
"version": candidate.version,
"license": candidate.license_type,
"nfr_score": normalized_score,
"weighted_nfr_avg": candidate.weighted_nfr_score,
"viable": candidate.is_viable,
"knockout_failures": candidate.knockout_failures,
"production_gates_pass": all_gates_pass,
"poc_results": candidate.poc_results,
"nfr_categories": list(candidate.nfr_scores.keys()),
})
return sorted(results, key=lambda r: r["nfr_score"], reverse=True)
@classmethod
def generate_summary(cls, results: list[dict]) -> str:
"""Generate a human-readable evaluation summary table."""
lines = [
"=" * 75,
"FRAMEWORK REQUIREMENTS EVALUATION SUMMARY",
"=" * 75,
f"{'Candidate':<20} {'Score':>6} {'Viable':>7} {'Gates':>6}",
"-" * 75,
]
for r in results:
viable = "Yes" if r["viable"] and r["production_gates_pass"] else "No"
lines.append(
f"{r['name']:<20} {r['nfr_score']:>6.2f} {viable:>7}"
f" {'Pass' if r['production_gates_pass'] else 'Fail':>6}"
)
if r["knockout_failures"]:
for failure in r["knockout_failures"]:
lines.append(f" ✗ Knock-out failed: {failure['requirement']}")
lines.append("=" * 75)
return "\n".join(lines)
# ─── Example Usage ──────────────────────────────────────────────────────────────
if __name__ == "__main__":
# Step 1: Initialize NFR criteria catalog
RequirementsEvaluator.initialize_criteria()
# Step 2: Define candidates
candidates = [
FrameworkCandidate(name="FastAPI", version="0.115.0", license_type="MIT"),
FrameworkCandidate(name="Django", version="5.1.0", license_type="BSD-3-Clause"),
FrameworkCandidate(name="Spring Boot", version="3.3.0", license_type="Apache-2.0"),
]
# Step 3: Simulate measured NFR metrics (from benchmarks or POC testing)
# Values represent actual measurements from identical test environments
measured = {
"FastAPI": {
"response_latency_p95": 45.0, # ms — target < 200ms
"idle_memory_mb": 62.0, # MB — target < 100MB
"cold_start_seconds": 1.2, # s — target < 5s
"docker_image_mb": 95.0, # MB — target < 200MB
"dependency_count": 8, # deps — target < 20
},
"Django": {
"response_latency_p95": 120.0, # ms
"idle_memory_mb": 145.0, # MB (heavier ORM + middleware)
"cold_start_seconds": 3.8, # s
"docker_image_mb": 185.0, # MB
"dependency_count": 28, # deps (ORM + admin + auth built-in)
},
"Spring Boot": {
"response_latency_p95": 85.0, # ms
"idle_memory_mb": 220.0, # MB (JVM baseline)
"cold_start_seconds": 8.5, # s (JVM warmup — fails gate)
"docker_image_mb": 160.0, # MB (JRE included)
"dependency_count": 35, # deps (Spring ecosystem is deep)
},
}
# Step 4: Score NFRs using measured data
RequirementsEvaluator.evaluate_nfrs(candidates, measured)
# Step 5: Simulate knock-out failures
# Django passes all, but Spring Boot fails cold start gate (MUST requirement)
spring_boot = next(c for c in candidates if c.name == "Spring Boot")
spring_boot.record_knockout_failure(
"cold_start_seconds",
"p95 cold start 8.5s exceeds MUST threshold of < 5s for serverless deployment target",
)
# Step 6: Record production gate results
for candidate in candidates:
if not candidate.is_viable:
continue
candidate.pass_production_gate("dependency_graph", True)
candidate.pass_production_gate("ci_cd_integration", True)
candidate.pass_production_gate("monitoring_hooks", True)
# Spring Boot also fails deployment baseline gate (cold start)
spring_boot.pass_production_gate("deployment_baseline", False)
# Step 7: Calculate and display final scores
nfr_weights = {cat: 1.0 / len(RequirementsEvaluator.all_nfr_criteria)
for cat in RequirementsEvaluator.all_nfr_criteria}
results = RequirementsEvaluator.calculate_final_scores(candidates, nfr_weights)
print(RequirementsEvaluator.generate_summary(results))
# Verify deterministic output — scores are derived from measured data
assert results[0]["nfr_score"] > results[-1]["nfr_score"] or len(results) <= 1, \
"Results must be sorted by descending NFR score"
A pydantic-based schema validation system for framework requirements documents. This ensures every requirement is properly structured with quantified thresholds, evidence citations, and priority classifications before any evaluation begins. Prevents vague or incomplete requirement definitions from entering the scoring process.
"""Framework requirements validation using Pydantic schemas.
Validates that all framework requirements are properly specified with:
- Quantified targets (not qualitative descriptions)
- MoSCoW priority classification
- Evidence citations for scores
- NFR category membership and threshold definitions
- Cross-criteria weight consistency checks
"""
from __future__ import annotations
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Optional
try:
from pydantic import BaseModel, Field, field_validator, model_validator
HAS_PYDANTIC = True
except ImportError:
HAS_PYDANTIC = False
# ─── Data Model (Pure Python Fallback) ──────────────────────────────────────────
class RequirementPriority(str, Enum):
MUST = "must"
SHOULD = "should"
COULD = "could"
WONT = "wont"
class NFRCategory(str, Enum):
PERFORMANCE = "performance"
SECURITY = "security"
EXTENSIBILITY = "extensibility"
OBSERVABILITY = "observability"
OPERABILITY = "operability"
PORTABILITY = "portability"
class ValidationResult:
"""Result of a requirements validation pass."""
def __init__(
self,
valid: bool = True,
errors: list[str] | None = None,
warnings: list[str] | None = None,
):
self.valid = valid
self.errors = errors or []
self.warnings = warnings or []
@property
def is_valid(self) -> bool:
return len(self.errors) == 0
def add_error(self, message: str) -> None:
self.errors.append(message)
self.valid = False
def add_warning(self, message: str) -> None:
self.warnings.append(message)
def __bool__(self) -> bool:
return self.is_valid
@dataclass
class FrameworkRequirementSchema:
"""A structured requirement with validation guarantees.
This dataclass ensures every requirement has the minimum fields needed
for evaluation, preventing vague or incomplete requirements from entering
the scoring pipeline.
"""
name: str
category: str
priority: RequirementPriority
target_value: float | None = None
unit: str = ""
description: str = ""
evidence: str = ""
@field_validator("name")
@classmethod
def name_not_empty(cls, v: str) -> str:
if not v.strip():
raise ValueError("Requirement name must not be empty")
return v
@dataclass
class NFRSchema:
"""A non-functional requirement with quantified threshold."""
name: str
category: NFRCategory
target_value: float
direction: str = "lower_is_better" # or "higher_is_better"
unit: str = ""
tolerance_multiplier: float = 1.5
def validate_target(self) -> ValidationResult:
"""Validate that the target value is reasonable and non-zero."""
result = ValidationResult()
if self.target_value <= 0:
result.add_error(
f"NFR '{self.name}' has non-positive target value {self.target_value}. "
"Target must be a positive number."
)
if not self.unit:
result.add_warning(f"NFR '{self.name}' is missing a unit specification.")
if self.direction not in ("lower_is_better", "higher_is_better"):
result.add_error(
f"NFR '{self.name}' has invalid direction '{self.direction}'. "
"Must be 'lower_is_better' or 'higher_is_better'."
)
return result
@dataclass
class FrameworkRequirementsDocument:
"""Complete framework requirements document with validation."""
project_name: str
functional_requirements: list[FrameworkRequirementSchema] = field(default_factory=list)
nfr_criteria: list[NFRSchema] = field(default_factory=list)
weights: dict[str, float] = field(default_factory=dict) # criterion_name -> normalized weight
knockout_requirements: list[str] = field(default_factory=list) # names of MUST requirements
def validate(self) -> ValidationResult:
"""Validate the complete requirements document.
Checks include:
- At least one MUST requirement exists
- All weights sum to exactly 1.0
- All NFR targets are valid and quantified
- Every requirement has an evidence field (non-empty for scored criteria)
- No duplicate requirement names
"""
result = ValidationResult()
# Check MUST requirements exist
must_count = sum(
1 for r in self.functional_requirements
if r.priority == RequirementPriority.MUST
)
if must_count == 0:
result.add_error(
"At least one MUST (knock-out) requirement is required. "
"A framework selection without non-negotiables is subjective speculation."
)
# Check weights sum to 1.0
weight_sum = sum(self.weights.values()) if self.weights else 0.0
if abs(weight_sum - 1.0) > 0.001:
result.add_error(
f"Scoring criterion weights must sum to 1.0, got {weight_sum:.4f}. "
f"Current breakdown: {self.weights}"
)
# Validate individual requirements
seen_names: set[str] = set()
for req in self.functional_requirements:
if req.name in seen_names:
result.add_error(f"Duplicate requirement name: '{req.name}'")
seen_names.add(req.name)
if not req.description:
result.add_warning(f"Requirement '{req.name}' has no description.")
# Must requirements need evidence (quantified thresholds)
if req.priority == RequirementPriority.MUST and not req.target_value:
result.add_error(
f"Must requirement '{req.name}' must have a quantified target_value. "
"Vague must-requirements cannot be objectively evaluated."
)
# Validate NFR criteria
for nfr in self.nfr_criteria:
nfr_result = nfr.validate_target()
result.errors.extend(nfr_result.errors)
result.warnings.extend(nfr_result.warnings)
return result
# ─── Example: Valid Requirements Document ────────────────────────────────────────
if __name__ == "__main__":
doc = FrameworkRequirementsDocument(
project_name="internal-api-gateway",
functional_requirements=[
FrameworkRequirementSchema(
name="async_support",
category="core_capability",
priority=RequirementPriority.MUST,
target_value=1.0,
description="Must support async/await for high-concurrency request handling",
evidence="Required by architecture spec section 3.2 — service must handle 5k+ concurrent connections",
),
FrameworkRequirementSchema(
name="orm_capability",
category="data_access",
priority=RequirementPriority.SHOULD,
target_value=10.0,
unit="features_covered",
description="Should include a production-grade ORM with migration tooling",
evidence="Django includes ORM; FastAPI requires SQLAlchemy integration — both viable options exist",
),
FrameworkRequirementSchema(
name="jwt_auth_support",
category="authentication",
priority=RequirementPriority.MUST,
target_value=1.0,
description="Must support JWT token-based authentication flows natively or via first-party plugin",
evidence="API gateway requires JWT for service-to-service auth per security architecture document",
),
],
nfr_criteria=[
NFRSchema(
name="response_latency_p95",
category=NFRCategory.PERFORMANCE,
target_value=200.0,
direction="lower_is_better",
unit="ms",
),
NFRSchema(
name="idle_memory_mb",
category=NFRCategory.OPERABILITY,
target_value=150.0,
direction="lower_is_better",
unit="MB",
),
],
weights={
"async_support": 0.0, # Knock-out (MUST) — weight is 0
"orm_capability": 0.30,
"jwt_auth_support": 0.0, # Knock-out (MUST) — weight is 0
"response_latency_p95": 0.35,
"idle_memory_mb": 0.35,
},
knockout_requirements=["async_support", "jwt_auth_support"],
)
validation = doc.validate()
print(f"Valid: {validation.is_valid}")
for error in validation.errors:
print(f" ERROR: {error}")
for warning in validation.warnings:
print(f" WARNING: {warning}")
A comprehensive ADR generator that produces MADR-compatible records with full consequence analysis — both positive and negative. The ADR serves as living documentation that enables informed re-evaluation when requirements change or the framework's trajectory shifts.
"""Architecture Decision Record (ADR) generator for framework selection decisions.
Produces MADR-compatible markdown documents that capture:
- Decision context and driving factors
- All candidates evaluated with weighted scores
- Knock-out criteria results for every candidate
- Both positive AND negative consequences
- Reversibility assessment (how costly is it to change?)
"""
from __future__ import annotations
import json
from dataclasses import dataclass, field
from datetime import date
from typing import Optional
@dataclass
class FrameworkADR:
"""Architecture Decision Record for framework selection.
Follows the MADR (Milestone-driven Architecture Decision Records) template
adapted specifically for technology and framework selection decisions.
Every ADR must include both positive and negative consequences — a decision
without documented trade-offs is not reviewable.
"""
title: str
status: str = "proposed" # proposed | rejected | accepted | deprecated | superseded
deciders: list[str] = field(default_factory=list)
date: str = field(default_factory=lambda: date.today().isoformat())
context_description: str = ""
decision_drivers: list[str] = field(default_factory=list)
# Requirements
must_requirements: list[str] = field(default_factory=list)
should_requirements: list[str] = field(default_factory=list)
# Knock-out results per candidate
knock_out_results: dict[str, list[dict[str, str]]] = field(default_factory=dict)
# Weighted evaluation
weighted_criteria: dict[str, float] = field(default_factory=dict)
candidate_scores: dict[str, dict[str, float]] = field(default_factory=dict)
# Candidates evaluated with selection marker
candidates_evaluated: list[dict[str, str | bool]] = field(default_factory=list)
selected_framework: Optional[str] = None
# Consequences — BOTH positive and negative are REQUIRED
positive_consequences: list[str] = field(default_factory=list)
negative_consequences: list[str] = field(default_factory=list)
# Reversibility assessment
estimated_migration_cost: str = "unknown" # low | medium | high
migration_effort_days: float = 0.0
exit_path_documented: bool = False
# POC findings summary
poc_findings: list[str] = field(default_factory=list)
def _requirement_status(self, name: str) -> str:
"""Get the status string for a requirement."""
if name in self.must_requirements:
return "MUST"
if name in self.should_requirements:
return "SHOULD"
return ""
@property
def has_required_sections(self) -> bool:
"""Check that this ADR has all mandatory sections for review."""
checks = [
(self.context_description, "context_description must not be empty"),
(len(self.decision_drivers) > 0, "decision_drivers must have at least one item"),
(len(self.positive_consequences) > 0, "positive_consequences must list at least one benefit"),
(len(self.negative_consequences) > 0, "negative_consequences must list at least one drawback"),
(self.selected_framework is not None, "selected_framework must be specified"),
(bool(self.candidate_scores), "candidate_scores must include all evaluated frameworks"),
]
for condition, error_msg in checks:
if not condition:
print(f" ✗ ADR validation failed: {error_msg}")
return False
return True
def generate_markdown(self) -> str:
"""Generate the ADR in MADR-compatible markdown format."""
sections = [
f"# {self.title}",
"",
f"* Status: {self.status}",
f"* Deciders: {', '.join(self.deciders)}",
f"* Date: {self.date}",
"",
"---",
"",
"## Context and Problem Statement",
self.context_description or "No context provided.",
"",
]
if self.decision_drivers:
sections.append("## Decision Drivers")
for i, driver in enumerate(self.decision_drivers, 1):
sections.append(f"{i}. {driver}")
sections.append("")
if self.must_requirements or self.should_requirements:
sections.append("## Requirements Summary")
if self.must_requirements:
sections.append("### Must (Knock-out)")
for req in self.must_requirements:
sections.append(f"- [ ] {req}")
if self.should_requirements:
sections.append("### Should (Scored)")
for req in self.should_requirements:
sections.append(f" - [ ] {req}")
sections.append("")
if self.knock_out_results:
sections.append("## Knock-out Criteria Results")
for candidate, failures in self.knock_out_results.items():
status = "PASSED" if not failures else "FAILED"
sections.append(f"### {candidate}: {status}")
if failures:
for failure in failures:
sections.append(f"- **{failure['requirement']}**: {failure['reason']}")
else:
sections.append("- Passed all knock-out criteria")
sections.append("")
if self.weighted_criteria:
total_weight = sum(self.weighted_criteria.values()) or 1.0
sections.append("## Weighted Evaluation Criteria")
for criterion, weight in sorted(
self.weighted_criteria.items(), key=lambda x: x[1], reverse=True
):
pct = (weight / total_weight) * 100
marker = " [KNOCK-OUT]" if criterion in self.must_requirements else ""
sections.append(f"- **{criterion}**: {pct:.0f}% weight{marker}")
sections.append("")
if self.candidate_scores:
sections.append("## Candidate Scores")
for name, scores in self.candidate_scores.items():
is_selected = " <-- SELECTED" if name == self.selected_framework else ""
total = sum(scores.values()) if scores else 0
sections.append(f"### {name}{is_selected}")
sections.append("| Criterion | Score (0-10) |")
sections.append("|-----------|-------------|")
for criterion, score in sorted(scores.items(), key=lambda x: x[1], reverse=True):
sections.append(f"| {criterion} | {score:.1f} |")
sections.append(f"**Weighted Total: {total:.1f}/10**")
sections.append("")
# Consequences — the critical section that prevents decision bias
sections.append("## Consequences")
sections.append("")
sections.append("### Positive (Benefits)")
if self.positive_consequences:
for consequence in self.positive_consequences:
sections.append(f"+ {consequence}")
else:
sections.append("+ No positive consequences documented (review required)")
sections.append("")
sections.append("### Negative (Drawbacks / Risks)")
if self.negative_consequences:
for consequence in self.negative_consequences:
sections.append(f"- {consequence}")
else:
sections.append("- No negative consequences documented (review required)")
sections.append("")
if self.poc_findings:
sections.append("## POC Findings")
for finding in self.poc_findings:
sections.append(f"- {finding}")
sections.append("")
# Reversibility assessment
sections.append("## Reversibility Assessment")
sections.append(f"- Migration cost estimate: {self.estimated_migration_cost}")
sections.append(f"- Estimated migration effort: {self.migration_effort_days:.0f} developer-days")
sections.append(
f"- Exit path documented: {'Yes' if self.exit_path_documented else 'No — risk flag'}"
)
sections.append("")
sections.append("---")
return "\n".join(sections)
def to_json(self) -> str:
"""Serialize ADR to JSON for machine-readable storage and review tracking."""
return json.dumps({
"title": self.title,
"status": self.status,
"deciders": self.deciders,
"date": self.date,
"context_description": self.context_description,
"decision_drivers": self.decision_drivers,
"must_requirements": self.must_requirements,
"should_requirements": self.should_requirements,
"knock_out_results": self.knock_out_results,
"weighted_criteria": self.weighted_criteria,
"candidate_scores": self.candidate_scores,
"candidates_evaluated": [c["name"] for c in self.candidates_evaluated],
"selected_framework": self.selected_framework,
"positive_consequences": self.positive_consequences,
"negative_consequences": self.negative_consequences,
"estimated_migration_cost": self.estimated_migration_cost,
"migration_effort_days": self.migration_effort_days,
"exit_path_documented": self.exit_path_documented,
"poc_findings": self.poc_findings,
}, indent=2)
# ─── Example: Generate a Complete ADR ────────────────────────────────────────────
if __name__ == "__main__":
adr = FrameworkADR(
title="Select FastAPI for Internal API Service Framework",
status="proposed",
deciders=["alice (tech lead)", "bob (backend engineer)", "carol (platform engineer)"],
context_description=(
"Our internal services require a lightweight, high-performance HTTP framework "
"for building REST APIs and microservice endpoints. The current service uses "
"a legacy framework with slow startup times (> 8s cold start), limited async "
"support, and no built-in OpenTelemetry instrumentation. We need a framework "
"that supports Python 3.12+, runs efficiently in containers (< 150MB image), "
"and integrates with our existing observability stack (Prometheus, Jaeger)."
),
decision_drivers=[
"Must support async/await natively for high-concurrency workloads",
"Must run on Linux x86_64 in Docker containers with < 150MB image footprint",
"Must use an OSI-approved license (MIT or Apache 2.0 preferred)",
"Team needs to achieve productivity within 2 weeks of introduction",
"Must integrate with existing JWT-based authentication via first-party plugin",
"Must provide OpenTelemetry instrumentation or Prometheus metrics endpoint",
],
must_requirements=[
"async_support (p95 < 500ms under load test)",
"docker_image_footprint (< 150MB compressed)",
"osi_approved_license",
],
should_requirements=[
"built_in_orm_capability",
"automated_api_documentation",
"jwt_auth_integration",
],
knock_out_results={
"FastAPI": [], # Passed all knock-outs
"Django": [], # Passed all knock-outs
"Sanic": [ # Failed — no active community
{
"requirement": "community_support",
"reason": "Fewer than 10 contributors with activity in last 90 days; 6-month release gap between versions",
}
],
},
weighted_criteria={
"async_performance": 0.20,
"docker_image_footprint": 0.15,
"built_in_orm_capability": 0.10,
"automated_api_documentation": 0.10,
"jwt_auth_integration": 0.10,
"team_productivity_curve": 0.20,
"observability_hooks": 0.15,
},
candidate_scores={
"FastAPI": {
"async_performance": 9.0,
"docker_image_footprint": 8.5,
"built_in_orm_capability": 4.0, # No built-in ORM (requires SQLAlchemy)
"automated_api_documentation": 9.0, # Auto OpenAPI/Swagger
"jwt_auth_integration": 7.0, # Via python-jose plugin
"team_productivity_curve": 6.0, # Team new to async patterns
"observability_hooks": 7.5, # Via opentelemetry-instrumentation-fastapi
},
"Django": {
"async_performance": 6.0, # Async support added in 3.1+ but limited
"docker_image_footprint": 5.0, # Heavier ORM + admin = larger image
"built_in_orm_capability": 9.5, # Full ORM + migrations built-in
"automated_api_documentation": 4.0, # Django REST Framework requires setup
"jwt_auth_integration": 8.0, # Via django-rest-framework-jwt
"team_productivity_curve": 7.0, # Team has moderate Django experience
"observability_hooks": 6.0, # Requires custom middleware
},
},
candidates_evaluated=[
{"name": "FastAPI", "selected": True},
{"name": "Django", "selected": False},
{"name": "Sanic", "selected": False},
],
selected_framework="FastAPI",
positive_consequences=[
"p95 latency ~45ms under 5k concurrent connections (vs 120ms with Django)",
"Docker image < 100MB compressed, starting from python:3.12-slim",
"Automatic OpenAPI/Swagger documentation at /docs endpoint — zero extra setup",
"Native async/await enables efficient request handling without thread pool overhead",
"Type-hint-based routing with auto-completion in modern IDEs reduces development time",
],
negative_consequences=[
"No built-in ORM — requires selecting and integrating SQLAlchemy or similar (adds maintenance surface)",
"Team has limited prior async experience — requires a 1-week learning sprint before full productivity",
"Smaller third-party ecosystem than Django — some common patterns lack first-party solutions",
"Rapidly evolving API with breaking changes between minor versions — requires quarterly dependency reviews",
"No built-in admin panel — must build or integrate external admin tooling for internal data management",
],
estimated_migration_cost="medium",
migration_effort_days=15.0,
exit_path_documented=True,
poc_findings=[
"FastAPI: 45ms p95 latency, 62MB idle memory, 1.2s cold start — all targets met",
"Django: 120ms p95 latency, 145MB idle memory, 3.8s cold start — meets performance but exceeds image size target marginally",
],
)
assert adr.has_required_sections, "ADR must have all required sections"
print(adr.generate_markdown())
When this skill is active, your output must contain all five sections below in order. Do not skip any section or combine them — each serves a distinct purpose in the decision-making process.
MoSCoW Requirements Classification — Structured list of all requirements organized by MoSCoW priority and functional category. Every requirement has: name, category, priority (Must/Should/Could/Wont), quantified target value with unit (for Must requirements), and evidence citation. Each NFR category that applies gets at least one quantified target. If any NFR category is genuinely inapplicable, document why explicitly with a reason code.
Weighted Evaluation Matrix — Table showing candidates × scoring criteria with: criterion name, weight (normalized to sum 1.0), score (0-10), weighted sub-score, and evidence citation. Include total weighted score per candidate sorted descending. Also show knock-out criteria results for each candidate as binary pass/fail with failure reasons if applicable. Weights must sum to 1.0 — state the sum explicitly in the output header.
POC Test Results — Measured metrics from proof-of-concept tests for the top two candidates. Each result includes: test name, metric measured, actual value, target threshold, pass/fail status, and notes on test environment configuration. Include startup time, memory footprint, request throughput, and the specific integration points exercised during testing.
Production Readiness Gate Report — For each remaining candidate, list all four production readiness gates with pass/fail status and measured values. Gates: dependency graph (direct deps, transitive depth, CVE count), deployment baseline (cold start, memory, image size), CI/CD integration (test runner compatibility, build impact), monitoring hooks (health check path, metrics format, log structure). Any candidate failing a Must-level gate requires explicit stakeholder exception documentation.
ADR Document — Complete Architecture Decision Record in MADR-compatible markdown format including: decision context and drivers, MoSCoW requirements summary, knock-out criteria results, weighted scores for all candidates, POC findings, documented positive consequences, documented negative consequences (both sections required), reversibility assessment with migration cost estimate, and decider signatures. Status should be proposed pending stakeholder sign-off.
| Skill | Purpose |
|---|---|
framework-evaluation-criteria | Structured criteria elicitation across 8 dimensions — use when you need deep requirement discovery before building the matrix |
test-driven-development | Ensures framework choices support testability requirements and enable TDD workflows from day one |
modular-design | Applies modular constraints to framework selection, ensuring integration boundaries are clean and swappable |
dependency-injection | Implements the chosen framework's DI patterns for loose coupling, enabling easier future framework swaps |
framework-utilization | After selection and implementation begins, use this skill for learning and leveraging the chosen framework effectively in production |
This skill is designed to be loaded before any framework selection begins. Its output becomes the structured foundation for scoring matrices, POC testing, and Architecture Decision Records that enable traceable, evidence-based technology decisions.