@vibecheckai/cli 3.2.2 → 3.2.4
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/bin/.generated +25 -25
- package/bin/dev/run-v2-torture.js +30 -30
- package/bin/runners/ENHANCEMENT_GUIDE.md +121 -121
- package/bin/runners/lib/__tests__/entitlements-v2.test.js +295 -295
- package/bin/runners/lib/agent-firewall/ai/false-positive-analyzer.js +474 -0
- package/bin/runners/lib/agent-firewall/claims/extractor.js +117 -28
- package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +23 -14
- package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +72 -1
- package/bin/runners/lib/agent-firewall/interceptor/base.js +2 -2
- package/bin/runners/lib/agent-firewall/policy/default-policy.json +6 -0
- package/bin/runners/lib/agent-firewall/policy/engine.js +34 -3
- package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +29 -4
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +12 -0
- package/bin/runners/lib/agent-firewall/truthpack/loader.js +21 -0
- package/bin/runners/lib/agent-firewall/utils/ignore-checker.js +118 -0
- package/bin/runners/lib/analyzers.js +606 -325
- package/bin/runners/lib/auth-truth.js +193 -193
- package/bin/runners/lib/backup.js +62 -62
- package/bin/runners/lib/billing.js +107 -107
- package/bin/runners/lib/claims.js +118 -118
- package/bin/runners/lib/cli-ui.js +540 -540
- package/bin/runners/lib/contracts/auth-contract.js +202 -202
- package/bin/runners/lib/contracts/env-contract.js +181 -181
- package/bin/runners/lib/contracts/external-contract.js +206 -206
- package/bin/runners/lib/contracts/guard.js +168 -168
- package/bin/runners/lib/contracts/index.js +89 -89
- package/bin/runners/lib/contracts/plan-validator.js +311 -311
- package/bin/runners/lib/contracts/route-contract.js +199 -199
- package/bin/runners/lib/contracts.js +804 -804
- package/bin/runners/lib/detect.js +89 -89
- package/bin/runners/lib/doctor/autofix.js +254 -254
- package/bin/runners/lib/doctor/index.js +37 -37
- package/bin/runners/lib/doctor/modules/dependencies.js +325 -325
- package/bin/runners/lib/doctor/modules/index.js +46 -46
- package/bin/runners/lib/doctor/modules/network.js +250 -250
- package/bin/runners/lib/doctor/modules/project.js +312 -312
- package/bin/runners/lib/doctor/modules/runtime.js +224 -224
- package/bin/runners/lib/doctor/modules/security.js +348 -348
- package/bin/runners/lib/doctor/modules/system.js +213 -213
- package/bin/runners/lib/doctor/modules/vibecheck.js +394 -394
- package/bin/runners/lib/doctor/reporter.js +262 -262
- package/bin/runners/lib/doctor/service.js +262 -262
- package/bin/runners/lib/doctor/types.js +113 -113
- package/bin/runners/lib/doctor/ui.js +263 -263
- package/bin/runners/lib/doctor-v2.js +608 -608
- package/bin/runners/lib/drift.js +425 -425
- package/bin/runners/lib/enforcement.js +72 -72
- package/bin/runners/lib/engines/accessibility-engine.js +190 -0
- package/bin/runners/lib/engines/api-consistency-engine.js +162 -0
- package/bin/runners/lib/engines/ast-cache.js +99 -0
- package/bin/runners/lib/engines/code-quality-engine.js +255 -0
- package/bin/runners/lib/engines/console-logs-engine.js +115 -0
- package/bin/runners/lib/engines/cross-file-analysis-engine.js +268 -0
- package/bin/runners/lib/engines/dead-code-engine.js +198 -0
- package/bin/runners/lib/engines/deprecated-api-engine.js +226 -0
- package/bin/runners/lib/engines/empty-catch-engine.js +150 -0
- package/bin/runners/lib/engines/file-filter.js +131 -0
- package/bin/runners/lib/engines/hardcoded-secrets-engine.js +251 -0
- package/bin/runners/lib/engines/mock-data-engine.js +272 -0
- package/bin/runners/lib/engines/parallel-processor.js +71 -0
- package/bin/runners/lib/engines/performance-issues-engine.js +265 -0
- package/bin/runners/lib/engines/security-vulnerabilities-engine.js +243 -0
- package/bin/runners/lib/engines/todo-fixme-engine.js +115 -0
- package/bin/runners/lib/engines/type-aware-engine.js +152 -0
- package/bin/runners/lib/engines/unsafe-regex-engine.js +225 -0
- package/bin/runners/lib/engines/vibecheck-engines/README.md +53 -0
- package/bin/runners/lib/engines/vibecheck-engines/index.js +15 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +291 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +83 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +198 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +275 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +167 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +139 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +140 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +234 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +78 -0
- package/bin/runners/lib/engines/vibecheck-engines/package.json +13 -0
- package/bin/runners/lib/enterprise-detect.js +603 -603
- package/bin/runners/lib/enterprise-init.js +942 -942
- package/bin/runners/lib/env-resolver.js +417 -417
- package/bin/runners/lib/env-template.js +66 -66
- package/bin/runners/lib/env.js +189 -189
- package/bin/runners/lib/extractors/client-calls.js +990 -990
- package/bin/runners/lib/extractors/fastify-route-dump.js +573 -573
- package/bin/runners/lib/extractors/fastify-routes.js +426 -426
- package/bin/runners/lib/extractors/index.js +363 -363
- package/bin/runners/lib/extractors/next-routes.js +524 -524
- package/bin/runners/lib/extractors/proof-graph.js +431 -431
- package/bin/runners/lib/extractors/route-matcher.js +451 -451
- package/bin/runners/lib/extractors/truthpack-v2.js +377 -377
- package/bin/runners/lib/extractors/ui-bindings.js +547 -547
- package/bin/runners/lib/findings-schema.js +281 -281
- package/bin/runners/lib/firewall-prompt.js +50 -50
- package/bin/runners/lib/global-flags.js +213 -213
- package/bin/runners/lib/graph/graph-builder.js +265 -265
- package/bin/runners/lib/graph/html-renderer.js +413 -413
- package/bin/runners/lib/graph/index.js +32 -32
- package/bin/runners/lib/graph/runtime-collector.js +215 -215
- package/bin/runners/lib/graph/static-extractor.js +518 -518
- package/bin/runners/lib/html-report.js +650 -650
- package/bin/runners/lib/interactive-menu.js +1496 -1496
- package/bin/runners/lib/llm.js +75 -75
- package/bin/runners/lib/meter.js +61 -61
- package/bin/runners/lib/missions/evidence.js +126 -126
- package/bin/runners/lib/patch.js +40 -40
- package/bin/runners/lib/permissions/auth-model.js +213 -213
- package/bin/runners/lib/permissions/idor-prover.js +205 -205
- package/bin/runners/lib/permissions/index.js +45 -45
- package/bin/runners/lib/permissions/matrix-builder.js +198 -198
- package/bin/runners/lib/pkgjson.js +28 -28
- package/bin/runners/lib/policy.js +295 -295
- package/bin/runners/lib/preflight.js +142 -142
- package/bin/runners/lib/reality/correlation-detectors.js +359 -359
- package/bin/runners/lib/reality/index.js +318 -318
- package/bin/runners/lib/reality/request-hashing.js +416 -416
- package/bin/runners/lib/reality/request-mapper.js +453 -453
- package/bin/runners/lib/reality/safety-rails.js +463 -463
- package/bin/runners/lib/reality/semantic-snapshot.js +408 -408
- package/bin/runners/lib/reality/toast-detector.js +393 -393
- package/bin/runners/lib/reality-findings.js +84 -84
- package/bin/runners/lib/receipts.js +179 -179
- package/bin/runners/lib/redact.js +29 -29
- package/bin/runners/lib/replay/capsule-manager.js +154 -154
- package/bin/runners/lib/replay/index.js +263 -263
- package/bin/runners/lib/replay/player.js +348 -348
- package/bin/runners/lib/replay/recorder.js +331 -331
- package/bin/runners/lib/report-output.js +187 -187
- package/bin/runners/lib/report.js +135 -135
- package/bin/runners/lib/route-detection.js +1140 -1140
- package/bin/runners/lib/sandbox/index.js +59 -59
- package/bin/runners/lib/sandbox/proof-chain.js +399 -399
- package/bin/runners/lib/sandbox/sandbox-runner.js +205 -205
- package/bin/runners/lib/sandbox/worktree.js +174 -174
- package/bin/runners/lib/scan-output.js +525 -190
- package/bin/runners/lib/schema-validator.js +350 -350
- package/bin/runners/lib/schemas/contracts.schema.json +160 -160
- package/bin/runners/lib/schemas/finding.schema.json +100 -100
- package/bin/runners/lib/schemas/mission-pack.schema.json +206 -206
- package/bin/runners/lib/schemas/proof-graph.schema.json +176 -176
- package/bin/runners/lib/schemas/reality-report.schema.json +162 -162
- package/bin/runners/lib/schemas/share-pack.schema.json +180 -180
- package/bin/runners/lib/schemas/ship-report.schema.json +117 -117
- package/bin/runners/lib/schemas/truthpack-v2.schema.json +303 -303
- package/bin/runners/lib/schemas/validator.js +438 -438
- package/bin/runners/lib/score-history.js +282 -282
- package/bin/runners/lib/share-pack.js +239 -239
- package/bin/runners/lib/snippets.js +67 -67
- package/bin/runners/lib/status-output.js +253 -253
- package/bin/runners/lib/terminal-ui.js +351 -271
- package/bin/runners/lib/upsell.js +510 -510
- package/bin/runners/lib/usage.js +153 -153
- package/bin/runners/lib/validate-patch.js +156 -156
- package/bin/runners/lib/verdict-engine.js +628 -628
- package/bin/runners/reality/engine.js +917 -917
- package/bin/runners/reality/flows.js +122 -122
- package/bin/runners/reality/report.js +378 -378
- package/bin/runners/reality/session.js +193 -193
- package/bin/runners/runGuard.js +168 -168
- package/bin/runners/runProof.zip +0 -0
- package/bin/runners/runProve.js +8 -0
- package/bin/runners/runReality.js +14 -0
- package/bin/runners/runScan.js +17 -1
- package/bin/runners/runTruth.js +15 -3
- package/mcp-server/tier-auth.js +4 -4
- package/mcp-server/tools/index.js +72 -72
- package/package.json +1 -1
|
@@ -1,282 +1,282 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Score History Tracking
|
|
3
|
-
*
|
|
4
|
-
* Tracks Vibe Score history over time for trend analysis,
|
|
5
|
-
* streaks, and gamification.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const fs = require("fs");
|
|
9
|
-
const path = require("path");
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Get history file path for a project
|
|
13
|
-
*/
|
|
14
|
-
function getHistoryPath(projectPath) {
|
|
15
|
-
return path.join(projectPath, ".vibecheck", "history.json");
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Load score history
|
|
20
|
-
*/
|
|
21
|
-
function loadHistory(projectPath) {
|
|
22
|
-
const historyPath = getHistoryPath(projectPath);
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
if (fs.existsSync(historyPath)) {
|
|
26
|
-
return JSON.parse(fs.readFileSync(historyPath, "utf8"));
|
|
27
|
-
}
|
|
28
|
-
} catch {}
|
|
29
|
-
|
|
30
|
-
return {
|
|
31
|
-
projectId: path.basename(projectPath),
|
|
32
|
-
checks: [],
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Save score history
|
|
38
|
-
*/
|
|
39
|
-
function saveHistory(projectPath, history) {
|
|
40
|
-
const historyPath = getHistoryPath(projectPath);
|
|
41
|
-
const dir = path.dirname(historyPath);
|
|
42
|
-
|
|
43
|
-
try {
|
|
44
|
-
if (!fs.existsSync(dir)) {
|
|
45
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
46
|
-
}
|
|
47
|
-
fs.writeFileSync(historyPath, JSON.stringify(history, null, 2));
|
|
48
|
-
} catch (err) {
|
|
49
|
-
console.warn(`Warning: Could not save history: ${err.message}`);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Record a new check
|
|
55
|
-
*/
|
|
56
|
-
function recordCheck(projectPath, checkData) {
|
|
57
|
-
const history = loadHistory(projectPath);
|
|
58
|
-
|
|
59
|
-
const check = {
|
|
60
|
-
timestamp: new Date().toISOString(),
|
|
61
|
-
score: checkData.score || 0,
|
|
62
|
-
verdict: checkData.verdict || "BLOCK",
|
|
63
|
-
commitSha: checkData.commitSha || null,
|
|
64
|
-
commitMessage: checkData.commitMessage || null,
|
|
65
|
-
branch: checkData.branch || null,
|
|
66
|
-
categoryScores: checkData.categoryScores || {},
|
|
67
|
-
findingCounts: checkData.findingCounts || {
|
|
68
|
-
critical: 0,
|
|
69
|
-
high: 0,
|
|
70
|
-
medium: 0,
|
|
71
|
-
low: 0,
|
|
72
|
-
},
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
history.checks.unshift(check);
|
|
76
|
-
|
|
77
|
-
// Keep only last 100 checks
|
|
78
|
-
if (history.checks.length > 100) {
|
|
79
|
-
history.checks = history.checks.slice(0, 100);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
saveHistory(projectPath, history);
|
|
83
|
-
|
|
84
|
-
return check;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Calculate trend from recent checks
|
|
89
|
-
*/
|
|
90
|
-
function calculateTrend(projectPath, limit = 10) {
|
|
91
|
-
const history = loadHistory(projectPath);
|
|
92
|
-
const recentChecks = history.checks.slice(0, limit);
|
|
93
|
-
|
|
94
|
-
if (recentChecks.length < 2) {
|
|
95
|
-
return {
|
|
96
|
-
direction: "stable",
|
|
97
|
-
delta: 0,
|
|
98
|
-
previousScore: null,
|
|
99
|
-
streak: null,
|
|
100
|
-
sparkline: [],
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const latest = recentChecks[0].score;
|
|
105
|
-
const previous = recentChecks[1].score;
|
|
106
|
-
const delta = latest - previous;
|
|
107
|
-
|
|
108
|
-
// Calculate streak
|
|
109
|
-
let streak = { count: 1, type: recentChecks[0].verdict };
|
|
110
|
-
for (let i = 1; i < recentChecks.length; i++) {
|
|
111
|
-
if (recentChecks[i].verdict === streak.type) {
|
|
112
|
-
streak.count++;
|
|
113
|
-
} else {
|
|
114
|
-
break;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Generate sparkline data (last 10 scores)
|
|
119
|
-
const sparkline = recentChecks.slice(0, 10).map(c => c.score).reverse();
|
|
120
|
-
|
|
121
|
-
return {
|
|
122
|
-
direction: delta > 0 ? "up" : delta < 0 ? "down" : "stable",
|
|
123
|
-
delta: Math.abs(delta),
|
|
124
|
-
previousScore: previous,
|
|
125
|
-
streak: streak.count >= 3 ? streak : null,
|
|
126
|
-
sparkline,
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Get milestones achieved
|
|
132
|
-
*/
|
|
133
|
-
function getMilestones(projectPath) {
|
|
134
|
-
const history = loadHistory(projectPath);
|
|
135
|
-
const milestones = [];
|
|
136
|
-
|
|
137
|
-
if (history.checks.length === 0) return milestones;
|
|
138
|
-
|
|
139
|
-
const latest = history.checks[0];
|
|
140
|
-
|
|
141
|
-
// First check
|
|
142
|
-
if (history.checks.length === 1) {
|
|
143
|
-
milestones.push("First check completed!");
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// First 90+ score
|
|
147
|
-
const first90 = history.checks.find(c => c.score >= 90);
|
|
148
|
-
if (first90 && first90 === latest) {
|
|
149
|
-
milestones.push("First 90+ score!");
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Perfect 100
|
|
153
|
-
if (latest.score === 100) {
|
|
154
|
-
milestones.push("Perfect 100! 🏆");
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Streak milestones
|
|
158
|
-
const trend = calculateTrend(projectPath);
|
|
159
|
-
if (trend.streak) {
|
|
160
|
-
if (trend.streak.count >= 10 && trend.streak.type === "SHIP") {
|
|
161
|
-
milestones.push("10 SHIP streak! 🔥🔥🔥");
|
|
162
|
-
} else if (trend.streak.count >= 5 && trend.streak.type === "SHIP") {
|
|
163
|
-
milestones.push("5 SHIP streak! 🔥");
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Improvement milestones
|
|
168
|
-
if (trend.direction === "up" && trend.delta >= 10) {
|
|
169
|
-
milestones.push(`+${trend.delta} points improvement!`);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return milestones;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Format history for CLI display
|
|
177
|
-
*/
|
|
178
|
-
function formatHistoryDisplay(projectPath, limit = 10) {
|
|
179
|
-
const c = {
|
|
180
|
-
reset: "\x1b[0m",
|
|
181
|
-
bold: "\x1b[1m",
|
|
182
|
-
dim: "\x1b[2m",
|
|
183
|
-
red: "\x1b[31m",
|
|
184
|
-
green: "\x1b[32m",
|
|
185
|
-
yellow: "\x1b[33m",
|
|
186
|
-
cyan: "\x1b[36m",
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
const history = loadHistory(projectPath);
|
|
190
|
-
const trend = calculateTrend(projectPath);
|
|
191
|
-
const milestones = getMilestones(projectPath);
|
|
192
|
-
|
|
193
|
-
if (history.checks.length === 0) {
|
|
194
|
-
return `${c.dim}No history yet. Run \`vibecheck ship\` to start tracking.${c.reset}`;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
let output = `\n${c.bold}📊 Score History${c.reset}\n\n`;
|
|
198
|
-
|
|
199
|
-
// Trend line
|
|
200
|
-
if (trend.direction !== "stable") {
|
|
201
|
-
const trendIcon = trend.direction === "up" ? "↑" : "↓";
|
|
202
|
-
const trendColor = trend.direction === "up" ? c.green : c.red;
|
|
203
|
-
output += ` ${trendColor}${trendIcon} ${trend.delta} since last check${c.reset}\n`;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Streak
|
|
207
|
-
if (trend.streak) {
|
|
208
|
-
output += ` ${c.yellow}🔥 ${trend.streak.count} consecutive ${trend.streak.type} verdicts${c.reset}\n`;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Milestones
|
|
212
|
-
for (const milestone of milestones) {
|
|
213
|
-
output += ` ${c.cyan}🏆 ${milestone}${c.reset}\n`;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
output += `\n ${c.dim}Recent checks:${c.reset}\n`;
|
|
217
|
-
|
|
218
|
-
// Recent checks
|
|
219
|
-
const recentChecks = history.checks.slice(0, limit);
|
|
220
|
-
for (const check of recentChecks) {
|
|
221
|
-
const date = new Date(check.timestamp);
|
|
222
|
-
const timeAgo = formatTimeAgo(date);
|
|
223
|
-
const verdictColor = check.verdict === "SHIP" ? c.green :
|
|
224
|
-
check.verdict === "WARN" ? c.yellow : c.red;
|
|
225
|
-
const verdictIcon = check.verdict === "SHIP" ? "✅" :
|
|
226
|
-
check.verdict === "WARN" ? "⚠️" : "🚫";
|
|
227
|
-
|
|
228
|
-
output += ` ${c.dim}${timeAgo.padEnd(12)}${c.reset} ${verdictColor}${check.score.toString().padStart(3)}${c.reset} ${verdictIcon} ${verdictColor}${check.verdict}${c.reset}`;
|
|
229
|
-
if (check.commitSha) {
|
|
230
|
-
output += ` ${c.dim}${check.commitSha.substring(0, 7)}${c.reset}`;
|
|
231
|
-
}
|
|
232
|
-
output += "\n";
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// Sparkline
|
|
236
|
-
if (trend.sparkline.length > 2) {
|
|
237
|
-
output += `\n ${c.dim}Trend:${c.reset} ${formatSparkline(trend.sparkline)}\n`;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
return output;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* Format relative time
|
|
245
|
-
*/
|
|
246
|
-
function formatTimeAgo(date) {
|
|
247
|
-
const now = new Date();
|
|
248
|
-
const diffMs = now - date;
|
|
249
|
-
const diffMins = Math.floor(diffMs / 60000);
|
|
250
|
-
const diffHours = Math.floor(diffMs / 3600000);
|
|
251
|
-
const diffDays = Math.floor(diffMs / 86400000);
|
|
252
|
-
|
|
253
|
-
if (diffMins < 1) return "just now";
|
|
254
|
-
if (diffMins < 60) return `${diffMins}m ago`;
|
|
255
|
-
if (diffHours < 24) return `${diffHours}h ago`;
|
|
256
|
-
if (diffDays < 7) return `${diffDays}d ago`;
|
|
257
|
-
return date.toLocaleDateString();
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Format sparkline from scores
|
|
262
|
-
*/
|
|
263
|
-
function formatSparkline(scores) {
|
|
264
|
-
const chars = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"];
|
|
265
|
-
const min = Math.min(...scores);
|
|
266
|
-
const max = Math.max(...scores);
|
|
267
|
-
const range = max - min || 1;
|
|
268
|
-
|
|
269
|
-
return scores.map(s => {
|
|
270
|
-
const idx = Math.floor(((s - min) / range) * (chars.length - 1));
|
|
271
|
-
return chars[idx];
|
|
272
|
-
}).join("");
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
module.exports = {
|
|
276
|
-
loadHistory,
|
|
277
|
-
saveHistory,
|
|
278
|
-
recordCheck,
|
|
279
|
-
calculateTrend,
|
|
280
|
-
getMilestones,
|
|
281
|
-
formatHistoryDisplay,
|
|
282
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Score History Tracking
|
|
3
|
+
*
|
|
4
|
+
* Tracks Vibe Score history over time for trend analysis,
|
|
5
|
+
* streaks, and gamification.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require("fs");
|
|
9
|
+
const path = require("path");
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get history file path for a project
|
|
13
|
+
*/
|
|
14
|
+
function getHistoryPath(projectPath) {
|
|
15
|
+
return path.join(projectPath, ".vibecheck", "history.json");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Load score history
|
|
20
|
+
*/
|
|
21
|
+
function loadHistory(projectPath) {
|
|
22
|
+
const historyPath = getHistoryPath(projectPath);
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
if (fs.existsSync(historyPath)) {
|
|
26
|
+
return JSON.parse(fs.readFileSync(historyPath, "utf8"));
|
|
27
|
+
}
|
|
28
|
+
} catch {}
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
projectId: path.basename(projectPath),
|
|
32
|
+
checks: [],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Save score history
|
|
38
|
+
*/
|
|
39
|
+
function saveHistory(projectPath, history) {
|
|
40
|
+
const historyPath = getHistoryPath(projectPath);
|
|
41
|
+
const dir = path.dirname(historyPath);
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
if (!fs.existsSync(dir)) {
|
|
45
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
46
|
+
}
|
|
47
|
+
fs.writeFileSync(historyPath, JSON.stringify(history, null, 2));
|
|
48
|
+
} catch (err) {
|
|
49
|
+
console.warn(`Warning: Could not save history: ${err.message}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Record a new check
|
|
55
|
+
*/
|
|
56
|
+
function recordCheck(projectPath, checkData) {
|
|
57
|
+
const history = loadHistory(projectPath);
|
|
58
|
+
|
|
59
|
+
const check = {
|
|
60
|
+
timestamp: new Date().toISOString(),
|
|
61
|
+
score: checkData.score || 0,
|
|
62
|
+
verdict: checkData.verdict || "BLOCK",
|
|
63
|
+
commitSha: checkData.commitSha || null,
|
|
64
|
+
commitMessage: checkData.commitMessage || null,
|
|
65
|
+
branch: checkData.branch || null,
|
|
66
|
+
categoryScores: checkData.categoryScores || {},
|
|
67
|
+
findingCounts: checkData.findingCounts || {
|
|
68
|
+
critical: 0,
|
|
69
|
+
high: 0,
|
|
70
|
+
medium: 0,
|
|
71
|
+
low: 0,
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
history.checks.unshift(check);
|
|
76
|
+
|
|
77
|
+
// Keep only last 100 checks
|
|
78
|
+
if (history.checks.length > 100) {
|
|
79
|
+
history.checks = history.checks.slice(0, 100);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
saveHistory(projectPath, history);
|
|
83
|
+
|
|
84
|
+
return check;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Calculate trend from recent checks
|
|
89
|
+
*/
|
|
90
|
+
function calculateTrend(projectPath, limit = 10) {
|
|
91
|
+
const history = loadHistory(projectPath);
|
|
92
|
+
const recentChecks = history.checks.slice(0, limit);
|
|
93
|
+
|
|
94
|
+
if (recentChecks.length < 2) {
|
|
95
|
+
return {
|
|
96
|
+
direction: "stable",
|
|
97
|
+
delta: 0,
|
|
98
|
+
previousScore: null,
|
|
99
|
+
streak: null,
|
|
100
|
+
sparkline: [],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const latest = recentChecks[0].score;
|
|
105
|
+
const previous = recentChecks[1].score;
|
|
106
|
+
const delta = latest - previous;
|
|
107
|
+
|
|
108
|
+
// Calculate streak
|
|
109
|
+
let streak = { count: 1, type: recentChecks[0].verdict };
|
|
110
|
+
for (let i = 1; i < recentChecks.length; i++) {
|
|
111
|
+
if (recentChecks[i].verdict === streak.type) {
|
|
112
|
+
streak.count++;
|
|
113
|
+
} else {
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Generate sparkline data (last 10 scores)
|
|
119
|
+
const sparkline = recentChecks.slice(0, 10).map(c => c.score).reverse();
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
direction: delta > 0 ? "up" : delta < 0 ? "down" : "stable",
|
|
123
|
+
delta: Math.abs(delta),
|
|
124
|
+
previousScore: previous,
|
|
125
|
+
streak: streak.count >= 3 ? streak : null,
|
|
126
|
+
sparkline,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Get milestones achieved
|
|
132
|
+
*/
|
|
133
|
+
function getMilestones(projectPath) {
|
|
134
|
+
const history = loadHistory(projectPath);
|
|
135
|
+
const milestones = [];
|
|
136
|
+
|
|
137
|
+
if (history.checks.length === 0) return milestones;
|
|
138
|
+
|
|
139
|
+
const latest = history.checks[0];
|
|
140
|
+
|
|
141
|
+
// First check
|
|
142
|
+
if (history.checks.length === 1) {
|
|
143
|
+
milestones.push("First check completed!");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// First 90+ score
|
|
147
|
+
const first90 = history.checks.find(c => c.score >= 90);
|
|
148
|
+
if (first90 && first90 === latest) {
|
|
149
|
+
milestones.push("First 90+ score!");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Perfect 100
|
|
153
|
+
if (latest.score === 100) {
|
|
154
|
+
milestones.push("Perfect 100! 🏆");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Streak milestones
|
|
158
|
+
const trend = calculateTrend(projectPath);
|
|
159
|
+
if (trend.streak) {
|
|
160
|
+
if (trend.streak.count >= 10 && trend.streak.type === "SHIP") {
|
|
161
|
+
milestones.push("10 SHIP streak! 🔥🔥🔥");
|
|
162
|
+
} else if (trend.streak.count >= 5 && trend.streak.type === "SHIP") {
|
|
163
|
+
milestones.push("5 SHIP streak! 🔥");
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Improvement milestones
|
|
168
|
+
if (trend.direction === "up" && trend.delta >= 10) {
|
|
169
|
+
milestones.push(`+${trend.delta} points improvement!`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return milestones;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Format history for CLI display
|
|
177
|
+
*/
|
|
178
|
+
function formatHistoryDisplay(projectPath, limit = 10) {
|
|
179
|
+
const c = {
|
|
180
|
+
reset: "\x1b[0m",
|
|
181
|
+
bold: "\x1b[1m",
|
|
182
|
+
dim: "\x1b[2m",
|
|
183
|
+
red: "\x1b[31m",
|
|
184
|
+
green: "\x1b[32m",
|
|
185
|
+
yellow: "\x1b[33m",
|
|
186
|
+
cyan: "\x1b[36m",
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const history = loadHistory(projectPath);
|
|
190
|
+
const trend = calculateTrend(projectPath);
|
|
191
|
+
const milestones = getMilestones(projectPath);
|
|
192
|
+
|
|
193
|
+
if (history.checks.length === 0) {
|
|
194
|
+
return `${c.dim}No history yet. Run \`vibecheck ship\` to start tracking.${c.reset}`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
let output = `\n${c.bold}📊 Score History${c.reset}\n\n`;
|
|
198
|
+
|
|
199
|
+
// Trend line
|
|
200
|
+
if (trend.direction !== "stable") {
|
|
201
|
+
const trendIcon = trend.direction === "up" ? "↑" : "↓";
|
|
202
|
+
const trendColor = trend.direction === "up" ? c.green : c.red;
|
|
203
|
+
output += ` ${trendColor}${trendIcon} ${trend.delta} since last check${c.reset}\n`;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Streak
|
|
207
|
+
if (trend.streak) {
|
|
208
|
+
output += ` ${c.yellow}🔥 ${trend.streak.count} consecutive ${trend.streak.type} verdicts${c.reset}\n`;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Milestones
|
|
212
|
+
for (const milestone of milestones) {
|
|
213
|
+
output += ` ${c.cyan}🏆 ${milestone}${c.reset}\n`;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
output += `\n ${c.dim}Recent checks:${c.reset}\n`;
|
|
217
|
+
|
|
218
|
+
// Recent checks
|
|
219
|
+
const recentChecks = history.checks.slice(0, limit);
|
|
220
|
+
for (const check of recentChecks) {
|
|
221
|
+
const date = new Date(check.timestamp);
|
|
222
|
+
const timeAgo = formatTimeAgo(date);
|
|
223
|
+
const verdictColor = check.verdict === "SHIP" ? c.green :
|
|
224
|
+
check.verdict === "WARN" ? c.yellow : c.red;
|
|
225
|
+
const verdictIcon = check.verdict === "SHIP" ? "✅" :
|
|
226
|
+
check.verdict === "WARN" ? "⚠️" : "🚫";
|
|
227
|
+
|
|
228
|
+
output += ` ${c.dim}${timeAgo.padEnd(12)}${c.reset} ${verdictColor}${check.score.toString().padStart(3)}${c.reset} ${verdictIcon} ${verdictColor}${check.verdict}${c.reset}`;
|
|
229
|
+
if (check.commitSha) {
|
|
230
|
+
output += ` ${c.dim}${check.commitSha.substring(0, 7)}${c.reset}`;
|
|
231
|
+
}
|
|
232
|
+
output += "\n";
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Sparkline
|
|
236
|
+
if (trend.sparkline.length > 2) {
|
|
237
|
+
output += `\n ${c.dim}Trend:${c.reset} ${formatSparkline(trend.sparkline)}\n`;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return output;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Format relative time
|
|
245
|
+
*/
|
|
246
|
+
function formatTimeAgo(date) {
|
|
247
|
+
const now = new Date();
|
|
248
|
+
const diffMs = now - date;
|
|
249
|
+
const diffMins = Math.floor(diffMs / 60000);
|
|
250
|
+
const diffHours = Math.floor(diffMs / 3600000);
|
|
251
|
+
const diffDays = Math.floor(diffMs / 86400000);
|
|
252
|
+
|
|
253
|
+
if (diffMins < 1) return "just now";
|
|
254
|
+
if (diffMins < 60) return `${diffMins}m ago`;
|
|
255
|
+
if (diffHours < 24) return `${diffHours}h ago`;
|
|
256
|
+
if (diffDays < 7) return `${diffDays}d ago`;
|
|
257
|
+
return date.toLocaleDateString();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Format sparkline from scores
|
|
262
|
+
*/
|
|
263
|
+
function formatSparkline(scores) {
|
|
264
|
+
const chars = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"];
|
|
265
|
+
const min = Math.min(...scores);
|
|
266
|
+
const max = Math.max(...scores);
|
|
267
|
+
const range = max - min || 1;
|
|
268
|
+
|
|
269
|
+
return scores.map(s => {
|
|
270
|
+
const idx = Math.floor(((s - min) / range) * (chars.length - 1));
|
|
271
|
+
return chars[idx];
|
|
272
|
+
}).join("");
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
module.exports = {
|
|
276
|
+
loadHistory,
|
|
277
|
+
saveHistory,
|
|
278
|
+
recordCheck,
|
|
279
|
+
calculateTrend,
|
|
280
|
+
getMilestones,
|
|
281
|
+
formatHistoryDisplay,
|
|
282
|
+
};
|