chekk 0.2.1 → 0.2.3

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 CHANGED
@@ -8,7 +8,7 @@ const program = new Command();
8
8
  program
9
9
  .name('chekk')
10
10
  .description('The engineering capability score. See how you prompt.')
11
- .version('0.2.1')
11
+ .version('0.2.3')
12
12
  .option('--offline', 'Skip AI prose generation, show data-driven output')
13
13
  .option('--verbose', 'Show detailed per-project and per-metric breakdowns')
14
14
  .option('--json', 'Output raw metrics as JSON')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chekk",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "See how you prompt. Chekk analyzes your AI coding workflow and tells you what kind of engineer you are.",
5
5
  "bin": {
6
6
  "chekk": "./bin/chekk.js"
package/src/display.js CHANGED
@@ -12,7 +12,8 @@ const white = chalk.white;
12
12
 
13
13
  function tierColor(tier) {
14
14
  if (tier === 'LEGENDARY') return gold;
15
- if (tier === 'RARE') return purple;
15
+ if (tier === 'ULTRA RARE') return purple;
16
+ if (tier === 'RARE') return cyan;
16
17
  if (tier === 'UNCOMMON') return blue;
17
18
  return dim;
18
19
  }
@@ -41,6 +42,28 @@ function pad(str, len) {
41
42
  return str + ' '.repeat(Math.max(0, len - visible.length));
42
43
  }
43
44
 
45
+ function snippet(prompt, maxLen = 70) {
46
+ if (!prompt) return null;
47
+ // Clean up whitespace / newlines
48
+ let clean = prompt.replace(/\s+/g, ' ').trim();
49
+ if (clean.length > maxLen) {
50
+ clean = clean.slice(0, maxLen - 1) + '\u2026';
51
+ }
52
+ return clean;
53
+ }
54
+
55
+ function displaySnippet(prompt, maxLen = 70) {
56
+ const s = snippet(prompt, maxLen);
57
+ if (!s) return;
58
+ console.log(` ${dim('\u201C')}${dim.italic(s)}${dim('\u201D')}`);
59
+ }
60
+
61
+ function pickExample(examples, type) {
62
+ if (!examples || !examples.length) return null;
63
+ const match = examples.find(e => e.type === type);
64
+ return match ? match.prompt : null;
65
+ }
66
+
44
67
  // ── Box drawing ──
45
68
 
46
69
  function box(lines, width = 43) {
@@ -67,7 +90,7 @@ export function displayHeader() {
67
90
  console.log();
68
91
  const lines = [
69
92
  '',
70
- ` ${bold.white('chekk')}${dim(' v0.2.1')}`,
93
+ ` ${bold.white('chekk')}${dim(' v0.2.3')}`,
71
94
  ` ${dim('the engineering capability score')}`,
72
95
  '',
73
96
  ];
@@ -130,7 +153,7 @@ export async function displayProgressBar(durationMs = 2000) {
130
153
  // ══════════════════════════════════════════════
131
154
 
132
155
  export function displayScore(result, prose) {
133
- const { overall, scores, archetype, tier } = result;
156
+ const { overall, scores, archetype, tier, tierBadge, tierPercentile } = result;
134
157
  const tc = tierColor(tier);
135
158
  const sc = scoreColor(overall);
136
159
 
@@ -140,7 +163,9 @@ export function displayScore(result, prose) {
140
163
  console.log(dim(' YOUR CHEKK SCORE'));
141
164
  console.log();
142
165
  console.log(` ${sc.bold(String(overall))}`);
143
- console.log(` ${tc('\u2500\u2500 ' + tier + ' \u2500\u2500')}`);
166
+ const badgeStr = tierBadge ? ` ${tierBadge}` : '';
167
+ const pctStr = tierPercentile ? ` ${dim(tierPercentile)}` : '';
168
+ console.log(` ${tc('\u2500\u2500 ' + tier + ' \u2500\u2500')}${badgeStr}${pctStr}`);
144
169
  console.log();
145
170
  console.log(` ${dim('Archetype:')} ${bold.white(archetype.name)}`);
146
171
 
@@ -193,8 +218,10 @@ export function displayNarratives(metrics, prose) {
193
218
  }
194
219
  console.log();
195
220
  }
221
+ // Show prompt evidence after AI prose
222
+ displayPromptEvidence(metrics);
196
223
  } else {
197
- // Fallback: data-driven bullet points
224
+ // Fallback: data-driven bullet points with inline snippets
198
225
  displayDataNarratives(metrics);
199
226
  }
200
227
  }
@@ -204,6 +231,20 @@ function displayDataNarratives(metrics) {
204
231
  const db = metrics.debugCycles.details;
205
232
  const ai = metrics.aiLeverage.details;
206
233
  const ss = metrics.sessionStructure.details;
234
+ const dEx = metrics.decomposition.examples || [];
235
+ const dbEx = metrics.debugCycles.examples || [];
236
+ const aiEx = metrics.aiLeverage.examples || [];
237
+ const ssEx = metrics.sessionStructure.examples || [];
238
+
239
+ // Track shown snippets to avoid duplicates
240
+ const shownSnippets = new Set();
241
+ function showUniqueSnippet(prompt) {
242
+ if (!prompt) return;
243
+ const s = snippet(prompt, 70);
244
+ if (shownSnippets.has(s)) return;
245
+ shownSnippets.add(s);
246
+ displaySnippet(prompt);
247
+ }
207
248
 
208
249
  // Thinking
209
250
  console.log(` ${bold('\uD83E\uDDE0 THINKING')}`);
@@ -211,6 +252,7 @@ function displayDataNarratives(metrics) {
211
252
  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`)}`);
212
253
  console.log(` ${dim(d.avgPromptLength > 500 ? `${numberFormat(d.avgPromptLength)} char avg prompt \u2014 thinks out loud` : `${d.avgPromptLength} char avg prompt \u2014 concise communicator`)}`);
213
254
  console.log(` ${dim(d.multiStepSessions > d.singleShotSessions * 2 ? 'Multi-step decomposition over single-shot' : 'Mix of multi-step and single-shot sessions')}`);
255
+ showUniqueSnippet(pickExample(dEx, 'decomposition'));
214
256
  console.log();
215
257
 
216
258
  // Debugging
@@ -219,6 +261,7 @@ function displayDataNarratives(metrics) {
219
261
  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`)}`);
220
262
  console.log(` ${dim(`${db.specificReportRatio}% specific error reports`)}`);
221
263
  console.log(` ${dim(db.longLoops === 0 ? 'Zero extended debug loops detected' : `${db.longLoops} extended debug loops`)}`);
264
+ showUniqueSnippet(pickExample(dbEx, 'specific_report') || pickExample(dbEx, 'quick_fix'));
222
265
  console.log();
223
266
 
224
267
  // AI Leverage
@@ -227,6 +270,7 @@ function displayDataNarratives(metrics) {
227
270
  const codingRatio = ai.toolDiversity.coding > ai.toolDiversity.research ? 'Coding-heavy' : 'Research-heavy';
228
271
  console.log(` ${dim(`${codingRatio} over ${ai.toolDiversity.coding > ai.toolDiversity.research ? 'research' : 'coding'}-heavy`)}`);
229
272
  console.log(` ${dim(`${ai.architecturalPrompts} architectural, ${ai.planningPrompts} planning, ${ai.exploratoryPrompts} exploratory`)}`);
273
+ showUniqueSnippet(pickExample(aiEx, 'architectural') || pickExample(aiEx, 'planning'));
230
274
  console.log();
231
275
 
232
276
  // Workflow
@@ -234,6 +278,51 @@ function displayDataNarratives(metrics) {
234
278
  console.log(` ${dim(`${ss.contextSetRatio}% context-setting rate \u2014 ${ss.contextSetRatio > 50 ? 'deliberate' : ss.contextSetRatio > 25 ? 'moderate' : 'low'}`)}`);
235
279
  console.log(` ${dim(`${ss.reviewEndRatio}% sessions end with review`)}`);
236
280
  console.log(` ${dim(`${ss.refinementRatio}% refinement rate \u2014 ${ss.refinementRatio > 20 ? 'critical eye' : 'accepts readily'}`)}`);
281
+ showUniqueSnippet(pickExample(ssEx, 'context_setting') || pickExample(ssEx, 'refinement'));
282
+ console.log();
283
+ }
284
+
285
+ // ── Prompt evidence block (shown after AI prose) ──
286
+
287
+ function displayPromptEvidence(metrics) {
288
+ const allExamples = [
289
+ ...(metrics.decomposition.examples || []),
290
+ ...(metrics.debugCycles.examples || []),
291
+ ...(metrics.aiLeverage.examples || []),
292
+ ...(metrics.sessionStructure.examples || []),
293
+ ];
294
+
295
+ if (allExamples.length === 0) return;
296
+
297
+ // Pick up to 3 best examples to show as evidence, deduplicated
298
+ const candidates = [];
299
+ const arch = pickExample(metrics.aiLeverage.examples, 'architectural');
300
+ const decomp = pickExample(metrics.decomposition.examples, 'decomposition');
301
+ const debug = pickExample(metrics.debugCycles.examples, 'specific_report');
302
+ const ctx = pickExample(metrics.sessionStructure.examples, 'context_setting');
303
+
304
+ if (arch) candidates.push({ label: 'Architecture', prompt: arch });
305
+ if (decomp) candidates.push({ label: 'Thinking', prompt: decomp });
306
+ if (debug) candidates.push({ label: 'Debugging', prompt: debug });
307
+ if (ctx) candidates.push({ label: 'Context', prompt: ctx });
308
+
309
+ // Deduplicate by snippet text
310
+ const picks = [];
311
+ const seen = new Set();
312
+ for (const c of candidates) {
313
+ const s = snippet(c.prompt, 65);
314
+ if (!seen.has(s)) {
315
+ seen.add(s);
316
+ picks.push(c);
317
+ }
318
+ }
319
+
320
+ if (picks.length === 0) return;
321
+
322
+ console.log(dim(' YOUR PROMPTS\n'));
323
+ for (const pick of picks.slice(0, 3)) {
324
+ console.log(` ${dim(pick.label + ':')} ${dim('\u201C')}${dim.italic(snippet(pick.prompt, 65))}${dim('\u201D')}`);
325
+ }
237
326
  console.log();
238
327
  }
239
328
 
@@ -272,7 +361,7 @@ export function displayEasterEggs(result, metrics) {
272
361
  // ══════════════════════════════════════════════
273
362
 
274
363
  export function displayEnding(result) {
275
- const { overall, archetype, tier } = result;
364
+ const { overall, archetype, tier, tierBadge } = result;
276
365
  const tc = tierColor(tier);
277
366
 
278
367
  console.log(doubleRule());
@@ -282,7 +371,8 @@ export function displayEnding(result) {
282
371
  console.log();
283
372
 
284
373
  // Copy-paste share line
285
- const shareLine = `${overall} \u2014 ${tier} \u2014 ${archetype.name}`;
374
+ const badge = tierBadge ? ` ${tierBadge}` : '';
375
+ const shareLine = `${overall} \u2014 ${tier}${badge} \u2014 ${archetype.name}`;
286
376
  console.log(` ${dim('"')}${tc(shareLine)}${dim('"')}`);
287
377
  console.log(` ${dim('\u2191 Copy this to share')}`);
288
378
  console.log();
@@ -362,8 +452,7 @@ export function displayOffline(result, metrics) {
362
452
  displayScore(result, null);
363
453
  displayDataNarratives(metrics);
364
454
  displayEasterEggs(result, metrics);
365
- console.log(dim(' Run without --offline for personalized AI-generated insights'));
366
- console.log(dim(' Run with --verbose for full per-project and metric breakdown\n'));
455
+ console.log(dim(' Run without --offline for personalized AI-generated insights\n'));
367
456
  displayEnding(result);
368
457
  }
369
458
 
@@ -375,7 +464,6 @@ export function displayFull(result, metrics, prose) {
375
464
  displayScore(result, prose);
376
465
  displayNarratives(metrics, prose);
377
466
  displayEasterEggs(result, metrics);
378
- console.log(dim(' Run with --verbose for full per-project and metric breakdown\n'));
379
467
  displayEnding(result);
380
468
  }
381
469
 
package/src/index.js CHANGED
@@ -15,7 +15,7 @@ import {
15
15
  displayOffline,
16
16
  displayVerbose,
17
17
  } from './display.js';
18
- import { generateProse, askClaim, uploadAndClaim } from './upload.js';
18
+ import { generateProse, askVerbose, askClaim, uploadAndClaim } from './upload.js';
19
19
 
20
20
  export async function run(options = {}) {
21
21
  // ── Header ──
@@ -93,35 +93,52 @@ export async function run(options = {}) {
93
93
  tools: supported.map(t => t.tool),
94
94
  };
95
95
 
96
- // Progress bar animation
97
- await displayProgressBar(1500);
98
-
99
96
  // ── JSON output ──
100
97
  if (options.json) {
101
98
  console.log(JSON.stringify({ metrics, result, sessionStats }, null, 2));
102
99
  return;
103
100
  }
104
101
 
105
- // ── Step 4: Display results ──
102
+ // ── Step 4: Progress bar + API call in parallel ──
103
+ // Run prose generation alongside the progress bar so score appears
104
+ // immediately when the bar hits 100% — no lag.
105
+ let prose = null;
106
+ if (!options.offline) {
107
+ const [, proseResult] = await Promise.all([
108
+ displayProgressBar(1500),
109
+ generateProse(metrics, result, sessionStats).catch(() => null),
110
+ ]);
111
+ prose = proseResult;
112
+ } else {
113
+ await displayProgressBar(1500);
114
+ }
115
+
116
+ // ── Step 5: Display results ──
106
117
  if (options.offline) {
107
118
  displayOffline(result, metrics);
108
119
  } else {
109
- // Generate prose from API
110
- let prose = null;
111
- try {
112
- prose = await generateProse(metrics, result, sessionStats);
113
- } catch {
114
- // Silently fall back to data-driven display
115
- }
116
120
  displayFull(result, metrics, prose);
117
121
  }
118
122
 
119
- // ── Verbose breakdown ──
123
+ // ── Step 6: Verbose prompt (interactive) ──
124
+ // If --verbose flag was passed, show immediately. Otherwise prompt.
120
125
  if (options.verbose) {
121
126
  displayVerbose(metrics, allSessions);
127
+ } else {
128
+ try {
129
+ const wantsVerbose = await askVerbose();
130
+ if (wantsVerbose) {
131
+ console.log();
132
+ displayVerbose(metrics, allSessions);
133
+ } else {
134
+ console.log();
135
+ }
136
+ } catch {
137
+ console.log();
138
+ }
122
139
  }
123
140
 
124
- // ── Step 5: Claim prompt ──
141
+ // ── Step 7: Claim prompt ──
125
142
  if (options.upload !== false) {
126
143
  try {
127
144
  const wantsClaim = await askClaim();
@@ -37,15 +37,32 @@ export function computeAILeverage(sessions) {
37
37
  let complexPrompts = 0; // > 200 chars with multiple sentences
38
38
  let trivialPrompts = 0; // < 50 chars, simple commands
39
39
 
40
+ // Capture representative examples
41
+ let bestArchPrompt = null; // best architectural prompt
42
+ let bestPlanPrompt = null; // best planning prompt
43
+ let bestExplorePrompt = null; // best exploratory prompt
44
+ let bestArchLen = 0;
45
+ let bestPlanLen = 0;
46
+ let bestExploreLen = 0;
47
+
40
48
  for (const session of sessions) {
41
49
  for (const exchange of session.exchanges) {
42
50
  const prompt = exchange.userPrompt || '';
43
51
  totalPrompts++;
44
52
 
45
53
  // Categorize prompt type
46
- if (architecturalPatterns.test(prompt)) architecturalPrompts++;
47
- if (planningPatterns.test(prompt)) planningPrompts++;
48
- if (exploratoryPatterns.test(prompt)) exploratoryPrompts++;
54
+ if (architecturalPatterns.test(prompt)) {
55
+ architecturalPrompts++;
56
+ if (prompt.length > bestArchLen) { bestArchLen = prompt.length; bestArchPrompt = prompt; }
57
+ }
58
+ if (planningPatterns.test(prompt)) {
59
+ planningPrompts++;
60
+ if (prompt.length > bestPlanLen) { bestPlanLen = prompt.length; bestPlanPrompt = prompt; }
61
+ }
62
+ if (exploratoryPatterns.test(prompt)) {
63
+ exploratoryPrompts++;
64
+ if (prompt.length > bestExploreLen) { bestExploreLen = prompt.length; bestExplorePrompt = prompt; }
65
+ }
49
66
  if (boilerplatePatterns.test(prompt)) boilerplatePrompts++;
50
67
  if (testingPatterns.test(prompt)) testingPrompts++;
51
68
 
@@ -103,6 +120,12 @@ export function computeAILeverage(sessions) {
103
120
  50 * 0 // baseline filler
104
121
  );
105
122
 
123
+ // Build examples — pick the best one available
124
+ const examples = [];
125
+ if (bestArchPrompt) examples.push({ type: 'architectural', prompt: bestArchPrompt });
126
+ if (bestPlanPrompt) examples.push({ type: 'planning', prompt: bestPlanPrompt });
127
+ if (bestExplorePrompt) examples.push({ type: 'exploratory', prompt: bestExplorePrompt });
128
+
106
129
  return {
107
130
  score: Math.max(0, Math.min(100, score)),
108
131
  details: {
@@ -120,5 +143,6 @@ export function computeAILeverage(sessions) {
120
143
  coding: codingToolUses,
121
144
  },
122
145
  },
146
+ examples,
123
147
  };
124
148
  }
@@ -26,10 +26,16 @@ export function computeDebugCycles(sessions) {
26
26
  let quickFixes = 0; // resolved in 1-2 turns
27
27
  let longLoops = 0; // > 5 turns to resolve
28
28
 
29
+ // Capture representative examples
30
+ let bestSpecificReport = null; // best specific error report
31
+ let bestQuickFix = null; // prompt that led to quick resolution
32
+ let bestSpecificLen = 0;
33
+
29
34
  for (const session of sessions) {
30
35
  const { exchanges } = session;
31
36
  let inDebugMode = false;
32
37
  let debugTurnCount = 0;
38
+ let debugStartPrompt = null;
33
39
 
34
40
  for (let i = 0; i < exchanges.length; i++) {
35
41
  const prompt = exchanges[i].userPrompt || '';
@@ -39,6 +45,7 @@ export function computeDebugCycles(sessions) {
39
45
  // Starting a new debug sequence
40
46
  inDebugMode = true;
41
47
  debugTurnCount = 1;
48
+ debugStartPrompt = prompt;
42
49
  totalDebugSequences++;
43
50
  } else {
44
51
  debugTurnCount++;
@@ -50,21 +57,31 @@ export function computeDebugCycles(sessions) {
50
57
  }
51
58
  if (specificDebugPatterns.test(prompt) || prompt.length > 200) {
52
59
  specificReports++;
60
+ // Track best specific report
61
+ if (prompt.length > bestSpecificLen) {
62
+ bestSpecificLen = prompt.length;
63
+ bestSpecificReport = prompt;
64
+ }
53
65
  }
54
66
  } else if (inDebugMode) {
55
67
  // Check if this exchange resolves the debug
56
68
  if (resolutionPatterns.test(prompt)) {
57
69
  totalTurnsToResolve += debugTurnCount;
58
- if (debugTurnCount <= 2) quickFixes++;
70
+ if (debugTurnCount <= 2) {
71
+ quickFixes++;
72
+ if (!bestQuickFix) bestQuickFix = debugStartPrompt;
73
+ }
59
74
  if (debugTurnCount > 5) longLoops++;
60
75
  inDebugMode = false;
61
76
  debugTurnCount = 0;
77
+ debugStartPrompt = null;
62
78
  } else {
63
79
  // Moved on without explicit resolution
64
80
  totalTurnsToResolve += debugTurnCount;
65
81
  unresolvedSequences++;
66
82
  inDebugMode = false;
67
83
  debugTurnCount = 0;
84
+ debugStartPrompt = null;
68
85
  }
69
86
  }
70
87
  }
@@ -109,6 +126,11 @@ export function computeDebugCycles(sessions) {
109
126
  (100 - longLoopPenalty) * 0.15
110
127
  );
111
128
 
129
+ // Build examples array
130
+ const examples = [];
131
+ if (bestSpecificReport) examples.push({ type: 'specific_report', prompt: bestSpecificReport });
132
+ if (bestQuickFix) examples.push({ type: 'quick_fix', prompt: bestQuickFix });
133
+
112
134
  return {
113
135
  score: Math.max(0, Math.min(100, score)),
114
136
  details: {
@@ -120,5 +142,6 @@ export function computeDebugCycles(sessions) {
120
142
  vagueReports,
121
143
  specificReports,
122
144
  },
145
+ examples,
123
146
  };
124
147
  }
@@ -26,6 +26,11 @@ export function computeDecomposition(sessions) {
26
26
  const followupPatterns = /^(now |next |then |also |and |ok |okay |great |good |perfect |after that|building on|following up|continuing)/i;
27
27
  const refinementPatterns = /^(actually |wait |hmm |instead |change |modify |update |tweak |adjust |fix |but )/i;
28
28
 
29
+ // Capture representative prompt examples
30
+ // Keep top 3 candidates and pick the 2nd-longest to avoid overlap with other metrics
31
+ const decompCandidates = [];
32
+ let bestFollowupPrompt = null;
33
+
29
34
  for (const session of sessions) {
30
35
  const { exchanges } = session;
31
36
  totalExchanges += exchanges.length;
@@ -45,10 +50,19 @@ export function computeDecomposition(sessions) {
45
50
  if (len > 500) longPromptCount++;
46
51
  if (len < 100) shortPromptCount++;
47
52
 
53
+ // Track decomposition examples (multi-sentence prompts showing task breakdown)
54
+ if (len > 150 && len < 2000) {
55
+ decompCandidates.push(prompt);
56
+ }
57
+
48
58
  // Check for contextual followups (not the first prompt in a session)
49
59
  if (i > 0) {
50
60
  if (followupPatterns.test(prompt) || refinementPatterns.test(prompt)) {
51
61
  contextualFollowups++;
62
+ // Capture best followup example
63
+ if (!bestFollowupPrompt || prompt.length > bestFollowupPrompt.length) {
64
+ bestFollowupPrompt = prompt;
65
+ }
52
66
  }
53
67
  }
54
68
  }
@@ -83,6 +97,16 @@ export function computeDecomposition(sessions) {
83
97
  depthScore * 0.2
84
98
  );
85
99
 
100
+ // Build examples array — pick a mid-length prompt to avoid overlap with other metrics
101
+ const examples = [];
102
+ if (decompCandidates.length > 0) {
103
+ decompCandidates.sort((a, b) => b.length - a.length);
104
+ // Pick ~median length to avoid the longest (which will also match ai-leverage)
105
+ const pickIdx = Math.min(Math.floor(decompCandidates.length / 3), decompCandidates.length - 1);
106
+ examples.push({ type: 'decomposition', prompt: decompCandidates[pickIdx] });
107
+ }
108
+ if (bestFollowupPrompt) examples.push({ type: 'followup', prompt: bestFollowupPrompt });
109
+
86
110
  return {
87
111
  score: Math.max(0, Math.min(100, score)),
88
112
  details: {
@@ -94,5 +118,6 @@ export function computeDecomposition(sessions) {
94
118
  longPromptRatio: promptCount > 0 ? Math.round(longPromptCount / promptCount * 100) : 0,
95
119
  contextualFollowupRatio: promptCount > 0 ? Math.round(followupRatio * 100) : 0,
96
120
  },
121
+ examples,
97
122
  };
98
123
  }
@@ -34,6 +34,11 @@ export function computeSessionStructure(sessions) {
34
34
  // First prompt length distribution (longer first prompts = more context setting)
35
35
  let firstPromptTotalLength = 0;
36
36
 
37
+ // Capture representative examples
38
+ let bestContextPrompt = null; // best context-setting opener
39
+ let bestRefinementPrompt = null; // best refinement/critical feedback
40
+ let bestContextLen = 0;
41
+
37
42
  for (const session of sessions) {
38
43
  const { exchanges, durationMinutes } = session;
39
44
  if (exchanges.length === 0) continue;
@@ -46,6 +51,11 @@ export function computeSessionStructure(sessions) {
46
51
 
47
52
  if (contextSettingPatterns.test(firstPrompt) || firstPrompt.length > 200) {
48
53
  contextSetSessions++;
54
+ // Track best context-setting prompt
55
+ if (firstPrompt.length > bestContextLen) {
56
+ bestContextLen = firstPrompt.length;
57
+ bestContextPrompt = firstPrompt;
58
+ }
49
59
  }
50
60
 
51
61
  if (planningStartPatterns.test(firstPrompt)) {
@@ -66,6 +76,10 @@ export function computeSessionStructure(sessions) {
66
76
  const prompt = exchanges[i].userPrompt || '';
67
77
  if (refinementPatterns.test(prompt)) {
68
78
  refinementCount++;
79
+ // Track best refinement example
80
+ if (!bestRefinementPrompt || prompt.length > bestRefinementPrompt.length) {
81
+ bestRefinementPrompt = prompt;
82
+ }
69
83
  }
70
84
  }
71
85
 
@@ -115,6 +129,11 @@ export function computeSessionStructure(sessions) {
115
129
  firstPromptScore * 0.15
116
130
  );
117
131
 
132
+ // Build examples array
133
+ const examples = [];
134
+ if (bestContextPrompt) examples.push({ type: 'context_setting', prompt: bestContextPrompt });
135
+ if (bestRefinementPrompt) examples.push({ type: 'refinement', prompt: bestRefinementPrompt });
136
+
118
137
  return {
119
138
  score: Math.max(0, Math.min(100, score)),
120
139
  details: {
@@ -130,5 +149,6 @@ export function computeSessionStructure(sessions) {
130
149
  focused: focusedSessions,
131
150
  },
132
151
  },
152
+ examples,
133
153
  };
134
154
  }
package/src/scorer.js CHANGED
@@ -1,79 +1,162 @@
1
1
  /**
2
2
  * Combine individual metric scores into composite scores and assign archetype.
3
+ *
4
+ * Archetypes and tiers are consistent with the GitHub analysis system.
5
+ * 15 archetypes across 5 tiers, mapped from repo-based signals to
6
+ * prompt-behavior signals measured by the CLI.
3
7
  */
4
8
 
9
+ // ── Tier thresholds (match GitHub analysis) ──
10
+
11
+ const TIERS = [
12
+ { name: 'LEGENDARY', min: 85, badge: '🌟🌟🌟', percentile: 'Top 1%' },
13
+ { name: 'ULTRA RARE', min: 73, badge: '🌟🌟', percentile: 'Top 5%' },
14
+ { name: 'RARE', min: 60, badge: '⭐', percentile: 'Top 15%' },
15
+ { name: 'UNCOMMON', min: 47, badge: '◆', percentile: 'Top 30%' },
16
+ { name: 'COMMON', min: 0, badge: '●', percentile: 'Top 50%' },
17
+ ];
18
+
19
+ // ── Archetype definitions ──
20
+ // Each archetype has a match function based on the 4 CLI dimension scores:
21
+ // decomposition (thinking), debugCycles (debugging),
22
+ // aiLeverage (AI leverage), sessionStructure (workflow)
23
+ //
24
+ // Tier-restricted: archetypes only match within their allowed tier range.
25
+
5
26
  const ARCHETYPES = [
27
+ // ── LEGENDARY (85+) ──
6
28
  {
7
- id: 'THE_ARCHITECT',
8
- name: 'THE ARCHITECT',
9
- // High decomposition + high planning + high leverage
10
- match: (s) => s.decomposition >= 70 && s.sessionStructure >= 65 && s.aiLeverage >= 65,
29
+ id: 'THE_10X_ENGINEER',
30
+ name: 'THE 10X ENGINEER',
31
+ tierRange: ['LEGENDARY'],
32
+ match: (s) => true, // If you hit LEGENDARY, you're a 10x
11
33
  priority: 1,
12
34
  },
35
+
36
+ // ── ULTRA RARE (73-84) ──
13
37
  {
14
- id: 'THE_SURGEON',
15
- name: 'THE SURGEON',
16
- // High debug efficiency + high leverage + good structure
17
- match: (s) => s.debugCycles >= 75 && s.aiLeverage >= 60,
38
+ id: 'THE_ARCHITECT',
39
+ name: 'THE ARCHITECT',
40
+ tierRange: ['ULTRA RARE'],
41
+ // High decomposition + structured workflow + strong leverage
42
+ match: (s) => s.decomposition >= 70 && s.sessionStructure >= 65,
18
43
  priority: 2,
19
44
  },
45
+ {
46
+ id: 'THE_PROFESSOR',
47
+ name: 'THE PROFESSOR',
48
+ tierRange: ['ULTRA RARE'],
49
+ // Exceptional at explaining: high decomposition, detailed prompts, strong context-setting
50
+ match: (s) => s.decomposition >= 65 && s.sessionStructure >= 70,
51
+ priority: 3,
52
+ },
53
+
54
+ // ── RARE (60-72) ──
55
+ {
56
+ id: 'THE_SPECIALIST',
57
+ name: 'THE SPECIALIST',
58
+ tierRange: ['RARE'],
59
+ // Deep focused work: high debug efficiency + high leverage in specific domain
60
+ match: (s) => s.debugCycles >= 70 && s.aiLeverage >= 65,
61
+ priority: 4,
62
+ },
63
+ {
64
+ id: 'THE_SYSTEMS_THINKER',
65
+ name: 'THE SYSTEMS THINKER',
66
+ tierRange: ['RARE'],
67
+ // Strong decomposition + structured approach to complex problems
68
+ match: (s) => s.decomposition >= 65 && s.sessionStructure >= 60,
69
+ priority: 5,
70
+ },
71
+ {
72
+ id: 'THE_MAINTAINER',
73
+ name: 'THE MAINTAINER',
74
+ tierRange: ['RARE'],
75
+ // Reliable workflow + solid debugging: keeps things running
76
+ match: (s) => s.sessionStructure >= 60 && s.debugCycles >= 60,
77
+ priority: 6,
78
+ },
79
+
80
+ // ── UNCOMMON (47-59) ──
81
+ {
82
+ id: 'THE_CRAFTSPERSON',
83
+ name: 'THE CRAFTSPERSON',
84
+ tierRange: ['UNCOMMON'],
85
+ // Quality obsession: high debug precision + structured sessions
86
+ match: (s) => s.debugCycles >= 60 && s.sessionStructure >= 55,
87
+ priority: 7,
88
+ },
20
89
  {
21
90
  id: 'THE_BUILDER',
22
91
  name: 'THE BUILDER',
23
- // High overall scores, well-rounded
92
+ tierRange: ['UNCOMMON'],
93
+ // Well-rounded: decent across all dimensions
24
94
  match: (s) => {
25
- const avg = (s.decomposition + s.debugCycles + s.aiLeverage + s.sessionStructure) / 4;
26
- return avg >= 65 && Math.min(s.decomposition, s.debugCycles, s.aiLeverage, s.sessionStructure) >= 55;
95
+ const min = Math.min(s.decomposition, s.debugCycles, s.aiLeverage, s.sessionStructure);
96
+ return min >= 45;
27
97
  },
28
- priority: 3,
98
+ priority: 8,
99
+ },
100
+ {
101
+ id: 'THE_CONTRIBUTOR',
102
+ name: 'THE CONTRIBUTOR',
103
+ tierRange: ['UNCOMMON'],
104
+ // Strong AI leverage + decent decomposition: collaborates well with AI
105
+ match: (s) => s.aiLeverage >= 55 && s.decomposition >= 50,
106
+ priority: 9,
107
+ },
108
+ {
109
+ id: 'THE_HIDDEN_GEM',
110
+ name: 'THE HIDDEN GEM',
111
+ tierRange: ['UNCOMMON'],
112
+ // Strong skills but unstructured: high debug or leverage but low structure
113
+ match: (s) => (s.debugCycles >= 55 || s.aiLeverage >= 55) && s.sessionStructure < 50,
114
+ priority: 10,
29
115
  },
30
116
  {
31
117
  id: 'THE_EXPLORER',
32
118
  name: 'THE EXPLORER',
33
- // High leverage + high decomposition, loves research
34
- match: (s) => s.aiLeverage >= 70 && s.decomposition >= 60,
35
- priority: 4,
119
+ tierRange: ['UNCOMMON'],
120
+ // High leverage + curiosity-driven: research-heavy prompting
121
+ match: (s) => s.aiLeverage >= 50,
122
+ priority: 11,
36
123
  },
124
+
125
+ // ── COMMON (0-46) ──
37
126
  {
38
- id: 'THE_SPEEDRUNNER',
39
- name: 'THE SPEEDRUNNER',
40
- // Quick debug cycles, less structured but efficient
41
- match: (s) => s.debugCycles >= 70 && s.sessionStructure < 60,
42
- priority: 5,
127
+ id: 'THE_TINKERER',
128
+ name: 'THE TINKERER',
129
+ tierRange: ['COMMON'],
130
+ // Practical problem-solving: decent debug + some leverage
131
+ match: (s) => s.debugCycles >= 40 && s.aiLeverage >= 35,
132
+ priority: 12,
43
133
  },
44
134
  {
45
- id: 'THE_APPRENTICE',
46
- name: 'THE APPRENTICE',
47
- // Learning patterns - lower scores but shows growth potential
48
- match: (s) => {
49
- const avg = (s.decomposition + s.debugCycles + s.aiLeverage + s.sessionStructure) / 4;
50
- return avg >= 40 && avg < 65;
51
- },
52
- priority: 6,
135
+ id: 'THE_GRINDER',
136
+ name: 'THE GRINDER',
137
+ tierRange: ['COMMON'],
138
+ // High volume, iterative: lots of exchanges, keeps going
139
+ match: (s) => s.decomposition >= 35,
140
+ priority: 13,
53
141
  },
54
142
  {
55
- id: 'THE_MAVERICK',
56
- name: 'THE MAVERICK',
57
- // Unstructured but somehow gets things done - low structure, decent other scores
58
- match: (s) => s.sessionStructure < 50 && (s.decomposition >= 55 || s.aiLeverage >= 55),
59
- priority: 7,
143
+ id: 'THE_HOBBYIST',
144
+ name: 'THE HOBBYIST',
145
+ tierRange: ['COMMON'],
146
+ // Active but early: some leverage or debugging skill
147
+ match: (s) => s.aiLeverage >= 30 || s.debugCycles >= 30,
148
+ priority: 14,
60
149
  },
61
150
  {
62
- id: 'THE_NEWCOMER',
63
- name: 'THE NEWCOMER',
64
- // Default fallback
151
+ id: 'THE_APPRENTICE',
152
+ name: 'THE APPRENTICE',
153
+ tierRange: ['COMMON'],
154
+ // Default fallback for COMMON tier
65
155
  match: () => true,
66
156
  priority: 99,
67
157
  },
68
158
  ];
69
159
 
70
- const TIERS = [
71
- { name: 'LEGENDARY', min: 90 },
72
- { name: 'RARE', min: 75 },
73
- { name: 'UNCOMMON', min: 55 },
74
- { name: 'COMMON', min: 0 },
75
- ];
76
-
77
160
  export function computeOverallScore(metrics) {
78
161
  const scores = {
79
162
  decomposition: metrics.decomposition.score,
@@ -90,13 +173,18 @@ export function computeOverallScore(metrics) {
90
173
  scores.sessionStructure * 0.2
91
174
  );
92
175
 
93
- // Assign archetype
94
- const sortedArchetypes = [...ARCHETYPES].sort((a, b) => a.priority - b.priority);
95
- const archetype = sortedArchetypes.find(a => a.match(scores)) || ARCHETYPES[ARCHETYPES.length - 1];
96
-
97
- // Assign tier
176
+ // Assign tier first
98
177
  const tier = TIERS.find(t => overall >= t.min) || TIERS[TIERS.length - 1];
99
178
 
179
+ // Assign archetype within tier
180
+ const tierArchetypes = ARCHETYPES
181
+ .filter(a => a.tierRange.includes(tier.name))
182
+ .sort((a, b) => a.priority - b.priority);
183
+
184
+ const archetype = tierArchetypes.find(a => a.match(scores))
185
+ || tierArchetypes[tierArchetypes.length - 1]
186
+ || { id: 'THE_APPRENTICE', name: 'THE APPRENTICE' };
187
+
100
188
  return {
101
189
  overall,
102
190
  scores,
@@ -105,5 +193,7 @@ export function computeOverallScore(metrics) {
105
193
  name: archetype.name,
106
194
  },
107
195
  tier: tier.name,
196
+ tierBadge: tier.badge,
197
+ tierPercentile: tier.percentile,
108
198
  };
109
199
  }
package/src/upload.js CHANGED
@@ -5,24 +5,38 @@ const API_BASE = 'https://chekk-production.up.railway.app/api/v1';
5
5
  /**
6
6
  * Call the Chekk API to generate personalized prose from metrics.
7
7
  */
8
+ function truncateExamples(examples, maxLen = 120) {
9
+ if (!examples || !examples.length) return [];
10
+ return examples.map(e => ({
11
+ type: e.type,
12
+ prompt: e.prompt && e.prompt.length > maxLen
13
+ ? e.prompt.replace(/\s+/g, ' ').trim().slice(0, maxLen) + '...'
14
+ : (e.prompt || '').replace(/\s+/g, ' ').trim(),
15
+ }));
16
+ }
17
+
8
18
  export async function generateProse(metrics, result, sessionStats) {
9
19
  const payload = {
10
20
  metrics: {
11
21
  decomposition: {
12
22
  score: metrics.decomposition.score,
13
23
  details: metrics.decomposition.details,
24
+ examples: truncateExamples(metrics.decomposition.examples),
14
25
  },
15
26
  debugCycles: {
16
27
  score: metrics.debugCycles.score,
17
28
  details: metrics.debugCycles.details,
29
+ examples: truncateExamples(metrics.debugCycles.examples),
18
30
  },
19
31
  aiLeverage: {
20
32
  score: metrics.aiLeverage.score,
21
33
  details: metrics.aiLeverage.details,
34
+ examples: truncateExamples(metrics.aiLeverage.examples),
22
35
  },
23
36
  sessionStructure: {
24
37
  score: metrics.sessionStructure.score,
25
38
  details: metrics.sessionStructure.details,
39
+ examples: truncateExamples(metrics.sessionStructure.examples),
26
40
  },
27
41
  },
28
42
  result: {
@@ -47,6 +61,23 @@ export async function generateProse(metrics, result, sessionStats) {
47
61
  return response.json();
48
62
  }
49
63
 
64
+ /**
65
+ * Ask user if they want to see detailed breakdown.
66
+ */
67
+ export function askVerbose() {
68
+ return new Promise((resolve) => {
69
+ const rl = createInterface({
70
+ input: process.stdin,
71
+ output: process.stdout,
72
+ });
73
+
74
+ rl.question(' See detailed breakdown? (y/n) ', (answer) => {
75
+ rl.close();
76
+ resolve(answer.toLowerCase().startsWith('y'));
77
+ });
78
+ });
79
+ }
80
+
50
81
  /**
51
82
  * Ask user if they want to claim their profile.
52
83
  */