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).
tool-evaluation-workflowApplies a structured evaluation framework to select tools, libraries, and frameworks based on technical fit, community health, security posture, performance benchmarks, and total cost of ownership for software projects.
Independent GitHub snapshot · Automated classification · Original documentation governs
name: tool-evaluation-workflow description: Applies a structured evaluation framework to select tools, libraries, and frameworks based on technical fit, community health, security posture, performance benchmarks, and total cost of ownership for software projects. license: MIT compatibility: opencode metadata: version: "1.0.0" domain: coding triggers: tool evaluation, library selection, framework comparison, proof of concept, technology assessment, how do i evaluate tools, build vs buy decision, dependency management archetypes:
Applies a structured evaluation framework to select tools, libraries, and frameworks for project adoption. This skill makes the model define measurable criteria, score candidates against weighted dimensions, execute focused proof-of-concept tests, review security posture, and produce a data-driven recommendation with documented trade-offs and migration planning.
Use this skill when:
Avoid this skill for:
Document all requirements using two categories: Functional (what the tool must do) and Non-functional (how well it must do it). Every requirement must be testable or verifiable — subjective preferences are not requirements.
Checkpoint: Review every requirement with the question "How will we verify this in a proof-of-concept?" If no verification path exists, convert it to an observation or discard it.
Functional requirements examples:
Non-functional requirements examples:
Stakeholder categories to document:
| Stakeholder | Concerns | Weight |
|---|---|---|
| Backend Engineers | API ergonomics, type safety, error handling | 35% |
| DevOps / SRE | Deployment model, monitoring, resource usage | 25% |
| Security Team | CVE history, license compliance, data handling | 20% |
| Product / Business | Time-to-market, vendor lock-in risk, cost | 15% |
| Data Team | Query expressiveness, migration tooling | 5% |
Checkpoint: Every requirement must have a measurable threshold. "Handles moderate traffic" is not measurable — "sustains 10k RPS with P95 < 100ms on 2 vCPU instances" is.
Gather tools meeting minimum criteria: active maintenance (release within last 6 months), license compatibility (permissive OSI-approved for commercial use, or acceptable copyleft), and platform support (operating system, language version).
Minimum shortlist composition:
Screening checklist per candidate:
☐ Released within last 6 months (or has LTS branch with recent patches)
☐ License is OSI-approved and compatible with project licensing model
☐ Supports required runtime/platform (Go 1.21+, Linux/Windows, arm64/x86_64)
☐ Has documented migration path or upgrade guide (for incumbent replacements)
Checkpoint: If screening eliminates all but one candidate, the evaluation is over — document the direct choice. If no candidates pass screening, expand the search criteria or reconsider the problem statement.
Define criteria categories with specific weights totaling 100%. Score each candidate 1-5 on every criterion, where the score must be justified with evidence.
Default criteria weights:
| Category | Weight | Description |
|---|---|---|
| Technical Fit | 30% | API design, type safety, documentation quality, integration ease |
| Community Health | 20% | Release frequency, issue resolution time, contributor diversity |
| Security Posture | 20% | CVE history, license compatibility, supply chain hygiene |
| Performance | 15% | Benchmark results, resource utilization, scalability characteristics |
| Operational Cost | 15% | Learning curve, team familiarity, monitoring/ops complexity |
Scoring scale (1-5):
| Score | Meaning | Evidence Required |
|---|---|---|
| 1 | Does not meet minimum threshold | Fails one or more must-have criteria |
| 2 | Below expectations | Meets basic requirements but has notable gaps |
| 3 | Meets expectations | Solid option with no major concerns |
| 4 | Above expectations | Notable strengths in this category |
| 5 | Exceeds expectations | Best-in-class; documented benchmark or case study |
Technical Fit sub-criteria:
Community Health metrics:
Checkpoint: Cross-validate scores between evaluators. If two people score independently, variance on any criterion should be ≤ 1 point. Larger variance means the scoring evidence is insufficient or the criterion needs clarification.
For the top 2 candidates (by weighted score), build a minimal integration test exercising the core use case under realistic conditions. The PoC must be equivalent in scope for both candidates — same feature, same data volume, same edge cases.
PoC requirements:
Checkpoint: The PoC must test the actual integration point where the tool enters your system — not an isolated unit in isolation. If you evaluate a web framework by testing only its router without database access, the benchmark is not representative.
This step is non-negotiable for any tool entering production. No amount of technical fit or community enthusiasm overrides security concerns.
Security review checklist:
| Check | Action | Threshold |
|---|---|---|
| CVE scanning | Run govulncheck / npm audit / pip-audit on the candidate and its transitive dependencies | Zero critical or high CVEs |
| License compatibility | Verify OSI license class; check copyleft propagation risk | MIT, Apache-2.0, BSD = green. GPL/LGPL = red flag for proprietary projects |
| Supply chain verification | Review dependency graph depth; count transitive dependencies | > 50 direct + transitive deps = requires deeper review |
| Data handling audit | Review what data the tool sends externally (telemetry, crash reports, license checks) | No unexpected network calls to third-party endpoints |
| Maintenance continuity | Check maintainer bus factor and financial backing | Single maintainer with no corporate backing = elevated risk |
| History of incidents | Search for security advisories in the project's issue tracker over last 24 months | More than 2 security advisories in 24 months = elevated risk |
Checkpoint: No tool with critical open CVEs or a copyleft license that conflicts with your project's licensing model can be recommended. This is a hard gate — technical score does not override it.
Compile all findings into a structured report with recommendation, risk assessment, and rollout plan. The report should be readable by engineers (technical detail), SREs (operational concerns), and decision-makers (cost/risk summary).
Required report sections:
Checkpoint: If the report does not include at least one specific risk for the recommended tool, it is incomplete. Every decision has trade-offs; hiding them creates false confidence.
Use this template to score all candidates systematically:
┌─────────────────────────┬──────────┬──────────────────────────────────────────────┐
│ CRITERION │ WEIGHT │ CANDIDATE A (X) │
├─────────────────────────┼──────────┼──────────────────────────────────────────────┤
│ Technical Fit │ 30% │ Score: __ / Justification: __ │
│ ── API ergonomics │ │ │
│ ── Type safety │ │ │
│ ── Docs quality │ │ │
│ Community Health │ 20% │ Score: __ / Justification: __ │
│ ── Release frequency │ │ │
│ ── Issue resolution │ │ │
│ Security Posture │ 20% │ Score: __ / Justification: __ │
│ ── CVE history │ │ │
│ ── License compatibility│ │ │
│ Performance │ 15% │ Score: __ / Justification: __ │
│ ── Benchmarks │ │ │
│ ── Resource usage │ │ │
│ Operational Cost │ 15% │ Score: __ / Justification: __ │
│ ── Learning curve │ │ │
│ ── Team familiarity │ │ │
├─────────────────────────┼──────────┼──────────────────────────────────────────────┤
│ WEIGHTED TOTAL │ 100% │ ______ / 5.0 │
└─────────────────────────┴──────────┴──────────────────────────────────────────────┘
Execute equivalent benchmarks for each candidate using a consistent methodology. The template below is in Go (the language-agnostic approach applies to any language):
Test structure:
Run automated security tooling before manual review. Results from these tools form the evidence base for the Security Posture criterion:
# Go ecosystem
govulncheck ./... # Checks for known vulnerabilities in dependencies
go vet ./... # Static analysis for common mistakes
# JavaScript/TypeScript ecosystem
npm audit --production # CVE scanning for npm dependencies
npx license-checker # License compatibility verification
# Python ecosystem
pip-audit # CVE scanning for pip dependencies
safety check -r requirements.txt # Alternative vulnerability scanner
The following Go code provides a reusable benchmarking framework for comparing two tool candidates under identical conditions. Use this as the foundation for any PoC performance comparison.
package evaluation
import (
"context"
"fmt"
"math"
"sort"
"testing"
"time"
)
// Candidate represents a tool/library being evaluated in a proof-of-concept.
type Candidate struct {
Name string
Version string
Implementation func(ctx context.Context) (Operation, error)
}
// Operation is the interface that all candidates must implement for benchmarking.
// The specific operations differ per evaluation — this example uses data serialization.
type Operation interface {
// Serialize produces output bytes from structured data.
Serialize(data any) ([]byte, error)
// Deserialize reconstructs structured data from input bytes.
Deserialize(input []byte) (any, error)
}
// BenchmarkResult holds aggregated performance metrics for a candidate.
type BenchmarkResult struct {
Name string
MedianLatency time.Duration
P95Latency time.Duration
P99Latency time.Duration
AvgMemoryBytes int64
TotalIterations int
ErrorCount int
}
// RunBenchmark executes a serialization/deserialization benchmark for a candidate
// and returns aggregated performance metrics. It performs warm-up, measures over
// many iterations, and tracks memory allocation per operation.
func RunBenchmark(ctx context.Context, c Candidate, iterations int) (BenchmarkResult, error) {
result := BenchmarkResult{
Name: c.Name,
TotalIterations: iterations,
}
op, err := c.Implementation(ctx)
if err != nil {
return result, fmt.Errorf("candidate %s: setup operation: %w", c.Name, err)
}
testData := map[string]any{
"user_id": 12345,
"email": "user@example.com",
"metadata": map[string]string{"role": "admin", "region": "us-east"},
"created_at": time.Now().Format(time.RFC3339),
}
latencies := make([]time.Duration, 0, iterations)
var totalAlloc int64
// Warm-up phase: discard first N iterations to stabilize measurements.
warmupCount := min(100, iterations/10)
for i := 0; i < warmupCount; i++ {
_, _ = op.Serialize(testData)
}
// Measurement phase: collect latency and memory for remaining iterations.
buf := make([]byte, 0, 4096)
for i := 0; i < iterations-warmupCount; i++ {
start := time.Now()
output, err := op.Serialize(testData)
if err != nil {
result.ErrorCount++
continue
}
_, err = op.Deserialize(output)
if err != nil {
result.ErrorCount++
continue
}
elapsed := time.Since(start)
latencies = append(latencies, elapsed)
buf = buf[:cap(buf)] // Reset without allocation for next iteration
totalAlloc += int64(len(output))
}
if len(latencies) == 0 {
return result, fmt.Errorf("candidate %s: no successful iterations", c.Name)
}
sort.Slice(latencies, func(i, j int) bool { return latencies[i] < latencies[j] })
result.MedianLatency = percentile(latencies, 50)
result.P95Latency = percentile(latencies, 95)
result.P99Latency = percentile(latencies, 99)
result.AvgMemoryBytes = totalAlloc / int64(len(latencies))
return result, nil
}
// percentile computes the given percentile from a sorted slice of durations.
func percentile(sorted []time.Duration, p float64) time.Duration {
if len(sorted) == 0 {
return 0
}
idx := int(math.Ceil(p/100*float64(len(sorted)))) - 1
if idx < 0 {
idx = 0
}
if idx >= len(sorted) {
idx = len(sorted) - 1
}
return sorted[idx]
}
// CompareResults prints a formatted comparison table for benchmark results from
// multiple candidates. Results must be sorted by name for consistent output.
func CompareResults(results []BenchmarkResult) string {
var sb strings.Builder
sb.WriteString("┌──────────┬──────────────────┬──────────────────┬──────────────────┬─────────────┐\n")
sb.WriteString("│ Name │ Median Latency │ P95 Latency │ P99 Latency │ Avg Bytes │\n")
sb.WriteString("├──────────┼──────────────────┼──────────────────┼──────────────────┼─────────────┤\n")
for _, r := range results {
sb.Fprintf("│ %-8s │ %16s │ %16s │ %16s │ %11d │\n",
r.Name,
r.MedianLatency.String(),
r.P95Latency.String(),
r.P99Latency.String(),
r.AvgMemoryBytes,
)
}
sb.WriteString("└──────────┴──────────────────┴──────────────────┴──────────────────┴─────────────┘\n")
if results[0].ErrorCount > 0 || results[1].ErrorCount > 0 {
sb.WriteString("\n⚠ Warnings:\n")
for _, r := range results {
if r.ErrorCount > 0 {
sb.Fprintf(" - %s: %d errors out of %d iterations (%.1f%% failure rate)\n",
r.Name, r.ErrorCount, r.TotalIterations,
float64(r.ErrorCount)/float64(r.TotalIterations)*100)
}
}
}
return sb.String()
}
The following Go code provides a calculator for computing weighted scores with validation and audit trails. It ensures criteria weights sum to exactly 100% and records justification for every score.
package evaluation
import (
"fmt"
"strings"
)
// Criterion defines a single evaluation criterion with its weight and category.
type Criterion struct {
Name string
Category string // "Technical Fit", "Community Health", etc.
Weight float64 // 0.0 to 1.0, must sum to 1.0 across all criteria
Description string // What this criterion measures
}
// ScoredCandidate holds the raw scores and justifications for one candidate.
type ScoredCandidate struct {
Name string
CriterionScores []CriterionScore
}
// CriterionScore records the score and evidence-based justification for one criterion.
type CriterionScore struct {
Criterion string
Score int // 1-5 scale
Justification string // Evidence or rationale
}
// WeightedScorer computes weighted scores across candidates with full validation.
type WeightedScorer struct {
criteria []Criterion
}
// NewWeightedScorer creates a scorer and validates that weights sum to 1.0.
func NewWeightedScorer(criteria []Criterion) (*WeightedScorer, error) {
total := 0.0
for _, c := range criteria {
if c.Weight < 0 || c.Weight > 1 {
return nil, fmt.Errorf("criterion %q: weight must be 0-1, got %f", c.Name, c.Weight)
}
total += c.Weight
}
if total > 1.0+1e-9 || total < 1.0-1e-9 {
return nil, fmt.Errorf("criterion weights must sum to 1.0, got %.4f", total)
}
return &WeightedScorer{criteria: criteria}, nil
}
// ScoreCandidate computes the weighted total for a candidate. Scores must include
// entries for every criterion; missing criteria default to score 0 (disqualified).
func (s *WeightedScorer) ScoreCandidate(cand ScoredCandidate) (float64, error) {
scoreMap := make(map[string]int)
for _, cs := range cand.CriterionScores {
if cs.Score < 1 || cs.Score > 5 {
return 0, fmt.Errorf("candidate %s: score for %q must be 1-5, got %d",
cand.Name, cs.Criterion, cs.Score)
}
scoreMap[cs.Criterion] = cs.Score
}
var weightedTotal float64
for _, c := range s.criteria {
score, ok := scoreMap[c.Name]
if !ok {
return 0, fmt.Errorf("candidate %s: missing score for criterion %q", cand.Name, c.Name)
}
weightedTotal += c.Weight * float64(score) / 5.0
}
return weightedTotal, nil
}
// Summary produces a formatted comparison table of scored candidates.
func (s *WeightedScorer) Summary(candidates []ScoredCandidate) (string, error) {
var sb strings.Builder
// Header row: show all criteria names.
sb.WriteString("┌───────────┬")
for _, c := range s.criteria {
sb.WriteString(fmt.Sprintf("─%12s─┬", "Score"))
}
sb.WriteString("─┐\n")
// Criteria headers with weights.
sb.WriteString("│ Candidate │")
for _, c := range s.criteria {
sb.WriteString(fmt.Sprintf(" %8s (%d%%) │", c.Name, int(c.Weight*100)))
}
sb.WriteString("\n├───────────┼")
for range s.criteria {
sb.WriteString("────────────┼")
}
sb.WriteString("┤\n")
// Score rows and totals.
type scoredCandidate struct {
name string
total float64
scores []int
}
var results []scoredCandidate
for _, cand := range candidates {
var scores []int
var total float64
for _, c := range s.criteria {
score := 0
for _, cs := range cand.CriterionScores {
if cs.Criterion == c.Name {
score = cs.Score
break
}
}
scores = append(scores, score)
total += c.Weight * float64(score) / 5.0
}
results = append(results, scoredCandidate{cand.Name, total, scores})
}
for _, r := range results {
sb.WriteString(fmt.Sprintf("│ %-9s │", r.name))
for _, sc := range r.scores {
sb.WriteString(fmt.Sprintf("%6d/5 │", sc))
}
sb.WriteString("\n")
}
sb.WriteString("├───────────┼")
for range s.criteria {
sb.WriteString("────────────┼")
}
sb.WriteString("┤\n")
sb.WriteString(fmt.Sprintf("│ %-9s │", "TOTAL"))
for _, r := range results {
sb.WriteString(fmt.Sprintf("%8.2f │", r.total))
}
sb.WriteString("\n")
return sb.String(), nil
}
Understanding what constitutes a bad evaluation is as important as knowing the right process. This section contrasts common anti-patterns with the correct approach.
// ❌ BAD — Common evaluation mistakes (what NOT to do)
// 1. Recency bias: scoring higher because a tool was released recently, not because
// it performs better on measured criteria.
badRecencyScore := func(name string) int {
if name == "NewFramework2025" {
return 5 // Arbitrary boost for recency — no evidence
}
return 3
}
// 2. Single-criterion evaluation: deciding based on one metric (e.g., GitHub stars).
func evaluateByStars(gitHubStars int) string {
if githubStars > 10000 {
return "This is the best tool" // Ignores security, license, performance
}
return "Not competitive"
}
// 3. Opinion-only scoring: assigning scores without evidence or benchmark data.
func opinionScore() []CriterionScore {
return []CriterionScore{
{Criterion: "Performance", Score: 4, Justification: "Feels fast to me"}, // ❌ Not verifiable
{Criterion: "API Quality", Score: 5, Justification: "I like the API"}, // ❌ Personal preference
}
}
// ✅ GOOD — Evidence-based evaluation (the correct approach)
// 1. Weighted multi-criteria scoring with evidence requirements.
func evidenceBasedScoring(candidate string) []CriterionScore {
scores := map[string]int{
"Performance": benchmarkMedian(candidate) < 50 * time.Millisecond ? 4 : 2,
"Type Safety": hasGenerics(candidate) && hasCompileTimeChecks(candidate) ? 5 : 3,
"CVE History": criticalCVEs(candidate) == 0 ? 5 : 1,
"License Fit": isOSIPermissive(candidate) ? 5 : 2, // Copyleft in proprietary project
}
return scoresToCriterionScores(scores) // Converts map to CriterionScore slice
}
// 2. PoC-based validation: measuring real integration performance, not benchmarks.
func runPoCCandidate(ctx context.Context, c Candidate) BenchmarkResult {
ctx, cancel := context.WithTimeout(ctx, 10*time.Minute)
defer cancel()
result, err := RunBenchmark(ctx, c, 5000) // 5k iterations under realistic load
if err != nil {
return BenchmarkResult{Name: c.Name, ErrorCount: 1}
}
return result
}
// 3. Security gate: hard rejection criteria that no amount of technical fit can override.
func securityGate(c Candidate) error {
// Check CVEs via govulncheck or equivalent.
vulns, err := ScanVulnerabilities(c)
if err != nil {
return fmt.Errorf("security scan failed: %w", err)
}
for _, v := range vulns {
if v.Severity == "CRITICAL" {
return fmt.Errorf("REJECTED: %s has critical CVE %s", c.Name, v.ID)
}
}
// Check license compatibility (OSI classification).
license := GetLicense(c)
if !isOSIPermissive(license) && !isOSICopyleftCompatible(license, "proprietary") {
return fmt.Errorf("REJECTED: %s has incompatible license %s", c.Name, license)
}
// Check supply chain: dependency depth and maintainer bus factor.
if transitiveDeps(c) > 50 {
return fmt.Errorf("ELEVATED RISK: %s has %d transitive dependencies (threshold: 50)",
c.Name, transitiveDeps(c))
}
return nil // Passes security gate — can be recommended pending other criteria
}
New tools receive inflated scores simply because they are recent. Fresh releases often have incomplete documentation, fewer production deployments, and unresolved edge cases that older tools have already encountered. Countermeasure: weight "years of production use" as a criterion alongside release recency.
Choosing a tool based on one metric — GitHub stars, benchmark speed, or API elegance — ignores the multi-dimensional nature of software engineering decisions. A fast library with GPL license is not faster if it cannot be used in your project. Countermeasure: always use weighted scoring across at least five categories.
The cost of upgrading a tool in the future (data migration, API changes, breaking version bumps) is rarely evaluated during selection. Tools with frequent major-version releases without deprecation periods create ongoing maintenance burden. Countermeasure: review the project's semver adherence — tools that release major versions for minor feature additions are higher-risk.
Benchmarks run on toy data with no real-world constraints (connection pooling, network latency, disk I/O) produce misleading results. A library that sorts a 100-element slice in 1μs may perform identically to its competitor when both are backed by the same slow database. Countermeasure: PoC benchmarks must use production-equivalent data and infrastructure.
While team expertise is a valid concern, it should not dominate the evaluation. Selecting an older tool because "we know it" creates long-term technical debt — security vulnerabilities go unfixed, performance ceilings are reached, and hiring becomes harder as the ecosystem moves on. Countermeasure: cap "Team Familiarity" weight at 15%. New tools can be learned.
Tools with hundreds of transitive dependencies carry compounding risk — every dependency is a potential CVE, license conflict, or maintenance burden. A tool with 5 direct deps and 20 total deps may be safer than a "minimal" tool with 1 dep and 200 transitive ones. Countermeasure: audit the full dependency graph, not just direct dependencies.
govulncheck, npm audit, pip-audit) on every candidate before manual review — never skip this stepWhen this skill is active, produce the following output:
| Skill | Purpose |
|---|---|
dependency-inversion-principle | Designs abstraction layers around the selected tool to minimize coupling and enable future replacement with minimal changes |
refactoring-techniques | Provides systematic migration strategies when replacing an incumbent tool with the selected alternative |
modular-design | Defines module boundaries before evaluation so criteria can be aligned with architectural concerns rather than feature checklists |
software-testing-strategy | Ensures the proof-of-concept includes representative test coverage that validates both correctness and performance characteristics |
When conducting security reviews and license analysis, reference these industry standards: