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 +1 -1
- package/package.json +1 -1
- package/src/display.js +98 -10
- package/src/index.js +31 -14
- package/src/metrics/ai-leverage.js +27 -3
- package/src/metrics/debug-cycles.js +24 -1
- package/src/metrics/decomposition.js +25 -0
- package/src/metrics/session-structure.js +20 -0
- package/src/scorer.js +138 -48
- package/src/upload.js +31 -0
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.
|
|
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
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.
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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))
|
|
47
|
-
|
|
48
|
-
|
|
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)
|
|
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: '
|
|
8
|
-
name: 'THE
|
|
9
|
-
|
|
10
|
-
match: (s) =>
|
|
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: '
|
|
15
|
-
name: 'THE
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
92
|
+
tierRange: ['UNCOMMON'],
|
|
93
|
+
// Well-rounded: decent across all dimensions
|
|
24
94
|
match: (s) => {
|
|
25
|
-
const
|
|
26
|
-
return
|
|
95
|
+
const min = Math.min(s.decomposition, s.debugCycles, s.aiLeverage, s.sessionStructure);
|
|
96
|
+
return min >= 45;
|
|
27
97
|
},
|
|
28
|
-
priority:
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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: '
|
|
39
|
-
name: 'THE
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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: '
|
|
46
|
-
name: 'THE
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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: '
|
|
56
|
-
name: 'THE
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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: '
|
|
63
|
-
name: 'THE
|
|
64
|
-
|
|
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
|
|
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
|
*/
|