claude-roi 0.7.3 → 0.8.1
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 +66 -17
- 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;
|
|
@@ -717,9 +740,20 @@
|
|
|
717
740
|
width: 100%;
|
|
718
741
|
border-collapse: collapse;
|
|
719
742
|
font-size: 0.85rem;
|
|
720
|
-
|
|
743
|
+
table-layout: fixed;
|
|
744
|
+
}
|
|
745
|
+
/* Column widths: Date | Project | Model | Msgs | Autopilot | Cost | Commits | Lines | Grade */
|
|
746
|
+
table col:nth-child(1) { width: 13%; }
|
|
747
|
+
table col:nth-child(2) { width: 14%; }
|
|
748
|
+
table col:nth-child(3) { width: 18%; }
|
|
749
|
+
table col:nth-child(4) { width: 7%; }
|
|
750
|
+
table col:nth-child(5) { width: 10%; }
|
|
751
|
+
table col:nth-child(6) { width: 8%; }
|
|
752
|
+
table col:nth-child(7) { width: 9%; }
|
|
753
|
+
table col:nth-child(8) { width: 13%; }
|
|
754
|
+
table col:nth-child(9) { width: 8%; }
|
|
721
755
|
thead th {
|
|
722
|
-
padding: 14px
|
|
756
|
+
padding: 14px 12px;
|
|
723
757
|
text-align: left;
|
|
724
758
|
color: var(--text-muted);
|
|
725
759
|
font-family: var(--font-display);
|
|
@@ -734,6 +768,8 @@
|
|
|
734
768
|
transition: color 0.2s;
|
|
735
769
|
position: relative;
|
|
736
770
|
}
|
|
771
|
+
/* Center-align numeric columns (4-9): Msgs, Autopilot, Cost, Commits, Lines, Grade */
|
|
772
|
+
thead th:nth-child(n+4), tbody td:nth-child(n+4) { text-align: center; }
|
|
737
773
|
thead th:hover { color: var(--text-primary); }
|
|
738
774
|
thead th.sorted { color: var(--accent-blue); }
|
|
739
775
|
thead th.sorted::after {
|
|
@@ -752,9 +788,11 @@
|
|
|
752
788
|
tbody tr:hover { background: var(--overlay-soft); }
|
|
753
789
|
tbody tr:last-child { border-bottom: none; }
|
|
754
790
|
tbody td {
|
|
755
|
-
padding: 12px
|
|
791
|
+
padding: 12px 12px;
|
|
756
792
|
white-space: nowrap;
|
|
757
793
|
font-size: 0.85rem;
|
|
794
|
+
overflow: hidden;
|
|
795
|
+
text-overflow: ellipsis;
|
|
758
796
|
}
|
|
759
797
|
.grade-badge {
|
|
760
798
|
display: inline-flex;
|
|
@@ -1859,6 +1897,13 @@
|
|
|
1859
1897
|
<script>
|
|
1860
1898
|
const GRADE_VAR = { A: 'var(--grade-a)', B: 'var(--grade-b)', C: 'var(--grade-c)', D: 'var(--grade-d)', F: 'var(--grade-f)' };
|
|
1861
1899
|
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)' };
|
|
1900
|
+
function scoreColor(score) {
|
|
1901
|
+
if (score >= 80) return 'var(--grade-a)';
|
|
1902
|
+
if (score >= 60) return 'var(--grade-b)';
|
|
1903
|
+
if (score >= 40) return 'var(--grade-c)';
|
|
1904
|
+
if (score >= 20) return 'var(--grade-d)';
|
|
1905
|
+
return 'var(--text-muted)';
|
|
1906
|
+
}
|
|
1862
1907
|
const INSIGHT_ICONS = { warning: '!', success: '+', info: 'i', tip: '*' };
|
|
1863
1908
|
const DAY_LABELS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
1864
1909
|
let DATA = null;
|
|
@@ -2066,11 +2111,11 @@ function render() {
|
|
|
2066
2111
|
}
|
|
2067
2112
|
|
|
2068
2113
|
function renderHeroStats(s) {
|
|
2069
|
-
const
|
|
2070
|
-
const
|
|
2071
|
-
const
|
|
2114
|
+
const es = s.efficiencyScore || { score: 0, tier: 'Getting Started', letter: 'F', explanation: '', tip: '' };
|
|
2115
|
+
const sColor = scoreColor(es.score);
|
|
2116
|
+
const scoreDeg = Math.round(es.score * 3.6);
|
|
2072
2117
|
return `<div class="stats-section">
|
|
2073
|
-
<h2>Performance Overview <i class="info-tip" data-tip="High-level metrics for your AI coding sessions — cost, output, efficiency, and overall
|
|
2118
|
+
<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>
|
|
2074
2119
|
<div class="hero-stats">
|
|
2075
2120
|
<div class="hero-stats-left">
|
|
2076
2121
|
<div class="stat-card glow cost-card">
|
|
@@ -2104,11 +2149,14 @@ function renderHeroStats(s) {
|
|
|
2104
2149
|
</div>
|
|
2105
2150
|
</div>
|
|
2106
2151
|
</div>
|
|
2107
|
-
<div class="stat-card grade glow grade-card" style="--grade-color: ${
|
|
2108
|
-
<div class="label">
|
|
2152
|
+
<div class="stat-card grade glow grade-card" style="--grade-color: ${sColor}; --grade-deg: ${scoreDeg}deg;">
|
|
2153
|
+
<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>
|
|
2109
2154
|
<div class="grade-circle">
|
|
2110
|
-
<div class="value" style="color: ${
|
|
2155
|
+
<div class="value" style="color: ${sColor};">${es.score}</div>
|
|
2111
2156
|
</div>
|
|
2157
|
+
<div class="score-tier" style="color: ${sColor};">${es.tier}</div>
|
|
2158
|
+
<div class="score-context">${es.explanation}</div>
|
|
2159
|
+
<div class="score-tip">${es.tip}</div>
|
|
2112
2160
|
<div class="sub">${s.orphanedSessionRate}% sessions orphaned</div>
|
|
2113
2161
|
</div>
|
|
2114
2162
|
</div>
|
|
@@ -2116,7 +2164,7 @@ function renderHeroStats(s) {
|
|
|
2116
2164
|
<span><span class="dot" style="background: var(--accent-orange);"></span>Cost</span>
|
|
2117
2165
|
<span><span class="dot" style="background: var(--accent-green);"></span>Output</span>
|
|
2118
2166
|
<span><span class="dot" style="background: var(--accent-blue);"></span>Efficiency</span>
|
|
2119
|
-
<span><span class="dot" style="background: ${
|
|
2167
|
+
<span><span class="dot" style="background: ${sColor};"></span>Score: ${es.score} · ${es.tier}</span>
|
|
2120
2168
|
</div>
|
|
2121
2169
|
</div>`;
|
|
2122
2170
|
}
|
|
@@ -2485,6 +2533,7 @@ function renderSessionsTable(sessions) {
|
|
|
2485
2533
|
<h2>Sessions (${sessions.length})</h2>
|
|
2486
2534
|
<div class="sessions-table-wrap">
|
|
2487
2535
|
<table>
|
|
2536
|
+
<colgroup><col><col><col><col><col><col><col><col><col></colgroup>
|
|
2488
2537
|
<thead>
|
|
2489
2538
|
<tr>
|
|
2490
2539
|
<th onclick="sortTable('startTime')" class="${sortCol === 'startTime' ? 'sorted' : ''}">Date${thArrow('startTime')}</th>
|
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,
|