ty.
- Impact: Revenue potential and user value.
- Risk: Technical debt, dependency stability, and market risk.
- Strategic Fit: Alignment with long-term goals and skill stack.
2. TypeScript Implementation
The core engine uses a weighted scoring algorithm. We define interfaces for assets and metrics, then implement a calculator that normalizes scores and applies dynamic weights.
// src/matrix/types.ts
export type AssetType = 'saas' | 'library' | 'template' | 'tool';
export interface AssetMetrics {
// Quantitative inputs
mrr: number;
monthlyActiveUsers: number;
openIssuesCount: number;
dependencyVulnerabilities: number;
lastCommitDaysAgo: number;
// Qualitative inputs (1-10 scale)
maintenanceComplexity: number; // 1=Easy, 10=Hard
marketGrowthPotential: number; // 1=Low, 10=High
strategicAlignment: number; // 1=None, 10=Core
}
export interface MatrixConfig {
weights: {
effort: number;
impact: number;
risk: number;
strategy: number;
};
thresholds: {
killScore: number;
investScore: number;
harvestScore: number;
};
}
export interface AssetScore {
total: number;
breakdown: {
effort: number;
impact: number;
risk: number;
strategy: number;
};
recommendation: 'invest' | 'harvest' | 'maintain' | 'sunset';
}
// src/matrix/engine.ts
import { AssetMetrics, MatrixConfig, AssetScore } from './types';
export class ProductMatrixEngine {
private config: MatrixConfig;
constructor(config: MatrixConfig) {
this.config = config;
}
/**
* Calculates the matrix score for a digital asset.
* Scores are normalized to a 0-100 scale.
*/
calculateScore(asset: { type: AssetType; metrics: AssetMetrics }): AssetScore {
const { mrr, openIssuesCount, maintenanceComplexity, marketGrowthPotential, strategicAlignment, lastCommitDaysAgo } = asset.metrics;
// 1. Impact Score: Revenue and Growth weighted
const impactScore = this.normalize(
(Math.log10(mrr + 1) * 40) +
(marketGrowthPotential * 6)
);
// 2. Effort Score: Complexity and Staleness (inverse relationship)
const effortScore = this.normalize(
(maintenanceComplexity * 8) +
(lastCommitDaysAgo > 30 ? 20 : 0)
);
// 3. Risk Score: Debt and Dependencies
const riskScore = this.normalize(
(openIssuesCount * 0.5) +
(maintenanceComplexity > 7 ? 30 : 0)
);
// 4. Strategy Score: Alignment
const strategyScore = this.normalize(
strategicAlignment * 10
);
// Weighted Total
const total =
(impactScore * this.config.weights.impact) +
(effortScore * this.config.weights.effort) +
(riskScore * this.config.weights.risk) +
(strategyScore * this.config.weights.strategy);
const recommendation = this.getRecommendation(total);
return {
total,
breakdown: { effort: effortScore, impact: impactScore, risk: riskScore, strategy: strategyScore },
recommendation
};
}
private normalize(value: number): number {
return Math.min(100, Math.max(0, value));
}
private getRecommendation(score: number): AssetScore['recommendation'] {
if (score >= this.config.thresholds.investScore) return 'invest';
if (score >= this.config.thresholds.harvestScore) return 'harvest';
if (score >= this.config.thresholds.killScore) return 'maintain';
return 'sunset';
}
}
3. Automated Data Ingestion
To prevent manual data entry, the system should ingest metrics from GitHub/GitLab APIs. A cron job or CI pipeline step can update the AssetMetrics based on repository activity.
// src/metrics/github-ingestor.ts
import { Octokit } from '@octokit/rest';
export async function fetchRepoMetrics(repo: string, token: string): Promise<Partial<AssetMetrics>> {
const octokit = new Octokit({ auth: token });
const [owner, name] = repo.split('/');
const { data: repoData } = await octokit.repos.get({ owner, repo: name });
const { data: issues } = await octokit.issues.listForRepo({ owner, repo: name, state: 'open' });
// Calculate days since last commit
const { data: commits } = await octokit.repos.listCommits({ owner, repo: name, per_page: 1 });
const lastCommitDate = commits[0]?.commit?.committer?.date
? new Date(commits[0].commit.committer.date)
: new Date();
const daysAgo = Math.floor((Date.now() - lastCommitDate.getTime()) / (1000 * 60 * 60 * 24));
return {
openIssuesCount: issues.length,
lastCommitDaysAgo: daysAgo,
// Star count can proxy for market interest
marketGrowthPotential: Math.min(10, Math.log10(repoData.stargazers_count + 1) * 3)
};
}
4. Dashboard Integration
The matrix scores should feed into a lightweight dashboard. For indie developers, a static site generator (like Next.js or Astro) consuming a JSON output from the matrix engine is sufficient. This dashboard visualizes the portfolio as a scatter plot (Effort vs. Impact) and lists assets by recommendation status, enabling rapid decision-making during weekly reviews.
Pitfall Guide
Implementing a Product Matrix introduces new complexities. Avoid these common mistakes based on production experience with indie portfolios.
-
Over-Engineering the Scoring Model:
- Mistake: Creating complex algorithms with 20+ variables.
- Consequence: The matrix becomes opaque and hard to maintain. Developers stop trusting the scores.
- Best Practice: Limit dimensions to four core pillars. Use logarithmic scaling for metrics like MRR to prevent outliers from skewing the entire matrix.
-
Ignoring "Zombie" Assets:
- Mistake: Focusing only on active projects and neglecting archived or low-traffic repositories.
- Consequence: Security vulnerabilities and dependency rot in unused assets cause incidents.
- Best Practice: Include all repositories in the matrix. Assign a "dormant" flag that triggers automated dependency updates but suppresses feature development recommendations.
-
Static Weighting:
- Mistake: Setting weights once and never adjusting them.
- Consequence: The matrix fails to adapt to changing business phases (e.g., growth vs. stability).
- Best Practice: Review weights quarterly. During a cash-flow crisis, increase the weight of
impact and risk. During R&D phases, increase strategy.
-
Analysis Paralysis:
- Mistake: Spending more time optimizing the matrix than building products.
- Consequence: Zero shipping velocity.
- Best Practice: Cap matrix review time to 30 minutes per week. The matrix is a decision aid, not a game. If a decision is obvious, skip the calculation.
-
Vanity Metrics Inclusion:
- Mistake: Including metrics like "GitHub Stars" or "Twitter Followers" as primary inputs.
- Consequence: Prioritizing marketing-friendly features over revenue-generating ones.
- Best Practice: Correlate vanity metrics with business outcomes. Only include them if there is a proven conversion path to revenue or retention.
-
Emotional Attachment Bias:
- Mistake: Manually overriding matrix recommendations to save a favorite project.
- Consequence: Resource drain on low-value assets.
- Best Practice: Treat the matrix as an objective advisor. If you override a "sunset" recommendation, document the business case and set a hard deadline for re-evaluation.
-
Lack of Actionable Outputs:
- Mistake: Generating scores without clear actions.
- Consequence: Developers see a score but don't know what to do.
- Best Practice: Map scores to explicit actions.
Invest = Allocate 70% dev time. Harvest = Optimize monetization, reduce dev time. Sunset = Archive code, notify users, redirect traffic.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| New Feature Request | Impact/Effort Quadrant | Quickly filters high-value, low-cost features. Prevents scope creep on mature products. | Low dev cost; increases retention if high impact. |
| Legacy Bug Report | Risk/Debt Assessment | Prioritizes bugs based on security risk and technical debt accumulation rather than user volume. | Reduces long-term maintenance cost; mitigates security liability. |
| New Project Idea | Strategy/Market Validation | Evaluates alignment with existing stack and market gaps before coding begins. | Prevents wasted dev time on misaligned or saturated markets. |
| Portfolio Review | Full Matrix Score Analysis | Holistic view of portfolio health; identifies assets requiring investment vs. sunsetting. | Optimizes resource allocation; stabilizes revenue streams. |
| Dependency Update | Automated Risk Scan | CI pipeline checks vulnerability scores; triggers updates only when risk threshold is breached. | Reduces manual overhead; ensures security compliance efficiently. |
Configuration Template
Copy this configuration to bootstrap your matrix. Adjust weights based on your current phase.
// product-matrix.config.ts
import { MatrixConfig } from './src/matrix/types';
export const matrixConfig: MatrixConfig = {
weights: {
// Current focus: Growth
impact: 0.40, // Revenue and user value
effort: 0.20, // Development cost
risk: 0.20, // Technical and market risk
strategy: 0.20 // Long-term alignment
},
thresholds: {
investScore: 75, // Top tier: Allocate maximum resources
harvestScore: 50, // Mid tier: Optimize revenue, minimize dev
killScore: 25 // Bottom tier: Plan sunset or pivot
}
};
// Usage
// const engine = new ProductMatrixEngine(matrixConfig);
Quick Start Guide
-
Initialize Repository:
mkdir indie-product-matrix && cd indie-product-matrix
npm init -y
npm install typescript @octokit/rest dotenv
npx tsc --init
-
Add Core Files:
Create src/matrix/types.ts, src/matrix/engine.ts, and product-matrix.config.ts using the code templates above.
-
Configure Environment:
Create .env with your GitHub token and asset list:
GITHUB_TOKEN=ghp_xxxx
ASSETS=my-saas-repo,my-tool-repo,my-template-repo
-
Run Audit:
Create src/audit.ts to load assets, fetch metrics, calculate scores, and output a JSON report.
npx ts-node src/audit.ts
Review the output JSON for recommendations.
-
Iterate:
Integrate the audit script into your weekly workflow. Update qualitative metrics manually in a assets.json file if needed, and let the engine drive your prioritization.