claudecto 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.
Files changed (115) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +275 -0
  3. package/dist/__tests__/package.test.d.ts +2 -0
  4. package/dist/__tests__/package.test.d.ts.map +1 -0
  5. package/dist/__tests__/package.test.js +53 -0
  6. package/dist/__tests__/package.test.js.map +1 -0
  7. package/dist/cli.d.ts +6 -0
  8. package/dist/cli.d.ts.map +1 -0
  9. package/dist/cli.js +200 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/index.d.ts +12 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +12 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/server/index.d.ts +11 -0
  16. package/dist/server/index.d.ts.map +1 -0
  17. package/dist/server/index.js +1207 -0
  18. package/dist/server/index.js.map +1 -0
  19. package/dist/services/advisor.d.ts +117 -0
  20. package/dist/services/advisor.d.ts.map +1 -0
  21. package/dist/services/advisor.js +2636 -0
  22. package/dist/services/advisor.js.map +1 -0
  23. package/dist/services/agent-generator.d.ts +71 -0
  24. package/dist/services/agent-generator.d.ts.map +1 -0
  25. package/dist/services/agent-generator.js +295 -0
  26. package/dist/services/agent-generator.js.map +1 -0
  27. package/dist/services/agents.d.ts +67 -0
  28. package/dist/services/agents.d.ts.map +1 -0
  29. package/dist/services/agents.js +405 -0
  30. package/dist/services/agents.js.map +1 -0
  31. package/dist/services/analytics.d.ts +145 -0
  32. package/dist/services/analytics.d.ts.map +1 -0
  33. package/dist/services/analytics.js +609 -0
  34. package/dist/services/analytics.js.map +1 -0
  35. package/dist/services/blueprints.d.ts +31 -0
  36. package/dist/services/blueprints.d.ts.map +1 -0
  37. package/dist/services/blueprints.js +317 -0
  38. package/dist/services/blueprints.js.map +1 -0
  39. package/dist/services/claude-dir.d.ts +50 -0
  40. package/dist/services/claude-dir.d.ts.map +1 -0
  41. package/dist/services/claude-dir.js +193 -0
  42. package/dist/services/claude-dir.js.map +1 -0
  43. package/dist/services/hooks.d.ts +38 -0
  44. package/dist/services/hooks.d.ts.map +1 -0
  45. package/dist/services/hooks.js +165 -0
  46. package/dist/services/hooks.js.map +1 -0
  47. package/dist/services/insights.d.ts +52 -0
  48. package/dist/services/insights.d.ts.map +1 -0
  49. package/dist/services/insights.js +1035 -0
  50. package/dist/services/insights.js.map +1 -0
  51. package/dist/services/memory.d.ts +14 -0
  52. package/dist/services/memory.d.ts.map +1 -0
  53. package/dist/services/memory.js +25 -0
  54. package/dist/services/memory.js.map +1 -0
  55. package/dist/services/plans.d.ts +20 -0
  56. package/dist/services/plans.d.ts.map +1 -0
  57. package/dist/services/plans.js +149 -0
  58. package/dist/services/plans.js.map +1 -0
  59. package/dist/services/project-intelligence.d.ts +75 -0
  60. package/dist/services/project-intelligence.d.ts.map +1 -0
  61. package/dist/services/project-intelligence.js +731 -0
  62. package/dist/services/project-intelligence.js.map +1 -0
  63. package/dist/services/search.d.ts +32 -0
  64. package/dist/services/search.d.ts.map +1 -0
  65. package/dist/services/search.js +203 -0
  66. package/dist/services/search.js.map +1 -0
  67. package/dist/services/sessions.d.ts +25 -0
  68. package/dist/services/sessions.d.ts.map +1 -0
  69. package/dist/services/sessions.js +248 -0
  70. package/dist/services/sessions.js.map +1 -0
  71. package/dist/services/skills.d.ts +30 -0
  72. package/dist/services/skills.d.ts.map +1 -0
  73. package/dist/services/skills.js +197 -0
  74. package/dist/services/skills.js.map +1 -0
  75. package/dist/services/stats.d.ts +23 -0
  76. package/dist/services/stats.d.ts.map +1 -0
  77. package/dist/services/stats.js +88 -0
  78. package/dist/services/stats.js.map +1 -0
  79. package/dist/services/teams.d.ts +115 -0
  80. package/dist/services/teams.d.ts.map +1 -0
  81. package/dist/services/teams.js +421 -0
  82. package/dist/services/teams.js.map +1 -0
  83. package/dist/services/tech-stack.d.ts +98 -0
  84. package/dist/services/tech-stack.d.ts.map +1 -0
  85. package/dist/services/tech-stack.js +1088 -0
  86. package/dist/services/tech-stack.js.map +1 -0
  87. package/dist/services/terminal.d.ts +75 -0
  88. package/dist/services/terminal.d.ts.map +1 -0
  89. package/dist/services/terminal.js +224 -0
  90. package/dist/services/terminal.js.map +1 -0
  91. package/dist/types.d.ts +1095 -0
  92. package/dist/types.d.ts.map +1 -0
  93. package/dist/types.js +18 -0
  94. package/dist/types.js.map +1 -0
  95. package/dist/ui/assets/index-BiH4Nhdk.css +1 -0
  96. package/dist/ui/assets/index-Brv-K8bd.css +1 -0
  97. package/dist/ui/assets/index-BwMBEdQz.js +3108 -0
  98. package/dist/ui/assets/index-BwMBEdQz.js.map +1 -0
  99. package/dist/ui/assets/index-CEWz7ABD.js +3108 -0
  100. package/dist/ui/assets/index-CEWz7ABD.js.map +1 -0
  101. package/dist/ui/assets/index-CIZ3vvc-.css +1 -0
  102. package/dist/ui/assets/index-CsU3cI0n.js +3108 -0
  103. package/dist/ui/assets/index-CsU3cI0n.js.map +1 -0
  104. package/dist/ui/assets/index-D3AY6iCS.js +3133 -0
  105. package/dist/ui/assets/index-D3AY6iCS.js.map +1 -0
  106. package/dist/ui/assets/index-D8lNZ0Ye.css +1 -0
  107. package/dist/ui/assets/index-DmgeppSA.js +3108 -0
  108. package/dist/ui/assets/index-DmgeppSA.js.map +1 -0
  109. package/dist/ui/favicon.svg +43 -0
  110. package/dist/ui/index.html +23 -0
  111. package/dist/utils/jsonl.d.ts +16 -0
  112. package/dist/utils/jsonl.d.ts.map +1 -0
  113. package/dist/utils/jsonl.js +51 -0
  114. package/dist/utils/jsonl.js.map +1 -0
  115. package/package.json +106 -0
@@ -0,0 +1,731 @@
1
+ /**
2
+ * Project Intelligence Service
3
+ *
4
+ * Generates cross-session, project-level insights including:
5
+ * - Project story and narrative
6
+ * - Code hotspots and evolution
7
+ * - Development milestones
8
+ * - Workflow patterns
9
+ * - Recurring challenges
10
+ * - Claude effectiveness analysis
11
+ * - Smart recommendations
12
+ *
13
+ * This is DIFFERENT from session insights (single conversation) and
14
+ * analytics (cost/token tracking). Project Intelligence looks at
15
+ * patterns and evolution across ALL sessions in a project.
16
+ */
17
+ import path from 'node:path';
18
+ import fs from 'node:fs/promises';
19
+ import { exec } from 'node:child_process';
20
+ import { promisify } from 'node:util';
21
+ const execAsync = promisify(exec);
22
+ const INTELLIGENCE_CACHE_DIR = 'intelligence';
23
+ const CACHE_VERSION = 1;
24
+ export class ProjectIntelligenceService {
25
+ claudeDir;
26
+ sessionService;
27
+ constructor(claudeDir, sessionService) {
28
+ this.claudeDir = claudeDir;
29
+ this.sessionService = sessionService;
30
+ }
31
+ /**
32
+ * Get intelligence cache directory
33
+ */
34
+ async getIntelligenceDir() {
35
+ const dir = path.join(this.claudeDir.root, INTELLIGENCE_CACHE_DIR);
36
+ await fs.mkdir(dir, { recursive: true });
37
+ return dir;
38
+ }
39
+ /**
40
+ * Get cache file path for a project
41
+ */
42
+ getProjectCachePath(projectPath) {
43
+ const sanitized = projectPath.replace(/[\/\\:]/g, '-').replace(/^-/, '');
44
+ return path.join(this.claudeDir.root, INTELLIGENCE_CACHE_DIR, `project-${sanitized}.json`);
45
+ }
46
+ /**
47
+ * Get cached intelligence for a project
48
+ */
49
+ async getProjectIntelligence(projectPath) {
50
+ try {
51
+ const cachePath = this.getProjectCachePath(projectPath);
52
+ const content = await fs.readFile(cachePath, 'utf-8');
53
+ return JSON.parse(content);
54
+ }
55
+ catch {
56
+ return null;
57
+ }
58
+ }
59
+ /**
60
+ * List all projects with their intelligence summaries
61
+ */
62
+ async listProjectIntelligence() {
63
+ const projects = await this.sessionService.listProjects();
64
+ const summaries = [];
65
+ for (const project of projects) {
66
+ const cached = await this.getProjectIntelligence(project.path);
67
+ if (cached && cached.status === 'completed') {
68
+ summaries.push({
69
+ projectPath: project.path,
70
+ projectName: project.name,
71
+ health: cached.health?.overall ?? 50,
72
+ healthTrend: cached.health?.trend ?? 'stable',
73
+ sessionCount: cached.sessionCount,
74
+ lastActive: cached.dateRange?.last ?? project.lastActivity,
75
+ currentFocus: cached.currentFocus,
76
+ topChallenges: cached.challenges?.slice(0, 2).map(c => c.title) ?? [],
77
+ topRecommendations: cached.recommendations?.slice(0, 2).map(r => r.title) ?? [],
78
+ hasFullIntelligence: true,
79
+ });
80
+ }
81
+ else {
82
+ // Return basic info without full intelligence
83
+ summaries.push({
84
+ projectPath: project.path,
85
+ projectName: project.name,
86
+ health: 50,
87
+ healthTrend: 'stable',
88
+ sessionCount: project.sessionCount,
89
+ lastActive: project.lastActivity,
90
+ topChallenges: [],
91
+ topRecommendations: [],
92
+ hasFullIntelligence: false,
93
+ });
94
+ }
95
+ }
96
+ // Sort by last activity
97
+ summaries.sort((a, b) => new Date(b.lastActive).getTime() - new Date(a.lastActive).getTime());
98
+ return summaries;
99
+ }
100
+ /**
101
+ * Generate intelligence for a specific project
102
+ */
103
+ async generateProjectIntelligence(projectPath, force = false) {
104
+ // Check cache first
105
+ if (!force) {
106
+ const cached = await this.getProjectIntelligence(projectPath);
107
+ if (cached && cached.status === 'completed') {
108
+ // Cache valid for 12 hours
109
+ const age = Date.now() - new Date(cached.generatedAt).getTime();
110
+ if (age < 12 * 60 * 60 * 1000) {
111
+ return cached;
112
+ }
113
+ }
114
+ }
115
+ const cachePath = this.getProjectCachePath(projectPath);
116
+ const projectName = path.basename(projectPath);
117
+ // Create pending entry
118
+ const pending = {
119
+ projectPath,
120
+ projectName,
121
+ generatedAt: new Date().toISOString(),
122
+ status: 'generating',
123
+ sessionCount: 0,
124
+ totalTokens: 0,
125
+ totalCost: 0,
126
+ dateRange: { first: '', last: '' },
127
+ filesCreated: 0,
128
+ filesEdited: 0,
129
+ };
130
+ await fs.writeFile(cachePath, JSON.stringify(pending, null, 2));
131
+ try {
132
+ // Gather session data for this project
133
+ const { sessions } = await this.sessionService.listSessions({ project: projectPath, limit: 500 });
134
+ if (sessions.length === 0) {
135
+ const empty = {
136
+ ...pending,
137
+ status: 'completed',
138
+ sessionCount: 0,
139
+ };
140
+ await fs.writeFile(cachePath, JSON.stringify(empty, null, 2));
141
+ return empty;
142
+ }
143
+ // Load session details (limit to recent 50 for performance)
144
+ const sessionDetails = await Promise.all(sessions.slice(0, 50).map(s => this.sessionService.getSession(s.id)));
145
+ const validSessions = sessionDetails.filter(s => s !== null);
146
+ // Analyze sessions
147
+ const analysis = this.analyzeProjectSessions(validSessions, projectPath);
148
+ // Generate AI narrative if Claude is available
149
+ let aiNarrative = null;
150
+ if (await this.isClaudeAvailable()) {
151
+ aiNarrative = await this.generateAINarrative(analysis, projectName);
152
+ }
153
+ // Build complete intelligence
154
+ const intelligence = {
155
+ projectPath,
156
+ projectName,
157
+ generatedAt: new Date().toISOString(),
158
+ status: 'completed',
159
+ // Summary
160
+ storyNarrative: aiNarrative?.story ?? this.generateBasicNarrative(analysis),
161
+ currentFocus: aiNarrative?.focus ?? analysis.recentTopics[0],
162
+ // Health & Metrics
163
+ health: analysis.health,
164
+ sessionCount: sessions.length,
165
+ totalTokens: analysis.totalTokens,
166
+ totalCost: analysis.totalCost,
167
+ dateRange: analysis.dateRange,
168
+ // Code Analysis
169
+ codeHotspots: analysis.hotspots,
170
+ primaryLanguages: analysis.languages,
171
+ filesCreated: analysis.filesCreated,
172
+ filesEdited: analysis.filesEdited,
173
+ // Timeline & Milestones
174
+ milestones: analysis.milestones,
175
+ recentActivity: analysis.recentActivity,
176
+ // Patterns & Workflows
177
+ workflowPatterns: analysis.workflowPatterns,
178
+ activityPattern: analysis.activityPattern,
179
+ // Challenges
180
+ challenges: analysis.challenges,
181
+ errorPatterns: analysis.errorPatterns,
182
+ // Claude Usage
183
+ claudeEffectiveness: analysis.claudeEffectiveness,
184
+ topTools: analysis.topTools,
185
+ // Recommendations
186
+ recommendations: aiNarrative?.recommendations ?? analysis.basicRecommendations,
187
+ // Quick Stats
188
+ quickStats: analysis.quickStats,
189
+ };
190
+ await fs.writeFile(cachePath, JSON.stringify(intelligence, null, 2));
191
+ return intelligence;
192
+ }
193
+ catch (error) {
194
+ const failed = {
195
+ ...pending,
196
+ status: 'error',
197
+ error: {
198
+ code: 'UNKNOWN',
199
+ message: error.message,
200
+ recoverable: true,
201
+ suggestion: 'Try regenerating the intelligence',
202
+ },
203
+ };
204
+ await fs.writeFile(cachePath, JSON.stringify(failed, null, 2));
205
+ return failed;
206
+ }
207
+ }
208
+ /**
209
+ * Analyze sessions to extract project intelligence
210
+ */
211
+ analyzeProjectSessions(sessions, _projectPath) {
212
+ // Initialize accumulators
213
+ const fileEdits = new Map();
214
+ const toolCalls = new Map();
215
+ const errorMessages = new Map();
216
+ const dailyActivity = new Map();
217
+ const hourlyActivity = new Map();
218
+ const weeklyActivity = new Map();
219
+ const languageUsage = new Map();
220
+ let totalTokens = 0;
221
+ let totalCost = 0;
222
+ let firstDate = '';
223
+ let lastDate = '';
224
+ let filesCreated = 0;
225
+ let filesEdited = 0;
226
+ let totalSuccess = 0;
227
+ let totalErrors = 0;
228
+ const now = new Date();
229
+ const oneWeekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
230
+ let sessionsThisWeek = 0;
231
+ let tokensThisWeek = 0;
232
+ let filesChangedThisWeek = 0;
233
+ // Process each session
234
+ for (const session of sessions) {
235
+ if (!session)
236
+ continue;
237
+ const sessionDate = session.startedAt || session.lastMessageAt;
238
+ if (!firstDate || sessionDate < firstDate)
239
+ firstDate = sessionDate;
240
+ if (!lastDate || sessionDate > lastDate)
241
+ lastDate = sessionDate;
242
+ const date = new Date(sessionDate);
243
+ const dateKey = date.toISOString().split('T')[0];
244
+ const hour = date.getHours();
245
+ const dayOfWeek = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][date.getDay()];
246
+ // Track activity patterns
247
+ dailyActivity.set(dateKey, (dailyActivity.get(dateKey) || 0) + 1);
248
+ hourlyActivity.set(hour, (hourlyActivity.get(hour) || 0) + 1);
249
+ weeklyActivity.set(dayOfWeek, (weeklyActivity.get(dayOfWeek) || 0) + 1);
250
+ // Check if this week
251
+ if (date >= oneWeekAgo) {
252
+ sessionsThisWeek++;
253
+ }
254
+ // Process messages
255
+ for (const msg of session.messages || []) {
256
+ // Track tokens
257
+ if (msg.metadata?.tokens) {
258
+ const tokens = (msg.metadata.tokens.input || 0) + (msg.metadata.tokens.output || 0);
259
+ totalTokens += tokens;
260
+ if (date >= oneWeekAgo)
261
+ tokensThisWeek += tokens;
262
+ // Estimate cost (rough estimate)
263
+ totalCost += tokens * 0.00001;
264
+ }
265
+ // Track tool usage
266
+ if (msg.type === 'tool-use' && msg.metadata?.toolName) {
267
+ const toolName = msg.metadata.toolName;
268
+ const existing = toolCalls.get(toolName) || { total: 0, errors: 0 };
269
+ existing.total++;
270
+ toolCalls.set(toolName, existing);
271
+ // Track file edits
272
+ const input = msg.metadata.toolInput;
273
+ const filePath = (input?.file_path || input?.path || input?.target_file);
274
+ if (filePath && ['Edit', 'Write', 'MultiEdit'].includes(toolName)) {
275
+ const existing = fileEdits.get(filePath) || {
276
+ count: 0,
277
+ lastEdited: sessionDate,
278
+ sessions: new Set(),
279
+ };
280
+ existing.count++;
281
+ existing.sessions.add(session.id);
282
+ if (sessionDate > existing.lastEdited)
283
+ existing.lastEdited = sessionDate;
284
+ fileEdits.set(filePath, existing);
285
+ if (toolName === 'Write')
286
+ filesCreated++;
287
+ else
288
+ filesEdited++;
289
+ if (date >= oneWeekAgo)
290
+ filesChangedThisWeek++;
291
+ // Track language
292
+ const ext = path.extname(filePath).toLowerCase();
293
+ if (ext) {
294
+ languageUsage.set(ext, (languageUsage.get(ext) || 0) + 1);
295
+ }
296
+ }
297
+ }
298
+ // Track errors
299
+ if (msg.type === 'tool-result' && msg.content?.includes('Error')) {
300
+ totalErrors++;
301
+ const errorType = this.categorizeError(msg.content);
302
+ const existing = errorMessages.get(errorType) || { count: 0, lastOccurred: sessionDate };
303
+ existing.count++;
304
+ if (sessionDate > existing.lastOccurred)
305
+ existing.lastOccurred = sessionDate;
306
+ errorMessages.set(errorType, existing);
307
+ // Mark tool as having an error
308
+ const prevMsg = session.messages[session.messages.indexOf(msg) - 1];
309
+ if (prevMsg?.metadata?.toolName) {
310
+ const toolData = toolCalls.get(prevMsg.metadata.toolName);
311
+ if (toolData)
312
+ toolData.errors++;
313
+ }
314
+ }
315
+ else if (msg.type === 'tool-result') {
316
+ totalSuccess++;
317
+ }
318
+ }
319
+ }
320
+ // Calculate health score
321
+ const errorRate = totalErrors / Math.max(1, totalSuccess + totalErrors);
322
+ const velocity = sessions.length / Math.max(1, this.daysBetween(firstDate, lastDate));
323
+ const churnScore = this.calculateChurnScore(fileEdits);
324
+ const health = {
325
+ overall: Math.round((1 - errorRate) * 30 + // Stability
326
+ Math.min(velocity * 10, 25) + // Velocity
327
+ (1 - Math.min(churnScore / 100, 1)) * 25 + // Maintainability
328
+ 20 // Base score
329
+ ),
330
+ velocity: Math.min(velocity * 20, 100),
331
+ stability: Math.round((1 - errorRate) * 100),
332
+ maintainability: Math.round((1 - Math.min(churnScore / 100, 1)) * 100),
333
+ completion: Math.round((totalSuccess / Math.max(1, totalSuccess + totalErrors)) * 100),
334
+ trend: this.determineTrend(sessions),
335
+ trendChange: 0,
336
+ };
337
+ // Build hotspots
338
+ const hotspots = Array.from(fileEdits.entries())
339
+ .map(([filePath, data]) => ({
340
+ filePath,
341
+ fileName: path.basename(filePath),
342
+ editCount: data.count,
343
+ lastEdited: data.lastEdited,
344
+ churnScore: data.count * data.sessions.size,
345
+ sessions: data.sessions.size,
346
+ contributionType: 'unknown',
347
+ }))
348
+ .sort((a, b) => b.editCount - a.editCount)
349
+ .slice(0, 10);
350
+ // Build top tools
351
+ const topTools = Array.from(toolCalls.entries())
352
+ .map(([name, data]) => ({ name, count: data.total }))
353
+ .sort((a, b) => b.count - a.count)
354
+ .slice(0, 10);
355
+ // Build languages
356
+ const languages = Array.from(languageUsage.entries())
357
+ .sort((a, b) => b[1] - a[1])
358
+ .slice(0, 5)
359
+ .map(([ext]) => this.extToLanguage(ext));
360
+ // Build error patterns
361
+ const errorPatterns = Array.from(errorMessages.entries())
362
+ .map(([type, data]) => ({ type, count: data.count, lastOccurred: data.lastOccurred }))
363
+ .sort((a, b) => b.count - a.count)
364
+ .slice(0, 5);
365
+ // Build activity pattern
366
+ const activityPattern = {
367
+ peakDays: Array.from(weeklyActivity.entries())
368
+ .map(([day, sessions]) => ({ day, sessions }))
369
+ .sort((a, b) => b.sessions - a.sessions),
370
+ peakHours: Array.from(hourlyActivity.entries())
371
+ .map(([hour, sessions]) => ({ hour, sessions }))
372
+ .sort((a, b) => b.sessions - a.sessions)
373
+ .slice(0, 5),
374
+ averageSessionsPerWeek: sessions.length / Math.max(1, this.daysBetween(firstDate, lastDate) / 7),
375
+ longestStreak: this.calculateStreak(dailyActivity),
376
+ lastActiveStreak: this.calculateCurrentStreak(dailyActivity),
377
+ };
378
+ // Build Claude effectiveness
379
+ const claudeEffectiveness = {
380
+ overallScore: health.completion,
381
+ strengths: this.identifyStrengths(toolCalls),
382
+ weaknesses: this.identifyWeaknesses(toolCalls),
383
+ bestToolUsage: Array.from(toolCalls.entries())
384
+ .filter(([_, d]) => d.errors === 0 && d.total > 3)
385
+ .map(([tool, d]) => ({ tool, successRate: 100 }))
386
+ .slice(0, 3),
387
+ worstToolUsage: Array.from(toolCalls.entries())
388
+ .filter(([_, d]) => d.errors > 0)
389
+ .map(([tool, d]) => ({ tool, errorRate: (d.errors / d.total) * 100 }))
390
+ .sort((a, b) => b.errorRate - a.errorRate)
391
+ .slice(0, 3),
392
+ averageTaskCompletion: health.completion,
393
+ iterationEfficiency: Math.round(100 - errorRate * 50),
394
+ };
395
+ // Build recent activity
396
+ const recentActivity = Array.from(dailyActivity.entries())
397
+ .sort((a, b) => b[0].localeCompare(a[0]))
398
+ .slice(0, 7)
399
+ .map(([date, count]) => ({
400
+ date,
401
+ summary: `${count} session${count > 1 ? 's' : ''}`,
402
+ }));
403
+ // Build basic recommendations
404
+ const basicRecommendations = [];
405
+ if (errorRate > 0.3) {
406
+ basicRecommendations.push({
407
+ id: 'reduce-errors',
408
+ type: 'process',
409
+ priority: 'high',
410
+ title: 'Reduce Error Rate',
411
+ description: 'Your sessions have a high error rate. Consider adding better validation.',
412
+ rationale: `Current error rate is ${(errorRate * 100).toFixed(0)}%`,
413
+ actionItems: ['Review common error patterns', 'Add pre-commit hooks'],
414
+ estimatedImpact: 'Reduce debugging time by 30%',
415
+ });
416
+ }
417
+ if (hotspots.length > 0 && hotspots[0].editCount > 20) {
418
+ basicRecommendations.push({
419
+ id: 'refactor-hotspot',
420
+ type: 'refactor',
421
+ priority: 'medium',
422
+ title: `Consider Refactoring ${hotspots[0].fileName}`,
423
+ description: 'This file has high churn and may benefit from refactoring.',
424
+ rationale: `Edited ${hotspots[0].editCount} times across ${hotspots[0].sessions} sessions`,
425
+ actionItems: ['Review file complexity', 'Consider splitting into smaller modules'],
426
+ estimatedImpact: 'Reduce maintenance overhead',
427
+ });
428
+ }
429
+ return {
430
+ health,
431
+ totalTokens,
432
+ totalCost,
433
+ dateRange: { first: firstDate, last: lastDate },
434
+ hotspots,
435
+ languages,
436
+ filesCreated,
437
+ filesEdited,
438
+ milestones: [], // Would need more sophisticated analysis
439
+ recentActivity,
440
+ recentTopics: languages,
441
+ workflowPatterns: [], // Would need more sophisticated analysis
442
+ activityPattern,
443
+ challenges: errorPatterns.map((e, i) => ({
444
+ id: `challenge-${i}`,
445
+ title: e.type,
446
+ description: `Encountered ${e.count} times`,
447
+ occurrences: e.count,
448
+ firstSeen: e.lastOccurred,
449
+ lastSeen: e.lastOccurred,
450
+ category: 'error',
451
+ relatedFiles: [],
452
+ resolved: false,
453
+ })),
454
+ errorPatterns,
455
+ claudeEffectiveness,
456
+ topTools,
457
+ basicRecommendations,
458
+ quickStats: {
459
+ sessionsThisWeek,
460
+ tokensThisWeek,
461
+ filesChangedThisWeek,
462
+ milestonesThisMonth: 0,
463
+ },
464
+ };
465
+ }
466
+ /**
467
+ * Generate AI-powered narrative for the project
468
+ */
469
+ async generateAINarrative(analysis, projectName) {
470
+ try {
471
+ const context = this.buildAIContext(analysis, projectName);
472
+ const prompt = `You are analyzing a software project's development history. Based on the data, provide a concise analysis.
473
+
474
+ PROJECT: ${projectName}
475
+
476
+ DATA:
477
+ ${context}
478
+
479
+ Respond with ONLY a JSON object (no markdown):
480
+ {
481
+ "story": "A 2-3 sentence narrative describing what this project is and its development journey",
482
+ "focus": "One sentence about the current focus or recent work",
483
+ "recommendations": [
484
+ {
485
+ "id": "rec-1",
486
+ "type": "skill|refactor|process|learning|automation",
487
+ "priority": "high|medium|low",
488
+ "title": "Short recommendation title",
489
+ "description": "What to do",
490
+ "rationale": "Why this matters",
491
+ "actionItems": ["Step 1", "Step 2"],
492
+ "estimatedImpact": "Expected benefit"
493
+ }
494
+ ]
495
+ }
496
+
497
+ Keep recommendations to 2-3 most impactful ones.`;
498
+ const tempFile = path.join(this.claudeDir.root, `intelligence-temp-${Date.now()}.txt`);
499
+ await fs.writeFile(tempFile, prompt, 'utf-8');
500
+ const { stdout } = await execAsync(`cat "${tempFile}" | claude --print`, {
501
+ timeout: 120000,
502
+ maxBuffer: 5 * 1024 * 1024,
503
+ });
504
+ await fs.unlink(tempFile).catch(() => { });
505
+ // Parse response
506
+ const jsonMatch = stdout.match(/\{[\s\S]*\}/);
507
+ if (!jsonMatch)
508
+ return null;
509
+ const parsed = JSON.parse(jsonMatch[0]);
510
+ return {
511
+ story: parsed.story || '',
512
+ focus: parsed.focus || '',
513
+ recommendations: parsed.recommendations || [],
514
+ };
515
+ }
516
+ catch (error) {
517
+ console.error('[ProjectIntelligence] AI narrative failed:', error);
518
+ return null;
519
+ }
520
+ }
521
+ /**
522
+ * Build context for AI analysis
523
+ */
524
+ buildAIContext(analysis, projectName) {
525
+ return `
526
+ Sessions: ${analysis.health.velocity > 0 ? Math.round(1 / analysis.health.velocity) : 0} total
527
+ Health Score: ${analysis.health.overall}/100
528
+ Languages: ${analysis.languages.join(', ') || 'Unknown'}
529
+ Top Files Changed: ${analysis.hotspots.slice(0, 3).map((h) => h.fileName).join(', ')}
530
+ Error Rate: ${(100 - analysis.health.stability)}%
531
+ Top Tools: ${analysis.topTools.slice(0, 3).map((t) => `${t.name}(${t.count})`).join(', ')}
532
+ Recent Activity: ${analysis.recentActivity.slice(0, 3).map((a) => `${a.date}: ${a.summary}`).join('; ')}
533
+ Peak Activity: ${analysis.activityPattern.peakDays[0]?.day || 'Unknown'} at ${analysis.activityPattern.peakHours[0]?.hour || 0}:00
534
+ `;
535
+ }
536
+ /**
537
+ * Generate basic narrative without AI
538
+ */
539
+ generateBasicNarrative(analysis) {
540
+ const languages = analysis.languages.slice(0, 2).join(' and ') || 'various technologies';
541
+ const files = analysis.hotspots.slice(0, 2).map((h) => h.fileName).join(', ');
542
+ return `A project using ${languages}. Most activity focuses on ${files || 'core functionality'}. ` +
543
+ `Health score: ${analysis.health.overall}/100 with ${analysis.health.trend} trend.`;
544
+ }
545
+ /**
546
+ * Get global intelligence across all projects
547
+ */
548
+ async getGlobalIntelligence(force = false) {
549
+ const cachePath = path.join(this.claudeDir.root, INTELLIGENCE_CACHE_DIR, 'global.json');
550
+ // Check cache
551
+ if (!force) {
552
+ try {
553
+ const content = await fs.readFile(cachePath, 'utf-8');
554
+ const cached = JSON.parse(content);
555
+ const age = Date.now() - new Date(cached.generatedAt).getTime();
556
+ if (age < 24 * 60 * 60 * 1000 && cached.status === 'completed') {
557
+ return cached;
558
+ }
559
+ }
560
+ catch {
561
+ // No cache
562
+ }
563
+ }
564
+ // Generate fresh
565
+ const summaries = await this.listProjectIntelligence();
566
+ const projects = await this.sessionService.listProjects();
567
+ const mostActiveProjects = summaries
568
+ .sort((a, b) => b.sessionCount - a.sessionCount)
569
+ .slice(0, 5)
570
+ .map(s => ({ path: s.projectPath, name: s.projectName, sessions: s.sessionCount }));
571
+ const healthiest = summaries.reduce((best, curr) => curr.health > (best?.health ?? 0) ? curr : best, summaries[0]);
572
+ const needsAttention = summaries.reduce((worst, curr) => curr.health < (worst?.health ?? 100) ? curr : worst, summaries[0]);
573
+ const global = {
574
+ generatedAt: new Date().toISOString(),
575
+ status: 'completed',
576
+ developerProfile: `Active across ${projects.length} projects with focus on ${summaries[0]?.projectName || 'development'}`,
577
+ overallNarrative: `Working on ${projects.length} projects. Most active: ${mostActiveProjects[0]?.name || 'N/A'}.`,
578
+ mostActiveProjects,
579
+ projectComparison: {
580
+ healthiest: healthiest?.projectName || 'N/A',
581
+ mostActive: mostActiveProjects[0]?.name || 'N/A',
582
+ needsAttention: needsAttention?.projectName || 'N/A',
583
+ },
584
+ workingPatterns: {
585
+ mostProductiveDay: 'Tuesday', // Would need actual calculation
586
+ mostProductiveHour: 14,
587
+ averageSessionDuration: 30,
588
+ },
589
+ recommendations: [],
590
+ };
591
+ await fs.mkdir(path.dirname(cachePath), { recursive: true });
592
+ await fs.writeFile(cachePath, JSON.stringify(global, null, 2));
593
+ return global;
594
+ }
595
+ // ============================================================================
596
+ // Helper Methods
597
+ // ============================================================================
598
+ async isClaudeAvailable() {
599
+ try {
600
+ await execAsync('which claude');
601
+ return true;
602
+ }
603
+ catch {
604
+ return false;
605
+ }
606
+ }
607
+ daysBetween(date1, date2) {
608
+ if (!date1 || !date2)
609
+ return 1;
610
+ const d1 = new Date(date1);
611
+ const d2 = new Date(date2);
612
+ return Math.max(1, Math.ceil((d2.getTime() - d1.getTime()) / (1000 * 60 * 60 * 24)));
613
+ }
614
+ calculateChurnScore(fileEdits) {
615
+ let score = 0;
616
+ for (const [_, data] of fileEdits) {
617
+ score += data.count * data.sessions.size;
618
+ }
619
+ return score / Math.max(1, fileEdits.size);
620
+ }
621
+ determineTrend(sessions) {
622
+ if (sessions.length < 5)
623
+ return 'stable';
624
+ // Simple trend based on recent activity
625
+ const sorted = sessions.sort((a, b) => new Date(b.startedAt || b.lastMessageAt).getTime() -
626
+ new Date(a.startedAt || a.lastMessageAt).getTime());
627
+ const recentCount = sorted.slice(0, 5).length;
628
+ const olderCount = sorted.slice(5, 10).length;
629
+ if (recentCount > olderCount * 1.2)
630
+ return 'improving';
631
+ if (recentCount < olderCount * 0.8)
632
+ return 'declining';
633
+ return 'stable';
634
+ }
635
+ calculateStreak(dailyActivity) {
636
+ const dates = Array.from(dailyActivity.keys()).sort();
637
+ let maxStreak = 0;
638
+ let currentStreak = 1;
639
+ for (let i = 1; i < dates.length; i++) {
640
+ const prev = new Date(dates[i - 1]);
641
+ const curr = new Date(dates[i]);
642
+ const diff = (curr.getTime() - prev.getTime()) / (1000 * 60 * 60 * 24);
643
+ if (diff === 1) {
644
+ currentStreak++;
645
+ maxStreak = Math.max(maxStreak, currentStreak);
646
+ }
647
+ else {
648
+ currentStreak = 1;
649
+ }
650
+ }
651
+ return maxStreak;
652
+ }
653
+ calculateCurrentStreak(dailyActivity) {
654
+ const dates = Array.from(dailyActivity.keys()).sort().reverse();
655
+ if (dates.length === 0)
656
+ return 0;
657
+ const today = new Date().toISOString().split('T')[0];
658
+ if (dates[0] !== today)
659
+ return 0;
660
+ let streak = 1;
661
+ for (let i = 1; i < dates.length; i++) {
662
+ const prev = new Date(dates[i - 1]);
663
+ const curr = new Date(dates[i]);
664
+ const diff = (prev.getTime() - curr.getTime()) / (1000 * 60 * 60 * 24);
665
+ if (diff === 1) {
666
+ streak++;
667
+ }
668
+ else {
669
+ break;
670
+ }
671
+ }
672
+ return streak;
673
+ }
674
+ categorizeError(content) {
675
+ if (content.includes('ENOENT') || content.includes('not found'))
676
+ return 'File Not Found';
677
+ if (content.includes('permission') || content.includes('EACCES'))
678
+ return 'Permission Error';
679
+ if (content.includes('syntax') || content.includes('parse'))
680
+ return 'Syntax Error';
681
+ if (content.includes('type') || content.includes('TypeScript'))
682
+ return 'Type Error';
683
+ if (content.includes('timeout'))
684
+ return 'Timeout';
685
+ if (content.includes('network') || content.includes('ECONNREFUSED'))
686
+ return 'Network Error';
687
+ return 'Other Error';
688
+ }
689
+ extToLanguage(ext) {
690
+ const map = {
691
+ '.ts': 'TypeScript',
692
+ '.tsx': 'TypeScript',
693
+ '.js': 'JavaScript',
694
+ '.jsx': 'JavaScript',
695
+ '.py': 'Python',
696
+ '.go': 'Go',
697
+ '.rs': 'Rust',
698
+ '.java': 'Java',
699
+ '.rb': 'Ruby',
700
+ '.swift': 'Swift',
701
+ '.kt': 'Kotlin',
702
+ '.css': 'CSS',
703
+ '.scss': 'Sass',
704
+ '.html': 'HTML',
705
+ '.vue': 'Vue',
706
+ '.svelte': 'Svelte',
707
+ };
708
+ return map[ext] || ext.replace('.', '').toUpperCase();
709
+ }
710
+ identifyStrengths(toolCalls) {
711
+ const strengths = [];
712
+ const totalCalls = Array.from(toolCalls.values()).reduce((sum, d) => sum + d.total, 0);
713
+ for (const [tool, data] of toolCalls) {
714
+ if (data.errors === 0 && data.total > totalCalls * 0.1) {
715
+ strengths.push(`Strong ${tool} usage`);
716
+ }
717
+ }
718
+ return strengths.slice(0, 3);
719
+ }
720
+ identifyWeaknesses(toolCalls) {
721
+ const weaknesses = [];
722
+ for (const [tool, data] of toolCalls) {
723
+ const errorRate = data.errors / data.total;
724
+ if (errorRate > 0.3 && data.total > 5) {
725
+ weaknesses.push(`High error rate with ${tool}`);
726
+ }
727
+ }
728
+ return weaknesses.slice(0, 3);
729
+ }
730
+ }
731
+ //# sourceMappingURL=project-intelligence.js.map