claude-roi 0.8.0 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/dashboard.html +65 -18
- package/src/metrics.js +43 -1
package/package.json
CHANGED
package/src/dashboard.html
CHANGED
|
@@ -297,7 +297,7 @@
|
|
|
297
297
|
/* Hero Stats — 3+1 layout */
|
|
298
298
|
.hero-stats {
|
|
299
299
|
display: grid;
|
|
300
|
-
grid-template-columns: 1fr
|
|
300
|
+
grid-template-columns: 1fr 240px;
|
|
301
301
|
gap: 20px;
|
|
302
302
|
align-items: stretch;
|
|
303
303
|
}
|
|
@@ -394,24 +394,47 @@
|
|
|
394
394
|
align-items: center;
|
|
395
395
|
justify-content: center;
|
|
396
396
|
text-align: center;
|
|
397
|
-
padding:
|
|
397
|
+
padding: 16px 16px 14px;
|
|
398
|
+
gap: 4px;
|
|
398
399
|
}
|
|
399
400
|
.stat-card.grade .value {
|
|
400
401
|
font-family: var(--font-display);
|
|
401
|
-
font-size:
|
|
402
|
+
font-size: 2rem;
|
|
402
403
|
line-height: 1;
|
|
403
404
|
font-weight: 800;
|
|
404
405
|
}
|
|
406
|
+
.stat-card.grade .label {
|
|
407
|
+
margin-bottom: 2px;
|
|
408
|
+
}
|
|
409
|
+
.stat-card.grade .sub {
|
|
410
|
+
margin: 0;
|
|
411
|
+
}
|
|
405
412
|
.grade-circle {
|
|
406
|
-
width:
|
|
407
|
-
height:
|
|
413
|
+
width: 80px;
|
|
414
|
+
height: 80px;
|
|
408
415
|
border-radius: 50%;
|
|
409
416
|
display: flex;
|
|
410
417
|
align-items: center;
|
|
411
418
|
justify-content: center;
|
|
412
|
-
margin-bottom:
|
|
419
|
+
margin-bottom: 2px;
|
|
413
420
|
position: relative;
|
|
414
421
|
}
|
|
422
|
+
.score-tier {
|
|
423
|
+
font-size: 0.75rem;
|
|
424
|
+
text-transform: uppercase;
|
|
425
|
+
letter-spacing: 0.1em;
|
|
426
|
+
font-weight: 700;
|
|
427
|
+
}
|
|
428
|
+
.score-context {
|
|
429
|
+
font-size: 0.68rem;
|
|
430
|
+
color: var(--text-secondary);
|
|
431
|
+
}
|
|
432
|
+
.score-tip {
|
|
433
|
+
font-size: 0.64rem;
|
|
434
|
+
color: var(--text-muted);
|
|
435
|
+
font-style: italic;
|
|
436
|
+
line-height: 1.3;
|
|
437
|
+
}
|
|
415
438
|
.grade-circle::before {
|
|
416
439
|
content: '';
|
|
417
440
|
position: absolute;
|
|
@@ -787,6 +810,19 @@
|
|
|
787
810
|
transform: scale(1.1);
|
|
788
811
|
}
|
|
789
812
|
tr.orphaned { background: rgba(245, 158, 11, 0.06); }
|
|
813
|
+
.expand-chevron {
|
|
814
|
+
display: inline-block;
|
|
815
|
+
width: 16px;
|
|
816
|
+
height: 16px;
|
|
817
|
+
margin-right: 6px;
|
|
818
|
+
vertical-align: middle;
|
|
819
|
+
transition: transform 0.2s ease;
|
|
820
|
+
color: var(--text-muted);
|
|
821
|
+
font-size: 0.7rem;
|
|
822
|
+
flex-shrink: 0;
|
|
823
|
+
}
|
|
824
|
+
tr:hover .expand-chevron { color: var(--accent-blue); }
|
|
825
|
+
tr.expanded .expand-chevron { transform: rotate(90deg); }
|
|
790
826
|
.expand-row {
|
|
791
827
|
display: none;
|
|
792
828
|
background: var(--overlay-subtle);
|
|
@@ -1874,6 +1910,13 @@
|
|
|
1874
1910
|
<script>
|
|
1875
1911
|
const GRADE_VAR = { A: 'var(--grade-a)', B: 'var(--grade-b)', C: 'var(--grade-c)', D: 'var(--grade-d)', F: 'var(--grade-f)' };
|
|
1876
1912
|
const GRADE_BG_VAR = { A: 'var(--grade-a-bg)', B: 'var(--grade-b-bg)', C: 'var(--grade-c-bg)', D: 'var(--grade-d-bg)', F: 'var(--grade-f-bg)' };
|
|
1913
|
+
function scoreColor(score) {
|
|
1914
|
+
if (score >= 80) return 'var(--grade-a)';
|
|
1915
|
+
if (score >= 60) return 'var(--grade-b)';
|
|
1916
|
+
if (score >= 40) return 'var(--grade-c)';
|
|
1917
|
+
if (score >= 20) return 'var(--grade-d)';
|
|
1918
|
+
return 'var(--text-muted)';
|
|
1919
|
+
}
|
|
1877
1920
|
const INSIGHT_ICONS = { warning: '!', success: '+', info: 'i', tip: '*' };
|
|
1878
1921
|
const DAY_LABELS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
1879
1922
|
let DATA = null;
|
|
@@ -2081,11 +2124,11 @@ function render() {
|
|
|
2081
2124
|
}
|
|
2082
2125
|
|
|
2083
2126
|
function renderHeroStats(s) {
|
|
2084
|
-
const
|
|
2085
|
-
const
|
|
2086
|
-
const
|
|
2127
|
+
const es = s.efficiencyScore || { score: 0, tier: 'Getting Started', letter: 'F', explanation: '', tip: '' };
|
|
2128
|
+
const sColor = scoreColor(es.score);
|
|
2129
|
+
const scoreDeg = Math.round(es.score * 3.6);
|
|
2087
2130
|
return `<div class="stats-section">
|
|
2088
|
-
<h2>Performance Overview <i class="info-tip" data-tip="High-level metrics for your AI coding sessions — cost, output, efficiency, and overall
|
|
2131
|
+
<h2>Performance Overview <i class="info-tip" data-tip="High-level metrics for your AI coding sessions — cost, output, efficiency, and overall score.">i</i></h2>
|
|
2089
2132
|
<div class="hero-stats">
|
|
2090
2133
|
<div class="hero-stats-left">
|
|
2091
2134
|
<div class="stat-card glow cost-card">
|
|
@@ -2119,11 +2162,14 @@ function renderHeroStats(s) {
|
|
|
2119
2162
|
</div>
|
|
2120
2163
|
</div>
|
|
2121
2164
|
</div>
|
|
2122
|
-
<div class="stat-card grade glow grade-card" style="--grade-color: ${
|
|
2123
|
-
<div class="label">
|
|
2165
|
+
<div class="stat-card grade glow grade-card" style="--grade-color: ${sColor}; --grade-deg: ${scoreDeg}deg;">
|
|
2166
|
+
<div class="label">Efficiency Score <i class="info-tip" data-tip="Score from 0–100 based on cost-per-commit and code survival rate. Higher is better.">i</i></div>
|
|
2124
2167
|
<div class="grade-circle">
|
|
2125
|
-
<div class="value" style="color: ${
|
|
2168
|
+
<div class="value" style="color: ${sColor};">${es.score}</div>
|
|
2126
2169
|
</div>
|
|
2170
|
+
<div class="score-tier" style="color: ${sColor};">${es.tier}</div>
|
|
2171
|
+
<div class="score-context">${es.explanation}</div>
|
|
2172
|
+
<div class="score-tip">${es.tip}</div>
|
|
2127
2173
|
<div class="sub">${s.orphanedSessionRate}% sessions orphaned</div>
|
|
2128
2174
|
</div>
|
|
2129
2175
|
</div>
|
|
@@ -2131,7 +2177,7 @@ function renderHeroStats(s) {
|
|
|
2131
2177
|
<span><span class="dot" style="background: var(--accent-orange);"></span>Cost</span>
|
|
2132
2178
|
<span><span class="dot" style="background: var(--accent-green);"></span>Output</span>
|
|
2133
2179
|
<span><span class="dot" style="background: var(--accent-blue);"></span>Efficiency</span>
|
|
2134
|
-
<span><span class="dot" style="background: ${
|
|
2180
|
+
<span><span class="dot" style="background: ${sColor};"></span>Score: ${es.score} · ${es.tier}</span>
|
|
2135
2181
|
</div>
|
|
2136
2182
|
</div>`;
|
|
2137
2183
|
}
|
|
@@ -2497,7 +2543,7 @@ function renderSessionsTable(sessions) {
|
|
|
2497
2543
|
const thArrow = col => col === sortCol ? (sortOrder === 1 ? ' ^' : ' v') : '';
|
|
2498
2544
|
|
|
2499
2545
|
return `<div class="sessions-section">
|
|
2500
|
-
<h2>Sessions (${sessions.length})</h2>
|
|
2546
|
+
<h2>Sessions (${sessions.length}) <span style="font-size:0.75rem;font-weight:400;color:var(--accent-blue);opacity:0.7;margin-left:6px;">▶ click a row to view git commits</span></h2>
|
|
2501
2547
|
<div class="sessions-table-wrap">
|
|
2502
2548
|
<table>
|
|
2503
2549
|
<colgroup><col><col><col><col><col><col><col><col><col></colgroup>
|
|
@@ -2529,8 +2575,8 @@ function renderSessionsTable(sessions) {
|
|
|
2529
2575
|
: primaryName;
|
|
2530
2576
|
const rowClass = s.isOrphaned ? 'orphaned' : '';
|
|
2531
2577
|
return `
|
|
2532
|
-
<tr class="${rowClass}" style="cursor:pointer;" onclick="toggleExpand(${idx})">
|
|
2533
|
-
<td>${formatDate(s.startTime)}</td>
|
|
2578
|
+
<tr class="${rowClass}" style="cursor:pointer;" onclick="toggleExpand(${idx}, this)">
|
|
2579
|
+
<td><span class="expand-chevron">▶</span>${formatDate(s.startTime)}</td>
|
|
2534
2580
|
<td>${s.projectName || '—'}</td>
|
|
2535
2581
|
<td>${modelDisplay}</td>
|
|
2536
2582
|
<td>${s.userMessageCount + s.assistantMessageCount}</td>
|
|
@@ -2947,9 +2993,10 @@ function bindEvents() {
|
|
|
2947
2993
|
}
|
|
2948
2994
|
}
|
|
2949
2995
|
|
|
2950
|
-
window.toggleExpand = function(idx) {
|
|
2996
|
+
window.toggleExpand = function(idx, clickedRow) {
|
|
2951
2997
|
const row = document.getElementById(`expand-${idx}`);
|
|
2952
2998
|
if (row) row.classList.toggle('open');
|
|
2999
|
+
if (clickedRow) clickedRow.classList.toggle('expanded');
|
|
2953
3000
|
};
|
|
2954
3001
|
|
|
2955
3002
|
window.sortTable = function(col) {
|
package/src/metrics.js
CHANGED
|
@@ -262,6 +262,45 @@ function computeEfficiencyGrade(costPerCommit, survivalRate) {
|
|
|
262
262
|
return 'F';
|
|
263
263
|
}
|
|
264
264
|
|
|
265
|
+
function computeEfficiencyScore(costPerCommit, survivalRate, orphanedRate, totalCommits) {
|
|
266
|
+
if (totalCommits === 0) {
|
|
267
|
+
return {
|
|
268
|
+
score: 0, tier: 'Getting Started', letter: 'F',
|
|
269
|
+
explanation: 'No commits matched to sessions yet — this is normal for exploratory work.',
|
|
270
|
+
tip: 'Commits are matched by file overlap with Claude-edited files.',
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Score: 50 pts from cost efficiency (log scale) + 50 pts from survival rate
|
|
275
|
+
let costScore;
|
|
276
|
+
if (costPerCommit <= 2) costScore = 50;
|
|
277
|
+
else if (costPerCommit >= 50) costScore = 0;
|
|
278
|
+
else costScore = Math.max(0, 50 * (1 - Math.log(costPerCommit / 2) / Math.log(25)));
|
|
279
|
+
const survivalScore = Math.min(survivalRate, 100) / 100 * 50;
|
|
280
|
+
const score = Math.round(costScore + survivalScore);
|
|
281
|
+
|
|
282
|
+
const tier = score >= 80 ? 'Excellent' : score >= 60 ? 'Solid' : score >= 40 ? 'Developing' : score >= 20 ? 'Early' : 'Getting Started';
|
|
283
|
+
const letter = score >= 80 ? 'A' : score >= 60 ? 'B' : score >= 40 ? 'C' : score >= 20 ? 'D' : 'F';
|
|
284
|
+
|
|
285
|
+
// Build explanation from actual metrics
|
|
286
|
+
const costAdj = costPerCommit <= 2 ? 'excellent' : costPerCommit <= 5 ? 'good' : costPerCommit <= 15 ? 'moderate' : 'high';
|
|
287
|
+
const explanation = `$${costPerCommit.toFixed(2)}/commit (${costAdj}) · ${Math.round(survivalRate)}% code survival`;
|
|
288
|
+
|
|
289
|
+
// Actionable tip based on weakest metric
|
|
290
|
+
let tip;
|
|
291
|
+
if (costScore < survivalScore) {
|
|
292
|
+
tip = 'Try shorter, focused sessions to reduce cost per commit.';
|
|
293
|
+
} else if (survivalRate < 50) {
|
|
294
|
+
tip = 'Review AI-generated code before committing to improve survival rate.';
|
|
295
|
+
} else if (orphanedRate > 40) {
|
|
296
|
+
tip = `${orphanedRate}% of sessions had no commits — some may be exploratory, which is fine.`;
|
|
297
|
+
} else {
|
|
298
|
+
tip = 'Keep it up — your efficiency is on track.';
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return { score, tier, letter, explanation, tip };
|
|
302
|
+
}
|
|
303
|
+
|
|
265
304
|
function computeSessionGrade(session) {
|
|
266
305
|
if (session.commitCount === 0) return 'F';
|
|
267
306
|
const costPerCommit = session.cost.totalCost / session.commitCount;
|
|
@@ -508,9 +547,11 @@ export function computeMetrics(correlatedSessions, organicCommits, commitsByRepo
|
|
|
508
547
|
const lineSurvival = computeLineSurvival(commitsByRepo);
|
|
509
548
|
|
|
510
549
|
const avgCost = totalCommits > 0 ? totalCost / totalCommits : 0;
|
|
550
|
+
const orphanedSessionRate = totalSessions > 0 ? Math.round((orphanedCount / totalSessions) * 100) : 0;
|
|
511
551
|
const overallGrade = totalCommits > 0
|
|
512
552
|
? computeEfficiencyGrade(avgCost, lineSurvival.survivalRate)
|
|
513
553
|
: 'F';
|
|
554
|
+
const efficiencyScore = computeEfficiencyScore(avgCost, lineSurvival.survivalRate, orphanedSessionRate, totalCommits);
|
|
514
555
|
|
|
515
556
|
// ---- Daily timeline ----
|
|
516
557
|
const dailyMap = new Map();
|
|
@@ -679,9 +720,10 @@ export function computeMetrics(correlatedSessions, organicCommits, commitsByRepo
|
|
|
679
720
|
avgCostPerLine: totalLinesAdded > 0 ? totalCost / totalLinesAdded : null,
|
|
680
721
|
totalInputTokens,
|
|
681
722
|
totalOutputTokens,
|
|
682
|
-
orphanedSessionRate
|
|
723
|
+
orphanedSessionRate,
|
|
683
724
|
lineSurvivalRate: lineSurvival.survivalRate,
|
|
684
725
|
overallGrade,
|
|
726
|
+
efficiencyScore,
|
|
685
727
|
totalCommitsOnMain,
|
|
686
728
|
mainBranchPct: totalCommits > 0 ? Math.round((totalCommitsOnMain / totalCommits) * 100) : 0,
|
|
687
729
|
organicCommitCount: organicCommits.length,
|