chekk 0.2.0 → 0.2.2

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.0')
11
+ .version('0.2.2')
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.0",
3
+ "version": "0.2.2",
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
  }
@@ -67,11 +68,11 @@ export function displayHeader() {
67
68
  console.log();
68
69
  const lines = [
69
70
  '',
70
- ` ${bold.white('chekk')}${dim(' v0.2.0')}`,
71
+ ` ${bold.white('chekk')}${dim(' v0.2.2')}`,
71
72
  ` ${dim('the engineering capability score')}`,
72
73
  '',
73
74
  ];
74
- for (const l of box(lines)) console.log(l);
75
+ for (const l of box(lines, 45)) console.log(l);
75
76
  console.log();
76
77
  }
77
78
 
@@ -130,7 +131,7 @@ export async function displayProgressBar(durationMs = 2000) {
130
131
  // ══════════════════════════════════════════════
131
132
 
132
133
  export function displayScore(result, prose) {
133
- const { overall, scores, archetype, tier } = result;
134
+ const { overall, scores, archetype, tier, tierBadge, tierPercentile } = result;
134
135
  const tc = tierColor(tier);
135
136
  const sc = scoreColor(overall);
136
137
 
@@ -140,7 +141,9 @@ export function displayScore(result, prose) {
140
141
  console.log(dim(' YOUR CHEKK SCORE'));
141
142
  console.log();
142
143
  console.log(` ${sc.bold(String(overall))}`);
143
- console.log(` ${tc('\u2500\u2500 ' + tier + ' \u2500\u2500')}`);
144
+ const badgeStr = tierBadge ? ` ${tierBadge}` : '';
145
+ const pctStr = tierPercentile ? ` ${dim(tierPercentile)}` : '';
146
+ console.log(` ${tc('\u2500\u2500 ' + tier + ' \u2500\u2500')}${badgeStr}${pctStr}`);
144
147
  console.log();
145
148
  console.log(` ${dim('Archetype:')} ${bold.white(archetype.name)}`);
146
149
 
@@ -153,15 +156,28 @@ export function displayScore(result, prose) {
153
156
 
154
157
  // Dimensions box
155
158
  console.log(dim(' DIMENSIONS\n'));
159
+
160
+ // Build dimension lines with fixed-width layout
161
+ // Format: " Label ██████████████░░░░ XX "
162
+ // Visible: 2 + 15 + 18 + 2 + 3 + 1 = ~41 chars
163
+ function dimLine(label, score) {
164
+ const labelStr = bold(label);
165
+ const labelVisible = label.length;
166
+ const labelPad = ' '.repeat(Math.max(0, 15 - labelVisible));
167
+ const scoreStr = String(score);
168
+ const scorePad = score < 10 ? ' ' : score < 100 ? ' ' : '';
169
+ return ` ${labelStr}${labelPad}${progressBar(score)} ${scorePad}${bold(scoreStr)} `;
170
+ }
171
+
156
172
  const dimLines = [
157
173
  '',
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))}`,
174
+ dimLine('Thinking', scores.decomposition),
175
+ dimLine('Debugging', scores.debugCycles),
176
+ dimLine('AI Leverage', scores.aiLeverage),
177
+ dimLine('Workflow', scores.sessionStructure),
162
178
  '',
163
179
  ];
164
- for (const l of box(dimLines)) console.log(l);
180
+ for (const l of box(dimLines, 45)) console.log(l);
165
181
  console.log();
166
182
  }
167
183
 
@@ -259,7 +275,7 @@ export function displayEasterEggs(result, metrics) {
259
275
  // ══════════════════════════════════════════════
260
276
 
261
277
  export function displayEnding(result) {
262
- const { overall, archetype, tier } = result;
278
+ const { overall, archetype, tier, tierBadge } = result;
263
279
  const tc = tierColor(tier);
264
280
 
265
281
  console.log(doubleRule());
@@ -269,7 +285,8 @@ export function displayEnding(result) {
269
285
  console.log();
270
286
 
271
287
  // Copy-paste share line
272
- const shareLine = `${overall} \u2014 ${tier} \u2014 ${archetype.name}`;
288
+ const badge = tierBadge ? ` ${tierBadge}` : '';
289
+ const shareLine = `${overall} \u2014 ${tier}${badge} \u2014 ${archetype.name}`;
273
290
  console.log(` ${dim('"')}${tc(shareLine)}${dim('"')}`);
274
291
  console.log(` ${dim('\u2191 Copy this to share')}`);
275
292
  console.log();
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();
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
@@ -47,6 +47,23 @@ export async function generateProse(metrics, result, sessionStats) {
47
47
  return response.json();
48
48
  }
49
49
 
50
+ /**
51
+ * Ask user if they want to see detailed breakdown.
52
+ */
53
+ export function askVerbose() {
54
+ return new Promise((resolve) => {
55
+ const rl = createInterface({
56
+ input: process.stdin,
57
+ output: process.stdout,
58
+ });
59
+
60
+ rl.question(' See detailed breakdown? (y/n) ', (answer) => {
61
+ rl.close();
62
+ resolve(answer.toLowerCase().startsWith('y'));
63
+ });
64
+ });
65
+ }
66
+
50
67
  /**
51
68
  * Ask user if they want to claim their profile.
52
69
  */