/**
* Development Velocity Measurement
*
* Task 1.4: Development Velocity Measurement
* Constraint: Unknown iteration speed → Measured 15x improvement
*
* Witness Outcome: Iteration time measured: before (30s) → after (2s) = 15x faster
*
* Acceptance Criteria:
* - [ ] Log timestamp on code change
* - [ ] Log timestamp on reload complete
* - [ ] Log timestamp on next test execution
* - [ ] Calculate: reload_latency = reload_complete - code_change
*/
interface MetricSnapshot {
changeTime?: number;
reloadTime?: number;
testTime?: number;
}
interface VelocityReport {
toolName: string;
reloadLatency: number;
totalCycleTime: number;
targetLatency: number;
targetCycleTime: number;
velocityMultiplier: number;
}
export class DevelopmentMetrics {
private metrics: Map<string, MetricSnapshot> = new Map();
private readonly targetReloadLatency = 100; // ms
private readonly targetCycleTime = 2000; // 2s (vs 30s baseline)
private readonly baselineCycleTime = 30000; // 30s without hot-reload
/**
* Record code change event
*/
onCodeChange(toolName: string): void {
const snapshot = this.getOrCreateSnapshot(toolName);
snapshot.changeTime = Date.now();
console.error(`[Metrics] Code changed: ${toolName} at ${snapshot.changeTime}`);
}
/**
* Record reload complete event
*/
onReloadComplete(toolName: string): void {
const snapshot = this.getOrCreateSnapshot(toolName);
snapshot.reloadTime = Date.now();
if (snapshot.changeTime) {
const latency = snapshot.reloadTime - snapshot.changeTime;
console.error(`[Metrics] Reload complete: ${toolName} (latency: ${latency}ms)`);
// Witness check: Reload latency <100ms
if (latency > this.targetReloadLatency) {
console.warn(
`[Metrics] ⚠️ Reload latency exceeded target: ${latency}ms > ${this.targetReloadLatency}ms`
);
} else {
console.error(`[Metrics] ✓ Reload latency within target: ${latency}ms`);
}
}
}
/**
* Record test execution event
*/
onTestExecution(toolName: string): void {
const snapshot = this.getOrCreateSnapshot(toolName);
snapshot.testTime = Date.now();
console.error(`[Metrics] Test executed: ${toolName} at ${snapshot.testTime}`);
// Generate velocity report if full cycle recorded
if (snapshot.changeTime && snapshot.reloadTime && snapshot.testTime) {
this.generateVelocityReport(toolName, snapshot);
}
}
/**
* Generate velocity report
*
* Calculates development velocity multiplier:
* - Baseline: 30s cycle time (manual restart)
* - Target: 2s cycle time (hot-reload)
* - Multiplier = baseline / actual
*/
private generateVelocityReport(toolName: string, snapshot: MetricSnapshot): void {
if (!snapshot.changeTime || !snapshot.reloadTime || !snapshot.testTime) {
return;
}
const reloadLatency = snapshot.reloadTime - snapshot.changeTime;
const totalCycleTime = snapshot.testTime - snapshot.changeTime;
const velocityMultiplier = this.baselineCycleTime / totalCycleTime;
const report: VelocityReport = {
toolName,
reloadLatency,
totalCycleTime,
targetLatency: this.targetReloadLatency,
targetCycleTime: this.targetCycleTime,
velocityMultiplier,
};
this.logVelocityReport(report);
// Reset snapshot for next cycle
this.metrics.delete(toolName);
}
/**
* Log velocity report
*/
private logVelocityReport(report: VelocityReport): void {
console.error('\n' + '='.repeat(60));
console.error('[Metrics] 🚀 Development Velocity Report');
console.error('='.repeat(60));
console.error(`Tool: ${report.toolName}`);
console.error(`Reload Latency: ${report.reloadLatency}ms (target: ${report.targetLatency}ms)`);
console.error(
`Total Cycle Time: ${report.totalCycleTime}ms (target: ${report.targetCycleTime}ms)`
);
console.error(`Velocity Multiplier: ${report.velocityMultiplier.toFixed(1)}x`);
// Witness check: 15x velocity improvement
if (report.velocityMultiplier >= 15) {
console.error(`[Metrics] ✓ Target velocity achieved: ${report.velocityMultiplier.toFixed(1)}x >= 15x`);
} else {
console.warn(
`[Metrics] ⚠️ Velocity below target: ${report.velocityMultiplier.toFixed(1)}x < 15x`
);
}
console.error('='.repeat(60) + '\n');
}
/**
* Get or create metric snapshot
*/
private getOrCreateSnapshot(toolName: string): MetricSnapshot {
let snapshot = this.metrics.get(toolName);
if (!snapshot) {
snapshot = {};
this.metrics.set(toolName, snapshot);
}
return snapshot;
}
/**
* Get current metrics (for debugging)
*/
getMetrics(): Map<string, MetricSnapshot> {
return new Map(this.metrics);
}
}