vibe-coding-mcp 2.6.0 → 2.12.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/README.md +122 -4
- package/dist/__tests__/exportSession.test.d.ts +2 -0
- package/dist/__tests__/exportSession.test.d.ts.map +1 -0
- package/dist/__tests__/exportSession.test.js +180 -0
- package/dist/__tests__/exportSession.test.js.map +1 -0
- package/dist/__tests__/git.test.d.ts +2 -0
- package/dist/__tests__/git.test.d.ts.map +1 -0
- package/dist/__tests__/git.test.js +282 -0
- package/dist/__tests__/git.test.js.map +1 -0
- package/dist/__tests__/projectProfile.test.d.ts +2 -0
- package/dist/__tests__/projectProfile.test.d.ts.map +1 -0
- package/dist/__tests__/projectProfile.test.js +216 -0
- package/dist/__tests__/projectProfile.test.js.map +1 -0
- package/dist/core/profileStorage.d.ts +117 -0
- package/dist/core/profileStorage.d.ts.map +1 -0
- package/dist/core/profileStorage.js +251 -0
- package/dist/core/profileStorage.js.map +1 -0
- package/dist/core/schemas.d.ts +576 -4
- package/dist/core/schemas.d.ts.map +1 -1
- package/dist/core/schemas.js +185 -0
- package/dist/core/schemas.js.map +1 -1
- package/dist/stdio.js +44 -2
- package/dist/stdio.js.map +1 -1
- package/dist/tools/autoTag.d.ts +145 -0
- package/dist/tools/autoTag.d.ts.map +1 -0
- package/dist/tools/autoTag.js +475 -0
- package/dist/tools/autoTag.js.map +1 -0
- package/dist/tools/batch.d.ts +119 -0
- package/dist/tools/batch.d.ts.map +1 -0
- package/dist/tools/batch.js +459 -0
- package/dist/tools/batch.js.map +1 -0
- package/dist/tools/exportSession.d.ts +79 -0
- package/dist/tools/exportSession.d.ts.map +1 -0
- package/dist/tools/exportSession.js +434 -0
- package/dist/tools/exportSession.js.map +1 -0
- package/dist/tools/git.d.ts +226 -0
- package/dist/tools/git.d.ts.map +1 -0
- package/dist/tools/git.js +493 -0
- package/dist/tools/git.js.map +1 -0
- package/dist/tools/projectProfile.d.ts +230 -0
- package/dist/tools/projectProfile.d.ts.map +1 -0
- package/dist/tools/projectProfile.js +367 -0
- package/dist/tools/projectProfile.js.map +1 -0
- package/dist/tools/sessionStats.d.ts +129 -0
- package/dist/tools/sessionStats.d.ts.map +1 -0
- package/dist/tools/sessionStats.js +554 -0
- package/dist/tools/sessionStats.js.map +1 -0
- package/dist/tools/template.d.ts +127 -0
- package/dist/tools/template.d.ts.map +1 -0
- package/dist/tools/template.js +617 -0
- package/dist/tools/template.js.map +1 -0
- package/dist/utils/gitExecutor.d.ts +34 -0
- package/dist/utils/gitExecutor.d.ts.map +1 -0
- package/dist/utils/gitExecutor.js +95 -0
- package/dist/utils/gitExecutor.js.map +1 -0
- package/dist/utils/gitParsers.d.ts +90 -0
- package/dist/utils/gitParsers.d.ts.map +1 -0
- package/dist/utils/gitParsers.js +286 -0
- package/dist/utils/gitParsers.js.map +1 -0
- package/package.json +3 -2
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Statistics Dashboard Tool (v2.9)
|
|
3
|
+
* Provides analytics and insights about coding sessions
|
|
4
|
+
*/
|
|
5
|
+
import type { SessionStatsInput } from '../core/schemas.js';
|
|
6
|
+
export type StatsAction = 'overview' | 'languages' | 'timeline' | 'tags' | 'productivity' | 'trends';
|
|
7
|
+
interface LanguageStat {
|
|
8
|
+
language: string;
|
|
9
|
+
count: number;
|
|
10
|
+
linesOfCode: number;
|
|
11
|
+
percentage: number;
|
|
12
|
+
}
|
|
13
|
+
interface TimelineStat {
|
|
14
|
+
period: string;
|
|
15
|
+
sessionCount: number;
|
|
16
|
+
codeBlockCount: number;
|
|
17
|
+
decisionsCount: number;
|
|
18
|
+
}
|
|
19
|
+
interface TagStat {
|
|
20
|
+
tag: string;
|
|
21
|
+
count: number;
|
|
22
|
+
percentage: number;
|
|
23
|
+
relatedTags: string[];
|
|
24
|
+
}
|
|
25
|
+
interface ProductivityStat {
|
|
26
|
+
averageSessionsPerDay: number;
|
|
27
|
+
averageCodeBlocksPerSession: number;
|
|
28
|
+
averageDecisionsPerSession: number;
|
|
29
|
+
mostProductiveDay: string;
|
|
30
|
+
mostProductiveHour: number;
|
|
31
|
+
totalCodingDays: number;
|
|
32
|
+
}
|
|
33
|
+
interface TrendStat {
|
|
34
|
+
metric: string;
|
|
35
|
+
current: number;
|
|
36
|
+
previous: number;
|
|
37
|
+
change: number;
|
|
38
|
+
changePercent: number;
|
|
39
|
+
trend: 'up' | 'down' | 'stable';
|
|
40
|
+
}
|
|
41
|
+
export interface SessionStatsOutput {
|
|
42
|
+
success: boolean;
|
|
43
|
+
action: StatsAction;
|
|
44
|
+
overview?: {
|
|
45
|
+
totalSessions: number;
|
|
46
|
+
totalCodeBlocks: number;
|
|
47
|
+
totalDesignDecisions: number;
|
|
48
|
+
totalLanguages: number;
|
|
49
|
+
totalTags: number;
|
|
50
|
+
dateRange: {
|
|
51
|
+
from: string;
|
|
52
|
+
to: string;
|
|
53
|
+
};
|
|
54
|
+
averages: {
|
|
55
|
+
codeBlocksPerSession: number;
|
|
56
|
+
decisionsPerSession: number;
|
|
57
|
+
tagsPerSession: number;
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
languages?: LanguageStat[];
|
|
61
|
+
timeline?: TimelineStat[];
|
|
62
|
+
tags?: TagStat[];
|
|
63
|
+
productivity?: ProductivityStat;
|
|
64
|
+
trends?: TrendStat[];
|
|
65
|
+
insights?: string[];
|
|
66
|
+
period?: string;
|
|
67
|
+
generatedAt: string;
|
|
68
|
+
message?: string;
|
|
69
|
+
error?: string;
|
|
70
|
+
}
|
|
71
|
+
export declare function sessionStatsTool(input: SessionStatsInput): Promise<SessionStatsOutput>;
|
|
72
|
+
export declare const sessionStatsSchema: {
|
|
73
|
+
name: string;
|
|
74
|
+
description: string;
|
|
75
|
+
inputSchema: {
|
|
76
|
+
type: string;
|
|
77
|
+
properties: {
|
|
78
|
+
action: {
|
|
79
|
+
type: string;
|
|
80
|
+
enum: string[];
|
|
81
|
+
description: string;
|
|
82
|
+
};
|
|
83
|
+
since: {
|
|
84
|
+
type: string;
|
|
85
|
+
description: string;
|
|
86
|
+
};
|
|
87
|
+
until: {
|
|
88
|
+
type: string;
|
|
89
|
+
description: string;
|
|
90
|
+
};
|
|
91
|
+
period: {
|
|
92
|
+
type: string;
|
|
93
|
+
enum: string[];
|
|
94
|
+
description: string;
|
|
95
|
+
};
|
|
96
|
+
tags: {
|
|
97
|
+
type: string;
|
|
98
|
+
items: {
|
|
99
|
+
type: string;
|
|
100
|
+
};
|
|
101
|
+
description: string;
|
|
102
|
+
};
|
|
103
|
+
languages: {
|
|
104
|
+
type: string;
|
|
105
|
+
items: {
|
|
106
|
+
type: string;
|
|
107
|
+
};
|
|
108
|
+
description: string;
|
|
109
|
+
};
|
|
110
|
+
format: {
|
|
111
|
+
type: string;
|
|
112
|
+
enum: string[];
|
|
113
|
+
description: string;
|
|
114
|
+
};
|
|
115
|
+
includeInsights: {
|
|
116
|
+
type: string;
|
|
117
|
+
description: string;
|
|
118
|
+
};
|
|
119
|
+
compareWith: {
|
|
120
|
+
type: string;
|
|
121
|
+
enum: string[];
|
|
122
|
+
description: string;
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
required: string[];
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
export {};
|
|
129
|
+
//# sourceMappingURL=sessionStats.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionStats.d.ts","sourceRoot":"","sources":["../../src/tools/sessionStats.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAK5D,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,WAAW,GAAG,UAAU,GAAG,MAAM,GAAG,cAAc,GAAG,QAAQ,CAAC;AAErG,UAAU,YAAY;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,YAAY;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,UAAU,OAAO;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,UAAU,gBAAgB;IACxB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,2BAA2B,EAAE,MAAM,CAAC;IACpC,0BAA0B,EAAE,MAAM,CAAC;IACnC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,UAAU,SAAS;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,IAAI,GAAG,MAAM,GAAG,QAAQ,CAAC;CACjC;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,WAAW,CAAC;IAGpB,QAAQ,CAAC,EAAE;QACT,aAAa,EAAE,MAAM,CAAC;QACtB,eAAe,EAAE,MAAM,CAAC;QACxB,oBAAoB,EAAE,MAAM,CAAC;QAC7B,cAAc,EAAE,MAAM,CAAC;QACvB,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,EAAE,EAAE,MAAM,CAAA;SAAE,CAAC;QACxC,QAAQ,EAAE;YACR,oBAAoB,EAAE,MAAM,CAAC;YAC7B,mBAAmB,EAAE,MAAM,CAAC;YAC5B,cAAc,EAAE,MAAM,CAAC;SACxB,CAAC;KACH,CAAC;IAEF,SAAS,CAAC,EAAE,YAAY,EAAE,CAAC;IAC3B,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAC;IAC1B,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC;IACjB,YAAY,CAAC,EAAE,gBAAgB,CAAC;IAChC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC;IAGrB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IAGpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA2HD,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAiZ5F;AA0DD,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmD9B,CAAC"}
|
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Statistics Dashboard Tool (v2.9)
|
|
3
|
+
* Provides analytics and insights about coding sessions
|
|
4
|
+
*/
|
|
5
|
+
import { createToolLogger } from '../core/logger.js';
|
|
6
|
+
import { listSessions } from '../core/sessionStorage.js';
|
|
7
|
+
const logger = createToolLogger('sessionStats');
|
|
8
|
+
// Helper functions
|
|
9
|
+
function parseDate(dateStr) {
|
|
10
|
+
// Support relative dates like "1 week ago", "last month"
|
|
11
|
+
const now = new Date();
|
|
12
|
+
const lowerStr = dateStr.toLowerCase();
|
|
13
|
+
if (lowerStr.includes('ago')) {
|
|
14
|
+
const match = lowerStr.match(/(\d+)\s*(day|week|month|year)s?\s*ago/);
|
|
15
|
+
if (match) {
|
|
16
|
+
const [, num, unit] = match;
|
|
17
|
+
const amount = parseInt(num, 10);
|
|
18
|
+
switch (unit) {
|
|
19
|
+
case 'day':
|
|
20
|
+
now.setDate(now.getDate() - amount);
|
|
21
|
+
break;
|
|
22
|
+
case 'week':
|
|
23
|
+
now.setDate(now.getDate() - amount * 7);
|
|
24
|
+
break;
|
|
25
|
+
case 'month':
|
|
26
|
+
now.setMonth(now.getMonth() - amount);
|
|
27
|
+
break;
|
|
28
|
+
case 'year':
|
|
29
|
+
now.setFullYear(now.getFullYear() - amount);
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
return now;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// Try parsing as ISO date
|
|
36
|
+
return new Date(dateStr);
|
|
37
|
+
}
|
|
38
|
+
function getPeriodStart(period) {
|
|
39
|
+
const now = new Date();
|
|
40
|
+
switch (period) {
|
|
41
|
+
case 'day':
|
|
42
|
+
now.setHours(0, 0, 0, 0);
|
|
43
|
+
return now;
|
|
44
|
+
case 'week':
|
|
45
|
+
now.setDate(now.getDate() - now.getDay());
|
|
46
|
+
now.setHours(0, 0, 0, 0);
|
|
47
|
+
return now;
|
|
48
|
+
case 'month':
|
|
49
|
+
now.setDate(1);
|
|
50
|
+
now.setHours(0, 0, 0, 0);
|
|
51
|
+
return now;
|
|
52
|
+
case 'year':
|
|
53
|
+
now.setMonth(0, 1);
|
|
54
|
+
now.setHours(0, 0, 0, 0);
|
|
55
|
+
return now;
|
|
56
|
+
default:
|
|
57
|
+
return new Date(0); // All time
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function formatPeriodLabel(date, period) {
|
|
61
|
+
switch (period) {
|
|
62
|
+
case 'day':
|
|
63
|
+
return date.toISOString().split('T')[0];
|
|
64
|
+
case 'week':
|
|
65
|
+
const weekStart = new Date(date);
|
|
66
|
+
weekStart.setDate(date.getDate() - date.getDay());
|
|
67
|
+
return `Week of ${weekStart.toISOString().split('T')[0]}`;
|
|
68
|
+
case 'month':
|
|
69
|
+
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
|
|
70
|
+
case 'year':
|
|
71
|
+
return String(date.getFullYear());
|
|
72
|
+
default:
|
|
73
|
+
return date.toISOString().split('T')[0];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function generateInsights(sessions, languages, tags, productivity) {
|
|
77
|
+
const insights = [];
|
|
78
|
+
// Language insights
|
|
79
|
+
if (languages.length > 0) {
|
|
80
|
+
const topLang = languages[0];
|
|
81
|
+
insights.push(`Your primary language is ${topLang.language} (${topLang.percentage.toFixed(1)}% of code blocks)`);
|
|
82
|
+
if (languages.length > 3) {
|
|
83
|
+
insights.push(`You work with ${languages.length} different languages, showing versatility`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Productivity insights
|
|
87
|
+
if (productivity.mostProductiveDay) {
|
|
88
|
+
insights.push(`You're most productive on ${productivity.mostProductiveDay}s`);
|
|
89
|
+
}
|
|
90
|
+
if (productivity.mostProductiveHour >= 0) {
|
|
91
|
+
const hour = productivity.mostProductiveHour;
|
|
92
|
+
const period = hour < 12 ? 'morning' : hour < 17 ? 'afternoon' : 'evening';
|
|
93
|
+
insights.push(`Peak coding time is in the ${period} around ${hour}:00`);
|
|
94
|
+
}
|
|
95
|
+
// Tag insights
|
|
96
|
+
if (tags.length > 0) {
|
|
97
|
+
const topTags = tags.slice(0, 3).map(t => t.tag);
|
|
98
|
+
insights.push(`Most common focus areas: ${topTags.join(', ')}`);
|
|
99
|
+
}
|
|
100
|
+
// Session patterns
|
|
101
|
+
if (productivity.averageCodeBlocksPerSession > 5) {
|
|
102
|
+
insights.push('Your sessions are feature-rich with high code output');
|
|
103
|
+
}
|
|
104
|
+
if (productivity.averageDecisionsPerSession > 2) {
|
|
105
|
+
insights.push('You document design decisions well - great for maintainability');
|
|
106
|
+
}
|
|
107
|
+
return insights;
|
|
108
|
+
}
|
|
109
|
+
// Main tool function
|
|
110
|
+
export async function sessionStatsTool(input) {
|
|
111
|
+
const { action, since, until, period = 'all', tags: filterTags, languages: filterLanguages, format = 'summary', includeInsights = true, compareWith, } = input;
|
|
112
|
+
logger.info('Session stats requested', { action, period });
|
|
113
|
+
try {
|
|
114
|
+
// Get all sessions
|
|
115
|
+
const { sessions } = await listSessions({ limit: 10000 });
|
|
116
|
+
// Filter by date range
|
|
117
|
+
let filteredSessions = sessions;
|
|
118
|
+
const periodStart = since ? parseDate(since) : getPeriodStart(period);
|
|
119
|
+
const periodEnd = until ? parseDate(until) : new Date();
|
|
120
|
+
filteredSessions = sessions.filter((s) => {
|
|
121
|
+
const sessionDate = new Date(s.createdAt);
|
|
122
|
+
return sessionDate >= periodStart && sessionDate <= periodEnd;
|
|
123
|
+
});
|
|
124
|
+
// Filter by tags if specified
|
|
125
|
+
if (filterTags && filterTags.length > 0) {
|
|
126
|
+
filteredSessions = filteredSessions.filter((s) => s.tags?.some((t) => filterTags.includes(t)));
|
|
127
|
+
}
|
|
128
|
+
switch (action) {
|
|
129
|
+
case 'overview': {
|
|
130
|
+
// Collect all code blocks and decisions
|
|
131
|
+
let totalCodeBlocks = 0;
|
|
132
|
+
let totalDecisions = 0;
|
|
133
|
+
const languageSet = new Set();
|
|
134
|
+
const tagSet = new Set();
|
|
135
|
+
filteredSessions.forEach((s) => {
|
|
136
|
+
if (s.codeContexts) {
|
|
137
|
+
s.codeContexts.forEach((ctx) => {
|
|
138
|
+
if (ctx.codeBlocks) {
|
|
139
|
+
totalCodeBlocks += ctx.codeBlocks.length;
|
|
140
|
+
ctx.codeBlocks.forEach((cb) => {
|
|
141
|
+
if (cb.language)
|
|
142
|
+
languageSet.add(cb.language);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
if (s.designDecisions) {
|
|
148
|
+
totalDecisions += s.designDecisions.length;
|
|
149
|
+
}
|
|
150
|
+
if (s.tags) {
|
|
151
|
+
s.tags.forEach((t) => tagSet.add(t));
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
const sessionCount = filteredSessions.length;
|
|
155
|
+
return {
|
|
156
|
+
success: true,
|
|
157
|
+
action,
|
|
158
|
+
overview: {
|
|
159
|
+
totalSessions: sessionCount,
|
|
160
|
+
totalCodeBlocks,
|
|
161
|
+
totalDesignDecisions: totalDecisions,
|
|
162
|
+
totalLanguages: languageSet.size,
|
|
163
|
+
totalTags: tagSet.size,
|
|
164
|
+
dateRange: {
|
|
165
|
+
from: periodStart.toISOString(),
|
|
166
|
+
to: periodEnd.toISOString(),
|
|
167
|
+
},
|
|
168
|
+
averages: {
|
|
169
|
+
codeBlocksPerSession: sessionCount > 0 ? totalCodeBlocks / sessionCount : 0,
|
|
170
|
+
decisionsPerSession: sessionCount > 0 ? totalDecisions / sessionCount : 0,
|
|
171
|
+
tagsPerSession: sessionCount > 0 ? tagSet.size / sessionCount : 0,
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
period,
|
|
175
|
+
generatedAt: new Date().toISOString(),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
case 'languages': {
|
|
179
|
+
const langMap = new Map();
|
|
180
|
+
filteredSessions.forEach((s) => {
|
|
181
|
+
if (s.codeContexts) {
|
|
182
|
+
s.codeContexts.forEach((ctx) => {
|
|
183
|
+
if (ctx.codeBlocks) {
|
|
184
|
+
ctx.codeBlocks.forEach((cb) => {
|
|
185
|
+
const lang = cb.language || 'unknown';
|
|
186
|
+
const existing = langMap.get(lang) || { count: 0, lines: 0 };
|
|
187
|
+
existing.count++;
|
|
188
|
+
existing.lines += (cb.code?.split('\n').length || 0);
|
|
189
|
+
langMap.set(lang, existing);
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
const totalBlocks = Array.from(langMap.values()).reduce((sum, v) => sum + v.count, 0);
|
|
196
|
+
const languages = Array.from(langMap.entries())
|
|
197
|
+
.map(([language, stats]) => ({
|
|
198
|
+
language,
|
|
199
|
+
count: stats.count,
|
|
200
|
+
linesOfCode: stats.lines,
|
|
201
|
+
percentage: totalBlocks > 0 ? (stats.count / totalBlocks) * 100 : 0,
|
|
202
|
+
}))
|
|
203
|
+
.filter(l => !filterLanguages || filterLanguages.includes(l.language))
|
|
204
|
+
.sort((a, b) => b.count - a.count);
|
|
205
|
+
const insights = includeInsights ? [
|
|
206
|
+
languages.length > 0 ? `Top language: ${languages[0].language} with ${languages[0].count} code blocks` : 'No code blocks found',
|
|
207
|
+
`Total lines of code: ${languages.reduce((sum, l) => sum + l.linesOfCode, 0)}`,
|
|
208
|
+
] : undefined;
|
|
209
|
+
return {
|
|
210
|
+
success: true,
|
|
211
|
+
action,
|
|
212
|
+
languages,
|
|
213
|
+
insights,
|
|
214
|
+
period,
|
|
215
|
+
generatedAt: new Date().toISOString(),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
case 'timeline': {
|
|
219
|
+
const timeMap = new Map();
|
|
220
|
+
filteredSessions.forEach((s) => {
|
|
221
|
+
const date = new Date(s.createdAt);
|
|
222
|
+
const label = formatPeriodLabel(date, period === 'all' ? 'month' : period);
|
|
223
|
+
const existing = timeMap.get(label) || { sessions: 0, codeBlocks: 0, decisions: 0 };
|
|
224
|
+
existing.sessions++;
|
|
225
|
+
if (s.codeContexts) {
|
|
226
|
+
s.codeContexts.forEach((ctx) => {
|
|
227
|
+
existing.codeBlocks += ctx.codeBlocks?.length || 0;
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
existing.decisions += s.designDecisions?.length || 0;
|
|
231
|
+
timeMap.set(label, existing);
|
|
232
|
+
});
|
|
233
|
+
const timeline = Array.from(timeMap.entries())
|
|
234
|
+
.map(([period, stats]) => ({
|
|
235
|
+
period,
|
|
236
|
+
sessionCount: stats.sessions,
|
|
237
|
+
codeBlockCount: stats.codeBlocks,
|
|
238
|
+
decisionsCount: stats.decisions,
|
|
239
|
+
}))
|
|
240
|
+
.sort((a, b) => a.period.localeCompare(b.period));
|
|
241
|
+
return {
|
|
242
|
+
success: true,
|
|
243
|
+
action,
|
|
244
|
+
timeline,
|
|
245
|
+
period,
|
|
246
|
+
generatedAt: new Date().toISOString(),
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
case 'tags': {
|
|
250
|
+
const tagMap = new Map();
|
|
251
|
+
const tagCoOccurrence = new Map();
|
|
252
|
+
filteredSessions.forEach((s) => {
|
|
253
|
+
if (s.tags) {
|
|
254
|
+
s.tags.forEach((tag) => {
|
|
255
|
+
const existing = tagMap.get(tag) || { count: 0, sessions: new Set() };
|
|
256
|
+
existing.count++;
|
|
257
|
+
existing.sessions.add(s.id);
|
|
258
|
+
tagMap.set(tag, existing);
|
|
259
|
+
// Track co-occurrence
|
|
260
|
+
s.tags.forEach((otherTag) => {
|
|
261
|
+
if (otherTag !== tag) {
|
|
262
|
+
if (!tagCoOccurrence.has(tag)) {
|
|
263
|
+
tagCoOccurrence.set(tag, new Map());
|
|
264
|
+
}
|
|
265
|
+
const coMap = tagCoOccurrence.get(tag);
|
|
266
|
+
coMap.set(otherTag, (coMap.get(otherTag) || 0) + 1);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
const totalTags = Array.from(tagMap.values()).reduce((sum, v) => sum + v.count, 0);
|
|
273
|
+
const tags = Array.from(tagMap.entries())
|
|
274
|
+
.map(([tag, stats]) => {
|
|
275
|
+
const coMap = tagCoOccurrence.get(tag);
|
|
276
|
+
const relatedTags = coMap
|
|
277
|
+
? Array.from(coMap.entries())
|
|
278
|
+
.sort((a, b) => b[1] - a[1])
|
|
279
|
+
.slice(0, 3)
|
|
280
|
+
.map(([t]) => t)
|
|
281
|
+
: [];
|
|
282
|
+
return {
|
|
283
|
+
tag,
|
|
284
|
+
count: stats.count,
|
|
285
|
+
percentage: totalTags > 0 ? (stats.count / totalTags) * 100 : 0,
|
|
286
|
+
relatedTags,
|
|
287
|
+
};
|
|
288
|
+
})
|
|
289
|
+
.sort((a, b) => b.count - a.count);
|
|
290
|
+
return {
|
|
291
|
+
success: true,
|
|
292
|
+
action,
|
|
293
|
+
tags,
|
|
294
|
+
period,
|
|
295
|
+
generatedAt: new Date().toISOString(),
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
case 'productivity': {
|
|
299
|
+
const dayCount = new Map();
|
|
300
|
+
const hourCount = new Map();
|
|
301
|
+
const uniqueDays = new Set();
|
|
302
|
+
let totalCodeBlocks = 0;
|
|
303
|
+
let totalDecisions = 0;
|
|
304
|
+
filteredSessions.forEach((s) => {
|
|
305
|
+
const date = new Date(s.createdAt);
|
|
306
|
+
const dayName = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][date.getDay()];
|
|
307
|
+
const hour = date.getHours();
|
|
308
|
+
const dayKey = date.toISOString().split('T')[0];
|
|
309
|
+
dayCount.set(dayName, (dayCount.get(dayName) || 0) + 1);
|
|
310
|
+
hourCount.set(hour, (hourCount.get(hour) || 0) + 1);
|
|
311
|
+
uniqueDays.add(dayKey);
|
|
312
|
+
if (s.codeContexts) {
|
|
313
|
+
s.codeContexts.forEach((ctx) => {
|
|
314
|
+
totalCodeBlocks += ctx.codeBlocks?.length || 0;
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
totalDecisions += s.designDecisions?.length || 0;
|
|
318
|
+
});
|
|
319
|
+
const sessionCount = filteredSessions.length;
|
|
320
|
+
const mostProductiveDay = Array.from(dayCount.entries())
|
|
321
|
+
.sort((a, b) => b[1] - a[1])[0]?.[0] || 'N/A';
|
|
322
|
+
const mostProductiveHour = Array.from(hourCount.entries())
|
|
323
|
+
.sort((a, b) => b[1] - a[1])[0]?.[0] ?? -1;
|
|
324
|
+
const productivity = {
|
|
325
|
+
averageSessionsPerDay: uniqueDays.size > 0 ? sessionCount / uniqueDays.size : 0,
|
|
326
|
+
averageCodeBlocksPerSession: sessionCount > 0 ? totalCodeBlocks / sessionCount : 0,
|
|
327
|
+
averageDecisionsPerSession: sessionCount > 0 ? totalDecisions / sessionCount : 0,
|
|
328
|
+
mostProductiveDay,
|
|
329
|
+
mostProductiveHour,
|
|
330
|
+
totalCodingDays: uniqueDays.size,
|
|
331
|
+
};
|
|
332
|
+
// Generate insights
|
|
333
|
+
const languageStats = await getLanguageStats(filteredSessions);
|
|
334
|
+
const tagStats = await getTagStats(filteredSessions);
|
|
335
|
+
const insights = includeInsights
|
|
336
|
+
? generateInsights(filteredSessions, languageStats, tagStats, productivity)
|
|
337
|
+
: undefined;
|
|
338
|
+
return {
|
|
339
|
+
success: true,
|
|
340
|
+
action,
|
|
341
|
+
productivity,
|
|
342
|
+
insights,
|
|
343
|
+
period,
|
|
344
|
+
generatedAt: new Date().toISOString(),
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
case 'trends': {
|
|
348
|
+
// Compare current period with previous period
|
|
349
|
+
const now = new Date();
|
|
350
|
+
let currentStart;
|
|
351
|
+
let previousStart;
|
|
352
|
+
let previousEnd;
|
|
353
|
+
switch (period) {
|
|
354
|
+
case 'week':
|
|
355
|
+
currentStart = new Date(now);
|
|
356
|
+
currentStart.setDate(now.getDate() - 7);
|
|
357
|
+
previousStart = new Date(currentStart);
|
|
358
|
+
previousStart.setDate(previousStart.getDate() - 7);
|
|
359
|
+
previousEnd = currentStart;
|
|
360
|
+
break;
|
|
361
|
+
case 'month':
|
|
362
|
+
currentStart = new Date(now);
|
|
363
|
+
currentStart.setMonth(now.getMonth() - 1);
|
|
364
|
+
previousStart = new Date(currentStart);
|
|
365
|
+
previousStart.setMonth(previousStart.getMonth() - 1);
|
|
366
|
+
previousEnd = currentStart;
|
|
367
|
+
break;
|
|
368
|
+
default:
|
|
369
|
+
currentStart = new Date(now);
|
|
370
|
+
currentStart.setDate(now.getDate() - 30);
|
|
371
|
+
previousStart = new Date(currentStart);
|
|
372
|
+
previousStart.setDate(previousStart.getDate() - 30);
|
|
373
|
+
previousEnd = currentStart;
|
|
374
|
+
}
|
|
375
|
+
const currentSessions = sessions.filter((s) => {
|
|
376
|
+
const d = new Date(s.createdAt);
|
|
377
|
+
return d >= currentStart && d <= now;
|
|
378
|
+
});
|
|
379
|
+
const previousSessions = sessions.filter((s) => {
|
|
380
|
+
const d = new Date(s.createdAt);
|
|
381
|
+
return d >= previousStart && d < previousEnd;
|
|
382
|
+
});
|
|
383
|
+
const calculateMetrics = (sessionList) => {
|
|
384
|
+
let codeBlocks = 0;
|
|
385
|
+
let decisions = 0;
|
|
386
|
+
sessionList.forEach((s) => {
|
|
387
|
+
if (s.codeContexts) {
|
|
388
|
+
s.codeContexts.forEach((ctx) => {
|
|
389
|
+
codeBlocks += ctx.codeBlocks?.length || 0;
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
decisions += s.designDecisions?.length || 0;
|
|
393
|
+
});
|
|
394
|
+
return { sessions: sessionList.length, codeBlocks, decisions };
|
|
395
|
+
};
|
|
396
|
+
const current = calculateMetrics(currentSessions);
|
|
397
|
+
const previous = calculateMetrics(previousSessions);
|
|
398
|
+
const createTrend = (metric, curr, prev) => {
|
|
399
|
+
const change = curr - prev;
|
|
400
|
+
const changePercent = prev > 0 ? ((curr - prev) / prev) * 100 : curr > 0 ? 100 : 0;
|
|
401
|
+
return {
|
|
402
|
+
metric,
|
|
403
|
+
current: curr,
|
|
404
|
+
previous: prev,
|
|
405
|
+
change,
|
|
406
|
+
changePercent,
|
|
407
|
+
trend: change > 0 ? 'up' : change < 0 ? 'down' : 'stable',
|
|
408
|
+
};
|
|
409
|
+
};
|
|
410
|
+
const trends = [
|
|
411
|
+
createTrend('Sessions', current.sessions, previous.sessions),
|
|
412
|
+
createTrend('Code Blocks', current.codeBlocks, previous.codeBlocks),
|
|
413
|
+
createTrend('Design Decisions', current.decisions, previous.decisions),
|
|
414
|
+
];
|
|
415
|
+
const insights = includeInsights
|
|
416
|
+
? trends.map((t) => {
|
|
417
|
+
if (t.trend === 'up') {
|
|
418
|
+
return `${t.metric} increased by ${t.changePercent.toFixed(1)}%`;
|
|
419
|
+
}
|
|
420
|
+
else if (t.trend === 'down') {
|
|
421
|
+
return `${t.metric} decreased by ${Math.abs(t.changePercent).toFixed(1)}%`;
|
|
422
|
+
}
|
|
423
|
+
return `${t.metric} remained stable`;
|
|
424
|
+
})
|
|
425
|
+
: undefined;
|
|
426
|
+
return {
|
|
427
|
+
success: true,
|
|
428
|
+
action,
|
|
429
|
+
trends,
|
|
430
|
+
insights,
|
|
431
|
+
period,
|
|
432
|
+
generatedAt: new Date().toISOString(),
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
default:
|
|
436
|
+
return {
|
|
437
|
+
success: false,
|
|
438
|
+
action,
|
|
439
|
+
error: `Unknown action: ${action}`,
|
|
440
|
+
generatedAt: new Date().toISOString(),
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
catch (error) {
|
|
445
|
+
logger.error('Session stats failed', error);
|
|
446
|
+
return {
|
|
447
|
+
success: false,
|
|
448
|
+
action,
|
|
449
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
450
|
+
generatedAt: new Date().toISOString(),
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
// Helper functions for generating insights
|
|
455
|
+
async function getLanguageStats(sessions) {
|
|
456
|
+
const langMap = new Map();
|
|
457
|
+
sessions.forEach((s) => {
|
|
458
|
+
if (s.codeContexts) {
|
|
459
|
+
s.codeContexts.forEach((ctx) => {
|
|
460
|
+
if (ctx.codeBlocks) {
|
|
461
|
+
ctx.codeBlocks.forEach((cb) => {
|
|
462
|
+
const lang = cb.language || 'unknown';
|
|
463
|
+
const existing = langMap.get(lang) || { count: 0, lines: 0 };
|
|
464
|
+
existing.count++;
|
|
465
|
+
existing.lines += (cb.code?.split('\n').length || 0);
|
|
466
|
+
langMap.set(lang, existing);
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
const totalBlocks = Array.from(langMap.values()).reduce((sum, v) => sum + v.count, 0);
|
|
473
|
+
return Array.from(langMap.entries())
|
|
474
|
+
.map(([language, stats]) => ({
|
|
475
|
+
language,
|
|
476
|
+
count: stats.count,
|
|
477
|
+
linesOfCode: stats.lines,
|
|
478
|
+
percentage: totalBlocks > 0 ? (stats.count / totalBlocks) * 100 : 0,
|
|
479
|
+
}))
|
|
480
|
+
.sort((a, b) => b.count - a.count);
|
|
481
|
+
}
|
|
482
|
+
async function getTagStats(sessions) {
|
|
483
|
+
const tagMap = new Map();
|
|
484
|
+
sessions.forEach((s) => {
|
|
485
|
+
if (s.tags) {
|
|
486
|
+
s.tags.forEach((tag) => {
|
|
487
|
+
tagMap.set(tag, (tagMap.get(tag) || 0) + 1);
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
const total = Array.from(tagMap.values()).reduce((sum, v) => sum + v, 0);
|
|
492
|
+
return Array.from(tagMap.entries())
|
|
493
|
+
.map(([tag, count]) => ({
|
|
494
|
+
tag,
|
|
495
|
+
count,
|
|
496
|
+
percentage: total > 0 ? (count / total) * 100 : 0,
|
|
497
|
+
relatedTags: [],
|
|
498
|
+
}))
|
|
499
|
+
.sort((a, b) => b.count - a.count);
|
|
500
|
+
}
|
|
501
|
+
// Schema for MCP registration
|
|
502
|
+
export const sessionStatsSchema = {
|
|
503
|
+
name: 'muse_session_stats',
|
|
504
|
+
description: 'Provides analytics and insights about coding sessions. Actions: overview (summary stats), languages (language breakdown), timeline (activity over time), tags (tag analysis), productivity (work patterns), trends (compare periods).',
|
|
505
|
+
inputSchema: {
|
|
506
|
+
type: 'object',
|
|
507
|
+
properties: {
|
|
508
|
+
action: {
|
|
509
|
+
type: 'string',
|
|
510
|
+
enum: ['overview', 'languages', 'timeline', 'tags', 'productivity', 'trends'],
|
|
511
|
+
description: 'Type of statistics to retrieve',
|
|
512
|
+
},
|
|
513
|
+
since: {
|
|
514
|
+
type: 'string',
|
|
515
|
+
description: 'Start date for filtering (ISO date or relative like "1 week ago")',
|
|
516
|
+
},
|
|
517
|
+
until: {
|
|
518
|
+
type: 'string',
|
|
519
|
+
description: 'End date for filtering',
|
|
520
|
+
},
|
|
521
|
+
period: {
|
|
522
|
+
type: 'string',
|
|
523
|
+
enum: ['day', 'week', 'month', 'year', 'all'],
|
|
524
|
+
description: 'Time period for grouping (default: all)',
|
|
525
|
+
},
|
|
526
|
+
tags: {
|
|
527
|
+
type: 'array',
|
|
528
|
+
items: { type: 'string' },
|
|
529
|
+
description: 'Filter by specific tags',
|
|
530
|
+
},
|
|
531
|
+
languages: {
|
|
532
|
+
type: 'array',
|
|
533
|
+
items: { type: 'string' },
|
|
534
|
+
description: 'Filter by specific languages',
|
|
535
|
+
},
|
|
536
|
+
format: {
|
|
537
|
+
type: 'string',
|
|
538
|
+
enum: ['summary', 'detailed', 'chart'],
|
|
539
|
+
description: 'Output format (default: summary)',
|
|
540
|
+
},
|
|
541
|
+
includeInsights: {
|
|
542
|
+
type: 'boolean',
|
|
543
|
+
description: 'Include AI-generated insights (default: true)',
|
|
544
|
+
},
|
|
545
|
+
compareWith: {
|
|
546
|
+
type: 'string',
|
|
547
|
+
enum: ['previous', 'average'],
|
|
548
|
+
description: 'Compare with previous period or average',
|
|
549
|
+
},
|
|
550
|
+
},
|
|
551
|
+
required: ['action'],
|
|
552
|
+
},
|
|
553
|
+
};
|
|
554
|
+
//# sourceMappingURL=sessionStats.js.map
|