agentfit 0.1.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/.claude/settings.local.json +26 -0
- package/.prettierignore +7 -0
- package/.prettierrc +11 -0
- package/CONTRIBUTING.md +209 -0
- package/LICENSE +21 -0
- package/README.md +109 -0
- package/app/(dashboard)/coach/page.tsx +11 -0
- package/app/(dashboard)/commands/page.tsx +7 -0
- package/app/(dashboard)/community/[slug]/page.tsx +23 -0
- package/app/(dashboard)/community/page.tsx +71 -0
- package/app/(dashboard)/daily/page.tsx +19 -0
- package/app/(dashboard)/images/page.tsx +5 -0
- package/app/(dashboard)/layout.tsx +12 -0
- package/app/(dashboard)/page.tsx +23 -0
- package/app/(dashboard)/personality/page.tsx +11 -0
- package/app/(dashboard)/projects/page.tsx +11 -0
- package/app/(dashboard)/sessions/page.tsx +11 -0
- package/app/(dashboard)/tokens/page.tsx +11 -0
- package/app/(dashboard)/tools/page.tsx +11 -0
- package/app/api/check/route.ts +13 -0
- package/app/api/commands/route.ts +16 -0
- package/app/api/images/[...path]/route.ts +33 -0
- package/app/api/images-analysis/route.ts +177 -0
- package/app/api/sync/route.ts +14 -0
- package/app/api/usage/route.ts +117 -0
- package/app/favicon.ico +0 -0
- package/app/globals.css +144 -0
- package/app/icon.svg +3 -0
- package/app/layout.tsx +35 -0
- package/bin/agentfit.mjs +69 -0
- package/components/.gitkeep +0 -0
- package/components/agent-coach.tsx +248 -0
- package/components/app-sidebar.tsx +161 -0
- package/components/command-usage.tsx +294 -0
- package/components/daily-chart.tsx +118 -0
- package/components/daily-table.tsx +115 -0
- package/components/dashboard-shell.tsx +149 -0
- package/components/data-provider.tsx +213 -0
- package/components/fitness-score.tsx +95 -0
- package/components/overview-cards.tsx +198 -0
- package/components/pagination-controls.tsx +104 -0
- package/components/personality-fit.tsx +446 -0
- package/components/projects-table.tsx +70 -0
- package/components/screenshots-analysis.tsx +359 -0
- package/components/sessions-table.tsx +97 -0
- package/components/theme-provider.tsx +71 -0
- package/components/token-breakdown.tsx +179 -0
- package/components/tool-usage-chart.tsx +63 -0
- package/components/ui/badge.tsx +52 -0
- package/components/ui/button.tsx +60 -0
- package/components/ui/card.tsx +103 -0
- package/components/ui/chart.tsx +373 -0
- package/components/ui/dialog.tsx +160 -0
- package/components/ui/input.tsx +20 -0
- package/components/ui/scroll-area.tsx +55 -0
- package/components/ui/select.tsx +201 -0
- package/components/ui/separator.tsx +25 -0
- package/components/ui/sheet.tsx +138 -0
- package/components/ui/sidebar.tsx +723 -0
- package/components/ui/skeleton.tsx +13 -0
- package/components/ui/table.tsx +116 -0
- package/components/ui/tabs.tsx +82 -0
- package/components/ui/tooltip.tsx +66 -0
- package/components.json +25 -0
- package/generated/prisma/browser.ts +34 -0
- package/generated/prisma/client.ts +58 -0
- package/generated/prisma/commonInputTypes.ts +237 -0
- package/generated/prisma/enums.ts +15 -0
- package/generated/prisma/internal/class.ts +224 -0
- package/generated/prisma/internal/prismaNamespace.ts +920 -0
- package/generated/prisma/internal/prismaNamespaceBrowser.ts +130 -0
- package/generated/prisma/models/Image.ts +1310 -0
- package/generated/prisma/models/Session.ts +1695 -0
- package/generated/prisma/models/SyncLog.ts +1203 -0
- package/generated/prisma/models.ts +14 -0
- package/hooks/.gitkeep +0 -0
- package/hooks/use-mobile.ts +19 -0
- package/hooks/use-pagination.ts +60 -0
- package/lib/.gitkeep +0 -0
- package/lib/coach.ts +425 -0
- package/lib/commands.ts +239 -0
- package/lib/db.ts +15 -0
- package/lib/format.ts +26 -0
- package/lib/parse-codex.ts +201 -0
- package/lib/parse-logs.ts +369 -0
- package/lib/personality.ts +481 -0
- package/lib/plugins.ts +107 -0
- package/lib/pricing.ts +112 -0
- package/lib/queries-codex.ts +130 -0
- package/lib/queries.ts +154 -0
- package/lib/resolve-icon.ts +12 -0
- package/lib/sync.ts +335 -0
- package/lib/utils.ts +6 -0
- package/next.config.mjs +4 -0
- package/package.json +73 -0
- package/plugins/cost-heatmap/component.test.tsx +52 -0
- package/plugins/cost-heatmap/component.tsx +227 -0
- package/plugins/cost-heatmap/manifest.ts +13 -0
- package/plugins/index.ts +18 -0
- package/prisma/migrations/20260328152517_init/migration.sql +41 -0
- package/prisma/migrations/20260328153801_add_image_model/migration.sql +18 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +57 -0
- package/prisma.config.ts +14 -0
- package/public/.gitkeep +0 -0
- package/public/logo.svg +3 -0
- package/setup.sh +73 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
// ─── Agent Personality Analysis ─────────────────────────────────────
|
|
2
|
+
// Derives Big Five (OCEAN) and MBTI personality profiles from
|
|
3
|
+
// conversation trace behavioral signals.
|
|
4
|
+
|
|
5
|
+
import type { SessionSummary, UsageData } from './parse-logs'
|
|
6
|
+
|
|
7
|
+
// ─── Types ───────────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
export interface BigFiveProfile {
|
|
10
|
+
openness: number // 0-100: tool diversity, approach variety
|
|
11
|
+
conscientiousness: number // 0-100: read-before-edit, low error/revert rate
|
|
12
|
+
extraversion: number // 0-100: output verbosity, proactive communication
|
|
13
|
+
agreeableness: number // 0-100: compliance, low friction, follows directions
|
|
14
|
+
neuroticism: number // 0-100: behavioral variance, instability (inverse = stability)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface MBTIProfile {
|
|
18
|
+
ei: number // -100 (Introversion) to +100 (Extraversion): verbosity ratio
|
|
19
|
+
sn: number // -100 (Sensing) to +100 (iNtuition): concrete vs. abstract tool patterns
|
|
20
|
+
tf: number // -100 (Thinking) to +100 (Feeling): execution vs. explanation ratio
|
|
21
|
+
jp: number // -100 (Judging) to +100 (Perceiving): structured vs. exploratory workflow
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface PersonalityProfile {
|
|
25
|
+
bigFive: BigFiveProfile
|
|
26
|
+
mbti: MBTIProfile
|
|
27
|
+
mbtiType: string // e.g., "ISTJ"
|
|
28
|
+
signals: PersonalitySignals
|
|
29
|
+
fitScores: TaskFitScores
|
|
30
|
+
recommendations: PersonalityRecommendation[]
|
|
31
|
+
systemPrompt: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface PersonalitySignals {
|
|
35
|
+
toolDiversity: number // unique tools / total tool calls
|
|
36
|
+
readBeforeEditRatio: number // read calls before edit / total edits
|
|
37
|
+
outputInputRatio: number // output tokens / input tokens
|
|
38
|
+
avgAssistantMsgLength: number // avg output tokens per assistant message
|
|
39
|
+
bashToTotalRatio: number // bash calls / total tool calls
|
|
40
|
+
editToReadRatio: number // edit calls / read calls
|
|
41
|
+
toolTransitionEntropy: number // entropy of tool bigrams
|
|
42
|
+
sessionDurationVariance: number// CV of session durations
|
|
43
|
+
avgToolCallsPerMessage: number // tool calls per assistant message
|
|
44
|
+
contextOverflowRate: number // sessions with high token density
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface TaskFitScores {
|
|
48
|
+
frontendDev: number
|
|
49
|
+
backendDev: number
|
|
50
|
+
dataEngineering: number
|
|
51
|
+
devOps: number
|
|
52
|
+
debugging: number
|
|
53
|
+
refactoring: number
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface PersonalityRecommendation {
|
|
57
|
+
trait: string
|
|
58
|
+
current: string
|
|
59
|
+
ideal: string
|
|
60
|
+
adjustment: string
|
|
61
|
+
promptSnippet: string
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ─── Signal Extraction ───────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
function extractSignals(data: UsageData): PersonalitySignals {
|
|
67
|
+
const sessions = data.sessions
|
|
68
|
+
const toolUsage = data.toolUsage
|
|
69
|
+
|
|
70
|
+
const totalToolCalls = Object.values(toolUsage).reduce((a, b) => a + b, 0)
|
|
71
|
+
const uniqueTools = Object.keys(toolUsage).length
|
|
72
|
+
|
|
73
|
+
// Tool diversity: unique tools / total calls (normalized)
|
|
74
|
+
const toolDiversity = totalToolCalls > 0
|
|
75
|
+
? Math.min(1, uniqueTools / Math.sqrt(totalToolCalls))
|
|
76
|
+
: 0
|
|
77
|
+
|
|
78
|
+
// Read-before-Edit ratio from tool patterns
|
|
79
|
+
const readCalls = (toolUsage['Read'] || 0) + (toolUsage['Grep'] || 0) + (toolUsage['Glob'] || 0)
|
|
80
|
+
const editCalls = (toolUsage['Edit'] || 0) + (toolUsage['Write'] || 0)
|
|
81
|
+
const readBeforeEditRatio = editCalls > 0
|
|
82
|
+
? Math.min(2, readCalls / editCalls)
|
|
83
|
+
: 1
|
|
84
|
+
|
|
85
|
+
// Output/Input token ratio
|
|
86
|
+
const totalOutput = data.overview.totalOutputTokens
|
|
87
|
+
const totalInput = data.overview.totalInputTokens
|
|
88
|
+
const outputInputRatio = totalInput > 0 ? totalOutput / totalInput : 1
|
|
89
|
+
|
|
90
|
+
// Average assistant message length (tokens per message)
|
|
91
|
+
const totalAssistantMsgs = data.overview.totalAssistantMessages
|
|
92
|
+
const avgAssistantMsgLength = totalAssistantMsgs > 0
|
|
93
|
+
? totalOutput / totalAssistantMsgs
|
|
94
|
+
: 0
|
|
95
|
+
|
|
96
|
+
// Bash to total ratio
|
|
97
|
+
const bashCalls = toolUsage['Bash'] || 0
|
|
98
|
+
const bashToTotalRatio = totalToolCalls > 0 ? bashCalls / totalToolCalls : 0
|
|
99
|
+
|
|
100
|
+
// Edit to Read ratio
|
|
101
|
+
const editToReadRatio = readCalls > 0 ? editCalls / readCalls : 0
|
|
102
|
+
|
|
103
|
+
// Tool transition entropy (approximated from tool distribution)
|
|
104
|
+
const toolProbs = Object.values(toolUsage).map(c => c / totalToolCalls)
|
|
105
|
+
const toolTransitionEntropy = totalToolCalls > 0
|
|
106
|
+
? -toolProbs.reduce((sum, p) => sum + (p > 0 ? p * Math.log2(p) : 0), 0)
|
|
107
|
+
: 0
|
|
108
|
+
|
|
109
|
+
// Session duration variance (coefficient of variation)
|
|
110
|
+
const durations = sessions.map(s => s.durationMinutes).filter(d => d > 0)
|
|
111
|
+
const meanDuration = durations.length > 0
|
|
112
|
+
? durations.reduce((a, b) => a + b, 0) / durations.length
|
|
113
|
+
: 0
|
|
114
|
+
const varianceDuration = durations.length > 1
|
|
115
|
+
? durations.reduce((sum, d) => sum + Math.pow(d - meanDuration, 2), 0) / durations.length
|
|
116
|
+
: 0
|
|
117
|
+
const sessionDurationVariance = meanDuration > 0
|
|
118
|
+
? Math.sqrt(varianceDuration) / meanDuration
|
|
119
|
+
: 0
|
|
120
|
+
|
|
121
|
+
// Tool calls per assistant message
|
|
122
|
+
const avgToolCallsPerMessage = totalAssistantMsgs > 0
|
|
123
|
+
? totalToolCalls / totalAssistantMsgs
|
|
124
|
+
: 0
|
|
125
|
+
|
|
126
|
+
// Context overflow rate (sessions where tokens > 100k suggesting long context usage)
|
|
127
|
+
const highTokenSessions = sessions.filter(s => s.totalTokens > 100000).length
|
|
128
|
+
const contextOverflowRate = sessions.length > 0
|
|
129
|
+
? highTokenSessions / sessions.length
|
|
130
|
+
: 0
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
toolDiversity,
|
|
134
|
+
readBeforeEditRatio,
|
|
135
|
+
outputInputRatio,
|
|
136
|
+
avgAssistantMsgLength,
|
|
137
|
+
bashToTotalRatio,
|
|
138
|
+
editToReadRatio,
|
|
139
|
+
toolTransitionEntropy,
|
|
140
|
+
sessionDurationVariance,
|
|
141
|
+
avgToolCallsPerMessage,
|
|
142
|
+
contextOverflowRate,
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ─── Big Five Derivation ─────────────────────────────────────────────
|
|
147
|
+
|
|
148
|
+
function deriveBigFive(signals: PersonalitySignals): BigFiveProfile {
|
|
149
|
+
// Openness: tool diversity + transition entropy (explores many approaches)
|
|
150
|
+
const openness = clamp(
|
|
151
|
+
(signals.toolDiversity * 60) +
|
|
152
|
+
(Math.min(signals.toolTransitionEntropy / 4, 1) * 40),
|
|
153
|
+
0, 100
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
// Conscientiousness: read-before-edit ratio + low bash ratio (careful, methodical)
|
|
157
|
+
const conscientiousness = clamp(
|
|
158
|
+
(Math.min(signals.readBeforeEditRatio / 2, 1) * 60) +
|
|
159
|
+
((1 - signals.bashToTotalRatio) * 20) +
|
|
160
|
+
((1 - signals.contextOverflowRate) * 20),
|
|
161
|
+
0, 100
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
// Extraversion: output verbosity + tool calls per message (communicative, active)
|
|
165
|
+
const extraversion = clamp(
|
|
166
|
+
(Math.min(signals.outputInputRatio / 2, 1) * 40) +
|
|
167
|
+
(Math.min(signals.avgAssistantMsgLength / 2000, 1) * 30) +
|
|
168
|
+
(Math.min(signals.avgToolCallsPerMessage / 3, 1) * 30),
|
|
169
|
+
0, 100
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
// Agreeableness: low edit-to-read ratio (listens more than changes) + consistency
|
|
173
|
+
const agreeableness = clamp(
|
|
174
|
+
((1 - Math.min(signals.editToReadRatio, 1)) * 40) +
|
|
175
|
+
(Math.min(signals.readBeforeEditRatio / 2, 1) * 40) +
|
|
176
|
+
((1 - signals.sessionDurationVariance) * 20),
|
|
177
|
+
0, 100
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
// Neuroticism: high variance + high overflow rate + inconsistent patterns
|
|
181
|
+
const neuroticism = clamp(
|
|
182
|
+
(signals.sessionDurationVariance * 40) +
|
|
183
|
+
(signals.contextOverflowRate * 40) +
|
|
184
|
+
((1 - signals.toolDiversity) * 20),
|
|
185
|
+
0, 100
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
return { openness, conscientiousness, extraversion, agreeableness, neuroticism }
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ─── MBTI Derivation ─────────────────────────────────────────────────
|
|
192
|
+
|
|
193
|
+
function deriveMBTI(signals: PersonalitySignals): MBTIProfile {
|
|
194
|
+
// E/I: Output verbosity — verbose = Extraversion, concise = Introversion
|
|
195
|
+
const ei = clamp(
|
|
196
|
+
((signals.outputInputRatio - 0.5) * 100) +
|
|
197
|
+
((signals.avgAssistantMsgLength / 2000 - 0.5) * 100),
|
|
198
|
+
-100, 100
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
// S/N: Read/Grep heavy (concrete, detail-oriented) = Sensing; diverse/exploratory = iNtuition
|
|
202
|
+
const sn = clamp(
|
|
203
|
+
((signals.toolDiversity - 0.3) * 150) +
|
|
204
|
+
((signals.toolTransitionEntropy / 4 - 0.5) * 100),
|
|
205
|
+
-100, 100
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
// T/F: Bash/Edit heavy (execution-focused) = Thinking; Read/explanation = Feeling
|
|
209
|
+
const tf = clamp(
|
|
210
|
+
((signals.bashToTotalRatio - 0.2) * -200) +
|
|
211
|
+
((signals.editToReadRatio - 0.5) * -100),
|
|
212
|
+
-100, 100
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
// J/P: Low variance (structured) = Judging; high variance (exploratory) = Perceiving
|
|
216
|
+
const jp = clamp(
|
|
217
|
+
((signals.sessionDurationVariance - 0.5) * 100) +
|
|
218
|
+
((signals.contextOverflowRate - 0.2) * 100),
|
|
219
|
+
-100, 100
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
return { ei, sn, tf, jp }
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function mbtiTypeFromProfile(mbti: MBTIProfile): string {
|
|
226
|
+
return (
|
|
227
|
+
(mbti.ei >= 0 ? 'E' : 'I') +
|
|
228
|
+
(mbti.sn >= 0 ? 'N' : 'S') +
|
|
229
|
+
(mbti.tf >= 0 ? 'F' : 'T') +
|
|
230
|
+
(mbti.jp >= 0 ? 'P' : 'J')
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ─── Task Fit Scores ─────────────────────────────────────────────────
|
|
235
|
+
// Ideal personality profiles for different task types
|
|
236
|
+
|
|
237
|
+
const IDEAL_PROFILES: Record<string, BigFiveProfile> = {
|
|
238
|
+
frontendDev: {
|
|
239
|
+
openness: 80, // Creative, explores UI approaches
|
|
240
|
+
conscientiousness: 60, // Moderate — needs iteration
|
|
241
|
+
extraversion: 70, // Communicative about visual choices
|
|
242
|
+
agreeableness: 75, // Responsive to design feedback
|
|
243
|
+
neuroticism: 20, // Stable under visual debugging
|
|
244
|
+
},
|
|
245
|
+
backendDev: {
|
|
246
|
+
openness: 50, // Moderate — proven patterns preferred
|
|
247
|
+
conscientiousness: 90, // Very careful with data/logic
|
|
248
|
+
extraversion: 40, // Concise, efficient
|
|
249
|
+
agreeableness: 60, // Follows specs but pushes back on bad ideas
|
|
250
|
+
neuroticism: 15, // Very stable
|
|
251
|
+
},
|
|
252
|
+
dataEngineering: {
|
|
253
|
+
openness: 45, // Conservative with data pipelines
|
|
254
|
+
conscientiousness: 95, // Extremely careful — data integrity matters
|
|
255
|
+
extraversion: 35, // Quiet, focused
|
|
256
|
+
agreeableness: 55, // Follows standards
|
|
257
|
+
neuroticism: 10, // Rock stable
|
|
258
|
+
},
|
|
259
|
+
devOps: {
|
|
260
|
+
openness: 55, // Moderate exploration
|
|
261
|
+
conscientiousness: 85, // Careful with infrastructure
|
|
262
|
+
extraversion: 50, // Communicates clearly about status
|
|
263
|
+
agreeableness: 65, // Follows runbooks
|
|
264
|
+
neuroticism: 15, // Calm under pressure
|
|
265
|
+
},
|
|
266
|
+
debugging: {
|
|
267
|
+
openness: 70, // Explores multiple hypotheses
|
|
268
|
+
conscientiousness: 80, // Methodical investigation
|
|
269
|
+
extraversion: 55, // Explains findings
|
|
270
|
+
agreeableness: 50, // Challenges assumptions
|
|
271
|
+
neuroticism: 25, // Handles frustration
|
|
272
|
+
},
|
|
273
|
+
refactoring: {
|
|
274
|
+
openness: 65, // Sees better patterns
|
|
275
|
+
conscientiousness: 90, // Doesn't break existing behavior
|
|
276
|
+
extraversion: 45, // Shows what changed, concisely
|
|
277
|
+
agreeableness: 70, // Respects existing code style
|
|
278
|
+
neuroticism: 15, // Steady, incremental
|
|
279
|
+
},
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function computeFitScores(actual: BigFiveProfile): TaskFitScores {
|
|
283
|
+
const scores: Record<string, number> = {}
|
|
284
|
+
for (const [task, ideal] of Object.entries(IDEAL_PROFILES)) {
|
|
285
|
+
const distance = Math.sqrt(
|
|
286
|
+
Math.pow(actual.openness - ideal.openness, 2) +
|
|
287
|
+
Math.pow(actual.conscientiousness - ideal.conscientiousness, 2) +
|
|
288
|
+
Math.pow(actual.extraversion - ideal.extraversion, 2) +
|
|
289
|
+
Math.pow(actual.agreeableness - ideal.agreeableness, 2) +
|
|
290
|
+
Math.pow(actual.neuroticism - ideal.neuroticism, 2)
|
|
291
|
+
)
|
|
292
|
+
// Max possible distance is sqrt(5 * 100^2) ≈ 223.6
|
|
293
|
+
scores[task] = Math.round(Math.max(0, 100 - (distance / 2.236)))
|
|
294
|
+
}
|
|
295
|
+
return scores as unknown as TaskFitScores
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ─── Recommendations & System Prompt Generation ──────────────────────
|
|
299
|
+
|
|
300
|
+
function generateRecommendations(
|
|
301
|
+
actual: BigFiveProfile,
|
|
302
|
+
targetTask: string
|
|
303
|
+
): PersonalityRecommendation[] {
|
|
304
|
+
const ideal = IDEAL_PROFILES[targetTask] || IDEAL_PROFILES.backendDev
|
|
305
|
+
const recommendations: PersonalityRecommendation[] = []
|
|
306
|
+
const threshold = 15 // Only recommend if gap > threshold
|
|
307
|
+
|
|
308
|
+
const traitMap: { trait: keyof BigFiveProfile; label: string }[] = [
|
|
309
|
+
{ trait: 'openness', label: 'Openness' },
|
|
310
|
+
{ trait: 'conscientiousness', label: 'Conscientiousness' },
|
|
311
|
+
{ trait: 'extraversion', label: 'Extraversion' },
|
|
312
|
+
{ trait: 'agreeableness', label: 'Agreeableness' },
|
|
313
|
+
{ trait: 'neuroticism', label: 'Neuroticism' },
|
|
314
|
+
]
|
|
315
|
+
|
|
316
|
+
for (const { trait, label } of traitMap) {
|
|
317
|
+
const gap = ideal[trait] - actual[trait]
|
|
318
|
+
if (Math.abs(gap) <= threshold) continue
|
|
319
|
+
|
|
320
|
+
const direction = gap > 0 ? 'increase' : 'decrease'
|
|
321
|
+
const currentLevel = describeLevel(actual[trait])
|
|
322
|
+
const idealLevel = describeLevel(ideal[trait])
|
|
323
|
+
|
|
324
|
+
recommendations.push({
|
|
325
|
+
trait: label,
|
|
326
|
+
current: `${currentLevel} (${Math.round(actual[trait])})`,
|
|
327
|
+
ideal: `${idealLevel} (${Math.round(ideal[trait])})`,
|
|
328
|
+
adjustment: `${direction} by ${Math.abs(Math.round(gap))} points`,
|
|
329
|
+
promptSnippet: getPromptSnippet(trait, direction, targetTask),
|
|
330
|
+
})
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return recommendations
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function describeLevel(score: number): string {
|
|
337
|
+
if (score >= 80) return 'Very High'
|
|
338
|
+
if (score >= 60) return 'High'
|
|
339
|
+
if (score >= 40) return 'Moderate'
|
|
340
|
+
if (score >= 20) return 'Low'
|
|
341
|
+
return 'Very Low'
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function getPromptSnippet(
|
|
345
|
+
trait: keyof BigFiveProfile,
|
|
346
|
+
direction: string,
|
|
347
|
+
task: string
|
|
348
|
+
): string {
|
|
349
|
+
const snippets: Record<string, Record<string, string>> = {
|
|
350
|
+
openness: {
|
|
351
|
+
increase: 'Explore multiple alternative approaches before settling on a solution. When faced with a problem, propose at least 2-3 different strategies and explain the trade-offs of each.',
|
|
352
|
+
decrease: 'Prefer established, proven patterns and conventions. Avoid novel approaches when standard solutions exist. Stick to the project\'s existing architectural patterns.',
|
|
353
|
+
},
|
|
354
|
+
conscientiousness: {
|
|
355
|
+
increase: 'Always read and understand existing code before making changes. Verify your changes don\'t break existing functionality. Run tests after every modification. Double-check edge cases.',
|
|
356
|
+
decrease: 'Move faster with less verification. Trust that the existing test suite will catch issues. Focus on getting a working solution rather than perfecting every edge case.',
|
|
357
|
+
},
|
|
358
|
+
extraversion: {
|
|
359
|
+
increase: 'Explain your reasoning and approach before writing code. Provide context for why you chose a particular solution. Proactively flag potential issues or improvements you notice.',
|
|
360
|
+
decrease: 'Be concise. Write code, not explanations. Only comment when the logic is non-obvious. Skip preamble and get straight to implementation.',
|
|
361
|
+
},
|
|
362
|
+
agreeableness: {
|
|
363
|
+
increase: 'Follow the user\'s directions closely. When you disagree with an approach, implement it as requested first, then briefly mention concerns. Adapt to the project\'s existing style.',
|
|
364
|
+
decrease: 'Push back when you see a better approach. If the requested change would introduce technical debt, explain why and propose alternatives. Prioritize code quality over compliance.',
|
|
365
|
+
},
|
|
366
|
+
neuroticism: {
|
|
367
|
+
increase: 'Be more cautious. Flag potential risks and failure modes. Ask clarifying questions before making assumptions. Prefer reversible changes.',
|
|
368
|
+
decrease: 'Stay calm and methodical. Don\'t overthink edge cases that are unlikely to occur. Trust your analysis and commit to your approach confidently.',
|
|
369
|
+
},
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return snippets[trait]?.[direction] || ''
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function generateSystemPrompt(
|
|
376
|
+
recommendations: PersonalityRecommendation[],
|
|
377
|
+
targetTask: string,
|
|
378
|
+
mbtiType: string
|
|
379
|
+
): string {
|
|
380
|
+
if (recommendations.length === 0) {
|
|
381
|
+
return `# Agent Personality Configuration\n\nCurrent personality profile (${mbtiType}) is well-suited for ${formatTaskName(targetTask)}. No adjustments needed.`
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const taskDescriptions: Record<string, string> = {
|
|
385
|
+
frontendDev: 'frontend development — building UI components, styling, responsive design, and visual debugging',
|
|
386
|
+
backendDev: 'backend development — API design, business logic, database interactions, and server-side architecture',
|
|
387
|
+
dataEngineering: 'data engineering — ETL pipelines, data parsing, web scraping, and data quality assurance',
|
|
388
|
+
devOps: 'DevOps — CI/CD pipelines, infrastructure, deployment, and monitoring',
|
|
389
|
+
debugging: 'debugging — root cause analysis, reproducing issues, and systematic problem investigation',
|
|
390
|
+
refactoring: 'refactoring — improving code structure, reducing duplication, and modernizing patterns without changing behavior',
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
let prompt = `# Agent Personality Configuration for ${formatTaskName(targetTask)}\n\n`
|
|
394
|
+
prompt += `You are assisting with ${taskDescriptions[targetTask] || targetTask}.\n\n`
|
|
395
|
+
prompt += `## Behavioral Guidelines\n\n`
|
|
396
|
+
|
|
397
|
+
for (const rec of recommendations) {
|
|
398
|
+
prompt += `### ${rec.trait} Adjustment\n`
|
|
399
|
+
prompt += `${rec.promptSnippet}\n\n`
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
prompt += `## Task-Specific Instructions\n\n`
|
|
403
|
+
|
|
404
|
+
switch (targetTask) {
|
|
405
|
+
case 'frontendDev':
|
|
406
|
+
prompt += `- When making visual changes, describe what the user should expect to see\n`
|
|
407
|
+
prompt += `- Suggest screenshots for verification after UI modifications\n`
|
|
408
|
+
prompt += `- Consider accessibility and responsive design in every component\n`
|
|
409
|
+
break
|
|
410
|
+
case 'backendDev':
|
|
411
|
+
prompt += `- Validate input at system boundaries, trust internal interfaces\n`
|
|
412
|
+
prompt += `- Consider error handling, but don't over-engineer for impossible scenarios\n`
|
|
413
|
+
prompt += `- Write clear, self-documenting function signatures\n`
|
|
414
|
+
break
|
|
415
|
+
case 'dataEngineering':
|
|
416
|
+
prompt += `- Always validate data at ingestion boundaries\n`
|
|
417
|
+
prompt += `- Log record counts at each pipeline stage for auditability\n`
|
|
418
|
+
prompt += `- Handle malformed data gracefully — skip and log, don't crash\n`
|
|
419
|
+
break
|
|
420
|
+
case 'devOps':
|
|
421
|
+
prompt += `- Prefer idempotent operations\n`
|
|
422
|
+
prompt += `- Always consider rollback paths before making infrastructure changes\n`
|
|
423
|
+
prompt += `- Document any manual steps required\n`
|
|
424
|
+
break
|
|
425
|
+
case 'debugging':
|
|
426
|
+
prompt += `- Form hypotheses before investigating. State what you expect to find\n`
|
|
427
|
+
prompt += `- Narrow down with binary search — don't read everything\n`
|
|
428
|
+
prompt += `- Preserve and explain the root cause, not just the fix\n`
|
|
429
|
+
break
|
|
430
|
+
case 'refactoring':
|
|
431
|
+
prompt += `- Ensure behavior is unchanged — preserve all existing tests\n`
|
|
432
|
+
prompt += `- Make small, incremental changes. Each step should be independently valid\n`
|
|
433
|
+
prompt += `- Don't change interfaces unless necessary for the refactor\n`
|
|
434
|
+
break
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return prompt
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function formatTaskName(task: string): string {
|
|
441
|
+
const names: Record<string, string> = {
|
|
442
|
+
frontendDev: 'Frontend Development',
|
|
443
|
+
backendDev: 'Backend Development',
|
|
444
|
+
dataEngineering: 'Data Engineering',
|
|
445
|
+
devOps: 'DevOps',
|
|
446
|
+
debugging: 'Debugging',
|
|
447
|
+
refactoring: 'Refactoring',
|
|
448
|
+
}
|
|
449
|
+
return names[task] || task
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// ─── Main Analysis Function ──────────────────────────────────────────
|
|
453
|
+
|
|
454
|
+
export function analyzePersonality(
|
|
455
|
+
data: UsageData,
|
|
456
|
+
targetTask: string = 'backendDev'
|
|
457
|
+
): PersonalityProfile {
|
|
458
|
+
const signals = extractSignals(data)
|
|
459
|
+
const bigFive = deriveBigFive(signals)
|
|
460
|
+
const mbti = deriveMBTI(signals)
|
|
461
|
+
const mbtiType = mbtiTypeFromProfile(mbti)
|
|
462
|
+
const fitScores = computeFitScores(bigFive)
|
|
463
|
+
const recommendations = generateRecommendations(bigFive, targetTask)
|
|
464
|
+
const systemPrompt = generateSystemPrompt(recommendations, targetTask, mbtiType)
|
|
465
|
+
|
|
466
|
+
return {
|
|
467
|
+
bigFive,
|
|
468
|
+
mbti,
|
|
469
|
+
mbtiType,
|
|
470
|
+
signals,
|
|
471
|
+
fitScores,
|
|
472
|
+
recommendations,
|
|
473
|
+
systemPrompt,
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// ─── Helpers ─────────────────────────────────────────────────────────
|
|
478
|
+
|
|
479
|
+
function clamp(value: number, min: number, max: number): number {
|
|
480
|
+
return Math.max(min, Math.min(max, Math.round(value * 100) / 100))
|
|
481
|
+
}
|
package/lib/plugins.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { ComponentType } from 'react'
|
|
2
|
+
import type { UsageData } from './parse-logs'
|
|
3
|
+
|
|
4
|
+
// ─── Plugin Contract ────────────────────────────────────────────────
|
|
5
|
+
// Every community plugin must export a manifest and a default component
|
|
6
|
+
// that conforms to these interfaces.
|
|
7
|
+
|
|
8
|
+
/** Props passed to every community plugin component */
|
|
9
|
+
export interface PluginProps {
|
|
10
|
+
data: UsageData
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Metadata every plugin must declare in its manifest.ts */
|
|
14
|
+
export interface PluginManifest {
|
|
15
|
+
/** Unique URL-safe slug (lowercase, hyphens only). Used as the route segment. */
|
|
16
|
+
slug: string
|
|
17
|
+
/** Human-readable name shown in the sidebar */
|
|
18
|
+
name: string
|
|
19
|
+
/** One-line description shown in tooltips */
|
|
20
|
+
description: string
|
|
21
|
+
/** Author name or GitHub handle */
|
|
22
|
+
author: string
|
|
23
|
+
/** Lucide icon name (e.g. "Flame"). Resolved at render time. */
|
|
24
|
+
icon: string
|
|
25
|
+
/** Semver version string */
|
|
26
|
+
version: string
|
|
27
|
+
/** Optional tags for discoverability */
|
|
28
|
+
tags?: string[]
|
|
29
|
+
/** Set to true if the plugin fetches its own data (hides time-range filter) */
|
|
30
|
+
customDataSource?: boolean
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** A fully resolved plugin ready to render */
|
|
34
|
+
export interface ResolvedPlugin {
|
|
35
|
+
manifest: PluginManifest
|
|
36
|
+
component: ComponentType<PluginProps>
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ─── Plugin Registry ────────────────────────────────────────────────
|
|
40
|
+
// Plugins register themselves by calling registerPlugin().
|
|
41
|
+
// The registry is populated at import time via plugins/index.ts.
|
|
42
|
+
|
|
43
|
+
const registry = new Map<string, ResolvedPlugin>()
|
|
44
|
+
|
|
45
|
+
/** Register a community plugin. Called from plugins/index.ts. */
|
|
46
|
+
export function registerPlugin(
|
|
47
|
+
manifest: PluginManifest,
|
|
48
|
+
component: ComponentType<PluginProps>,
|
|
49
|
+
) {
|
|
50
|
+
if (registry.has(manifest.slug)) {
|
|
51
|
+
console.warn(`[AgentFit] Duplicate plugin slug: "${manifest.slug}" — skipping.`)
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(manifest.slug)) {
|
|
55
|
+
console.warn(
|
|
56
|
+
`[AgentFit] Invalid plugin slug: "${manifest.slug}". ` +
|
|
57
|
+
'Use lowercase letters, numbers, and hyphens only.',
|
|
58
|
+
)
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
registry.set(manifest.slug, { manifest, component })
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Get all registered plugins (sorted alphabetically by name) */
|
|
65
|
+
export function getPlugins(): ResolvedPlugin[] {
|
|
66
|
+
return Array.from(registry.values()).sort((a, b) =>
|
|
67
|
+
a.manifest.name.localeCompare(b.manifest.name),
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Look up a single plugin by slug */
|
|
72
|
+
export function getPlugin(slug: string): ResolvedPlugin | undefined {
|
|
73
|
+
return registry.get(slug)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ─── Validation helper (used by tests) ──────────────────────────────
|
|
77
|
+
|
|
78
|
+
export function validateManifest(m: unknown): string[] {
|
|
79
|
+
const errors: string[] = []
|
|
80
|
+
if (!m || typeof m !== 'object') {
|
|
81
|
+
return ['Manifest must be an object']
|
|
82
|
+
}
|
|
83
|
+
const obj = m as Record<string, unknown>
|
|
84
|
+
|
|
85
|
+
const requiredStrings = ['slug', 'name', 'description', 'author', 'icon', 'version'] as const
|
|
86
|
+
for (const key of requiredStrings) {
|
|
87
|
+
if (typeof obj[key] !== 'string' || (obj[key] as string).trim() === '') {
|
|
88
|
+
errors.push(`"${key}" must be a non-empty string`)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (typeof obj.slug === 'string' && !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(obj.slug)) {
|
|
93
|
+
errors.push('"slug" must be lowercase alphanumeric with hyphens (e.g. "my-plugin")')
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (typeof obj.version === 'string' && !/^\d+\.\d+\.\d+/.test(obj.version)) {
|
|
97
|
+
errors.push('"version" must follow semver (e.g. "1.0.0")')
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (obj.tags !== undefined) {
|
|
101
|
+
if (!Array.isArray(obj.tags) || !obj.tags.every((t: unknown) => typeof t === 'string')) {
|
|
102
|
+
errors.push('"tags" must be an array of strings')
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return errors
|
|
107
|
+
}
|