chekk 0.2.4 → 0.3.0
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/chekk.js +57 -15
- package/package.json +1 -1
- package/src/display.js +186 -67
- package/src/index.js +80 -10
package/bin/chekk.js
CHANGED
|
@@ -1,20 +1,62 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import { execSync, spawn } from 'child_process';
|
|
3
4
|
import { Command } from 'commander';
|
|
4
5
|
import { run } from '../src/index.js';
|
|
5
6
|
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
7
|
+
const LOCAL_VERSION = '0.3.0';
|
|
8
|
+
|
|
9
|
+
// ── Auto-update check ──
|
|
10
|
+
// If running from a cached npx install, check if there's a newer version
|
|
11
|
+
// and re-exec with @latest to ensure users always get the freshest build.
|
|
12
|
+
async function checkForUpdate() {
|
|
13
|
+
try {
|
|
14
|
+
const latest = execSync('npm view chekk version 2>/dev/null', {
|
|
15
|
+
encoding: 'utf-8',
|
|
16
|
+
timeout: 3000,
|
|
17
|
+
}).trim();
|
|
18
|
+
|
|
19
|
+
if (latest && latest !== LOCAL_VERSION && isNewer(latest, LOCAL_VERSION)) {
|
|
20
|
+
// Re-run with the latest version
|
|
21
|
+
const args = process.argv.slice(2);
|
|
22
|
+
const child = spawn('npx', ['--yes', `chekk@${latest}`, ...args], {
|
|
23
|
+
stdio: 'inherit',
|
|
24
|
+
shell: true,
|
|
25
|
+
});
|
|
26
|
+
child.on('exit', (code) => process.exit(code || 0));
|
|
27
|
+
return true; // signal that we're handing off
|
|
28
|
+
}
|
|
29
|
+
} catch {
|
|
30
|
+
// Network down or timeout — just run the local version
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function isNewer(remote, local) {
|
|
36
|
+
const r = remote.split('.').map(Number);
|
|
37
|
+
const l = local.split('.').map(Number);
|
|
38
|
+
for (let i = 0; i < 3; i++) {
|
|
39
|
+
if ((r[i] || 0) > (l[i] || 0)) return true;
|
|
40
|
+
if ((r[i] || 0) < (l[i] || 0)) return false;
|
|
41
|
+
}
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const handedOff = await checkForUpdate();
|
|
46
|
+
if (!handedOff) {
|
|
47
|
+
const program = new Command();
|
|
48
|
+
|
|
49
|
+
program
|
|
50
|
+
.name('chekk')
|
|
51
|
+
.description('The engineering capability score. See how you prompt.')
|
|
52
|
+
.version(LOCAL_VERSION)
|
|
53
|
+
.option('--offline', 'Skip AI prose generation, show data-driven output')
|
|
54
|
+
.option('--verbose', 'Show detailed per-project and per-metric breakdowns')
|
|
55
|
+
.option('--json', 'Output raw metrics as JSON')
|
|
56
|
+
.option('--no-upload', 'Skip the claim prompt')
|
|
57
|
+
.action(async (options) => {
|
|
58
|
+
await run(options);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
program.parse();
|
|
62
|
+
}
|
package/package.json
CHANGED
package/src/display.js
CHANGED
|
@@ -6,6 +6,7 @@ const purple = chalk.hex('#A855F7');
|
|
|
6
6
|
const blue = chalk.hex('#3B82F6');
|
|
7
7
|
const green = chalk.hex('#22C55E');
|
|
8
8
|
const cyan = chalk.hex('#06B6D4');
|
|
9
|
+
const orange = chalk.hex('#F97316');
|
|
9
10
|
const dim = chalk.dim;
|
|
10
11
|
const bold = chalk.bold;
|
|
11
12
|
const white = chalk.white;
|
|
@@ -22,7 +23,7 @@ function scoreColor(score) {
|
|
|
22
23
|
if (score >= 85) return green;
|
|
23
24
|
if (score >= 70) return cyan;
|
|
24
25
|
if (score >= 55) return chalk.hex('#EAB308');
|
|
25
|
-
if (score >= 40) return
|
|
26
|
+
if (score >= 40) return orange;
|
|
26
27
|
return chalk.hex('#EF4444');
|
|
27
28
|
}
|
|
28
29
|
|
|
@@ -42,9 +43,28 @@ function pad(str, len) {
|
|
|
42
43
|
return str + ' '.repeat(Math.max(0, len - visible.length));
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
|
|
46
|
+
// ── Qualitative tier labels for dimensions ──
|
|
47
|
+
function dimTierLabel(score) {
|
|
48
|
+
if (score >= 80) return 'exceptional';
|
|
49
|
+
if (score >= 65) return 'strong';
|
|
50
|
+
if (score >= 50) return 'solid';
|
|
51
|
+
if (score >= 35) return 'developing';
|
|
52
|
+
return 'needs work';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function dimTierColor(score) {
|
|
56
|
+
if (score >= 80) return green;
|
|
57
|
+
if (score >= 65) return cyan;
|
|
58
|
+
if (score >= 50) return chalk.hex('#EAB308');
|
|
59
|
+
if (score >= 35) return orange;
|
|
60
|
+
return chalk.hex('#EF4444');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ── Snippet helpers ──
|
|
64
|
+
// Longer snippets with context labels instead of truncated decorations
|
|
65
|
+
|
|
66
|
+
function cleanPrompt(prompt, maxLen = 120) {
|
|
46
67
|
if (!prompt) return null;
|
|
47
|
-
// Clean up whitespace / newlines
|
|
48
68
|
let clean = prompt.replace(/\s+/g, ' ').trim();
|
|
49
69
|
if (clean.length > maxLen) {
|
|
50
70
|
clean = clean.slice(0, maxLen - 1) + '\u2026';
|
|
@@ -52,10 +72,10 @@ function snippet(prompt, maxLen = 70) {
|
|
|
52
72
|
return clean;
|
|
53
73
|
}
|
|
54
74
|
|
|
55
|
-
function
|
|
56
|
-
const s =
|
|
75
|
+
function displayLabeledSnippet(label, prompt, maxLen = 120) {
|
|
76
|
+
const s = cleanPrompt(prompt, maxLen);
|
|
57
77
|
if (!s) return;
|
|
58
|
-
console.log(` ${dim('\
|
|
78
|
+
console.log(` ${dim('\u21B3')} ${dim(label + ':')} ${dim.italic('\u201C' + s + '\u201D')}`);
|
|
59
79
|
}
|
|
60
80
|
|
|
61
81
|
function pickExample(examples, type) {
|
|
@@ -90,7 +110,7 @@ export function displayHeader() {
|
|
|
90
110
|
console.log();
|
|
91
111
|
const lines = [
|
|
92
112
|
'',
|
|
93
|
-
` ${bold.white('chekk')}${dim(' v0.
|
|
113
|
+
` ${bold.white('chekk')}${dim(' v0.3.0')}`,
|
|
94
114
|
` ${dim('the engineering capability score')}`,
|
|
95
115
|
'',
|
|
96
116
|
];
|
|
@@ -147,8 +167,9 @@ export async function displayProgressBar(durationMs = 2000) {
|
|
|
147
167
|
// MAIN SCORE DISPLAY
|
|
148
168
|
// ══════════════════════════════════════════════
|
|
149
169
|
|
|
150
|
-
export function displayScore(result, prose) {
|
|
170
|
+
export function displayScore(result, prose, extra = {}) {
|
|
151
171
|
const { overall, scores, archetype, tier, tierBadge, tierPercentile } = result;
|
|
172
|
+
const { scoreDelta, perToolScores } = extra;
|
|
152
173
|
const tc = tierColor(tier);
|
|
153
174
|
const sc = scoreColor(overall);
|
|
154
175
|
|
|
@@ -157,10 +178,19 @@ export function displayScore(result, prose) {
|
|
|
157
178
|
console.log();
|
|
158
179
|
console.log(dim(' YOUR CHEKK SCORE'));
|
|
159
180
|
console.log();
|
|
181
|
+
|
|
182
|
+
// Score + delta
|
|
183
|
+
let deltaStr = '';
|
|
184
|
+
if (scoreDelta !== null && scoreDelta !== undefined) {
|
|
185
|
+
if (scoreDelta > 0) deltaStr = ` ${green('\u2191 +' + scoreDelta + ' since last scan')}`;
|
|
186
|
+
else if (scoreDelta < 0) deltaStr = ` ${orange('\u2193 ' + scoreDelta + ' since last scan')}`;
|
|
187
|
+
else deltaStr = ` ${dim('\u2192 same as last scan')}`;
|
|
188
|
+
}
|
|
160
189
|
console.log(` ${sc.bold(String(overall))}`);
|
|
161
190
|
const badgeStr = tierBadge ? ` ${tierBadge}` : '';
|
|
162
191
|
const pctStr = tierPercentile ? ` ${dim(tierPercentile)}` : '';
|
|
163
192
|
console.log(` ${tc('\u2500\u2500 ' + tier + ' \u2500\u2500')}${badgeStr}${pctStr}`);
|
|
193
|
+
if (deltaStr) console.log(` ${deltaStr}`);
|
|
164
194
|
console.log();
|
|
165
195
|
console.log(` ${dim('Archetype:')} ${bold.white(archetype.name)}`);
|
|
166
196
|
|
|
@@ -168,22 +198,25 @@ export function displayScore(result, prose) {
|
|
|
168
198
|
console.log(` ${dim('"' + prose.tagline + '"')}`);
|
|
169
199
|
}
|
|
170
200
|
console.log();
|
|
201
|
+
|
|
202
|
+
// ── Score weights (transparency) ──
|
|
203
|
+
console.log(` ${dim('Score = Thinking (25%) + Debugging (25%) + AI Leverage (30%) + Workflow (20%)')}`);
|
|
204
|
+
console.log();
|
|
171
205
|
console.log(doubleRule());
|
|
172
206
|
console.log();
|
|
173
207
|
|
|
174
|
-
// Dimensions box
|
|
208
|
+
// Dimensions box with qualitative labels
|
|
175
209
|
console.log(dim(' DIMENSIONS\n'));
|
|
176
210
|
|
|
177
|
-
// Build dimension lines with fixed-width layout
|
|
178
|
-
// Format: " Label ██████████████░░░░ XX "
|
|
179
|
-
// Visible: 2 + 15 + 18 + 2 + 3 + 1 = ~41 chars
|
|
180
211
|
function dimLine(label, score) {
|
|
181
212
|
const labelStr = bold(label);
|
|
182
213
|
const labelVisible = label.length;
|
|
183
214
|
const labelPad = ' '.repeat(Math.max(0, 15 - labelVisible));
|
|
184
215
|
const scoreStr = String(score);
|
|
185
216
|
const scorePad = score < 10 ? ' ' : score < 100 ? ' ' : '';
|
|
186
|
-
|
|
217
|
+
const tierLabel = dimTierLabel(score);
|
|
218
|
+
const tierStr = dimTierColor(score)(tierLabel);
|
|
219
|
+
return ` ${labelStr}${labelPad}${progressBar(score)} ${scorePad}${bold(scoreStr)} ${dim('\u2500\u2500')} ${tierStr} `;
|
|
187
220
|
}
|
|
188
221
|
|
|
189
222
|
const dimLines = [
|
|
@@ -194,8 +227,36 @@ export function displayScore(result, prose) {
|
|
|
194
227
|
dimLine('Workflow', scores.sessionStructure),
|
|
195
228
|
'',
|
|
196
229
|
];
|
|
197
|
-
for (const l of box(dimLines,
|
|
230
|
+
for (const l of box(dimLines, 53)) console.log(l);
|
|
198
231
|
console.log();
|
|
232
|
+
|
|
233
|
+
// ── Cross-platform breakdown ──
|
|
234
|
+
if (perToolScores && Object.keys(perToolScores).length > 1) {
|
|
235
|
+
console.log(dim(' CROSS-PLATFORM\n'));
|
|
236
|
+
// Sort by score descending, null scores last
|
|
237
|
+
const sorted = Object.entries(perToolScores).sort((a, b) => {
|
|
238
|
+
if (a[1].score === null) return 1;
|
|
239
|
+
if (b[1].score === null) return -1;
|
|
240
|
+
return b[1].score - a[1].score;
|
|
241
|
+
});
|
|
242
|
+
// Find primary (most sessions)
|
|
243
|
+
const maxSessions = Math.max(...sorted.map(([, v]) => v.sessions));
|
|
244
|
+
|
|
245
|
+
for (const [tool, data] of sorted) {
|
|
246
|
+
if (data.score !== null) {
|
|
247
|
+
const isPrimary = data.sessions === maxSessions;
|
|
248
|
+
const label = isPrimary ? 'primary tool' : data.sessions < 5 ? 'limited usage' : (data.label || 'active');
|
|
249
|
+
const barWidth = 18;
|
|
250
|
+
const filled = Math.round((data.score / 100) * barWidth);
|
|
251
|
+
const empty = barWidth - filled;
|
|
252
|
+
const bar = scoreColor(data.score)('\u2588'.repeat(filled)) + dim('\u2591'.repeat(empty));
|
|
253
|
+
console.log(` ${pad(bold(tool), 18)} ${bold(String(data.score))} ${bar} ${dim(label)}`);
|
|
254
|
+
} else {
|
|
255
|
+
console.log(` ${pad(bold(tool), 18)} ${dim('--')} ${dim('\u2591'.repeat(18))} ${dim('limited data')}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
console.log();
|
|
259
|
+
}
|
|
199
260
|
}
|
|
200
261
|
|
|
201
262
|
// ══════════════════════════════════════════════
|
|
@@ -203,23 +264,21 @@ export function displayScore(result, prose) {
|
|
|
203
264
|
// ══════════════════════════════════════════════
|
|
204
265
|
|
|
205
266
|
export function displayNarratives(metrics, prose) {
|
|
206
|
-
// Track shown snippets globally to avoid duplicates across sections
|
|
207
267
|
const shownSnippets = new Set();
|
|
208
|
-
function
|
|
268
|
+
function showLabeledSnippet(label, prompt) {
|
|
209
269
|
if (!prompt) return;
|
|
210
|
-
const s =
|
|
270
|
+
const s = cleanPrompt(prompt, 120);
|
|
211
271
|
if (shownSnippets.has(s)) return;
|
|
212
272
|
shownSnippets.add(s);
|
|
213
|
-
|
|
273
|
+
displayLabeledSnippet(label, prompt);
|
|
214
274
|
}
|
|
215
275
|
|
|
216
276
|
if (prose && prose.sections) {
|
|
217
|
-
// Use DeepSeek-generated prose with inline prompt snippets per section
|
|
218
277
|
const sectionSnippetMap = {
|
|
219
|
-
'thinking': pickExample(metrics.decomposition.examples, 'decomposition'),
|
|
220
|
-
'debugging': pickExample(metrics.debugCycles.examples, 'specific_report') || pickExample(metrics.debugCycles.examples, 'quick_fix'),
|
|
221
|
-
'ai leverage': pickExample(metrics.aiLeverage.examples, 'architectural') || pickExample(metrics.aiLeverage.examples, 'planning'),
|
|
222
|
-
'workflow': pickExample(metrics.sessionStructure.examples, 'context_setting') || pickExample(metrics.sessionStructure.examples, 'refinement'),
|
|
278
|
+
'thinking': { label: 'Best decomposition prompt', prompt: pickExample(metrics.decomposition.examples, 'decomposition') },
|
|
279
|
+
'debugging': { label: 'Best debug prompt', prompt: pickExample(metrics.debugCycles.examples, 'specific_report') || pickExample(metrics.debugCycles.examples, 'quick_fix') },
|
|
280
|
+
'ai leverage': { label: 'Best leverage prompt', prompt: pickExample(metrics.aiLeverage.examples, 'architectural') || pickExample(metrics.aiLeverage.examples, 'planning') },
|
|
281
|
+
'workflow': { label: 'Best workflow prompt', prompt: pickExample(metrics.sessionStructure.examples, 'context_setting') || pickExample(metrics.sessionStructure.examples, 'refinement') },
|
|
223
282
|
};
|
|
224
283
|
|
|
225
284
|
for (const section of prose.sections) {
|
|
@@ -228,14 +287,12 @@ export function displayNarratives(metrics, prose) {
|
|
|
228
287
|
for (const line of lines) {
|
|
229
288
|
console.log(` ${dim(line.trim())}`);
|
|
230
289
|
}
|
|
231
|
-
// Show relevant prompt snippet under this section
|
|
232
290
|
const titleLower = section.title.toLowerCase();
|
|
233
|
-
const
|
|
234
|
-
if (
|
|
291
|
+
const matched = sectionSnippetMap[titleLower];
|
|
292
|
+
if (matched && matched.prompt) showLabeledSnippet(matched.label, matched.prompt);
|
|
235
293
|
console.log();
|
|
236
294
|
}
|
|
237
295
|
} else {
|
|
238
|
-
// Fallback: data-driven bullet points with inline snippets
|
|
239
296
|
displayDataNarrativesWithTracker(metrics, shownSnippets);
|
|
240
297
|
}
|
|
241
298
|
}
|
|
@@ -250,12 +307,12 @@ function displayDataNarrativesWithTracker(metrics, shownSnippets) {
|
|
|
250
307
|
const aiEx = metrics.aiLeverage.examples || [];
|
|
251
308
|
const ssEx = metrics.sessionStructure.examples || [];
|
|
252
309
|
|
|
253
|
-
function
|
|
310
|
+
function showLabeledSnippet(label, prompt) {
|
|
254
311
|
if (!prompt) return;
|
|
255
|
-
const s =
|
|
312
|
+
const s = cleanPrompt(prompt, 120);
|
|
256
313
|
if (shownSnippets.has(s)) return;
|
|
257
314
|
shownSnippets.add(s);
|
|
258
|
-
|
|
315
|
+
displayLabeledSnippet(label, prompt);
|
|
259
316
|
}
|
|
260
317
|
|
|
261
318
|
// Thinking
|
|
@@ -264,16 +321,16 @@ function displayDataNarrativesWithTracker(metrics, shownSnippets) {
|
|
|
264
321
|
console.log(` ${dim(exchPerSession > 20 ? `${exchPerSession} avg exchanges/session \u2014 marathon builder` : exchPerSession > 8 ? `${exchPerSession} avg exchanges/session \u2014 iterative` : `${exchPerSession} avg exchanges/session \u2014 concise`)}`);
|
|
265
322
|
console.log(` ${dim(d.avgPromptLength > 500 ? `${numberFormat(d.avgPromptLength)} char avg prompt \u2014 thinks out loud` : `${d.avgPromptLength} char avg prompt \u2014 concise communicator`)}`);
|
|
266
323
|
console.log(` ${dim(d.multiStepSessions > d.singleShotSessions * 2 ? 'Multi-step decomposition over single-shot' : 'Mix of multi-step and single-shot sessions')}`);
|
|
267
|
-
|
|
324
|
+
showLabeledSnippet('Best decomposition prompt', pickExample(dEx, 'decomposition'));
|
|
268
325
|
console.log();
|
|
269
326
|
|
|
270
327
|
// Debugging
|
|
271
328
|
console.log(` ${bold('\u26A1 DEBUGGING')}`);
|
|
272
329
|
const turns = db.avgTurnsToResolve;
|
|
273
330
|
console.log(` ${dim(turns <= 2 ? `${turns} turns to resolve \u2014 surgical` : turns <= 4 ? `${turns} turns to resolve \u2014 efficient` : `${turns} turns to resolve \u2014 iterative`)}`);
|
|
274
|
-
console.log(` ${dim(`${db.specificReportRatio}% specific error reports`)}`);
|
|
275
|
-
console.log(` ${dim(db.longLoops === 0 ? 'Zero
|
|
276
|
-
|
|
331
|
+
console.log(` ${dim(`${db.specificReportRatio}% specific error reports \u2014 ${db.specificReportRatio >= 80 ? 'precise' : db.specificReportRatio >= 50 ? 'decent' : 'vague'}`)}`);
|
|
332
|
+
console.log(` ${dim(db.longLoops === 0 ? 'Zero long loops \u2014 efficient debugger' : `${db.longLoops} extended debug loops`)}`);
|
|
333
|
+
showLabeledSnippet('Best debug prompt', pickExample(dbEx, 'specific_report') || pickExample(dbEx, 'quick_fix'));
|
|
277
334
|
console.log();
|
|
278
335
|
|
|
279
336
|
// AI Leverage
|
|
@@ -282,7 +339,7 @@ function displayDataNarrativesWithTracker(metrics, shownSnippets) {
|
|
|
282
339
|
const codingRatio = ai.toolDiversity.coding > ai.toolDiversity.research ? 'Coding-heavy' : 'Research-heavy';
|
|
283
340
|
console.log(` ${dim(`${codingRatio} over ${ai.toolDiversity.coding > ai.toolDiversity.research ? 'research' : 'coding'}-heavy`)}`);
|
|
284
341
|
console.log(` ${dim(`${ai.architecturalPrompts} architectural, ${ai.planningPrompts} planning, ${ai.exploratoryPrompts} exploratory`)}`);
|
|
285
|
-
|
|
342
|
+
showLabeledSnippet('Best leverage prompt', pickExample(aiEx, 'architectural') || pickExample(aiEx, 'planning'));
|
|
286
343
|
console.log();
|
|
287
344
|
|
|
288
345
|
// Workflow
|
|
@@ -290,7 +347,43 @@ function displayDataNarrativesWithTracker(metrics, shownSnippets) {
|
|
|
290
347
|
console.log(` ${dim(`${ss.contextSetRatio}% context-setting rate \u2014 ${ss.contextSetRatio > 50 ? 'deliberate' : ss.contextSetRatio > 25 ? 'moderate' : 'low'}`)}`);
|
|
291
348
|
console.log(` ${dim(`${ss.reviewEndRatio}% sessions end with review`)}`);
|
|
292
349
|
console.log(` ${dim(`${ss.refinementRatio}% refinement rate \u2014 ${ss.refinementRatio > 20 ? 'critical eye' : 'accepts readily'}`)}`);
|
|
293
|
-
|
|
350
|
+
showLabeledSnippet('Best workflow prompt', pickExample(ssEx, 'context_setting') || pickExample(ssEx, 'refinement'));
|
|
351
|
+
console.log();
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
// ══════════════════════════════════════════════
|
|
356
|
+
// COACHING RECOMMENDATION
|
|
357
|
+
// ══════════════════════════════════════════════
|
|
358
|
+
|
|
359
|
+
function displayCoaching(result, metrics) {
|
|
360
|
+
const { scores } = result;
|
|
361
|
+
// Find the weakest dimension
|
|
362
|
+
const dims = [
|
|
363
|
+
{ key: 'decomposition', label: 'Thinking', score: scores.decomposition },
|
|
364
|
+
{ key: 'debugCycles', label: 'Debugging', score: scores.debugCycles },
|
|
365
|
+
{ key: 'aiLeverage', label: 'AI Leverage', score: scores.aiLeverage },
|
|
366
|
+
{ key: 'sessionStructure', label: 'Workflow', score: scores.sessionStructure },
|
|
367
|
+
];
|
|
368
|
+
dims.sort((a, b) => a.score - b.score);
|
|
369
|
+
const weakest = dims[0];
|
|
370
|
+
|
|
371
|
+
// Only show coaching if the weakest is actually dragging things down
|
|
372
|
+
if (weakest.score >= 70) return;
|
|
373
|
+
|
|
374
|
+
const tips = {
|
|
375
|
+
decomposition: 'Break complex tasks into subtasks before prompting.\nEngineers who decompose problems score 15+ points higher.',
|
|
376
|
+
debugCycles: 'Include specific error messages and stack traces in debug prompts.\nPrecise reports resolve 2x faster than "it\'s broken, fix it."',
|
|
377
|
+
aiLeverage: 'Use AI for architecture and planning, not just code generation.\nAsk "what\'s the best approach" before "write the code."',
|
|
378
|
+
sessionStructure: 'Set context at the start of each session and review at the end.\nEngineers who do this score 12 points higher on average.',
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
console.log(` ${bold('\uD83D\uDCA1 TO LEVEL UP')}`);
|
|
382
|
+
console.log(` ${dim(`Your ${weakest.label} score (${weakest.score}) is your biggest opportunity.`)}`);
|
|
383
|
+
const tipLines = tips[weakest.key].split('\n');
|
|
384
|
+
for (const line of tipLines) {
|
|
385
|
+
console.log(` ${dim(line)}`);
|
|
386
|
+
}
|
|
294
387
|
console.log();
|
|
295
388
|
}
|
|
296
389
|
|
|
@@ -335,8 +428,15 @@ export function displayEnding(result) {
|
|
|
335
428
|
|
|
336
429
|
console.log(doubleRule());
|
|
337
430
|
console.log();
|
|
338
|
-
|
|
339
|
-
|
|
431
|
+
|
|
432
|
+
// Value-driven claim CTA
|
|
433
|
+
console.log(` ${dim('\u2192 Claim your profile to:')}`);
|
|
434
|
+
console.log(` ${dim(' \u00B7 See how you compare across 10K+ engineers')}`);
|
|
435
|
+
console.log(` ${dim(' \u00B7 Get matched with roles at top startups')}`);
|
|
436
|
+
console.log(` ${dim(' \u00B7 Track your score over time')}`);
|
|
437
|
+
console.log(` ${cyan.underline(' chekk.dev/claim')}`);
|
|
438
|
+
console.log();
|
|
439
|
+
console.log(` ${dim('\u2192 Leaderboard:')} ${cyan.underline('chekk.dev/leaderboard')}`);
|
|
340
440
|
console.log();
|
|
341
441
|
|
|
342
442
|
// Copy-paste share line
|
|
@@ -351,7 +451,7 @@ export function displayEnding(result) {
|
|
|
351
451
|
}
|
|
352
452
|
|
|
353
453
|
// ══════════════════════════════════════════════
|
|
354
|
-
// VERBOSE: DETAILED BREAKDOWN
|
|
454
|
+
// VERBOSE: DETAILED BREAKDOWN (with interpretation)
|
|
355
455
|
// ══════════════════════════════════════════════
|
|
356
456
|
|
|
357
457
|
export function displayVerbose(metrics, sessions) {
|
|
@@ -375,40 +475,57 @@ export function displayVerbose(metrics, sessions) {
|
|
|
375
475
|
}
|
|
376
476
|
console.log();
|
|
377
477
|
|
|
378
|
-
//
|
|
478
|
+
// Decomposition details with interpretation
|
|
479
|
+
const d = metrics.decomposition.details;
|
|
379
480
|
console.log(bold(' DECOMPOSITION DETAILS'));
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
}
|
|
481
|
+
console.log(` ${dim(pad('totalSessions', 30))} ${dim(String(d.totalSessions))}`);
|
|
482
|
+
console.log(` ${dim(pad('multiStepSessions', 30))} ${dim(String(d.multiStepSessions))} ${dim(d.multiStepSessions > d.totalSessions * 0.5 ? '\u2014 strong iterative work' : '\u2014 room for more iteration')}`);
|
|
483
|
+
console.log(` ${dim(pad('singleShotSessions', 30))} ${dim(String(d.singleShotSessions))} ${dim(d.singleShotSessions < d.totalSessions * 0.3 ? '\u2014 good, not over-relying on one-shots' : '\u2014 consider breaking tasks down more')}`);
|
|
484
|
+
console.log(` ${dim(pad('avgExchangesPerSession', 30))} ${dim(String(d.avgExchangesPerSession))} ${dim(d.avgExchangesPerSession > 12 ? '\u2014 deep sessions' : d.avgExchangesPerSession > 5 ? '\u2014 moderate depth' : '\u2014 shallow sessions')}`);
|
|
485
|
+
console.log(` ${dim(pad('avgPromptLength', 30))} ${dim(String(d.avgPromptLength) + ' chars')} ${dim(d.avgPromptLength > 300 ? '\u2014 detailed communicator' : d.avgPromptLength > 100 ? '\u2014 moderate detail' : '\u2014 terse')}`);
|
|
486
|
+
console.log(` ${dim(pad('longPromptRatio', 30))} ${dim(d.longPromptRatio + '%')}`);
|
|
487
|
+
console.log(` ${dim(pad('contextualFollowupRatio', 30))} ${dim(d.contextualFollowupRatio + '%')} ${dim(d.contextualFollowupRatio > 20 ? '\u2014 builds on previous context well' : '\u2014 could reference prior context more')}`);
|
|
383
488
|
console.log();
|
|
384
489
|
|
|
490
|
+
// Debug cycle details
|
|
491
|
+
const db = metrics.debugCycles.details;
|
|
385
492
|
console.log(bold(' DEBUG CYCLE DETAILS'));
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
}
|
|
493
|
+
console.log(` ${dim(pad('totalDebugSequences', 30))} ${dim(String(db.totalDebugSequences))}`);
|
|
494
|
+
console.log(` ${dim(pad('avgTurnsToResolve', 30))} ${dim(String(db.avgTurnsToResolve))} ${dim(db.avgTurnsToResolve <= 2 ? '\u2014 surgical precision' : db.avgTurnsToResolve <= 4 ? '\u2014 efficient' : '\u2014 could be tighter')}`);
|
|
495
|
+
console.log(` ${dim(pad('quickFixes', 30))} ${dim(String(db.quickFixes))} ${dim(db.quickFixes > db.totalDebugSequences * 0.5 ? '\u2014 most issues resolved fast' : '')}`);
|
|
496
|
+
console.log(` ${dim(pad('longLoops', 30))} ${dim(String(db.longLoops))} ${dim(db.longLoops === 0 ? '\u2014 zero spirals' : '\u2014 some extended debugging')}`);
|
|
497
|
+
console.log(` ${dim(pad('specificReportRatio', 30))} ${dim(db.specificReportRatio + '%')} ${dim(db.specificReportRatio >= 80 ? '\u2014 precise error reporting' : '\u2014 include more stack traces/line numbers')}`);
|
|
498
|
+
console.log(` ${dim(pad('vagueReports', 30))} ${dim(String(db.vagueReports))} ${dim(db.vagueReports === 0 ? '\u2014 never vague' : `\u2014 ${db.vagueReports} vague reports`)}`);
|
|
389
499
|
console.log();
|
|
390
500
|
|
|
501
|
+
// AI Leverage details
|
|
502
|
+
const ai = metrics.aiLeverage.details;
|
|
391
503
|
console.log(bold(' AI LEVERAGE DETAILS'));
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
}
|
|
504
|
+
console.log(` ${dim(pad('totalPrompts', 30))} ${dim(numberFormat(ai.totalPrompts))}`);
|
|
505
|
+
console.log(` ${dim(pad('architecturalPrompts', 30))} ${dim(String(ai.architecturalPrompts))} ${dim(`(${Math.round(ai.architecturalPrompts / ai.totalPrompts * 100)}%)`)} ${dim(ai.architecturalPrompts > ai.totalPrompts * 0.1 ? '\u2014 strategic usage' : '\u2014 could use AI for more design work')}`);
|
|
506
|
+
console.log(` ${dim(pad('planningPrompts', 30))} ${dim(String(ai.planningPrompts))} ${dim(`(${Math.round(ai.planningPrompts / ai.totalPrompts * 100)}%)`)}`);
|
|
507
|
+
console.log(` ${dim(pad('exploratoryPrompts', 30))} ${dim(String(ai.exploratoryPrompts))} ${dim(`(${Math.round(ai.exploratoryPrompts / ai.totalPrompts * 100)}%)`)}`);
|
|
508
|
+
console.log(` ${dim(pad('boilerplatePrompts', 30))} ${dim(String(ai.boilerplatePrompts))} ${dim(`(${Math.round(ai.boilerplatePrompts / ai.totalPrompts * 100)}%)`)} ${dim(ai.boilerplatePrompts < ai.totalPrompts * 0.05 ? '\u2014 very low, minimal repetitive work' : '\u2014 some boilerplate delegation')}`);
|
|
509
|
+
console.log(` ${dim(pad('testingPrompts', 30))} ${dim(String(ai.testingPrompts))} ${dim(`(${Math.round(ai.testingPrompts / ai.totalPrompts * 100)}%)`)}`);
|
|
510
|
+
console.log(` ${dim(pad('highLevelRatio', 30))} ${dim(ai.highLevelRatio + '%')} ${dim(ai.highLevelRatio > 30 ? '\u2014 strong strategic AI usage' : '\u2014 room to leverage AI for higher-level work')}`);
|
|
511
|
+
console.log(` ${dim(pad('toolDiversity.research', 30))} ${dim(String(ai.toolDiversity.research))}`);
|
|
512
|
+
console.log(` ${dim(pad('toolDiversity.coding', 30))} ${dim(String(ai.toolDiversity.coding))}`);
|
|
401
513
|
console.log();
|
|
402
514
|
|
|
515
|
+
// Session structure details
|
|
516
|
+
const ss = metrics.sessionStructure.details;
|
|
403
517
|
console.log(bold(' SESSION STRUCTURE DETAILS'));
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
}
|
|
518
|
+
console.log(` ${dim(pad('contextSetRatio', 30))} ${dim(ss.contextSetRatio + '%')} ${dim(ss.contextSetRatio > 50 ? '\u2014 deliberate context-setting' : '\u2014 try explaining goals upfront')}`);
|
|
519
|
+
console.log(` ${dim(pad('planBeforeCodeRatio', 30))} ${dim(ss.planBeforeCodeRatio + '%')} ${dim(ss.planBeforeCodeRatio > 20 ? '\u2014 plans before coding' : '\u2014 consider planning before diving in')}`);
|
|
520
|
+
console.log(` ${dim(pad('reviewEndRatio', 30))} ${dim(ss.reviewEndRatio + '%')} ${dim(ss.reviewEndRatio > 30 ? '\u2014 reviews work before shipping' : '\u2014 add review steps before finishing')}`);
|
|
521
|
+
console.log(` ${dim(pad('refinementRatio', 30))} ${dim(ss.refinementRatio + '%')} ${dim(ss.refinementRatio > 20 ? '\u2014 critically evaluates output' : '\u2014 push back on AI output more')}`);
|
|
522
|
+
console.log(` ${dim(pad('avgFirstPromptLength', 30))} ${dim(ss.avgFirstPromptLength + ' chars')} ${dim(ss.avgFirstPromptLength > 200 ? '\u2014 thorough session openers' : '\u2014 try longer context-setting openers')}`);
|
|
523
|
+
if (ss.durationDistribution) {
|
|
524
|
+
const dur = ss.durationDistribution;
|
|
525
|
+
console.log(` ${dim(pad('duration.focused (10-45m)', 30))} ${dim(String(dur.focused))} ${dim('\u2014 sweet spot sessions')}`);
|
|
526
|
+
console.log(` ${dim(pad('duration.short (<5m)', 30))} ${dim(String(dur.short))}`);
|
|
527
|
+
console.log(` ${dim(pad('duration.medium (5-60m)', 30))} ${dim(String(dur.medium))}`);
|
|
528
|
+
console.log(` ${dim(pad('duration.long (>60m)', 30))} ${dim(String(dur.long))}`);
|
|
412
529
|
}
|
|
413
530
|
console.log();
|
|
414
531
|
}
|
|
@@ -417,9 +534,10 @@ export function displayVerbose(metrics, sessions) {
|
|
|
417
534
|
// OFFLINE DISPLAY (no API)
|
|
418
535
|
// ══════════════════════════════════════════════
|
|
419
536
|
|
|
420
|
-
export function displayOffline(result, metrics) {
|
|
421
|
-
displayScore(result, null);
|
|
537
|
+
export function displayOffline(result, metrics, extra = {}) {
|
|
538
|
+
displayScore(result, null, extra);
|
|
422
539
|
displayDataNarrativesWithTracker(metrics, new Set());
|
|
540
|
+
displayCoaching(result, metrics);
|
|
423
541
|
displayEasterEggs(result, metrics);
|
|
424
542
|
console.log(dim(' Run without --offline for personalized AI-generated insights\n'));
|
|
425
543
|
displayEnding(result);
|
|
@@ -429,9 +547,10 @@ export function displayOffline(result, metrics) {
|
|
|
429
547
|
// FULL ONLINE DISPLAY
|
|
430
548
|
// ══════════════════════════════════════════════
|
|
431
549
|
|
|
432
|
-
export function displayFull(result, metrics, prose) {
|
|
433
|
-
displayScore(result, prose);
|
|
550
|
+
export function displayFull(result, metrics, prose, extra = {}) {
|
|
551
|
+
displayScore(result, prose, extra);
|
|
434
552
|
displayNarratives(metrics, prose);
|
|
553
|
+
displayCoaching(result, metrics);
|
|
435
554
|
displayEasterEggs(result, metrics);
|
|
436
555
|
displayEnding(result);
|
|
437
556
|
}
|
package/src/index.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { homedir } from 'os';
|
|
2
5
|
import { detectTools } from './detect.js';
|
|
3
6
|
import { parseAllProjects } from './parsers/claude-code.js';
|
|
4
7
|
import { parseAllWorkspaces } from './parsers/cursor.js';
|
|
@@ -19,6 +22,52 @@ import {
|
|
|
19
22
|
} from './display.js';
|
|
20
23
|
import { generateProse, askVerbose, askClaim, uploadAndClaim } from './upload.js';
|
|
21
24
|
|
|
25
|
+
// ── Score history ──
|
|
26
|
+
const HISTORY_DIR = join(homedir(), '.chekk');
|
|
27
|
+
const HISTORY_FILE = join(HISTORY_DIR, 'history.json');
|
|
28
|
+
|
|
29
|
+
function loadHistory() {
|
|
30
|
+
try {
|
|
31
|
+
if (existsSync(HISTORY_FILE)) {
|
|
32
|
+
return JSON.parse(readFileSync(HISTORY_FILE, 'utf-8'));
|
|
33
|
+
}
|
|
34
|
+
} catch {}
|
|
35
|
+
return { scans: [] };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function saveHistory(history) {
|
|
39
|
+
try {
|
|
40
|
+
if (!existsSync(HISTORY_DIR)) mkdirSync(HISTORY_DIR, { recursive: true });
|
|
41
|
+
writeFileSync(HISTORY_FILE, JSON.stringify(history, null, 2));
|
|
42
|
+
} catch {}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function computePerToolScores(allSessions) {
|
|
46
|
+
const toolSessions = {};
|
|
47
|
+
for (const s of allSessions) {
|
|
48
|
+
const t = s.sourceTool || 'Unknown';
|
|
49
|
+
if (!toolSessions[t]) toolSessions[t] = [];
|
|
50
|
+
toolSessions[t].push(s);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const perTool = {};
|
|
54
|
+
for (const [tool, sessions] of Object.entries(toolSessions)) {
|
|
55
|
+
if (sessions.length < 2) {
|
|
56
|
+
perTool[tool] = { sessions: sessions.length, score: null, label: 'limited data' };
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const m = {
|
|
60
|
+
decomposition: computeDecomposition(sessions),
|
|
61
|
+
debugCycles: computeDebugCycles(sessions),
|
|
62
|
+
aiLeverage: computeAILeverage(sessions),
|
|
63
|
+
sessionStructure: computeSessionStructure(sessions),
|
|
64
|
+
};
|
|
65
|
+
const r = computeOverallScore(m);
|
|
66
|
+
perTool[tool] = { sessions: sessions.length, score: r.overall, label: null };
|
|
67
|
+
}
|
|
68
|
+
return perTool;
|
|
69
|
+
}
|
|
70
|
+
|
|
22
71
|
export async function run(options = {}) {
|
|
23
72
|
// ── Header ──
|
|
24
73
|
displayHeader();
|
|
@@ -34,16 +83,21 @@ export async function run(options = {}) {
|
|
|
34
83
|
|
|
35
84
|
displayScan(tools);
|
|
36
85
|
|
|
37
|
-
// ── Step 2: Parse sessions ──
|
|
86
|
+
// ── Step 2: Parse sessions (tag each with sourceTool) ──
|
|
38
87
|
let allSessions = [];
|
|
39
88
|
for (const tool of tools) {
|
|
89
|
+
let parsed = [];
|
|
40
90
|
if (tool.tool === 'Claude Code') {
|
|
41
|
-
|
|
91
|
+
parsed = parseAllProjects(tool.basePath);
|
|
42
92
|
} else if (tool.tool === 'Cursor') {
|
|
43
|
-
|
|
93
|
+
parsed = parseAllWorkspaces(tool.basePath);
|
|
44
94
|
} else if (tool.tool === 'Codex') {
|
|
45
|
-
|
|
95
|
+
parsed = parseCodexSessions(tool.basePath);
|
|
96
|
+
}
|
|
97
|
+
for (const s of parsed) {
|
|
98
|
+
s.sourceTool = tool.tool;
|
|
46
99
|
}
|
|
100
|
+
allSessions.push(...parsed);
|
|
47
101
|
}
|
|
48
102
|
|
|
49
103
|
if (allSessions.length === 0) {
|
|
@@ -80,6 +134,25 @@ export async function run(options = {}) {
|
|
|
80
134
|
|
|
81
135
|
const result = computeOverallScore(metrics);
|
|
82
136
|
|
|
137
|
+
// ── Cross-platform scores ──
|
|
138
|
+
const perToolScores = tools.length > 1 ? computePerToolScores(allSessions) : null;
|
|
139
|
+
|
|
140
|
+
// ── Score delta (compare to last scan) ──
|
|
141
|
+
const history = loadHistory();
|
|
142
|
+
const lastScan = history.scans.length > 0 ? history.scans[history.scans.length - 1] : null;
|
|
143
|
+
const scoreDelta = lastScan ? result.overall - lastScan.overall : null;
|
|
144
|
+
|
|
145
|
+
// Save current scan
|
|
146
|
+
history.scans.push({
|
|
147
|
+
overall: result.overall,
|
|
148
|
+
tier: result.tier,
|
|
149
|
+
archetype: result.archetype.name,
|
|
150
|
+
date: new Date().toISOString(),
|
|
151
|
+
});
|
|
152
|
+
// Keep last 20 scans
|
|
153
|
+
if (history.scans.length > 20) history.scans = history.scans.slice(-20);
|
|
154
|
+
saveHistory(history);
|
|
155
|
+
|
|
83
156
|
const sessionStats = {
|
|
84
157
|
totalSessions: allSessions.length,
|
|
85
158
|
totalExchanges,
|
|
@@ -90,13 +163,11 @@ export async function run(options = {}) {
|
|
|
90
163
|
|
|
91
164
|
// ── JSON output ──
|
|
92
165
|
if (options.json) {
|
|
93
|
-
console.log(JSON.stringify({ metrics, result, sessionStats }, null, 2));
|
|
166
|
+
console.log(JSON.stringify({ metrics, result, sessionStats, perToolScores, scoreDelta }, null, 2));
|
|
94
167
|
return;
|
|
95
168
|
}
|
|
96
169
|
|
|
97
170
|
// ── Step 4: Progress bar + API call in parallel ──
|
|
98
|
-
// Run prose generation alongside the progress bar so score appears
|
|
99
|
-
// immediately when the bar hits 100% — no lag.
|
|
100
171
|
let prose = null;
|
|
101
172
|
if (!options.offline) {
|
|
102
173
|
const [, proseResult] = await Promise.all([
|
|
@@ -110,13 +181,12 @@ export async function run(options = {}) {
|
|
|
110
181
|
|
|
111
182
|
// ── Step 5: Display results ──
|
|
112
183
|
if (options.offline) {
|
|
113
|
-
displayOffline(result, metrics);
|
|
184
|
+
displayOffline(result, metrics, { scoreDelta, perToolScores });
|
|
114
185
|
} else {
|
|
115
|
-
displayFull(result, metrics, prose);
|
|
186
|
+
displayFull(result, metrics, prose, { scoreDelta, perToolScores });
|
|
116
187
|
}
|
|
117
188
|
|
|
118
189
|
// ── Step 6: Verbose prompt (interactive) ──
|
|
119
|
-
// If --verbose flag was passed, show immediately. Otherwise prompt.
|
|
120
190
|
if (options.verbose) {
|
|
121
191
|
displayVerbose(metrics, allSessions);
|
|
122
192
|
} else {
|