chekk 0.3.0 → 0.4.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 +1 -1
- package/package.json +1 -1
- package/src/display.js +399 -238
- package/src/index.js +24 -3
- package/src/insights.js +503 -0
- package/src/scorer.js +55 -26
package/src/index.js
CHANGED
|
@@ -11,6 +11,14 @@ import { computeDebugCycles } from './metrics/debug-cycles.js';
|
|
|
11
11
|
import { computeAILeverage } from './metrics/ai-leverage.js';
|
|
12
12
|
import { computeSessionStructure } from './metrics/session-structure.js';
|
|
13
13
|
import { computeOverallScore } from './scorer.js';
|
|
14
|
+
import {
|
|
15
|
+
computeSignatures,
|
|
16
|
+
computeWatchPoints,
|
|
17
|
+
computeTrajectory,
|
|
18
|
+
computeProjectComplexity,
|
|
19
|
+
generateAssessment,
|
|
20
|
+
computeConfidence,
|
|
21
|
+
} from './insights.js';
|
|
14
22
|
import {
|
|
15
23
|
displayHeader,
|
|
16
24
|
displayScan,
|
|
@@ -147,6 +155,7 @@ export async function run(options = {}) {
|
|
|
147
155
|
overall: result.overall,
|
|
148
156
|
tier: result.tier,
|
|
149
157
|
archetype: result.archetype.name,
|
|
158
|
+
scores: result.scores,
|
|
150
159
|
date: new Date().toISOString(),
|
|
151
160
|
});
|
|
152
161
|
// Keep last 20 scans
|
|
@@ -158,12 +167,23 @@ export async function run(options = {}) {
|
|
|
158
167
|
totalExchanges,
|
|
159
168
|
projectCount: projects.length,
|
|
160
169
|
dateRange: dateRangeFull,
|
|
170
|
+
dateRangeShort,
|
|
161
171
|
tools: tools.map(t => t.tool),
|
|
162
172
|
};
|
|
163
173
|
|
|
174
|
+
// ── Step 3b: Compute insights ──
|
|
175
|
+
const signatures = computeSignatures(allSessions, metrics);
|
|
176
|
+
const watchPoints = computeWatchPoints(allSessions, metrics);
|
|
177
|
+
const trajectory = computeTrajectory(allSessions);
|
|
178
|
+
const projectComplexity = computeProjectComplexity(allSessions);
|
|
179
|
+
const assessment = generateAssessment(result, metrics, signatures, watchPoints);
|
|
180
|
+
const confidence = computeConfidence(sessionStats);
|
|
181
|
+
|
|
182
|
+
const insights = { signatures, watchPoints, trajectory, projectComplexity, assessment, confidence };
|
|
183
|
+
|
|
164
184
|
// ── JSON output ──
|
|
165
185
|
if (options.json) {
|
|
166
|
-
console.log(JSON.stringify({ metrics, result, sessionStats, perToolScores, scoreDelta }, null, 2));
|
|
186
|
+
console.log(JSON.stringify({ metrics, result, sessionStats, perToolScores, scoreDelta, insights }, null, 2));
|
|
167
187
|
return;
|
|
168
188
|
}
|
|
169
189
|
|
|
@@ -180,10 +200,11 @@ export async function run(options = {}) {
|
|
|
180
200
|
}
|
|
181
201
|
|
|
182
202
|
// ── Step 5: Display results ──
|
|
203
|
+
const extra = { scoreDelta, perToolScores, insights, sessionStats };
|
|
183
204
|
if (options.offline) {
|
|
184
|
-
displayOffline(result, metrics,
|
|
205
|
+
displayOffline(result, metrics, extra);
|
|
185
206
|
} else {
|
|
186
|
-
displayFull(result, metrics, prose,
|
|
207
|
+
displayFull(result, metrics, prose, extra);
|
|
187
208
|
}
|
|
188
209
|
|
|
189
210
|
// ── Step 6: Verbose prompt (interactive) ──
|
package/src/insights.js
ADDED
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Insights Engine
|
|
3
|
+
*
|
|
4
|
+
* Computes higher-order analysis from raw metrics and sessions:
|
|
5
|
+
* - Signatures: distinctive patterns that make an engineer unique
|
|
6
|
+
* - Watch Points: anti-patterns and areas for improvement
|
|
7
|
+
* - Trajectory: weekly score evolution over time
|
|
8
|
+
* - Project Complexity: classification of project sophistication
|
|
9
|
+
* - Assessment: narrative paragraph for the engineer's profile
|
|
10
|
+
* - Confidence: statistical confidence based on data volume
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { computeDecomposition } from './metrics/decomposition.js';
|
|
14
|
+
import { computeDebugCycles } from './metrics/debug-cycles.js';
|
|
15
|
+
import { computeAILeverage } from './metrics/ai-leverage.js';
|
|
16
|
+
import { computeSessionStructure } from './metrics/session-structure.js';
|
|
17
|
+
import { computeOverallScore } from './scorer.js';
|
|
18
|
+
|
|
19
|
+
// ── Benchmarks (early-stage estimates, refined as data grows) ──
|
|
20
|
+
export const BENCHMARKS = {
|
|
21
|
+
avgExchangesPerSession: 34.2,
|
|
22
|
+
avgPromptLength: 187,
|
|
23
|
+
avgTurnsToResolve: 3.8,
|
|
24
|
+
specificReportRatio: 62,
|
|
25
|
+
highLevelRatio: 18,
|
|
26
|
+
contextSetRatio: 35,
|
|
27
|
+
refinementRatio: 15,
|
|
28
|
+
reviewEndRatio: 28,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// ── Dimension score ranges (observed distribution) ──
|
|
32
|
+
export const DIM_RANGES = {
|
|
33
|
+
decomposition: { min: 15, max: 95 },
|
|
34
|
+
debugCycles: { min: 20, max: 98 },
|
|
35
|
+
aiLeverage: { min: 10, max: 92 },
|
|
36
|
+
sessionStructure: { min: 12, max: 88 },
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// ══════════════════════════════════════════════
|
|
40
|
+
// SIGNATURES — Distinctive patterns
|
|
41
|
+
// ══════════════════════════════════════════════
|
|
42
|
+
|
|
43
|
+
const constraintPatterns = /\b(don'?t|do not|never|avoid|without|no |not |shouldn'?t|must not|skip|exclude)\b/i;
|
|
44
|
+
const preflightPatterns = /^(before (we|you|i)|don'?t code|review (first|this|my|the plan)|let'?s (think|plan|discuss)|check my (approach|plan|thinking))/i;
|
|
45
|
+
const testFirstPatterns = /\b(write (the )?tests? (first|before)|test.?driven|TDD|spec first|start with (tests?|specs?))\b/i;
|
|
46
|
+
const negativeConstraintPatterns = /\b(don'?t|do not|never|avoid|must not|shouldn'?t)\b.*\b(add|create|use|include|change|modify|touch|remove)\b/i;
|
|
47
|
+
|
|
48
|
+
export function computeSignatures(allSessions, metrics) {
|
|
49
|
+
const signatures = [];
|
|
50
|
+
const d = metrics.decomposition.details;
|
|
51
|
+
const db = metrics.debugCycles.details;
|
|
52
|
+
const ai = metrics.aiLeverage.details;
|
|
53
|
+
const ss = metrics.sessionStructure.details;
|
|
54
|
+
|
|
55
|
+
let totalPrompts = 0;
|
|
56
|
+
let constraintPrompts = 0;
|
|
57
|
+
let preflightSessions = 0;
|
|
58
|
+
let testFirstSessions = 0;
|
|
59
|
+
let modificationCount = 0;
|
|
60
|
+
let acceptCount = 0;
|
|
61
|
+
|
|
62
|
+
for (const session of allSessions) {
|
|
63
|
+
const { exchanges } = session;
|
|
64
|
+
if (exchanges.length === 0) continue;
|
|
65
|
+
|
|
66
|
+
// Check first prompt for preflight review
|
|
67
|
+
const firstPrompt = exchanges[0].userPrompt || '';
|
|
68
|
+
if (preflightPatterns.test(firstPrompt)) {
|
|
69
|
+
preflightSessions++;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let hasTestFirst = false;
|
|
73
|
+
for (let i = 0; i < exchanges.length; i++) {
|
|
74
|
+
const prompt = exchanges[i].userPrompt || '';
|
|
75
|
+
totalPrompts++;
|
|
76
|
+
|
|
77
|
+
if (constraintPatterns.test(prompt) && negativeConstraintPatterns.test(prompt)) {
|
|
78
|
+
constraintPrompts++;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (testFirstPatterns.test(prompt)) {
|
|
82
|
+
hasTestFirst = true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Track modification vs acceptance
|
|
86
|
+
if (i > 0 && /\b(actually|wait|instead|change|no,?|not quite|modify|tweak)\b/i.test(prompt)) {
|
|
87
|
+
modificationCount++;
|
|
88
|
+
} else if (i > 0) {
|
|
89
|
+
acceptCount++;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (hasTestFirst) testFirstSessions++;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const sessionsWithExchanges = allSessions.filter(s => s.exchanges.length > 0).length;
|
|
96
|
+
|
|
97
|
+
// Pre-flight reviews
|
|
98
|
+
const preflightRatio = sessionsWithExchanges > 0 ? preflightSessions / sessionsWithExchanges : 0;
|
|
99
|
+
if (preflightRatio > 0.15 && preflightSessions >= 3) {
|
|
100
|
+
signatures.push({
|
|
101
|
+
name: 'Pre-flight reviews',
|
|
102
|
+
detail: `You ask AI to review your plan before coding in ${Math.round(preflightRatio * 100)}% of sessions. Only 8% of engineers do this consistently. This correlates with fewer debug cycles.`,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Constraint-first prompting
|
|
107
|
+
const constraintRatio = totalPrompts > 0 ? constraintPrompts / totalPrompts : 0;
|
|
108
|
+
if (constraintRatio > 0.1 && constraintPrompts >= 5) {
|
|
109
|
+
signatures.push({
|
|
110
|
+
name: 'Constraint-first prompting',
|
|
111
|
+
detail: `You specify what NOT to do in ${Math.round(constraintRatio * 100)}% of prompts. This is a hallmark of senior architectural thinking that prevents scope creep.`,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Test-driven AI usage
|
|
116
|
+
const testFirstRatio = sessionsWithExchanges > 0 ? testFirstSessions / sessionsWithExchanges : 0;
|
|
117
|
+
if (testFirstRatio > 0.05 && testFirstSessions >= 2) {
|
|
118
|
+
signatures.push({
|
|
119
|
+
name: 'Test-driven AI usage',
|
|
120
|
+
detail: `You request tests before implementation in ${Math.round(testFirstRatio * 100)}% of sessions. Engineers who do this ship fewer bugs post-merge.`,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Deep session marathons
|
|
125
|
+
if (d.avgExchangesPerSession > BENCHMARKS.avgExchangesPerSession * 2) {
|
|
126
|
+
signatures.push({
|
|
127
|
+
name: 'Marathon sessions',
|
|
128
|
+
detail: `Avg session depth of ${d.avgExchangesPerSession} exchanges is ${Math.round(d.avgExchangesPerSession / BENCHMARKS.avgExchangesPerSession)}x the benchmark (${BENCHMARKS.avgExchangesPerSession}). You sustain deep, focused work.`,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Zero vague debugging
|
|
133
|
+
if (db.vagueReports === 0 && db.totalDebugSequences > 5) {
|
|
134
|
+
signatures.push({
|
|
135
|
+
name: 'Precision debugging',
|
|
136
|
+
detail: `Zero vague error reports across ${db.totalDebugSequences} debug sequences. Every bug report includes specific context. This is rare.`,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// High architectural ratio
|
|
141
|
+
if (ai.highLevelRatio > 30) {
|
|
142
|
+
signatures.push({
|
|
143
|
+
name: 'Strategic AI usage',
|
|
144
|
+
detail: `${ai.highLevelRatio}% of prompts are architectural or planning-level (benchmark: ${BENCHMARKS.highLevelRatio}%). You use AI as a thinking partner, not just a code generator.`,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Critical reviewer
|
|
149
|
+
const totalFollowups = modificationCount + acceptCount;
|
|
150
|
+
const modRatio = totalFollowups > 0 ? modificationCount / totalFollowups : 0;
|
|
151
|
+
if (modRatio > 0.25 && modificationCount > 10) {
|
|
152
|
+
signatures.push({
|
|
153
|
+
name: 'Critical reviewer',
|
|
154
|
+
detail: `You modify or redirect AI output in ${Math.round(modRatio * 100)}% of follow-up prompts. This indicates active evaluation rather than passive acceptance.`,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return signatures.slice(0, 4); // Max 4 signatures
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ══════════════════════════════════════════════
|
|
162
|
+
// WATCH POINTS — Anti-patterns
|
|
163
|
+
// ══════════════════════════════════════════════
|
|
164
|
+
|
|
165
|
+
export function computeWatchPoints(allSessions, metrics) {
|
|
166
|
+
const watchPoints = [];
|
|
167
|
+
const d = metrics.decomposition.details;
|
|
168
|
+
const db = metrics.debugCycles.details;
|
|
169
|
+
const ai = metrics.aiLeverage.details;
|
|
170
|
+
const ss = metrics.sessionStructure.details;
|
|
171
|
+
|
|
172
|
+
// Context amnesia — restarting from scratch on same project
|
|
173
|
+
const projectSessions = {};
|
|
174
|
+
for (const s of allSessions) {
|
|
175
|
+
const p = s.project || 'unknown';
|
|
176
|
+
if (!projectSessions[p]) projectSessions[p] = [];
|
|
177
|
+
projectSessions[p].push(s);
|
|
178
|
+
}
|
|
179
|
+
let contextRestarts = 0;
|
|
180
|
+
let multiSessionProjects = 0;
|
|
181
|
+
for (const [, sessions] of Object.entries(projectSessions)) {
|
|
182
|
+
if (sessions.length < 2) continue;
|
|
183
|
+
multiSessionProjects++;
|
|
184
|
+
for (let i = 1; i < sessions.length; i++) {
|
|
185
|
+
const firstPrompt = sessions[i].exchanges[0]?.userPrompt || '';
|
|
186
|
+
// If first prompt doesn't reference previous work, it's a context restart
|
|
187
|
+
if (firstPrompt.length > 50 && !/\b(continuing|following up|as discussed|last time|previously|where we left|earlier)\b/i.test(firstPrompt)) {
|
|
188
|
+
contextRestarts++;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const totalFollowupSessions = Object.values(projectSessions).reduce((sum, s) => sum + Math.max(0, s.length - 1), 0);
|
|
193
|
+
if (totalFollowupSessions > 3 && contextRestarts / totalFollowupSessions > 0.5) {
|
|
194
|
+
watchPoints.push({
|
|
195
|
+
name: 'Context amnesia',
|
|
196
|
+
detail: `You restart context from scratch in ${Math.round(contextRestarts / totalFollowupSessions * 100)}% of follow-up sessions on the same project. Engineers who maintain context across sessions are more efficient.`,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Low modification rate — accepting AI output without review
|
|
201
|
+
let modCount = 0;
|
|
202
|
+
let followupCount = 0;
|
|
203
|
+
for (const session of allSessions) {
|
|
204
|
+
for (let i = 1; i < session.exchanges.length; i++) {
|
|
205
|
+
followupCount++;
|
|
206
|
+
const prompt = session.exchanges[i].userPrompt || '';
|
|
207
|
+
if (/\b(actually|wait|instead|change|no,?|not quite|modify|tweak|hmm|but )\b/i.test(prompt)) {
|
|
208
|
+
modCount++;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const modRatio = followupCount > 10 ? modCount / followupCount : 0.5;
|
|
213
|
+
if (modRatio < 0.15 && followupCount > 20) {
|
|
214
|
+
watchPoints.push({
|
|
215
|
+
name: 'Acceptance without review',
|
|
216
|
+
detail: `You accept AI output without modification in ${Math.round((1 - modRatio) * 100)}% of cases. Top engineers modify or redirect 30%+ of initial suggestions.`,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Monologue prompting — excessively long first prompts
|
|
221
|
+
if (d.avgPromptLength > 2000) {
|
|
222
|
+
watchPoints.push({
|
|
223
|
+
name: 'Monologue prompting',
|
|
224
|
+
detail: `Avg prompt length of ${d.avgPromptLength} chars is ${Math.round(d.avgPromptLength / BENCHMARKS.avgPromptLength)}x the benchmark. Breaking complex requests into 2-3 shorter prompts typically yields better AI output.`,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Low context-setting
|
|
229
|
+
if (ss.contextSetRatio < 20) {
|
|
230
|
+
watchPoints.push({
|
|
231
|
+
name: 'Missing context',
|
|
232
|
+
detail: `Only ${ss.contextSetRatio}% of sessions start with context-setting (benchmark: ${BENCHMARKS.contextSetRatio}%). Upfront context leads to better first responses and fewer corrections.`,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Extended debug spirals
|
|
237
|
+
if (db.longLoops > 2) {
|
|
238
|
+
watchPoints.push({
|
|
239
|
+
name: 'Debug spirals',
|
|
240
|
+
detail: `${db.longLoops} extended debug loops (>5 turns) detected. When stuck, try providing more specific error context or breaking the problem differently.`,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return watchPoints.slice(0, 3); // Max 3 watch points
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ══════════════════════════════════════════════
|
|
248
|
+
// TRAJECTORY — Weekly score evolution
|
|
249
|
+
// ══════════════════════════════════════════════
|
|
250
|
+
|
|
251
|
+
export function computeTrajectory(allSessions) {
|
|
252
|
+
// Group sessions by week
|
|
253
|
+
const sessionsWithTime = allSessions.filter(s => s.startTime);
|
|
254
|
+
if (sessionsWithTime.length < 5) return null;
|
|
255
|
+
|
|
256
|
+
sessionsWithTime.sort((a, b) => new Date(a.startTime) - new Date(b.startTime));
|
|
257
|
+
|
|
258
|
+
const firstDate = new Date(sessionsWithTime[0].startTime);
|
|
259
|
+
const lastDate = new Date(sessionsWithTime[sessionsWithTime.length - 1].startTime);
|
|
260
|
+
|
|
261
|
+
// Need at least 2 weeks of data
|
|
262
|
+
const daySpan = (lastDate - firstDate) / (1000 * 60 * 60 * 24);
|
|
263
|
+
if (daySpan < 10) return null;
|
|
264
|
+
|
|
265
|
+
// Create weekly buckets
|
|
266
|
+
const weeks = [];
|
|
267
|
+
let weekStart = new Date(firstDate);
|
|
268
|
+
weekStart.setHours(0, 0, 0, 0);
|
|
269
|
+
// Align to Monday
|
|
270
|
+
weekStart.setDate(weekStart.getDate() - weekStart.getDay() + 1);
|
|
271
|
+
|
|
272
|
+
while (weekStart <= lastDate) {
|
|
273
|
+
const weekEnd = new Date(weekStart);
|
|
274
|
+
weekEnd.setDate(weekEnd.getDate() + 7);
|
|
275
|
+
const weekSessions = sessionsWithTime.filter(s => {
|
|
276
|
+
const t = new Date(s.startTime);
|
|
277
|
+
return t >= weekStart && t < weekEnd;
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
if (weekSessions.length >= 2) {
|
|
281
|
+
// Compute score for this week
|
|
282
|
+
const m = {
|
|
283
|
+
decomposition: computeDecomposition(weekSessions),
|
|
284
|
+
debugCycles: computeDebugCycles(weekSessions),
|
|
285
|
+
aiLeverage: computeAILeverage(weekSessions),
|
|
286
|
+
sessionStructure: computeSessionStructure(weekSessions),
|
|
287
|
+
};
|
|
288
|
+
const r = computeOverallScore(m);
|
|
289
|
+
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
290
|
+
weeks.push({
|
|
291
|
+
label: `${months[weekStart.getMonth()]} ${weekStart.getDate()}-${weekEnd.getDate() - 1}`,
|
|
292
|
+
score: r.overall,
|
|
293
|
+
sessions: weekSessions.length,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
weekStart = new Date(weekStart);
|
|
298
|
+
weekStart.setDate(weekStart.getDate() + 7);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (weeks.length < 2) return null;
|
|
302
|
+
|
|
303
|
+
// Compute learning velocity
|
|
304
|
+
const firstScore = weeks[0].score;
|
|
305
|
+
const lastScore = weeks[weeks.length - 1].score;
|
|
306
|
+
const delta = lastScore - firstScore;
|
|
307
|
+
const weeksCount = weeks.length;
|
|
308
|
+
const velocityPerWeek = delta / weeksCount;
|
|
309
|
+
|
|
310
|
+
let velocityLabel;
|
|
311
|
+
if (velocityPerWeek > 3) velocityLabel = 'FAST';
|
|
312
|
+
else if (velocityPerWeek > 1) velocityLabel = 'STEADY';
|
|
313
|
+
else if (velocityPerWeek > -1) velocityLabel = 'STABLE';
|
|
314
|
+
else velocityLabel = 'DECLINING';
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
weeks,
|
|
318
|
+
delta,
|
|
319
|
+
daysSpan: Math.round(daySpan),
|
|
320
|
+
velocityLabel,
|
|
321
|
+
velocityDetail: delta !== 0
|
|
322
|
+
? `${Math.abs(delta)} point ${delta > 0 ? 'improvement' : 'change'} over ${Math.round(daySpan)} days`
|
|
323
|
+
: `Stable over ${Math.round(daySpan)} days`,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// ══════════════════════════════════════════════
|
|
328
|
+
// PROJECT COMPLEXITY — What did they build?
|
|
329
|
+
// ══════════════════════════════════════════════
|
|
330
|
+
|
|
331
|
+
const complexitySignals = {
|
|
332
|
+
high: /\b(pipeline|distributed|real.?time|analytics|classification|machine learning|ml |auth|oauth|websocket|streaming|queue|worker|migration|microservice|kubernetes|docker|deployment|ci.?cd|infrastructure|database design|data model|schema design|api design|caching|rate limit)\b/i,
|
|
333
|
+
medium: /\b(api|crud|component|feature|integration|testing|refactor|database|query|endpoint|route|middleware|hook|state management|responsive|animation|chart|graph|dashboard)\b/i,
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
export function computeProjectComplexity(allSessions) {
|
|
337
|
+
const projectData = {};
|
|
338
|
+
|
|
339
|
+
for (const s of allSessions) {
|
|
340
|
+
const p = s.project || 'unknown';
|
|
341
|
+
if (!projectData[p]) {
|
|
342
|
+
projectData[p] = { sessions: 0, exchanges: 0, daysActive: new Set(), highSignals: new Set(), medSignals: new Set(), prompts: [] };
|
|
343
|
+
}
|
|
344
|
+
projectData[p].sessions++;
|
|
345
|
+
projectData[p].exchanges += s.exchangeCount;
|
|
346
|
+
if (s.startTime) {
|
|
347
|
+
projectData[p].daysActive.add(new Date(s.startTime).toISOString().split('T')[0]);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
for (const ex of s.exchanges) {
|
|
351
|
+
const prompt = ex.userPrompt || '';
|
|
352
|
+
projectData[p].prompts.push(prompt);
|
|
353
|
+
|
|
354
|
+
// Extract complexity signals
|
|
355
|
+
const highMatches = prompt.match(complexitySignals.high);
|
|
356
|
+
const medMatches = prompt.match(complexitySignals.medium);
|
|
357
|
+
if (highMatches) {
|
|
358
|
+
for (const m of highMatches) projectData[p].highSignals.add(m.toLowerCase().trim());
|
|
359
|
+
}
|
|
360
|
+
if (medMatches) {
|
|
361
|
+
for (const m of medMatches) projectData[p].medSignals.add(m.toLowerCase().trim());
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const projects = [];
|
|
367
|
+
for (const [name, data] of Object.entries(projectData)) {
|
|
368
|
+
if (data.exchanges < 3) continue; // Skip trivial projects
|
|
369
|
+
|
|
370
|
+
let complexity;
|
|
371
|
+
const signals = [...data.highSignals, ...data.medSignals].slice(0, 5);
|
|
372
|
+
if (data.highSignals.size >= 3 || (data.highSignals.size >= 1 && data.exchanges > 50)) {
|
|
373
|
+
complexity = 'HIGH';
|
|
374
|
+
} else if (data.medSignals.size >= 3 || data.highSignals.size >= 1 || data.exchanges > 30) {
|
|
375
|
+
complexity = 'MEDIUM';
|
|
376
|
+
} else {
|
|
377
|
+
complexity = 'LOW';
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const shortName = name.length > 28 ? '...' + name.slice(-25) : name;
|
|
381
|
+
|
|
382
|
+
projects.push({
|
|
383
|
+
name: shortName,
|
|
384
|
+
complexity,
|
|
385
|
+
sessions: data.sessions,
|
|
386
|
+
exchanges: data.exchanges,
|
|
387
|
+
daysActive: data.daysActive.size,
|
|
388
|
+
signals,
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Sort by exchanges descending
|
|
393
|
+
projects.sort((a, b) => b.exchanges - a.exchanges);
|
|
394
|
+
return projects.slice(0, 5); // Top 5 projects
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// ══════════════════════════════════════════════
|
|
398
|
+
// ASSESSMENT — Narrative paragraph
|
|
399
|
+
// ══════════════════════════════════════════════
|
|
400
|
+
|
|
401
|
+
export function generateAssessment(result, metrics, signatures, watchPoints) {
|
|
402
|
+
const { overall, scores, archetype, tier } = result;
|
|
403
|
+
const d = metrics.decomposition.details;
|
|
404
|
+
const db = metrics.debugCycles.details;
|
|
405
|
+
const ai = metrics.aiLeverage.details;
|
|
406
|
+
const ss = metrics.sessionStructure.details;
|
|
407
|
+
|
|
408
|
+
// Find strongest and weakest dimensions
|
|
409
|
+
const dims = [
|
|
410
|
+
{ key: 'decomposition', label: 'problem decomposition', score: scores.decomposition },
|
|
411
|
+
{ key: 'debugCycles', label: 'debugging efficiency', score: scores.debugCycles },
|
|
412
|
+
{ key: 'aiLeverage', label: 'AI leverage', score: scores.aiLeverage },
|
|
413
|
+
{ key: 'sessionStructure', label: 'workflow discipline', score: scores.sessionStructure },
|
|
414
|
+
];
|
|
415
|
+
dims.sort((a, b) => b.score - a.score);
|
|
416
|
+
const strongest = dims[0];
|
|
417
|
+
const weakest = dims[dims.length - 1];
|
|
418
|
+
|
|
419
|
+
// Build assessment parts
|
|
420
|
+
let assessment = `This engineer demonstrates ${dimQualitative(strongest.score)} ${strongest.label}`;
|
|
421
|
+
|
|
422
|
+
// Add signature mention if available
|
|
423
|
+
if (signatures.length > 0) {
|
|
424
|
+
assessment += ` with a distinctive pattern of ${signatures[0].name.toLowerCase()}`;
|
|
425
|
+
}
|
|
426
|
+
assessment += '.';
|
|
427
|
+
|
|
428
|
+
// Second sentence — second strength or debugging detail
|
|
429
|
+
if (dims[1].score >= 65) {
|
|
430
|
+
assessment += ` Their ${dims[1].label} is also ${dimQualitative(dims[1].score).toLowerCase()}`;
|
|
431
|
+
if (db.avgTurnsToResolve <= 2 && dims[1].key === 'debugCycles') {
|
|
432
|
+
assessment += ' \u2014 surgical and specific with ' + (db.longLoops === 0 ? 'zero' : 'minimal') + ' extended loops';
|
|
433
|
+
}
|
|
434
|
+
assessment += '.';
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Third sentence — growth area
|
|
438
|
+
if (weakest.score < 65) {
|
|
439
|
+
assessment += ` Primary growth opportunity is in ${weakest.label}`;
|
|
440
|
+
if (weakest.key === 'sessionStructure') {
|
|
441
|
+
assessment += ': context-setting and upfront planning are below benchmark';
|
|
442
|
+
if (ss.refinementRatio > 15) {
|
|
443
|
+
assessment += ', though iterative refinement partially compensates';
|
|
444
|
+
}
|
|
445
|
+
} else if (weakest.key === 'decomposition') {
|
|
446
|
+
assessment += ': more task breakdown and structured thinking would yield significant score improvement';
|
|
447
|
+
} else if (weakest.key === 'aiLeverage') {
|
|
448
|
+
assessment += ': using AI for architecture and planning, not just code generation, would increase impact';
|
|
449
|
+
} else {
|
|
450
|
+
assessment += ': stronger error reporting and systematic resolution would improve efficiency';
|
|
451
|
+
}
|
|
452
|
+
assessment += '.';
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Fourth sentence — best for
|
|
456
|
+
assessment += ' ' + archetype.bestFor;
|
|
457
|
+
|
|
458
|
+
return assessment;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function dimQualitative(score) {
|
|
462
|
+
if (score >= 80) return 'Exceptional';
|
|
463
|
+
if (score >= 65) return 'Strong';
|
|
464
|
+
if (score >= 50) return 'Solid';
|
|
465
|
+
if (score >= 35) return 'Developing';
|
|
466
|
+
return 'Early-stage';
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// ══════════════════════════════════════════════
|
|
470
|
+
// CONFIDENCE — Data volume indicator
|
|
471
|
+
// ══════════════════════════════════════════════
|
|
472
|
+
|
|
473
|
+
export function computeConfidence(sessionStats) {
|
|
474
|
+
const { totalSessions, totalExchanges, tools } = sessionStats;
|
|
475
|
+
const toolCount = tools.length;
|
|
476
|
+
|
|
477
|
+
// Score confidence on sessions, exchanges, and tool diversity
|
|
478
|
+
let score = 0;
|
|
479
|
+
if (totalSessions >= 50) score += 40;
|
|
480
|
+
else if (totalSessions >= 20) score += 30;
|
|
481
|
+
else if (totalSessions >= 10) score += 20;
|
|
482
|
+
else score += 10;
|
|
483
|
+
|
|
484
|
+
if (totalExchanges >= 500) score += 30;
|
|
485
|
+
else if (totalExchanges >= 200) score += 20;
|
|
486
|
+
else if (totalExchanges >= 50) score += 10;
|
|
487
|
+
|
|
488
|
+
if (toolCount >= 3) score += 20;
|
|
489
|
+
else if (toolCount >= 2) score += 15;
|
|
490
|
+
else score += 10;
|
|
491
|
+
|
|
492
|
+
// Bonus for enough data
|
|
493
|
+
if (totalSessions >= 30 && totalExchanges >= 300) score += 10;
|
|
494
|
+
|
|
495
|
+
score = Math.min(100, score);
|
|
496
|
+
|
|
497
|
+
let level;
|
|
498
|
+
if (score >= 80) level = 'HIGH';
|
|
499
|
+
else if (score >= 50) level = 'MODERATE';
|
|
500
|
+
else level = 'LOW';
|
|
501
|
+
|
|
502
|
+
return { score, level };
|
|
503
|
+
}
|