chekk 0.1.0 → 0.2.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 +5 -4
- package/package.json +1 -1
- package/src/display.js +316 -82
- package/src/index.js +65 -62
package/bin/chekk.js
CHANGED
|
@@ -7,11 +7,12 @@ const program = new Command();
|
|
|
7
7
|
|
|
8
8
|
program
|
|
9
9
|
.name('chekk')
|
|
10
|
-
.description('
|
|
11
|
-
.version('0.
|
|
12
|
-
.option('--offline', 'Skip prose generation
|
|
10
|
+
.description('The engineering capability score. See how you prompt.')
|
|
11
|
+
.version('0.2.0')
|
|
12
|
+
.option('--offline', 'Skip AI prose generation, show data-driven output')
|
|
13
|
+
.option('--verbose', 'Show detailed per-project and per-metric breakdowns')
|
|
13
14
|
.option('--json', 'Output raw metrics as JSON')
|
|
14
|
-
.option('--no-upload', 'Skip the claim
|
|
15
|
+
.option('--no-upload', 'Skip the claim prompt')
|
|
15
16
|
.action(async (options) => {
|
|
16
17
|
await run(options);
|
|
17
18
|
});
|
package/package.json
CHANGED
package/src/display.js
CHANGED
|
@@ -1,137 +1,371 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
// ── Color palette ──
|
|
4
|
+
const gold = chalk.hex('#FFD700');
|
|
5
|
+
const purple = chalk.hex('#A855F7');
|
|
6
|
+
const blue = chalk.hex('#3B82F6');
|
|
7
|
+
const green = chalk.hex('#22C55E');
|
|
8
|
+
const cyan = chalk.hex('#06B6D4');
|
|
9
|
+
const dim = chalk.dim;
|
|
10
|
+
const bold = chalk.bold;
|
|
11
|
+
const white = chalk.white;
|
|
12
|
+
|
|
13
|
+
function tierColor(tier) {
|
|
14
|
+
if (tier === 'LEGENDARY') return gold;
|
|
15
|
+
if (tier === 'RARE') return purple;
|
|
16
|
+
if (tier === 'UNCOMMON') return blue;
|
|
17
|
+
return dim;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function scoreColor(score) {
|
|
21
|
+
if (score >= 85) return green;
|
|
22
|
+
if (score >= 70) return cyan;
|
|
23
|
+
if (score >= 55) return chalk.hex('#EAB308');
|
|
24
|
+
if (score >= 40) return chalk.hex('#F97316');
|
|
25
|
+
return chalk.hex('#EF4444');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function progressBar(score, width = 18) {
|
|
29
|
+
const filled = Math.round((score / 100) * width);
|
|
30
|
+
const empty = width - filled;
|
|
31
|
+
return scoreColor(score)('\u2588'.repeat(filled)) + dim('\u2591'.repeat(empty));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function numberFormat(n) {
|
|
35
|
+
if (n >= 1000) return (n / 1000).toFixed(1).replace(/\.0$/, '') + 'k';
|
|
36
|
+
return String(n);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function pad(str, len) {
|
|
40
|
+
const visible = str.replace(/\u001b\[[0-9;]*m/g, '');
|
|
41
|
+
return str + ' '.repeat(Math.max(0, len - visible.length));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ── Box drawing ──
|
|
45
|
+
|
|
46
|
+
function box(lines, width = 43) {
|
|
47
|
+
const out = [];
|
|
48
|
+
out.push(dim(' \u250C' + '\u2500'.repeat(width) + '\u2510'));
|
|
49
|
+
for (const line of lines) {
|
|
50
|
+
const visible = line.replace(/\u001b\[[0-9;]*m/g, '');
|
|
51
|
+
const padding = Math.max(0, width - visible.length);
|
|
52
|
+
out.push(dim(' \u2502') + line + ' '.repeat(padding) + dim('\u2502'));
|
|
53
|
+
}
|
|
54
|
+
out.push(dim(' \u2514' + '\u2500'.repeat(width) + '\u2518'));
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function doubleRule(width = 47) {
|
|
59
|
+
return dim(' ' + '\u2550'.repeat(width));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ══════════════════════════════════════════════
|
|
63
|
+
// HEADER
|
|
64
|
+
// ══════════════════════════════════════════════
|
|
65
|
+
|
|
66
|
+
export function displayHeader() {
|
|
67
|
+
console.log();
|
|
68
|
+
const lines = [
|
|
69
|
+
'',
|
|
70
|
+
` ${bold.white('chekk')}${dim(' v0.2.0')}`,
|
|
71
|
+
` ${dim('the engineering capability score')}`,
|
|
72
|
+
'',
|
|
73
|
+
];
|
|
74
|
+
for (const l of box(lines)) console.log(l);
|
|
7
75
|
console.log();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ══════════════════════════════════════════════
|
|
79
|
+
// SCAN RESULTS
|
|
80
|
+
// ══════════════════════════════════════════════
|
|
81
|
+
|
|
82
|
+
export function displayScan(tools) {
|
|
83
|
+
console.log(dim(' Scanning local AI tools...\n'));
|
|
8
84
|
|
|
9
85
|
for (const tool of tools) {
|
|
10
86
|
if (tool.status === 'detected_not_supported') {
|
|
11
|
-
console.log(
|
|
87
|
+
console.log(` ${dim('\u25CB')} ${dim(tool.tool)}${dim(' detected \u2014 V2')}`);
|
|
12
88
|
} else {
|
|
89
|
+
const sessions = numberFormat(tool.sessions);
|
|
90
|
+
const projects = tool.projects.length;
|
|
91
|
+
const exchanges = numberFormat(tool.estimatedPrompts);
|
|
13
92
|
console.log(
|
|
14
|
-
|
|
15
|
-
|
|
93
|
+
` ${cyan('\u2726')} ${bold.white(tool.tool)} ` +
|
|
94
|
+
dim(`${sessions} sessions \u00B7 ${projects} projects`)
|
|
16
95
|
);
|
|
17
96
|
}
|
|
18
97
|
}
|
|
19
98
|
console.log();
|
|
20
99
|
}
|
|
21
100
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
101
|
+
// ══════════════════════════════════════════════
|
|
102
|
+
// ANALYSIS PROGRESS BAR
|
|
103
|
+
// ══════════════════════════════════════════════
|
|
104
|
+
|
|
105
|
+
export function displayAnalysisStart(dateRange) {
|
|
106
|
+
console.log(` ${dim('Analyzing')} ${white(dateRange)}${dim('...\n')}`);
|
|
107
|
+
}
|
|
28
108
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
109
|
+
export async function displayProgressBar(durationMs = 2000) {
|
|
110
|
+
const width = 40;
|
|
111
|
+
const steps = 40;
|
|
112
|
+
const stepTime = durationMs / steps;
|
|
33
113
|
|
|
34
|
-
|
|
114
|
+
for (let i = 0; i <= steps; i++) {
|
|
115
|
+
const filled = i;
|
|
116
|
+
const empty = width - filled;
|
|
117
|
+
const pct = Math.round((i / steps) * 100);
|
|
118
|
+
const bar = green('\u2588'.repeat(filled)) + dim('\u2591'.repeat(empty));
|
|
119
|
+
process.stdout.write(`\r ${bar} ${dim(pct + '%')}`);
|
|
120
|
+
if (i < steps) await sleep(stepTime);
|
|
121
|
+
}
|
|
122
|
+
console.log();
|
|
123
|
+
console.log();
|
|
124
|
+
console.log(` ${green('\u2713')} ${dim('Score generated.')}`);
|
|
125
|
+
console.log();
|
|
35
126
|
}
|
|
36
127
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
export function displayResults(result, prose) {
|
|
41
|
-
const { overall, archetype, tier } = result;
|
|
128
|
+
// ══════════════════════════════════════════════
|
|
129
|
+
// MAIN SCORE DISPLAY
|
|
130
|
+
// ══════════════════════════════════════════════
|
|
42
131
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
132
|
+
export function displayScore(result, prose) {
|
|
133
|
+
const { overall, scores, archetype, tier } = result;
|
|
134
|
+
const tc = tierColor(tier);
|
|
135
|
+
const sc = scoreColor(overall);
|
|
47
136
|
|
|
48
|
-
|
|
137
|
+
// Big score block
|
|
138
|
+
console.log(doubleRule());
|
|
139
|
+
console.log();
|
|
140
|
+
console.log(dim(' YOUR CHEKK SCORE'));
|
|
141
|
+
console.log();
|
|
142
|
+
console.log(` ${sc.bold(String(overall))}`);
|
|
143
|
+
console.log(` ${tc('\u2500\u2500 ' + tier + ' \u2500\u2500')}`);
|
|
49
144
|
console.log();
|
|
145
|
+
console.log(` ${dim('Archetype:')} ${bold.white(archetype.name)}`);
|
|
50
146
|
|
|
51
|
-
|
|
147
|
+
if (prose && prose.tagline) {
|
|
148
|
+
console.log(` ${dim('"' + prose.tagline + '"')}`);
|
|
149
|
+
}
|
|
150
|
+
console.log();
|
|
151
|
+
console.log(doubleRule());
|
|
152
|
+
console.log();
|
|
153
|
+
|
|
154
|
+
// Dimensions box
|
|
155
|
+
console.log(dim(' DIMENSIONS\n'));
|
|
156
|
+
const dimLines = [
|
|
157
|
+
'',
|
|
158
|
+
` ${pad(bold('Thinking'), 20)} ${progressBar(scores.decomposition)} ${bold(String(scores.decomposition))}`,
|
|
159
|
+
` ${pad(bold('Debugging'), 20)} ${progressBar(scores.debugCycles)} ${bold(String(scores.debugCycles))}`,
|
|
160
|
+
` ${pad(bold('AI Leverage'), 20)}${progressBar(scores.aiLeverage)} ${bold(String(scores.aiLeverage))}`,
|
|
161
|
+
` ${pad(bold('Workflow'), 20)} ${progressBar(scores.sessionStructure)} ${bold(String(scores.sessionStructure))}`,
|
|
162
|
+
'',
|
|
163
|
+
];
|
|
164
|
+
for (const l of box(dimLines)) console.log(l);
|
|
165
|
+
console.log();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ══════════════════════════════════════════════
|
|
169
|
+
// DIMENSION NARRATIVES (data-driven, punchy)
|
|
170
|
+
// ══════════════════════════════════════════════
|
|
171
|
+
|
|
172
|
+
export function displayNarratives(metrics, prose) {
|
|
52
173
|
if (prose && prose.sections) {
|
|
174
|
+
// Use DeepSeek-generated prose
|
|
53
175
|
for (const section of prose.sections) {
|
|
54
|
-
console.log(`
|
|
55
|
-
|
|
56
|
-
const lines = wordWrap(section.description, 45);
|
|
176
|
+
console.log(` ${section.emoji} ${bold(section.title.toUpperCase())}`);
|
|
177
|
+
const lines = section.description.split('\n').filter(l => l.trim());
|
|
57
178
|
for (const line of lines) {
|
|
58
|
-
console.log(
|
|
179
|
+
console.log(` ${dim(line.trim())}`);
|
|
59
180
|
}
|
|
60
181
|
console.log();
|
|
61
182
|
}
|
|
183
|
+
} else {
|
|
184
|
+
// Fallback: data-driven bullet points
|
|
185
|
+
displayDataNarratives(metrics);
|
|
62
186
|
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function displayDataNarratives(metrics) {
|
|
190
|
+
const d = metrics.decomposition.details;
|
|
191
|
+
const db = metrics.debugCycles.details;
|
|
192
|
+
const ai = metrics.aiLeverage.details;
|
|
193
|
+
const ss = metrics.sessionStructure.details;
|
|
63
194
|
|
|
64
|
-
|
|
195
|
+
// Thinking
|
|
196
|
+
console.log(` ${bold('\uD83E\uDDE0 THINKING')}`);
|
|
197
|
+
const exchPerSession = d.avgExchangesPerSession;
|
|
198
|
+
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`)}`);
|
|
199
|
+
console.log(` ${dim(d.avgPromptLength > 500 ? `${numberFormat(d.avgPromptLength)} char avg prompt \u2014 thinks out loud` : `${d.avgPromptLength} char avg prompt \u2014 concise communicator`)}`);
|
|
200
|
+
console.log(` ${dim(d.multiStepSessions > d.singleShotSessions * 2 ? 'Multi-step decomposition over single-shot' : 'Mix of multi-step and single-shot sessions')}`);
|
|
65
201
|
console.log();
|
|
66
202
|
|
|
67
|
-
//
|
|
68
|
-
console.log(`
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
203
|
+
// Debugging
|
|
204
|
+
console.log(` ${bold('\u26A1 DEBUGGING')}`);
|
|
205
|
+
const turns = db.avgTurnsToResolve;
|
|
206
|
+
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`)}`);
|
|
207
|
+
console.log(` ${dim(`${db.specificReportRatio}% specific error reports`)}`);
|
|
208
|
+
console.log(` ${dim(db.longLoops === 0 ? 'Zero extended debug loops detected' : `${db.longLoops} extended debug loops`)}`);
|
|
209
|
+
console.log();
|
|
210
|
+
|
|
211
|
+
// AI Leverage
|
|
212
|
+
console.log(` ${bold('\uD83D\uDD27 AI LEVERAGE')}`);
|
|
213
|
+
console.log(` ${dim(`${ai.highLevelRatio}% architecture prompts \u2014 ${ai.highLevelRatio > 25 ? 'high-level partner usage' : 'room to leverage AI more strategically'}`)}`);
|
|
214
|
+
const codingRatio = ai.toolDiversity.coding > ai.toolDiversity.research ? 'Coding-heavy' : 'Research-heavy';
|
|
215
|
+
console.log(` ${dim(`${codingRatio} over ${ai.toolDiversity.coding > ai.toolDiversity.research ? 'research' : 'coding'}-heavy`)}`);
|
|
216
|
+
console.log(` ${dim(`${ai.architecturalPrompts} architectural, ${ai.planningPrompts} planning, ${ai.exploratoryPrompts} exploratory`)}`);
|
|
72
217
|
console.log();
|
|
73
|
-
|
|
218
|
+
|
|
219
|
+
// Workflow
|
|
220
|
+
console.log(` ${bold('\uD83D\uDCD0 WORKFLOW')}`);
|
|
221
|
+
console.log(` ${dim(`${ss.contextSetRatio}% context-setting rate \u2014 ${ss.contextSetRatio > 50 ? 'deliberate' : ss.contextSetRatio > 25 ? 'moderate' : 'low'}`)}`);
|
|
222
|
+
console.log(` ${dim(`${ss.reviewEndRatio}% sessions end with review`)}`);
|
|
223
|
+
console.log(` ${dim(`${ss.refinementRatio}% refinement rate \u2014 ${ss.refinementRatio > 20 ? 'critical eye' : 'accepts readily'}`)}`);
|
|
74
224
|
console.log();
|
|
75
225
|
}
|
|
76
226
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
export function displayOfflineResults(result) {
|
|
81
|
-
const { overall, scores, archetype, tier } = result;
|
|
227
|
+
// ══════════════════════════════════════════════
|
|
228
|
+
// EASTER EGGS
|
|
229
|
+
// ══════════════════════════════════════════════
|
|
82
230
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
chalk.dim;
|
|
231
|
+
export function displayEasterEggs(result, metrics) {
|
|
232
|
+
const { overall } = result;
|
|
233
|
+
const db = metrics.debugCycles.details;
|
|
87
234
|
|
|
88
|
-
|
|
89
|
-
|
|
235
|
+
const eggs = [];
|
|
236
|
+
|
|
237
|
+
if (overall >= 90) {
|
|
238
|
+
eggs.push('Top-tier. Companies should be applying to you.');
|
|
239
|
+
}
|
|
90
240
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
241
|
+
if (db.longLoops === 0 && db.totalDebugSequences > 10) {
|
|
242
|
+
eggs.push('Zero debug spirals. Either you\'re exceptional or your AI is.');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (db.vagueReports === 0 && db.totalDebugSequences > 20) {
|
|
246
|
+
eggs.push('Never once said "it\'s broken, fix it." Respect.');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (eggs.length > 0) {
|
|
250
|
+
for (const egg of eggs) {
|
|
251
|
+
console.log(` ${dim('\u2727')} ${dim.italic(egg)}`);
|
|
252
|
+
}
|
|
253
|
+
console.log();
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ══════════════════════════════════════════════
|
|
258
|
+
// VIRAL ENDING
|
|
259
|
+
// ══════════════════════════════════════════════
|
|
260
|
+
|
|
261
|
+
export function displayEnding(result) {
|
|
262
|
+
const { overall, archetype, tier } = result;
|
|
263
|
+
const tc = tierColor(tier);
|
|
264
|
+
|
|
265
|
+
console.log(doubleRule());
|
|
95
266
|
console.log();
|
|
96
|
-
console.log(
|
|
267
|
+
console.log(` ${dim('\u2192 Claim your profile:')} ${cyan.underline('chekk.dev/claim')}`);
|
|
268
|
+
console.log(` ${dim('\u2192 Leaderboard:')} ${cyan.underline('chekk.dev/leaderboard')}`);
|
|
97
269
|
console.log();
|
|
98
270
|
|
|
99
|
-
|
|
100
|
-
|
|
271
|
+
// Copy-paste share line
|
|
272
|
+
const shareLine = `${overall} \u2014 ${tier} \u2014 ${archetype.name}`;
|
|
273
|
+
console.log(` ${dim('"')}${tc(shareLine)}${dim('"')}`);
|
|
274
|
+
console.log(` ${dim('\u2191 Copy this to share')}`);
|
|
101
275
|
console.log();
|
|
102
|
-
console.log(
|
|
276
|
+
console.log(doubleRule());
|
|
277
|
+
console.log(` ${dim('chekk.dev \u2014 the engineering capability score')}`);
|
|
103
278
|
console.log();
|
|
104
279
|
}
|
|
105
280
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
281
|
+
// ══════════════════════════════════════════════
|
|
282
|
+
// VERBOSE: DETAILED BREAKDOWN
|
|
283
|
+
// ══════════════════════════════════════════════
|
|
284
|
+
|
|
285
|
+
export function displayVerbose(metrics, sessions) {
|
|
286
|
+
console.log(doubleRule());
|
|
287
|
+
console.log(dim('\n DETAILED BREAKDOWN\n'));
|
|
288
|
+
|
|
289
|
+
// Per-project stats
|
|
290
|
+
const projects = {};
|
|
291
|
+
for (const s of sessions) {
|
|
292
|
+
const p = s.project || 'unknown';
|
|
293
|
+
if (!projects[p]) projects[p] = { sessions: 0, exchanges: 0, minutes: 0 };
|
|
294
|
+
projects[p].sessions++;
|
|
295
|
+
projects[p].exchanges += s.exchangeCount;
|
|
296
|
+
projects[p].minutes += s.durationMinutes || 0;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
console.log(bold(' PROJECTS'));
|
|
300
|
+
for (const [name, data] of Object.entries(projects).sort((a, b) => b[1].exchanges - a[1].exchanges)) {
|
|
301
|
+
const shortName = name.length > 30 ? '...' + name.slice(-27) : name;
|
|
302
|
+
console.log(` ${dim(pad(shortName, 32))} ${dim(data.sessions + ' sessions')} ${dim(numberFormat(data.exchanges) + ' exchanges')}`);
|
|
303
|
+
}
|
|
111
304
|
console.log();
|
|
112
|
-
|
|
305
|
+
|
|
306
|
+
// Full metric details
|
|
307
|
+
console.log(bold(' DECOMPOSITION DETAILS'));
|
|
308
|
+
for (const [k, v] of Object.entries(metrics.decomposition.details)) {
|
|
309
|
+
console.log(` ${dim(pad(k, 30))} ${dim(String(v) + (typeof v === 'number' && k.includes('Ratio') ? '%' : ''))}`);
|
|
310
|
+
}
|
|
113
311
|
console.log();
|
|
114
|
-
|
|
115
|
-
console.log(
|
|
312
|
+
|
|
313
|
+
console.log(bold(' DEBUG CYCLE DETAILS'));
|
|
314
|
+
for (const [k, v] of Object.entries(metrics.debugCycles.details)) {
|
|
315
|
+
console.log(` ${dim(pad(k, 30))} ${dim(String(v))}`);
|
|
316
|
+
}
|
|
116
317
|
console.log();
|
|
117
|
-
}
|
|
118
318
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
319
|
+
console.log(bold(' AI LEVERAGE DETAILS'));
|
|
320
|
+
for (const [k, v] of Object.entries(metrics.aiLeverage.details)) {
|
|
321
|
+
if (typeof v === 'object') {
|
|
322
|
+
for (const [k2, v2] of Object.entries(v)) {
|
|
323
|
+
console.log(` ${dim(pad(` ${k}.${k2}`, 30))} ${dim(String(v2))}`);
|
|
324
|
+
}
|
|
325
|
+
} else {
|
|
326
|
+
console.log(` ${dim(pad(k, 30))} ${dim(String(v))}`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
console.log();
|
|
126
330
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
331
|
+
console.log(bold(' SESSION STRUCTURE DETAILS'));
|
|
332
|
+
for (const [k, v] of Object.entries(metrics.sessionStructure.details)) {
|
|
333
|
+
if (typeof v === 'object') {
|
|
334
|
+
for (const [k2, v2] of Object.entries(v)) {
|
|
335
|
+
console.log(` ${dim(pad(` ${k}.${k2}`, 30))} ${dim(String(v2))}`);
|
|
336
|
+
}
|
|
131
337
|
} else {
|
|
132
|
-
|
|
338
|
+
console.log(` ${dim(pad(k, 30))} ${dim(String(v) + (typeof v === 'number' && k.includes('Ratio') ? '%' : ''))}`);
|
|
133
339
|
}
|
|
134
340
|
}
|
|
135
|
-
|
|
136
|
-
|
|
341
|
+
console.log();
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// ══════════════════════════════════════════════
|
|
345
|
+
// OFFLINE DISPLAY (no API)
|
|
346
|
+
// ══════════════════════════════════════════════
|
|
347
|
+
|
|
348
|
+
export function displayOffline(result, metrics) {
|
|
349
|
+
displayScore(result, null);
|
|
350
|
+
displayDataNarratives(metrics);
|
|
351
|
+
displayEasterEggs(result, metrics);
|
|
352
|
+
console.log(dim(' Run without --offline for personalized AI-generated insights\n'));
|
|
353
|
+
displayEnding(result);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// ══════════════════════════════════════════════
|
|
357
|
+
// FULL ONLINE DISPLAY
|
|
358
|
+
// ══════════════════════════════════════════════
|
|
359
|
+
|
|
360
|
+
export function displayFull(result, metrics, prose) {
|
|
361
|
+
displayScore(result, prose);
|
|
362
|
+
displayNarratives(metrics, prose);
|
|
363
|
+
displayEasterEggs(result, metrics);
|
|
364
|
+
displayEnding(result);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// ── Utility ──
|
|
368
|
+
|
|
369
|
+
function sleep(ms) {
|
|
370
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
137
371
|
}
|
package/src/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import ora from 'ora';
|
|
3
2
|
import { detectTools } from './detect.js';
|
|
4
3
|
import { parseAllProjects } from './parsers/claude-code.js';
|
|
5
4
|
import { computeDecomposition } from './metrics/decomposition.js';
|
|
@@ -7,81 +6,76 @@ import { computeDebugCycles } from './metrics/debug-cycles.js';
|
|
|
7
6
|
import { computeAILeverage } from './metrics/ai-leverage.js';
|
|
8
7
|
import { computeSessionStructure } from './metrics/session-structure.js';
|
|
9
8
|
import { computeOverallScore } from './scorer.js';
|
|
10
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
displayHeader,
|
|
11
|
+
displayScan,
|
|
12
|
+
displayAnalysisStart,
|
|
13
|
+
displayProgressBar,
|
|
14
|
+
displayFull,
|
|
15
|
+
displayOffline,
|
|
16
|
+
displayVerbose,
|
|
17
|
+
} from './display.js';
|
|
11
18
|
import { generateProse, askClaim, uploadAndClaim } from './upload.js';
|
|
12
19
|
|
|
13
20
|
export async function run(options = {}) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
console.log();
|
|
21
|
+
// ── Header ──
|
|
22
|
+
displayHeader();
|
|
17
23
|
|
|
18
|
-
// Step 1: Detect tools
|
|
19
|
-
const spinner = ora({ text: 'Scanning for AI coding tools...', indent: 3 }).start();
|
|
24
|
+
// ── Step 1: Detect tools ──
|
|
20
25
|
const tools = detectTools();
|
|
21
26
|
|
|
22
27
|
if (tools.length === 0) {
|
|
23
|
-
|
|
24
|
-
console.log();
|
|
25
|
-
console.log(chalk.dim('
|
|
26
|
-
console.log(chalk.dim(' Cursor and Codex support coming in V2.'));
|
|
27
|
-
console.log();
|
|
28
|
+
console.log(chalk.dim(' No AI coding tools detected.'));
|
|
29
|
+
console.log(chalk.dim(' Chekk currently supports Claude Code.'));
|
|
30
|
+
console.log(chalk.dim(' Cursor and Codex coming in V2.\n'));
|
|
28
31
|
process.exit(1);
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
const supported = tools.filter(t => t.status !== 'detected_not_supported');
|
|
32
35
|
if (supported.length === 0) {
|
|
33
|
-
|
|
34
|
-
console.log();
|
|
36
|
+
console.log(chalk.dim(' No supported tools found.\n'));
|
|
35
37
|
for (const t of tools) {
|
|
36
|
-
console.log(chalk.dim(`
|
|
38
|
+
console.log(chalk.dim(` ${t.tool} \u2014 ${t.message}`));
|
|
37
39
|
}
|
|
38
40
|
console.log();
|
|
39
41
|
process.exit(1);
|
|
40
42
|
}
|
|
41
43
|
|
|
42
|
-
|
|
43
|
-
displayDetection(tools);
|
|
44
|
-
|
|
45
|
-
// Step 2: Parse sessions
|
|
46
|
-
const parseSpinner = ora({ text: 'Parsing session history...', indent: 3 }).start();
|
|
44
|
+
displayScan(tools);
|
|
47
45
|
|
|
46
|
+
// ── Step 2: Parse sessions ──
|
|
48
47
|
let allSessions = [];
|
|
49
48
|
for (const tool of supported) {
|
|
50
49
|
if (tool.tool === 'Claude Code') {
|
|
51
|
-
|
|
52
|
-
allSessions.push(...sessions);
|
|
50
|
+
allSessions.push(...parseAllProjects(tool.basePath));
|
|
53
51
|
}
|
|
54
52
|
}
|
|
55
53
|
|
|
56
54
|
if (allSessions.length === 0) {
|
|
57
|
-
|
|
58
|
-
console.log();
|
|
55
|
+
console.log(chalk.dim(' No sessions found to analyze.\n'));
|
|
59
56
|
process.exit(1);
|
|
60
57
|
}
|
|
61
58
|
|
|
62
|
-
//
|
|
59
|
+
// Stats
|
|
63
60
|
const totalExchanges = allSessions.reduce((sum, s) => sum + s.exchangeCount, 0);
|
|
64
61
|
const projects = [...new Set(allSessions.map(s => s.project))];
|
|
65
|
-
|
|
66
|
-
// Date range
|
|
67
62
|
const allTimestamps = allSessions
|
|
68
63
|
.map(s => s.startTime)
|
|
69
64
|
.filter(Boolean)
|
|
70
65
|
.map(t => new Date(t))
|
|
71
66
|
.sort((a, b) => a - b);
|
|
72
|
-
|
|
67
|
+
|
|
68
|
+
const dateRangeShort = allTimestamps.length >= 2
|
|
69
|
+
? formatDateRange(allTimestamps[0], allTimestamps[allTimestamps.length - 1])
|
|
70
|
+
: 'recent history';
|
|
71
|
+
|
|
72
|
+
const dateRangeFull = allTimestamps.length >= 2
|
|
73
73
|
? `${allTimestamps[0].toLocaleDateString()} to ${allTimestamps[allTimestamps.length - 1].toLocaleDateString()}`
|
|
74
74
|
: 'unknown';
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
`Parsed ${chalk.bold(allSessions.length)} sessions, ${chalk.bold(totalExchanges)} exchanges`
|
|
78
|
-
);
|
|
79
|
-
console.log(chalk.dim(` ${projects.length} projects — ${dateRange}`));
|
|
80
|
-
console.log();
|
|
81
|
-
|
|
82
|
-
// Step 3: Compute metrics
|
|
83
|
-
const metricsSpinner = ora({ text: 'Analyzing your workflow patterns...', indent: 3 }).start();
|
|
76
|
+
displayAnalysisStart(dateRangeShort);
|
|
84
77
|
|
|
78
|
+
// ── Step 3: Compute metrics ──
|
|
85
79
|
const metrics = {
|
|
86
80
|
decomposition: computeDecomposition(allSessions),
|
|
87
81
|
debugCycles: computeDebugCycles(allSessions),
|
|
@@ -91,64 +85,73 @@ export async function run(options = {}) {
|
|
|
91
85
|
|
|
92
86
|
const result = computeOverallScore(metrics);
|
|
93
87
|
|
|
94
|
-
metricsSpinner.succeed('Analysis complete');
|
|
95
|
-
console.log();
|
|
96
|
-
|
|
97
88
|
const sessionStats = {
|
|
98
89
|
totalSessions: allSessions.length,
|
|
99
90
|
totalExchanges,
|
|
100
91
|
projectCount: projects.length,
|
|
101
|
-
dateRange,
|
|
92
|
+
dateRange: dateRangeFull,
|
|
102
93
|
tools: supported.map(t => t.tool),
|
|
103
94
|
};
|
|
104
95
|
|
|
105
|
-
//
|
|
96
|
+
// Progress bar animation
|
|
97
|
+
await displayProgressBar(1500);
|
|
98
|
+
|
|
99
|
+
// ── JSON output ──
|
|
106
100
|
if (options.json) {
|
|
107
101
|
console.log(JSON.stringify({ metrics, result, sessionStats }, null, 2));
|
|
108
102
|
return;
|
|
109
103
|
}
|
|
110
104
|
|
|
111
|
-
// Step 4:
|
|
105
|
+
// ── Step 4: Display results ──
|
|
112
106
|
if (options.offline) {
|
|
113
|
-
|
|
107
|
+
displayOffline(result, metrics);
|
|
114
108
|
} else {
|
|
115
|
-
|
|
116
|
-
|
|
109
|
+
// Generate prose from API
|
|
110
|
+
let prose = null;
|
|
117
111
|
try {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
displayResults(result, proseResponse);
|
|
122
|
-
} catch (err) {
|
|
123
|
-
proseSpinner.warn('Could not reach chekk.dev — showing raw scores');
|
|
124
|
-
console.log();
|
|
125
|
-
displayOfflineResults(result);
|
|
112
|
+
prose = await generateProse(metrics, result, sessionStats);
|
|
113
|
+
} catch {
|
|
114
|
+
// Silently fall back to data-driven display
|
|
126
115
|
}
|
|
116
|
+
displayFull(result, metrics, prose);
|
|
127
117
|
}
|
|
128
118
|
|
|
129
|
-
//
|
|
119
|
+
// ── Verbose breakdown ──
|
|
120
|
+
if (options.verbose) {
|
|
121
|
+
displayVerbose(metrics, allSessions);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ── Step 5: Claim prompt ──
|
|
130
125
|
if (options.upload !== false) {
|
|
131
126
|
try {
|
|
132
127
|
const wantsClaim = await askClaim();
|
|
133
128
|
if (wantsClaim) {
|
|
134
|
-
const claimSpinner = ora({ text: 'Creating your profile...', indent: 3 }).start();
|
|
135
129
|
try {
|
|
136
130
|
const claimResult = await uploadAndClaim(metrics, result, sessionStats);
|
|
137
|
-
|
|
138
|
-
|
|
131
|
+
console.log();
|
|
132
|
+
console.log(` ${chalk.green('\u2713')} ${chalk.dim('Profile created:')} ${chalk.cyan.underline(claimResult.claimUrl || 'https://chekk.dev/claim')}`);
|
|
133
|
+
console.log();
|
|
139
134
|
} catch (err) {
|
|
140
|
-
|
|
141
|
-
console.log(chalk.dim(`
|
|
135
|
+
console.log();
|
|
136
|
+
console.log(chalk.dim(` Could not create profile \u2014 try again later`));
|
|
142
137
|
console.log();
|
|
143
138
|
}
|
|
144
139
|
} else {
|
|
145
140
|
console.log();
|
|
146
|
-
console.log(chalk.dim('
|
|
147
|
-
console.log();
|
|
141
|
+
console.log(chalk.dim(' Run `npx chekk` again anytime.\n'));
|
|
148
142
|
}
|
|
149
143
|
} catch {
|
|
150
|
-
// stdin not available (piped), skip claim
|
|
151
144
|
console.log();
|
|
152
145
|
}
|
|
153
146
|
}
|
|
154
147
|
}
|
|
148
|
+
|
|
149
|
+
function formatDateRange(start, end) {
|
|
150
|
+
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
151
|
+
const s = `${months[start.getMonth()]} ${start.getDate()}`;
|
|
152
|
+
const e = `${months[end.getMonth()]} ${end.getDate()}, ${end.getFullYear()}`;
|
|
153
|
+
if (start.getFullYear() === end.getFullYear()) {
|
|
154
|
+
return `${s} \u2013 ${e}`;
|
|
155
|
+
}
|
|
156
|
+
return `${s}, ${start.getFullYear()} \u2013 ${e}`;
|
|
157
|
+
}
|