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,1088 @@
1
+ /**
2
+ * Tech Stack Service - Aggregates technology usage across all Claude Code sessions
3
+ *
4
+ * This service scans session files to detect:
5
+ * - Programming languages used (via file extensions)
6
+ * - Frameworks and libraries (via file patterns and imports)
7
+ * - Cloud providers and DevOps tools
8
+ * - Databases and infrastructure
9
+ *
10
+ * It tracks Claude's contributions by analyzing Edit/Write tool calls.
11
+ * Also provides AI-powered insights via Claude CLI.
12
+ */
13
+ import path from 'node:path';
14
+ import fs from 'node:fs/promises';
15
+ import os from 'node:os';
16
+ import { exec } from 'node:child_process';
17
+ const EXTENSION_MAPPINGS = {
18
+ // Languages
19
+ '.ts': { name: 'TypeScript', type: 'language' },
20
+ '.tsx': { name: 'TypeScript', type: 'language' },
21
+ '.js': { name: 'JavaScript', type: 'language' },
22
+ '.jsx': { name: 'JavaScript', type: 'language' },
23
+ '.mjs': { name: 'JavaScript', type: 'language' },
24
+ '.cjs': { name: 'JavaScript', type: 'language' },
25
+ '.py': { name: 'Python', type: 'language' },
26
+ '.go': { name: 'Go', type: 'language' },
27
+ '.rs': { name: 'Rust', type: 'language' },
28
+ '.rb': { name: 'Ruby', type: 'language' },
29
+ '.java': { name: 'Java', type: 'language' },
30
+ '.kt': { name: 'Kotlin', type: 'language' },
31
+ '.swift': { name: 'Swift', type: 'language' },
32
+ '.c': { name: 'C', type: 'language' },
33
+ '.cpp': { name: 'C++', type: 'language' },
34
+ '.cc': { name: 'C++', type: 'language' },
35
+ '.h': { name: 'C/C++', type: 'language' },
36
+ '.hpp': { name: 'C++', type: 'language' },
37
+ '.cs': { name: 'C#', type: 'language' },
38
+ '.php': { name: 'PHP', type: 'language' },
39
+ '.scala': { name: 'Scala', type: 'language' },
40
+ '.clj': { name: 'Clojure', type: 'language' },
41
+ '.ex': { name: 'Elixir', type: 'language' },
42
+ '.exs': { name: 'Elixir', type: 'language' },
43
+ '.erl': { name: 'Erlang', type: 'language' },
44
+ '.hs': { name: 'Haskell', type: 'language' },
45
+ '.lua': { name: 'Lua', type: 'language' },
46
+ '.r': { name: 'R', type: 'language' },
47
+ '.dart': { name: 'Dart', type: 'language' },
48
+ '.vue': { name: 'Vue.js', type: 'framework' },
49
+ '.svelte': { name: 'Svelte', type: 'framework' },
50
+ // Config/Data
51
+ '.json': { name: 'JSON', type: 'tool' },
52
+ '.yaml': { name: 'YAML', type: 'tool' },
53
+ '.yml': { name: 'YAML', type: 'tool' },
54
+ '.toml': { name: 'TOML', type: 'tool' },
55
+ '.xml': { name: 'XML', type: 'tool' },
56
+ '.ini': { name: 'INI', type: 'tool' },
57
+ '.env': { name: 'Environment', type: 'tool' },
58
+ // Web
59
+ '.html': { name: 'HTML', type: 'language' },
60
+ '.htm': { name: 'HTML', type: 'language' },
61
+ '.css': { name: 'CSS', type: 'language' },
62
+ '.scss': { name: 'Sass', type: 'tool' },
63
+ '.sass': { name: 'Sass', type: 'tool' },
64
+ '.less': { name: 'Less', type: 'tool' },
65
+ // DevOps/Cloud
66
+ '.tf': { name: 'Terraform', type: 'devops' },
67
+ '.tfvars': { name: 'Terraform', type: 'devops' },
68
+ '.hcl': { name: 'HashiCorp', type: 'devops' },
69
+ // Documentation
70
+ '.md': { name: 'Markdown', type: 'tool' },
71
+ '.mdx': { name: 'MDX', type: 'tool' },
72
+ '.rst': { name: 'reStructuredText', type: 'tool' },
73
+ // Shell
74
+ '.sh': { name: 'Shell', type: 'tool' },
75
+ '.bash': { name: 'Bash', type: 'tool' },
76
+ '.zsh': { name: 'Zsh', type: 'tool' },
77
+ '.fish': { name: 'Fish', type: 'tool' },
78
+ '.ps1': { name: 'PowerShell', type: 'tool' },
79
+ // Database
80
+ '.sql': { name: 'SQL', type: 'database' },
81
+ '.prisma': { name: 'Prisma', type: 'database' },
82
+ // Testing
83
+ '.test.ts': { name: 'Jest', type: 'testing' },
84
+ '.spec.ts': { name: 'Jest', type: 'testing' },
85
+ '.test.js': { name: 'Jest', type: 'testing' },
86
+ '.spec.js': { name: 'Jest', type: 'testing' },
87
+ };
88
+ // File path pattern detection
89
+ const PATH_PATTERNS = [
90
+ // Frameworks
91
+ { pattern: /next\.config\.(js|ts|mjs)$/, tech: { name: 'Next.js', type: 'framework' } },
92
+ { pattern: /vite\.config\.(js|ts|mjs)$/, tech: { name: 'Vite', type: 'tool' } },
93
+ { pattern: /webpack\.config\.(js|ts)$/, tech: { name: 'Webpack', type: 'tool' } },
94
+ { pattern: /tailwind\.config\.(js|ts)$/, tech: { name: 'Tailwind CSS', type: 'framework' } },
95
+ { pattern: /postcss\.config\.(js|ts)$/, tech: { name: 'PostCSS', type: 'tool' } },
96
+ { pattern: /tsconfig\.json$/, tech: { name: 'TypeScript', type: 'language' } },
97
+ { pattern: /package\.json$/, tech: { name: 'Node.js', type: 'tool' } },
98
+ { pattern: /cargo\.toml$/i, tech: { name: 'Rust', type: 'language' } },
99
+ { pattern: /go\.mod$/, tech: { name: 'Go', type: 'language' } },
100
+ { pattern: /requirements\.txt$/, tech: { name: 'Python', type: 'language' } },
101
+ { pattern: /pyproject\.toml$/, tech: { name: 'Python', type: 'language' } },
102
+ { pattern: /gemfile$/i, tech: { name: 'Ruby', type: 'language' } },
103
+ { pattern: /pom\.xml$/, tech: { name: 'Maven', type: 'tool' } },
104
+ { pattern: /build\.gradle(\.kts)?$/, tech: { name: 'Gradle', type: 'tool' } },
105
+ // DevOps
106
+ { pattern: /dockerfile$/i, tech: { name: 'Docker', type: 'devops' } },
107
+ { pattern: /docker-compose\.(yml|yaml)$/i, tech: { name: 'Docker Compose', type: 'devops' } },
108
+ { pattern: /\.github\/workflows\//, tech: { name: 'GitHub Actions', type: 'devops' } },
109
+ { pattern: /\.gitlab-ci\.yml$/, tech: { name: 'GitLab CI', type: 'devops' } },
110
+ { pattern: /jenkinsfile$/i, tech: { name: 'Jenkins', type: 'devops' } },
111
+ { pattern: /kubernetes|k8s/i, tech: { name: 'Kubernetes', type: 'devops' } },
112
+ { pattern: /helm/i, tech: { name: 'Helm', type: 'devops' } },
113
+ // Cloud
114
+ { pattern: /\.aws\//, tech: { name: 'AWS', type: 'cloud' } },
115
+ { pattern: /serverless\.(yml|yaml|json)$/i, tech: { name: 'Serverless', type: 'cloud' } },
116
+ { pattern: /cloudformation/i, tech: { name: 'AWS CloudFormation', type: 'cloud' } },
117
+ { pattern: /\.gcloud\//, tech: { name: 'Google Cloud', type: 'cloud' } },
118
+ { pattern: /firebase\.(json|rc)$/i, tech: { name: 'Firebase', type: 'cloud' } },
119
+ { pattern: /vercel\.json$/i, tech: { name: 'Vercel', type: 'cloud' } },
120
+ { pattern: /netlify\.toml$/i, tech: { name: 'Netlify', type: 'cloud' } },
121
+ // Testing
122
+ { pattern: /jest\.config\.(js|ts|json)$/, tech: { name: 'Jest', type: 'testing' } },
123
+ { pattern: /vitest\.config\.(js|ts)$/, tech: { name: 'Vitest', type: 'testing' } },
124
+ { pattern: /playwright\.config\.(js|ts)$/, tech: { name: 'Playwright', type: 'testing' } },
125
+ { pattern: /cypress\.config\.(js|ts)$/, tech: { name: 'Cypress', type: 'testing' } },
126
+ { pattern: /pytest\.ini$/, tech: { name: 'Pytest', type: 'testing' } },
127
+ // Databases
128
+ { pattern: /prisma\/schema\.prisma$/, tech: { name: 'Prisma', type: 'database' } },
129
+ { pattern: /drizzle\.config\.(js|ts)$/, tech: { name: 'Drizzle', type: 'database' } },
130
+ { pattern: /migrations\//, tech: { name: 'Database Migrations', type: 'database' } },
131
+ ];
132
+ const CACHE_VERSION = 1;
133
+ export class TechStackService {
134
+ claudeDir;
135
+ cacheFile;
136
+ cache = null;
137
+ constructor(claudeDir) {
138
+ this.claudeDir = claudeDir;
139
+ this.cacheFile = path.join(claudeDir.root, 'tech-stack-cache.json');
140
+ }
141
+ /**
142
+ * Get the global tech stack summary (uses cache if available)
143
+ */
144
+ async getSummary(forceRefresh = false) {
145
+ if (!forceRefresh && this.cache) {
146
+ return this.cache;
147
+ }
148
+ // Try loading from disk cache
149
+ if (!forceRefresh) {
150
+ const cached = await this.loadCache();
151
+ if (cached) {
152
+ this.cache = cached;
153
+ return cached;
154
+ }
155
+ }
156
+ // Generate fresh
157
+ return this.refresh();
158
+ }
159
+ /**
160
+ * Force refresh the tech stack data by scanning all sessions
161
+ */
162
+ async refresh() {
163
+ console.log('[TechStack] Starting full scan...');
164
+ const techMap = new Map();
165
+ const projectDirs = await this.claudeDir.listProjects();
166
+ let totalSessions = 0;
167
+ let totalFiles = 0;
168
+ for (const projectDir of projectDirs) {
169
+ const projectPath = this.claudeDir.projectDirToPath(projectDir);
170
+ const sessionFiles = await this.claudeDir.listSessionFiles(projectDir);
171
+ for (const sessionFile of sessionFiles) {
172
+ totalSessions++;
173
+ const messages = await this.parseSessionFile(sessionFile);
174
+ for (const msg of messages) {
175
+ this.processMessage(msg, projectPath, techMap);
176
+ }
177
+ }
178
+ }
179
+ // Convert accumulator to entries
180
+ const technologies = [];
181
+ for (const [name, acc] of techMap) {
182
+ totalFiles += acc.files.size;
183
+ technologies.push({
184
+ name,
185
+ type: acc.type,
186
+ fileExtensions: Array.from(acc.extensions),
187
+ usageCount: acc.usageCount,
188
+ filesModified: acc.files.size,
189
+ projectsUsed: Array.from(acc.projects),
190
+ firstSeen: acc.firstSeen,
191
+ lastUsed: acc.lastUsed,
192
+ proficiency: this.calculateProficiency(acc),
193
+ claudeContributions: acc.claudeContributions,
194
+ patterns: [],
195
+ });
196
+ }
197
+ // Sort by usage
198
+ technologies.sort((a, b) => b.usageCount - a.usageCount);
199
+ // Extract top items by type
200
+ const languages = technologies.filter(t => t.type === 'language');
201
+ const frameworks = technologies.filter(t => t.type === 'framework');
202
+ const cloudProviders = technologies.filter(t => t.type === 'cloud');
203
+ const devOpsTools = technologies.filter(t => t.type === 'devops');
204
+ const databases = technologies.filter(t => t.type === 'database');
205
+ const summary = {
206
+ generatedAt: new Date().toISOString(),
207
+ version: CACHE_VERSION,
208
+ totalProjects: projectDirs.length,
209
+ totalSessions,
210
+ totalFiles,
211
+ technologies,
212
+ primaryLanguages: languages.slice(0, 3).map(t => t.name),
213
+ topFrameworks: frameworks.slice(0, 3).map(t => t.name),
214
+ cloudProviders: cloudProviders.map(t => t.name),
215
+ devOpsTools: devOpsTools.map(t => t.name),
216
+ databases: databases.map(t => t.name),
217
+ recommendations: this.generateRecommendations(technologies),
218
+ insights: {
219
+ mostActiveProject: this.findMostActiveProject(technologies),
220
+ mostUsedTech: technologies[0]?.name || 'None',
221
+ growthAreas: this.findGrowthAreas(technologies),
222
+ expertise: technologies.filter(t => t.proficiency === 'expert').map(t => t.name),
223
+ },
224
+ };
225
+ // Save cache
226
+ await this.saveCache(summary);
227
+ this.cache = summary;
228
+ console.log(`[TechStack] Scan complete. Found ${technologies.length} technologies across ${totalSessions} sessions.`);
229
+ return summary;
230
+ }
231
+ /**
232
+ * Get tech stack breakdown for a specific project
233
+ */
234
+ async getProjectBreakdown(projectPath) {
235
+ const projectDirs = await this.claudeDir.listProjects();
236
+ const projectDir = projectDirs.find(d => this.claudeDir.projectDirToPath(d) === projectPath);
237
+ if (!projectDir) {
238
+ return null;
239
+ }
240
+ const techMap = new Map();
241
+ const extensionCounts = new Map();
242
+ let totalEdits = 0;
243
+ let totalReads = 0;
244
+ let totalWrites = 0;
245
+ const sessionFiles = await this.claudeDir.listSessionFiles(projectDir);
246
+ for (const sessionFile of sessionFiles) {
247
+ const messages = await this.parseSessionFile(sessionFile);
248
+ for (const msg of messages) {
249
+ const result = this.processMessage(msg, projectPath, techMap);
250
+ if (result) {
251
+ // Track extension counts
252
+ const ext = path.extname(result.filePath).toLowerCase();
253
+ if (ext) {
254
+ extensionCounts.set(ext, (extensionCounts.get(ext) || 0) + 1);
255
+ }
256
+ // Track activity types
257
+ if (result.toolName === 'Edit' || result.toolName === 'MultiEdit') {
258
+ totalEdits++;
259
+ }
260
+ else if (result.toolName === 'Read' || result.toolName === 'View') {
261
+ totalReads++;
262
+ }
263
+ else if (result.toolName === 'Write') {
264
+ totalWrites++;
265
+ }
266
+ }
267
+ }
268
+ }
269
+ // Convert to entries
270
+ const technologies = [];
271
+ for (const [name, acc] of techMap) {
272
+ technologies.push({
273
+ name,
274
+ type: acc.type,
275
+ fileExtensions: Array.from(acc.extensions),
276
+ usageCount: acc.usageCount,
277
+ filesModified: acc.files.size,
278
+ projectsUsed: [projectPath],
279
+ firstSeen: acc.firstSeen,
280
+ lastUsed: acc.lastUsed,
281
+ proficiency: this.calculateProficiency(acc),
282
+ claudeContributions: acc.claudeContributions,
283
+ patterns: [],
284
+ });
285
+ }
286
+ technologies.sort((a, b) => b.usageCount - a.usageCount);
287
+ // Calculate file breakdown percentages
288
+ const totalExtensionCount = Array.from(extensionCounts.values()).reduce((a, b) => a + b, 0);
289
+ const fileBreakdown = Array.from(extensionCounts.entries())
290
+ .map(([extension, count]) => ({
291
+ extension,
292
+ count,
293
+ percentage: totalExtensionCount > 0 ? (count / totalExtensionCount) * 100 : 0,
294
+ }))
295
+ .sort((a, b) => b.count - a.count);
296
+ return {
297
+ projectPath,
298
+ projectName: this.claudeDir.projectDirToName(projectDir),
299
+ generatedAt: new Date().toISOString(),
300
+ technologies,
301
+ fileBreakdown,
302
+ claudeActivity: {
303
+ totalEdits,
304
+ totalReads,
305
+ totalWrites,
306
+ },
307
+ };
308
+ }
309
+ /**
310
+ * Get deep insights including patterns, correlations, evolution, and similarity
311
+ */
312
+ async getDeepInsights(forceRefresh = false) {
313
+ // First get the base summary
314
+ const summary = await this.getSummary(forceRefresh);
315
+ // Build project -> technologies mapping for analysis
316
+ const projectTechMap = this.buildProjectTechMap(summary.technologies);
317
+ // Generate deep insights
318
+ const stackPatterns = this.detectStackPatterns(summary.technologies, projectTechMap);
319
+ const correlations = this.calculateCorrelations(summary.technologies, projectTechMap);
320
+ const evolution = this.analyzeEvolution(summary.technologies);
321
+ const projectSimilarity = this.computeProjectSimilarity(projectTechMap);
322
+ // Generate highlights
323
+ const highlights = this.generateHighlights(summary, stackPatterns, correlations, evolution);
324
+ return {
325
+ summary,
326
+ stackPatterns,
327
+ correlations,
328
+ evolution,
329
+ projectSimilarity,
330
+ highlights,
331
+ };
332
+ }
333
+ /**
334
+ * Generate AI-powered tech stack insight using Claude CLI
335
+ */
336
+ async generateAIInsight(force = false) {
337
+ const cacheFile = path.join(this.claudeDir.root, 'tech-stack-ai-insight.json');
338
+ // Check for cached insight
339
+ if (!force) {
340
+ try {
341
+ const cached = await fs.readFile(cacheFile, 'utf-8');
342
+ const insight = JSON.parse(cached);
343
+ // Cache valid for 24 hours
344
+ const age = Date.now() - new Date(insight.generatedAt).getTime();
345
+ if (age < 24 * 60 * 60 * 1000 && insight.status === 'completed') {
346
+ return insight;
347
+ }
348
+ }
349
+ catch {
350
+ // No cache or invalid
351
+ }
352
+ }
353
+ // Create pending insight
354
+ const pendingInsight = {
355
+ id: `tech-ai-${Date.now()}`,
356
+ generatedAt: new Date().toISOString(),
357
+ status: 'generating',
358
+ };
359
+ await fs.writeFile(cacheFile, JSON.stringify(pendingInsight, null, 2));
360
+ try {
361
+ // Check if Claude is available
362
+ const claudeAvailable = await this.isClaudeCodeAvailable();
363
+ if (!claudeAvailable) {
364
+ const errorInsight = {
365
+ ...pendingInsight,
366
+ status: 'error',
367
+ error: {
368
+ code: 'CLAUDE_NOT_FOUND',
369
+ message: 'Claude Code CLI is not installed',
370
+ recoverable: false,
371
+ suggestion: 'Install Claude Code: npm install -g @anthropic-ai/claude-code',
372
+ },
373
+ };
374
+ await fs.writeFile(cacheFile, JSON.stringify(errorInsight, null, 2));
375
+ return errorInsight;
376
+ }
377
+ // Get the deep insights data
378
+ const deepInsights = await this.getDeepInsights();
379
+ // Build context for Claude
380
+ const context = this.buildAIContext(deepInsights);
381
+ // Call Claude
382
+ console.log('[TechStack] Calling Claude for AI insight...');
383
+ const response = await this.callClaudeCode(context);
384
+ // Parse response
385
+ const parsed = this.parseAIResponse(response);
386
+ const completedInsight = {
387
+ id: pendingInsight.id,
388
+ generatedAt: pendingInsight.generatedAt,
389
+ status: 'completed',
390
+ ...parsed,
391
+ };
392
+ await fs.writeFile(cacheFile, JSON.stringify(completedInsight, null, 2));
393
+ console.log('[TechStack] AI insight generated successfully');
394
+ return completedInsight;
395
+ }
396
+ catch (error) {
397
+ const errorInsight = {
398
+ ...pendingInsight,
399
+ status: 'error',
400
+ error: {
401
+ code: 'UNKNOWN',
402
+ message: error.message,
403
+ recoverable: true,
404
+ suggestion: 'Try generating the insight again',
405
+ },
406
+ };
407
+ await fs.writeFile(cacheFile, JSON.stringify(errorInsight, null, 2));
408
+ return errorInsight;
409
+ }
410
+ }
411
+ /**
412
+ * Get cached AI insight without regenerating
413
+ */
414
+ async getAIInsight() {
415
+ const cacheFile = path.join(this.claudeDir.root, 'tech-stack-ai-insight.json');
416
+ try {
417
+ const cached = await fs.readFile(cacheFile, 'utf-8');
418
+ return JSON.parse(cached);
419
+ }
420
+ catch {
421
+ return null;
422
+ }
423
+ }
424
+ /**
425
+ * Check if Claude Code CLI is available
426
+ */
427
+ async isClaudeCodeAvailable() {
428
+ return new Promise((resolve) => {
429
+ exec('which claude', (error) => {
430
+ resolve(!error);
431
+ });
432
+ });
433
+ }
434
+ /**
435
+ * Build context string for Claude AI analysis
436
+ */
437
+ buildAIContext(insights) {
438
+ const { summary, stackPatterns, correlations, evolution, projectSimilarity, highlights } = insights;
439
+ let context = `# Tech Stack Analysis Request
440
+
441
+ ## Overview
442
+ - Total Technologies: ${summary.technologies.length}
443
+ - Total Projects: ${summary.totalProjects}
444
+ - Total Sessions: ${summary.totalSessions}
445
+ - Files Touched: ${summary.totalFiles}
446
+
447
+ ## Primary Languages
448
+ ${summary.primaryLanguages.join(', ') || 'None detected'}
449
+
450
+ ## Top Frameworks
451
+ ${summary.topFrameworks.join(', ') || 'None detected'}
452
+
453
+ ## All Technologies (by usage)
454
+ ${summary.technologies.slice(0, 20).map(t => `- ${t.name} (${t.type}): ${t.usageCount} uses, ${t.filesModified} files, proficiency: ${t.proficiency}`).join('\n')}
455
+
456
+ ## Detected Stack Patterns
457
+ ${stackPatterns.map(p => `- ${p.name}: ${p.technologies.join(', ')} (${p.projectCount} projects)`).join('\n') || 'No patterns detected'}
458
+
459
+ ## Technology Correlations (tech pairs that appear together)
460
+ ${correlations.slice(0, 10).map(c => `- ${c.tech1} + ${c.tech2}: ${c.correlation}% correlation (${c.strength})`).join('\n') || 'Insufficient data'}
461
+
462
+ ## Usage Trends
463
+ ${evolution.slice(0, 10).map(e => `- ${e.tech}: ${e.trend} (${e.changePercent > 0 ? '+' : ''}${e.changePercent}%)`).join('\n') || 'No trends detected'}
464
+
465
+ ## Similar Projects
466
+ ${projectSimilarity.slice(0, 5).map(s => `- ${s.project1Name} ↔ ${s.project2Name}: ${s.similarity}% similar (${s.sharedTechnologies.join(', ')})`).join('\n') || 'No similar projects found'}
467
+
468
+ ## Current Highlights
469
+ - Shared Foundations: ${highlights.sharedFoundations}
470
+ - Dominant Pattern: ${highlights.dominantPattern}
471
+ - Rising Tech: ${highlights.risingTech}
472
+ - Tech Diversity Score: ${highlights.techDiversity}%
473
+ `;
474
+ return context;
475
+ }
476
+ /**
477
+ * Call Claude Code CLI with context
478
+ */
479
+ async callClaudeCode(context) {
480
+ const prompt = `You are an expert developer coach and technology advisor. Analyze this developer's tech stack data and provide personalized insights.
481
+
482
+ IMPORTANT: Return ONLY a valid JSON object. No markdown, no explanation, no code blocks - just the raw JSON.
483
+
484
+ The JSON must follow this exact structure:
485
+ {
486
+ "overview": "A 2-3 sentence narrative overview of this developer's tech profile",
487
+ "developerProfile": "A single sentence describing what type of developer they are (e.g., 'Full-stack TypeScript specialist with strong React ecosystem expertise')",
488
+ "stackHealth": 75,
489
+
490
+ "stackPatterns": [
491
+ {
492
+ "id": "unique-pattern-id",
493
+ "name": "Pattern Name (e.g., 'Modern Full-Stack TypeScript')",
494
+ "description": "Brief description of what this pattern represents",
495
+ "technologies": ["Tech1", "Tech2", "Tech3"],
496
+ "icon": "layers|code|database|cloud|cpu|globe"
497
+ }
498
+ ],
499
+
500
+ "strengths": [
501
+ {
502
+ "title": "Strong in X",
503
+ "description": "Explanation of why this is a strength",
504
+ "technologies": ["Tech1", "Tech2"]
505
+ }
506
+ ],
507
+
508
+ "blindSpots": [
509
+ {
510
+ "title": "Area to improve",
511
+ "description": "Why this matters",
512
+ "suggestion": "Concrete action to take"
513
+ }
514
+ ],
515
+
516
+ "learningPath": [
517
+ {
518
+ "skill": "Technology or skill to learn",
519
+ "reason": "Why this would benefit them",
520
+ "priority": "high|medium|low"
521
+ }
522
+ ],
523
+
524
+ "technicalDebt": [
525
+ {
526
+ "issue": "Potential issue",
527
+ "impact": "How it affects them",
528
+ "fix": "How to address it"
529
+ }
530
+ ],
531
+
532
+ "architectureInsights": [
533
+ {
534
+ "title": "Pattern observed",
535
+ "observation": "What you noticed",
536
+ "recommendation": "Suggestion if applicable"
537
+ }
538
+ ],
539
+
540
+ "quickHighlights": [
541
+ {
542
+ "title": "Short highlight",
543
+ "description": "Brief explanation",
544
+ "type": "positive|warning|info|suggestion"
545
+ }
546
+ ]
547
+ }
548
+
549
+ CRITICAL INSTRUCTIONS:
550
+ 1. For stackPatterns: Identify 2-4 meaningful technology patterns/archetypes from their ACTUAL technologies. Examples:
551
+ - "Modern React Ecosystem" if you see React + TypeScript + Vite/Next.js
552
+ - "Python Data Science" if you see Python + pandas + numpy
553
+ - "Cloud-Native DevOps" if you see Docker + Kubernetes + AWS/GCP
554
+ - Create patterns that reflect their REAL usage, not generic templates
555
+ - Each pattern should group 3-6 related technologies
556
+
557
+ 2. Base stackHealth score on:
558
+ - Technology diversity (more types = better)
559
+ - Modern technologies (up-to-date = better)
560
+ - Testing presence (having testing = better)
561
+ - DevOps/CI presence (automation = better)
562
+
563
+ 3. Provide 2-4 items for each array. Be specific and actionable. Reference their actual technologies.
564
+
565
+ Here is their tech stack data:
566
+
567
+ ${context}`;
568
+ const tempFile = path.join(os.tmpdir(), `tech-stack-ai-${Date.now()}.txt`);
569
+ await fs.writeFile(tempFile, prompt, 'utf-8');
570
+ const timeout = 180000; // 3 minutes
571
+ return new Promise((resolve, reject) => {
572
+ const command = `cat "${tempFile}" | claude --print`;
573
+ const timer = setTimeout(() => {
574
+ reject(new Error('Claude Code execution timed out after 3 minutes'));
575
+ }, timeout + 5000);
576
+ exec(command, {
577
+ cwd: process.cwd(),
578
+ env: { ...process.env, NO_COLOR: '1', FORCE_COLOR: '0' },
579
+ maxBuffer: 10 * 1024 * 1024,
580
+ timeout: timeout,
581
+ }, (error, stdout, stderr) => {
582
+ clearTimeout(timer);
583
+ fs.unlink(tempFile).catch(() => { });
584
+ if (stderr) {
585
+ console.log(`[TechStack] stderr: ${stderr.slice(0, 200)}`);
586
+ }
587
+ if (error) {
588
+ console.log(`[TechStack] Error: ${error.message}`);
589
+ reject(new Error(`Failed to run claude: ${error.message}`));
590
+ return;
591
+ }
592
+ console.log(`[TechStack] Response received, length: ${stdout.length} chars`);
593
+ resolve(stdout);
594
+ });
595
+ });
596
+ }
597
+ /**
598
+ * Parse Claude's JSON response into TechStackAIInsight fields
599
+ */
600
+ parseAIResponse(response) {
601
+ const cleanedResponse = response
602
+ .replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '')
603
+ .replace(/[\r\n]+/g, '\n')
604
+ .trim();
605
+ const jsonMatch = cleanedResponse.match(/\{[\s\S]*\}/);
606
+ if (!jsonMatch) {
607
+ console.log('[TechStack] No JSON found in response, using fallback');
608
+ return this.generateFallbackAIInsight();
609
+ }
610
+ try {
611
+ const parsed = JSON.parse(jsonMatch[0]);
612
+ return {
613
+ overview: parsed.overview || '',
614
+ developerProfile: parsed.developerProfile || '',
615
+ stackHealth: typeof parsed.stackHealth === 'number' ? parsed.stackHealth : 50,
616
+ stackPatterns: Array.isArray(parsed.stackPatterns) ? parsed.stackPatterns : [],
617
+ strengths: Array.isArray(parsed.strengths) ? parsed.strengths : [],
618
+ blindSpots: Array.isArray(parsed.blindSpots) ? parsed.blindSpots : [],
619
+ learningPath: Array.isArray(parsed.learningPath) ? parsed.learningPath : [],
620
+ technicalDebt: Array.isArray(parsed.technicalDebt) ? parsed.technicalDebt : [],
621
+ architectureInsights: Array.isArray(parsed.architectureInsights) ? parsed.architectureInsights : [],
622
+ quickHighlights: Array.isArray(parsed.quickHighlights) ? parsed.quickHighlights : [],
623
+ };
624
+ }
625
+ catch (e) {
626
+ console.log('[TechStack] Failed to parse JSON:', e);
627
+ return this.generateFallbackAIInsight();
628
+ }
629
+ }
630
+ /**
631
+ * Generate fallback insight when AI fails
632
+ */
633
+ generateFallbackAIInsight() {
634
+ return {
635
+ overview: 'Unable to generate AI insight. The analysis is based on detected technologies.',
636
+ developerProfile: 'Developer profile unavailable',
637
+ stackHealth: 50,
638
+ strengths: [],
639
+ blindSpots: [],
640
+ learningPath: [],
641
+ technicalDebt: [],
642
+ architectureInsights: [],
643
+ quickHighlights: [
644
+ {
645
+ title: 'AI Analysis Unavailable',
646
+ description: 'Try regenerating the insight',
647
+ type: 'info',
648
+ },
649
+ ],
650
+ };
651
+ }
652
+ /**
653
+ * Build a map of project -> technologies for cross-project analysis
654
+ */
655
+ buildProjectTechMap(technologies) {
656
+ const projectTechMap = new Map();
657
+ for (const tech of technologies) {
658
+ for (const project of tech.projectsUsed) {
659
+ if (!projectTechMap.has(project)) {
660
+ projectTechMap.set(project, new Set());
661
+ }
662
+ projectTechMap.get(project).add(tech.name);
663
+ }
664
+ }
665
+ return projectTechMap;
666
+ }
667
+ /**
668
+ * Detect common stack patterns across projects
669
+ */
670
+ detectStackPatterns(technologies, projectTechMap) {
671
+ const patterns = [];
672
+ // Define known stack archetypes to look for
673
+ const archetypes = [
674
+ {
675
+ id: 'fullstack-ts',
676
+ name: 'Full-stack TypeScript',
677
+ required: ['TypeScript', 'React'],
678
+ optional: ['Next.js', 'Tailwind CSS', 'Node.js', 'Prisma'],
679
+ description: 'Modern TypeScript-first web development stack',
680
+ icon: 'layers',
681
+ },
682
+ {
683
+ id: 'python-ml',
684
+ name: 'Python ML/Data',
685
+ required: ['Python'],
686
+ optional: ['FastAPI', 'Jupyter', 'Pandas', 'TensorFlow', 'PyTorch'],
687
+ description: 'Python-based machine learning and data science',
688
+ icon: 'brain',
689
+ },
690
+ {
691
+ id: 'go-backend',
692
+ name: 'Go Backend',
693
+ required: ['Go'],
694
+ optional: ['Docker', 'PostgreSQL', 'Redis', 'gRPC'],
695
+ description: 'High-performance Go backend services',
696
+ icon: 'server',
697
+ },
698
+ {
699
+ id: 'mobile-rn',
700
+ name: 'React Native Mobile',
701
+ required: ['TypeScript', 'React'],
702
+ optional: ['Expo', 'React Native'],
703
+ description: 'Cross-platform mobile development',
704
+ icon: 'smartphone',
705
+ },
706
+ {
707
+ id: 'devops',
708
+ name: 'DevOps/Platform',
709
+ required: ['Docker'],
710
+ optional: ['Kubernetes', 'Terraform', 'GitHub Actions', 'AWS'],
711
+ description: 'Infrastructure and deployment automation',
712
+ icon: 'cloud',
713
+ },
714
+ ];
715
+ const techNames = new Set(technologies.map(t => t.name));
716
+ for (const archetype of archetypes) {
717
+ // Check if all required technologies are present
718
+ const hasRequired = archetype.required.every(r => techNames.has(r));
719
+ if (!hasRequired)
720
+ continue;
721
+ // Find projects using this pattern
722
+ const matchingProjects = [];
723
+ for (const [project, projectTechs] of projectTechMap) {
724
+ const hasAllRequired = archetype.required.every(r => projectTechs.has(r));
725
+ if (hasAllRequired) {
726
+ matchingProjects.push(project);
727
+ }
728
+ }
729
+ if (matchingProjects.length > 0) {
730
+ // Build list of matched technologies
731
+ const matchedTechs = [
732
+ ...archetype.required,
733
+ ...archetype.optional.filter(o => techNames.has(o)),
734
+ ];
735
+ patterns.push({
736
+ id: archetype.id,
737
+ name: archetype.name,
738
+ description: archetype.description,
739
+ technologies: matchedTechs,
740
+ projectCount: matchingProjects.length,
741
+ projects: matchingProjects,
742
+ icon: archetype.icon,
743
+ });
744
+ }
745
+ }
746
+ // Sort by project count
747
+ patterns.sort((a, b) => b.projectCount - a.projectCount);
748
+ return patterns;
749
+ }
750
+ /**
751
+ * Calculate technology co-occurrence correlations
752
+ */
753
+ calculateCorrelations(technologies, projectTechMap) {
754
+ const correlations = [];
755
+ const significantTechs = technologies.filter(t => t.type !== 'tool' && t.usageCount >= 5).slice(0, 20); // Top 20 non-tool technologies
756
+ for (let i = 0; i < significantTechs.length; i++) {
757
+ for (let j = i + 1; j < significantTechs.length; j++) {
758
+ const tech1 = significantTechs[i];
759
+ const tech2 = significantTechs[j];
760
+ // Count projects with both technologies
761
+ let sharedProjects = 0;
762
+ let tech1Projects = 0;
763
+ let tech2Projects = 0;
764
+ for (const [, projectTechs] of projectTechMap) {
765
+ const hasTech1 = projectTechs.has(tech1.name);
766
+ const hasTech2 = projectTechs.has(tech2.name);
767
+ if (hasTech1)
768
+ tech1Projects++;
769
+ if (hasTech2)
770
+ tech2Projects++;
771
+ if (hasTech1 && hasTech2)
772
+ sharedProjects++;
773
+ }
774
+ // Calculate Jaccard-like correlation
775
+ const unionProjects = tech1Projects + tech2Projects - sharedProjects;
776
+ const correlation = unionProjects > 0
777
+ ? Math.round((sharedProjects / unionProjects) * 100)
778
+ : 0;
779
+ if (sharedProjects >= 2 && correlation >= 30) {
780
+ correlations.push({
781
+ tech1: tech1.name,
782
+ tech2: tech2.name,
783
+ correlation,
784
+ projectsShared: sharedProjects,
785
+ strength: correlation >= 70 ? 'strong' : correlation >= 50 ? 'moderate' : 'weak',
786
+ });
787
+ }
788
+ }
789
+ }
790
+ // Sort by correlation strength
791
+ correlations.sort((a, b) => b.correlation - a.correlation);
792
+ return correlations.slice(0, 15); // Top 15 correlations
793
+ }
794
+ /**
795
+ * Analyze technology evolution trends
796
+ */
797
+ analyzeEvolution(technologies) {
798
+ const evolution = [];
799
+ const now = new Date();
800
+ const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
801
+ const sixtyDaysAgo = new Date(now.getTime() - 60 * 24 * 60 * 60 * 1000);
802
+ for (const tech of technologies.slice(0, 20)) {
803
+ const firstSeenDate = new Date(tech.firstSeen);
804
+ const lastUsedDate = new Date(tech.lastUsed);
805
+ // Estimate recent vs older usage (simplified heuristic)
806
+ const daysSinceLastUse = Math.max(0, (now.getTime() - lastUsedDate.getTime()) / (24 * 60 * 60 * 1000));
807
+ const totalDays = Math.max(1, (lastUsedDate.getTime() - firstSeenDate.getTime()) / (24 * 60 * 60 * 1000));
808
+ // Calculate trend based on recency
809
+ let trend;
810
+ let changePercent;
811
+ if (daysSinceLastUse <= 7) {
812
+ // Used recently - likely rising or stable
813
+ const usageRate = tech.usageCount / Math.max(1, totalDays);
814
+ if (usageRate > 2) {
815
+ trend = 'rising';
816
+ changePercent = Math.min(50, Math.round(usageRate * 10));
817
+ }
818
+ else {
819
+ trend = 'stable';
820
+ changePercent = 0;
821
+ }
822
+ }
823
+ else if (daysSinceLastUse <= 30) {
824
+ trend = 'stable';
825
+ changePercent = 0;
826
+ }
827
+ else {
828
+ trend = 'declining';
829
+ changePercent = -Math.min(30, Math.round(daysSinceLastUse / 2));
830
+ }
831
+ evolution.push({
832
+ tech: tech.name,
833
+ trend,
834
+ changePercent,
835
+ recentUsage: daysSinceLastUse <= 30 ? tech.usageCount : Math.round(tech.usageCount * 0.3),
836
+ totalUsage: tech.usageCount,
837
+ firstSeen: tech.firstSeen,
838
+ monthlyTrend: [], // Would need more granular data
839
+ });
840
+ }
841
+ // Sort: rising first, then stable, then declining
842
+ const trendOrder = { rising: 0, stable: 1, declining: 2 };
843
+ evolution.sort((a, b) => trendOrder[a.trend] - trendOrder[b.trend]);
844
+ return evolution;
845
+ }
846
+ /**
847
+ * Compute project similarity based on shared technologies
848
+ */
849
+ computeProjectSimilarity(projectTechMap) {
850
+ const similarities = [];
851
+ const projects = Array.from(projectTechMap.keys());
852
+ for (let i = 0; i < projects.length; i++) {
853
+ for (let j = i + 1; j < projects.length; j++) {
854
+ const project1 = projects[i];
855
+ const project2 = projects[j];
856
+ const techs1 = projectTechMap.get(project1);
857
+ const techs2 = projectTechMap.get(project2);
858
+ // Calculate Jaccard similarity
859
+ const intersection = new Set([...techs1].filter(t => techs2.has(t)));
860
+ const union = new Set([...techs1, ...techs2]);
861
+ const similarity = Math.round((intersection.size / union.size) * 100);
862
+ if (similarity >= 40 && intersection.size >= 3) {
863
+ similarities.push({
864
+ project1,
865
+ project2,
866
+ project1Name: path.basename(project1),
867
+ project2Name: path.basename(project2),
868
+ similarity,
869
+ sharedTechnologies: Array.from(intersection),
870
+ });
871
+ }
872
+ }
873
+ }
874
+ // Sort by similarity descending
875
+ similarities.sort((a, b) => b.similarity - a.similarity);
876
+ return similarities.slice(0, 10); // Top 10 similar project pairs
877
+ }
878
+ /**
879
+ * Generate human-readable highlights from insights
880
+ */
881
+ generateHighlights(summary, patterns, correlations, evolution) {
882
+ // Shared foundations
883
+ const topPattern = patterns[0];
884
+ const sharedFoundations = topPattern
885
+ ? `${topPattern.projectCount} projects share ${topPattern.technologies.slice(0, 3).join(' + ')}`
886
+ : 'No common patterns detected yet';
887
+ // Dominant pattern
888
+ const dominantPattern = topPattern
889
+ ? `${topPattern.name} is your go-to stack`
890
+ : 'No dominant stack pattern';
891
+ // Rising tech
892
+ const risingTechs = evolution.filter(e => e.trend === 'rising');
893
+ const risingTech = risingTechs.length > 0
894
+ ? `${risingTechs[0].tech} usage trending up`
895
+ : 'No significant trends';
896
+ // Tech diversity score
897
+ const typeSet = new Set(summary.technologies.map(t => t.type));
898
+ const techDiversity = Math.round((typeSet.size / 8) * 100); // 8 possible types
899
+ return {
900
+ sharedFoundations,
901
+ dominantPattern,
902
+ risingTech,
903
+ techDiversity,
904
+ };
905
+ }
906
+ // ============================================================================
907
+ // Private Methods
908
+ // ============================================================================
909
+ async parseSessionFile(filePath) {
910
+ const messages = [];
911
+ try {
912
+ const fs = await import('node:fs/promises');
913
+ const content = await fs.readFile(filePath, 'utf-8');
914
+ const lines = content.split('\n').filter(l => l.trim());
915
+ for (const line of lines) {
916
+ try {
917
+ const parsed = JSON.parse(line);
918
+ messages.push(parsed);
919
+ }
920
+ catch {
921
+ // Skip malformed lines
922
+ }
923
+ }
924
+ }
925
+ catch {
926
+ // File read error
927
+ }
928
+ return messages;
929
+ }
930
+ processMessage(msg, projectPath, techMap) {
931
+ if (msg.type !== 'assistant')
932
+ return null;
933
+ const content = msg.message?.content;
934
+ if (!Array.isArray(content))
935
+ return null;
936
+ for (const block of content) {
937
+ if (block.type !== 'tool_use')
938
+ continue;
939
+ const toolName = block.name;
940
+ const input = block.input;
941
+ if (!input)
942
+ continue;
943
+ // Extract file path from various tool inputs
944
+ const filePath = (input.file_path ||
945
+ input.path ||
946
+ input.target_file ||
947
+ input.file);
948
+ if (!filePath || typeof filePath !== 'string')
949
+ continue;
950
+ const timestamp = msg.timestamp;
951
+ const isModification = ['Edit', 'Write', 'MultiEdit', 'CreateFile'].includes(toolName || '');
952
+ // Detect technology from file path
953
+ const techs = this.detectTechnology(filePath);
954
+ for (const tech of techs) {
955
+ let acc = techMap.get(tech.name);
956
+ if (!acc) {
957
+ acc = {
958
+ name: tech.name,
959
+ type: tech.type,
960
+ extensions: new Set(),
961
+ files: new Set(),
962
+ projects: new Set(),
963
+ usageCount: 0,
964
+ claudeContributions: 0,
965
+ firstSeen: timestamp,
966
+ lastUsed: timestamp,
967
+ };
968
+ techMap.set(tech.name, acc);
969
+ }
970
+ const ext = path.extname(filePath).toLowerCase();
971
+ if (ext)
972
+ acc.extensions.add(ext);
973
+ acc.files.add(filePath);
974
+ acc.projects.add(projectPath);
975
+ acc.usageCount++;
976
+ if (isModification)
977
+ acc.claudeContributions++;
978
+ if (timestamp < acc.firstSeen)
979
+ acc.firstSeen = timestamp;
980
+ if (timestamp > acc.lastUsed)
981
+ acc.lastUsed = timestamp;
982
+ }
983
+ return { filePath, toolName: toolName || '' };
984
+ }
985
+ return null;
986
+ }
987
+ detectTechnology(filePath) {
988
+ const results = [];
989
+ const seen = new Set();
990
+ // Check extension mappings
991
+ const ext = path.extname(filePath).toLowerCase();
992
+ if (ext && EXTENSION_MAPPINGS[ext]) {
993
+ const tech = EXTENSION_MAPPINGS[ext];
994
+ if (!seen.has(tech.name)) {
995
+ results.push(tech);
996
+ seen.add(tech.name);
997
+ }
998
+ }
999
+ // Check path patterns
1000
+ for (const { pattern, tech } of PATH_PATTERNS) {
1001
+ if (pattern.test(filePath) && !seen.has(tech.name)) {
1002
+ results.push(tech);
1003
+ seen.add(tech.name);
1004
+ }
1005
+ }
1006
+ return results;
1007
+ }
1008
+ calculateProficiency(acc) {
1009
+ const score = acc.usageCount + (acc.claudeContributions * 2) + (acc.projects.size * 5);
1010
+ if (score >= 100)
1011
+ return 'expert';
1012
+ if (score >= 50)
1013
+ return 'proficient';
1014
+ if (score >= 20)
1015
+ return 'familiar';
1016
+ return 'learning';
1017
+ }
1018
+ generateRecommendations(technologies) {
1019
+ const recommendations = [];
1020
+ // Check for missing testing frameworks
1021
+ const hasTests = technologies.some(t => t.type === 'testing');
1022
+ if (!hasTests && technologies.length > 5) {
1023
+ recommendations.push('Consider adding automated tests to your projects');
1024
+ }
1025
+ // Check for TypeScript adoption
1026
+ const hasJS = technologies.some(t => t.name === 'JavaScript');
1027
+ const hasTS = technologies.some(t => t.name === 'TypeScript');
1028
+ if (hasJS && !hasTS) {
1029
+ recommendations.push('Consider migrating to TypeScript for better type safety');
1030
+ }
1031
+ // Check for DevOps
1032
+ const hasDevOps = technologies.some(t => t.type === 'devops');
1033
+ if (!hasDevOps && technologies.length > 10) {
1034
+ recommendations.push('Consider adding CI/CD pipelines for automated deployments');
1035
+ }
1036
+ return recommendations;
1037
+ }
1038
+ findMostActiveProject(technologies) {
1039
+ const projectCounts = new Map();
1040
+ for (const tech of technologies) {
1041
+ for (const project of tech.projectsUsed) {
1042
+ projectCounts.set(project, (projectCounts.get(project) || 0) + tech.usageCount);
1043
+ }
1044
+ }
1045
+ let maxProject = '';
1046
+ let maxCount = 0;
1047
+ for (const [project, count] of projectCounts) {
1048
+ if (count > maxCount) {
1049
+ maxProject = project;
1050
+ maxCount = count;
1051
+ }
1052
+ }
1053
+ return path.basename(maxProject) || 'Unknown';
1054
+ }
1055
+ findGrowthAreas(technologies) {
1056
+ return technologies
1057
+ .filter(t => t.proficiency === 'learning' || t.proficiency === 'familiar')
1058
+ .slice(0, 3)
1059
+ .map(t => t.name);
1060
+ }
1061
+ async loadCache() {
1062
+ try {
1063
+ const fs = await import('node:fs/promises');
1064
+ const content = await fs.readFile(this.cacheFile, 'utf-8');
1065
+ const cached = JSON.parse(content);
1066
+ // Check if cache is still valid (less than 24 hours old)
1067
+ const cacheAge = Date.now() - new Date(cached.generatedAt).getTime();
1068
+ const maxAge = 24 * 60 * 60 * 1000; // 24 hours
1069
+ if (cacheAge < maxAge && cached.version === CACHE_VERSION) {
1070
+ return cached;
1071
+ }
1072
+ }
1073
+ catch {
1074
+ // No cache or invalid
1075
+ }
1076
+ return null;
1077
+ }
1078
+ async saveCache(summary) {
1079
+ try {
1080
+ const fs = await import('node:fs/promises');
1081
+ await fs.writeFile(this.cacheFile, JSON.stringify(summary, null, 2));
1082
+ }
1083
+ catch (error) {
1084
+ console.error('[TechStack] Failed to save cache:', error);
1085
+ }
1086
+ }
1087
+ }
1088
+ //# sourceMappingURL=tech-stack.js.map