nodebench-mcp 2.32.0 → 2.33.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/dist/tools/founderTrackingTools.d.ts +9 -0
- package/dist/tools/founderTrackingTools.js +644 -0
- package/dist/tools/founderTrackingTools.js.map +1 -0
- package/dist/tools/toolRegistry.js +99 -0
- package/dist/tools/toolRegistry.js.map +1 -1
- package/dist/toolsetRegistry.js +2 -1
- package/dist/toolsetRegistry.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* founderTrackingTools — Temporal action tracking across sessions, days, weeks,
|
|
3
|
+
* months, quarters, and years.
|
|
4
|
+
*
|
|
5
|
+
* Every significant action records before/after state and reasoning.
|
|
6
|
+
* Aggregation queries run in SQL for efficiency.
|
|
7
|
+
*/
|
|
8
|
+
import type { McpTool } from "../types.js";
|
|
9
|
+
export declare const founderTrackingTools: McpTool[];
|
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* founderTrackingTools — Temporal action tracking across sessions, days, weeks,
|
|
3
|
+
* months, quarters, and years.
|
|
4
|
+
*
|
|
5
|
+
* Every significant action records before/after state and reasoning.
|
|
6
|
+
* Aggregation queries run in SQL for efficiency.
|
|
7
|
+
*/
|
|
8
|
+
import { getDb, genId } from "../db.js";
|
|
9
|
+
/* ------------------------------------------------------------------ */
|
|
10
|
+
/* Module-level session ID (unique per process) */
|
|
11
|
+
/* ------------------------------------------------------------------ */
|
|
12
|
+
const SESSION_ID = `sess_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
|
|
13
|
+
/* ------------------------------------------------------------------ */
|
|
14
|
+
/* Schema bootstrap (idempotent — runs on first getDb() call per */
|
|
15
|
+
/* process) */
|
|
16
|
+
/* ------------------------------------------------------------------ */
|
|
17
|
+
let _schemaReady = false;
|
|
18
|
+
function ensureSchema() {
|
|
19
|
+
if (_schemaReady)
|
|
20
|
+
return;
|
|
21
|
+
const db = getDb();
|
|
22
|
+
db.exec(`
|
|
23
|
+
CREATE TABLE IF NOT EXISTS tracking_actions (
|
|
24
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
25
|
+
actionId TEXT UNIQUE NOT NULL,
|
|
26
|
+
sessionId TEXT NOT NULL,
|
|
27
|
+
timestamp TEXT NOT NULL,
|
|
28
|
+
action TEXT NOT NULL,
|
|
29
|
+
category TEXT NOT NULL,
|
|
30
|
+
beforeState TEXT,
|
|
31
|
+
afterState TEXT,
|
|
32
|
+
reasoning TEXT,
|
|
33
|
+
filesChanged TEXT,
|
|
34
|
+
impactLevel TEXT NOT NULL,
|
|
35
|
+
dayOfWeek TEXT NOT NULL,
|
|
36
|
+
weekNumber INTEGER NOT NULL,
|
|
37
|
+
month TEXT NOT NULL,
|
|
38
|
+
quarter TEXT NOT NULL,
|
|
39
|
+
year INTEGER NOT NULL
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
CREATE INDEX IF NOT EXISTS idx_tracking_actions_session ON tracking_actions(sessionId);
|
|
43
|
+
CREATE INDEX IF NOT EXISTS idx_tracking_actions_timestamp ON tracking_actions(timestamp);
|
|
44
|
+
CREATE INDEX IF NOT EXISTS idx_tracking_actions_category ON tracking_actions(category);
|
|
45
|
+
CREATE INDEX IF NOT EXISTS idx_tracking_actions_year ON tracking_actions(year);
|
|
46
|
+
CREATE INDEX IF NOT EXISTS idx_tracking_actions_month ON tracking_actions(month);
|
|
47
|
+
CREATE INDEX IF NOT EXISTS idx_tracking_actions_quarter ON tracking_actions(quarter);
|
|
48
|
+
|
|
49
|
+
CREATE TABLE IF NOT EXISTS tracking_milestones (
|
|
50
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
51
|
+
milestoneId TEXT UNIQUE NOT NULL,
|
|
52
|
+
sessionId TEXT NOT NULL,
|
|
53
|
+
timestamp TEXT NOT NULL,
|
|
54
|
+
title TEXT NOT NULL,
|
|
55
|
+
description TEXT NOT NULL,
|
|
56
|
+
category TEXT NOT NULL,
|
|
57
|
+
evidence TEXT,
|
|
58
|
+
metrics TEXT,
|
|
59
|
+
dayOfWeek TEXT NOT NULL,
|
|
60
|
+
weekNumber INTEGER NOT NULL,
|
|
61
|
+
month TEXT NOT NULL,
|
|
62
|
+
quarter TEXT NOT NULL,
|
|
63
|
+
year INTEGER NOT NULL
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
CREATE INDEX IF NOT EXISTS idx_tracking_milestones_session ON tracking_milestones(sessionId);
|
|
67
|
+
CREATE INDEX IF NOT EXISTS idx_tracking_milestones_timestamp ON tracking_milestones(timestamp);
|
|
68
|
+
CREATE INDEX IF NOT EXISTS idx_tracking_milestones_category ON tracking_milestones(category);
|
|
69
|
+
CREATE INDEX IF NOT EXISTS idx_tracking_milestones_year ON tracking_milestones(year);
|
|
70
|
+
CREATE INDEX IF NOT EXISTS idx_tracking_milestones_month ON tracking_milestones(month);
|
|
71
|
+
CREATE INDEX IF NOT EXISTS idx_tracking_milestones_quarter ON tracking_milestones(quarter);
|
|
72
|
+
`);
|
|
73
|
+
_schemaReady = true;
|
|
74
|
+
}
|
|
75
|
+
/* ------------------------------------------------------------------ */
|
|
76
|
+
/* Temporal helpers */
|
|
77
|
+
/* ------------------------------------------------------------------ */
|
|
78
|
+
const DAY_NAMES = [
|
|
79
|
+
"Sunday",
|
|
80
|
+
"Monday",
|
|
81
|
+
"Tuesday",
|
|
82
|
+
"Wednesday",
|
|
83
|
+
"Thursday",
|
|
84
|
+
"Friday",
|
|
85
|
+
"Saturday",
|
|
86
|
+
];
|
|
87
|
+
function getISOWeek(d) {
|
|
88
|
+
const tmp = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
|
|
89
|
+
tmp.setUTCDate(tmp.getUTCDate() + 4 - (tmp.getUTCDay() || 7));
|
|
90
|
+
const yearStart = new Date(Date.UTC(tmp.getUTCFullYear(), 0, 1));
|
|
91
|
+
return Math.ceil(((tmp.getTime() - yearStart.getTime()) / 86400000 + 1) / 7);
|
|
92
|
+
}
|
|
93
|
+
function deriveQuarter(month) {
|
|
94
|
+
if (month <= 3)
|
|
95
|
+
return "Q1";
|
|
96
|
+
if (month <= 6)
|
|
97
|
+
return "Q2";
|
|
98
|
+
if (month <= 9)
|
|
99
|
+
return "Q3";
|
|
100
|
+
return "Q4";
|
|
101
|
+
}
|
|
102
|
+
function temporalMeta(now) {
|
|
103
|
+
const d = now ?? new Date();
|
|
104
|
+
const m = d.getMonth() + 1;
|
|
105
|
+
const y = d.getFullYear();
|
|
106
|
+
return {
|
|
107
|
+
timestamp: d.toISOString(),
|
|
108
|
+
dayOfWeek: DAY_NAMES[d.getDay()],
|
|
109
|
+
weekNumber: getISOWeek(d),
|
|
110
|
+
month: `${y}-${String(m).padStart(2, "0")}`,
|
|
111
|
+
quarter: `${y}-${deriveQuarter(m)}`,
|
|
112
|
+
year: y,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/* ------------------------------------------------------------------ */
|
|
116
|
+
/* Tools */
|
|
117
|
+
/* ------------------------------------------------------------------ */
|
|
118
|
+
export const founderTrackingTools = [
|
|
119
|
+
// ─── 1. track_action ─────────────────────────────────────────────
|
|
120
|
+
{
|
|
121
|
+
name: "track_action",
|
|
122
|
+
description: "Record any significant action with before/after state, reasoning, and temporal metadata. Auto-captures session, day, week, month, quarter, year.",
|
|
123
|
+
inputSchema: {
|
|
124
|
+
type: "object",
|
|
125
|
+
properties: {
|
|
126
|
+
action: {
|
|
127
|
+
type: "string",
|
|
128
|
+
description: "What was done (concise imperative description)",
|
|
129
|
+
},
|
|
130
|
+
category: {
|
|
131
|
+
type: "string",
|
|
132
|
+
enum: [
|
|
133
|
+
"build",
|
|
134
|
+
"deploy",
|
|
135
|
+
"fix",
|
|
136
|
+
"design",
|
|
137
|
+
"research",
|
|
138
|
+
"decision",
|
|
139
|
+
"config",
|
|
140
|
+
"test",
|
|
141
|
+
],
|
|
142
|
+
description: "Action category",
|
|
143
|
+
},
|
|
144
|
+
beforeState: {
|
|
145
|
+
type: "string",
|
|
146
|
+
description: "State before the action (optional)",
|
|
147
|
+
},
|
|
148
|
+
afterState: {
|
|
149
|
+
type: "string",
|
|
150
|
+
description: "State after the action (optional)",
|
|
151
|
+
},
|
|
152
|
+
reasoning: {
|
|
153
|
+
type: "string",
|
|
154
|
+
description: "Why this action was taken (optional)",
|
|
155
|
+
},
|
|
156
|
+
filesChanged: {
|
|
157
|
+
type: "array",
|
|
158
|
+
items: { type: "string" },
|
|
159
|
+
description: "File paths changed (optional)",
|
|
160
|
+
},
|
|
161
|
+
impactLevel: {
|
|
162
|
+
type: "string",
|
|
163
|
+
enum: ["minor", "moderate", "major", "critical"],
|
|
164
|
+
description: "Impact level of the action",
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
required: ["action", "category", "impactLevel"],
|
|
168
|
+
},
|
|
169
|
+
handler: async (args) => {
|
|
170
|
+
ensureSchema();
|
|
171
|
+
const db = getDb();
|
|
172
|
+
const meta = temporalMeta();
|
|
173
|
+
const actionId = genId("act");
|
|
174
|
+
db.prepare(`INSERT INTO tracking_actions
|
|
175
|
+
(actionId, sessionId, timestamp, action, category, beforeState, afterState, reasoning, filesChanged, impactLevel, dayOfWeek, weekNumber, month, quarter, year)
|
|
176
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(actionId, SESSION_ID, meta.timestamp, args.action, args.category, args.beforeState ?? null, args.afterState ?? null, args.reasoning ?? null, args.filesChanged ? JSON.stringify(args.filesChanged) : null, args.impactLevel, meta.dayOfWeek, meta.weekNumber, meta.month, meta.quarter, meta.year);
|
|
177
|
+
return {
|
|
178
|
+
actionId,
|
|
179
|
+
sessionId: SESSION_ID,
|
|
180
|
+
timestamp: meta.timestamp,
|
|
181
|
+
recorded: true,
|
|
182
|
+
};
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
// ─── 2. track_milestone ──────────────────────────────────────────
|
|
186
|
+
{
|
|
187
|
+
name: "track_milestone",
|
|
188
|
+
description: "Record a significant milestone (phase complete, deploy, ship, launch, pivot, decision) with optional evidence and metrics.",
|
|
189
|
+
inputSchema: {
|
|
190
|
+
type: "object",
|
|
191
|
+
properties: {
|
|
192
|
+
title: { type: "string", description: "Milestone title" },
|
|
193
|
+
description: {
|
|
194
|
+
type: "string",
|
|
195
|
+
description: "What was achieved and why it matters",
|
|
196
|
+
},
|
|
197
|
+
category: {
|
|
198
|
+
type: "string",
|
|
199
|
+
enum: [
|
|
200
|
+
"phase_complete",
|
|
201
|
+
"deploy",
|
|
202
|
+
"ship",
|
|
203
|
+
"launch",
|
|
204
|
+
"pivot",
|
|
205
|
+
"decision",
|
|
206
|
+
],
|
|
207
|
+
description: "Milestone category",
|
|
208
|
+
},
|
|
209
|
+
evidence: {
|
|
210
|
+
type: "string",
|
|
211
|
+
description: "Evidence or proof (URL, screenshot path, metric)",
|
|
212
|
+
},
|
|
213
|
+
metrics: {
|
|
214
|
+
type: "object",
|
|
215
|
+
description: "Key metrics at milestone time (e.g. {tools: 304, tests: 1510})",
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
required: ["title", "description", "category"],
|
|
219
|
+
},
|
|
220
|
+
handler: async (args) => {
|
|
221
|
+
ensureSchema();
|
|
222
|
+
const db = getDb();
|
|
223
|
+
const meta = temporalMeta();
|
|
224
|
+
const milestoneId = genId("ms");
|
|
225
|
+
db.prepare(`INSERT INTO tracking_milestones
|
|
226
|
+
(milestoneId, sessionId, timestamp, title, description, category, evidence, metrics, dayOfWeek, weekNumber, month, quarter, year)
|
|
227
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(milestoneId, SESSION_ID, meta.timestamp, args.title, args.description, args.category, args.evidence ?? null, args.metrics ? JSON.stringify(args.metrics) : null, meta.dayOfWeek, meta.weekNumber, meta.month, meta.quarter, meta.year);
|
|
228
|
+
return {
|
|
229
|
+
milestoneId,
|
|
230
|
+
sessionId: SESSION_ID,
|
|
231
|
+
timestamp: meta.timestamp,
|
|
232
|
+
recorded: true,
|
|
233
|
+
};
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
// ─── 3. get_session_journal ──────────────────────────────────────
|
|
237
|
+
{
|
|
238
|
+
name: "get_session_journal",
|
|
239
|
+
description: "Get all tracked actions from the current or a specified session, in chronological order.",
|
|
240
|
+
inputSchema: {
|
|
241
|
+
type: "object",
|
|
242
|
+
properties: {
|
|
243
|
+
sessionId: {
|
|
244
|
+
type: "string",
|
|
245
|
+
description: "Session ID to query (defaults to current process session)",
|
|
246
|
+
},
|
|
247
|
+
limit: {
|
|
248
|
+
type: "number",
|
|
249
|
+
description: "Max actions to return (default 100)",
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
annotations: { readOnlyHint: true },
|
|
254
|
+
handler: async (args) => {
|
|
255
|
+
ensureSchema();
|
|
256
|
+
const db = getDb();
|
|
257
|
+
const sid = args.sessionId ?? SESSION_ID;
|
|
258
|
+
const limit = args.limit ?? 100;
|
|
259
|
+
const actions = db
|
|
260
|
+
.prepare(`SELECT actionId, timestamp, action, category, beforeState, afterState, reasoning, filesChanged, impactLevel
|
|
261
|
+
FROM tracking_actions WHERE sessionId = ? ORDER BY timestamp ASC LIMIT ?`)
|
|
262
|
+
.all(sid, limit);
|
|
263
|
+
const milestones = db
|
|
264
|
+
.prepare(`SELECT milestoneId, timestamp, title, description, category, evidence, metrics
|
|
265
|
+
FROM tracking_milestones WHERE sessionId = ? ORDER BY timestamp ASC`)
|
|
266
|
+
.all(sid);
|
|
267
|
+
return {
|
|
268
|
+
sessionId: sid,
|
|
269
|
+
currentSession: sid === SESSION_ID,
|
|
270
|
+
actionCount: actions.length,
|
|
271
|
+
milestoneCount: milestones.length,
|
|
272
|
+
actions: actions.map((a) => ({
|
|
273
|
+
...a,
|
|
274
|
+
filesChanged: a.filesChanged ? JSON.parse(a.filesChanged) : null,
|
|
275
|
+
})),
|
|
276
|
+
milestones: milestones.map((m) => ({
|
|
277
|
+
...m,
|
|
278
|
+
metrics: m.metrics ? JSON.parse(m.metrics) : null,
|
|
279
|
+
})),
|
|
280
|
+
};
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
// ─── 4. get_daily_log ────────────────────────────────────────────
|
|
284
|
+
{
|
|
285
|
+
name: "get_daily_log",
|
|
286
|
+
description: "Get all tracked actions for a specific date, grouped by session with milestone highlights.",
|
|
287
|
+
inputSchema: {
|
|
288
|
+
type: "object",
|
|
289
|
+
properties: {
|
|
290
|
+
date: {
|
|
291
|
+
type: "string",
|
|
292
|
+
description: "Date in YYYY-MM-DD format (defaults to today)",
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
annotations: { readOnlyHint: true },
|
|
297
|
+
handler: async (args) => {
|
|
298
|
+
ensureSchema();
|
|
299
|
+
const db = getDb();
|
|
300
|
+
const targetDate = args.date ?? new Date().toISOString().slice(0, 10);
|
|
301
|
+
const actions = db
|
|
302
|
+
.prepare(`SELECT actionId, sessionId, timestamp, action, category, beforeState, afterState, reasoning, filesChanged, impactLevel
|
|
303
|
+
FROM tracking_actions
|
|
304
|
+
WHERE date(timestamp) = ?
|
|
305
|
+
ORDER BY timestamp ASC`)
|
|
306
|
+
.all(targetDate);
|
|
307
|
+
const milestones = db
|
|
308
|
+
.prepare(`SELECT milestoneId, sessionId, timestamp, title, description, category, evidence, metrics
|
|
309
|
+
FROM tracking_milestones
|
|
310
|
+
WHERE date(timestamp) = ?
|
|
311
|
+
ORDER BY timestamp ASC`)
|
|
312
|
+
.all(targetDate);
|
|
313
|
+
// Group actions by session
|
|
314
|
+
const bySession = {};
|
|
315
|
+
for (const a of actions) {
|
|
316
|
+
const sid = a.sessionId;
|
|
317
|
+
if (!bySession[sid])
|
|
318
|
+
bySession[sid] = [];
|
|
319
|
+
bySession[sid].push({
|
|
320
|
+
...a,
|
|
321
|
+
filesChanged: a.filesChanged ? JSON.parse(a.filesChanged) : null,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
return {
|
|
325
|
+
date: targetDate,
|
|
326
|
+
totalActions: actions.length,
|
|
327
|
+
totalMilestones: milestones.length,
|
|
328
|
+
sessions: Object.entries(bySession).map(([sid, acts]) => ({
|
|
329
|
+
sessionId: sid,
|
|
330
|
+
actionCount: acts.length,
|
|
331
|
+
actions: acts,
|
|
332
|
+
})),
|
|
333
|
+
milestones: milestones.map((m) => ({
|
|
334
|
+
...m,
|
|
335
|
+
metrics: m.metrics ? JSON.parse(m.metrics) : null,
|
|
336
|
+
})),
|
|
337
|
+
};
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
// ─── 5. get_weekly_summary ───────────────────────────────────────
|
|
341
|
+
{
|
|
342
|
+
name: "get_weekly_summary",
|
|
343
|
+
description: "Summarize a week's tracked actions: totals by category and impact, files most changed, milestones.",
|
|
344
|
+
inputSchema: {
|
|
345
|
+
type: "object",
|
|
346
|
+
properties: {
|
|
347
|
+
weekStart: {
|
|
348
|
+
type: "string",
|
|
349
|
+
description: "Monday of the week in YYYY-MM-DD (defaults to current week's Monday)",
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
},
|
|
353
|
+
annotations: { readOnlyHint: true },
|
|
354
|
+
handler: async (args) => {
|
|
355
|
+
ensureSchema();
|
|
356
|
+
const db = getDb();
|
|
357
|
+
let monday;
|
|
358
|
+
if (args.weekStart) {
|
|
359
|
+
monday = args.weekStart;
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
const now = new Date();
|
|
363
|
+
const day = now.getDay();
|
|
364
|
+
const diff = day === 0 ? 6 : day - 1;
|
|
365
|
+
const m = new Date(now);
|
|
366
|
+
m.setDate(m.getDate() - diff);
|
|
367
|
+
monday = m.toISOString().slice(0, 10);
|
|
368
|
+
}
|
|
369
|
+
const sundayDate = new Date(monday);
|
|
370
|
+
sundayDate.setDate(sundayDate.getDate() + 6);
|
|
371
|
+
const sunday = sundayDate.toISOString().slice(0, 10);
|
|
372
|
+
const byCategory = db
|
|
373
|
+
.prepare(`SELECT category, COUNT(*) as count
|
|
374
|
+
FROM tracking_actions
|
|
375
|
+
WHERE date(timestamp) BETWEEN ? AND ?
|
|
376
|
+
GROUP BY category ORDER BY count DESC`)
|
|
377
|
+
.all(monday, sunday);
|
|
378
|
+
const byImpact = db
|
|
379
|
+
.prepare(`SELECT impactLevel, COUNT(*) as count
|
|
380
|
+
FROM tracking_actions
|
|
381
|
+
WHERE date(timestamp) BETWEEN ? AND ?
|
|
382
|
+
GROUP BY impactLevel ORDER BY count DESC`)
|
|
383
|
+
.all(monday, sunday);
|
|
384
|
+
const totalActions = db
|
|
385
|
+
.prepare(`SELECT COUNT(*) as count
|
|
386
|
+
FROM tracking_actions
|
|
387
|
+
WHERE date(timestamp) BETWEEN ? AND ?`)
|
|
388
|
+
.get(monday, sunday);
|
|
389
|
+
const milestones = db
|
|
390
|
+
.prepare(`SELECT milestoneId, timestamp, title, category
|
|
391
|
+
FROM tracking_milestones
|
|
392
|
+
WHERE date(timestamp) BETWEEN ? AND ?
|
|
393
|
+
ORDER BY timestamp ASC`)
|
|
394
|
+
.all(monday, sunday);
|
|
395
|
+
// Files most changed
|
|
396
|
+
const allFiles = db
|
|
397
|
+
.prepare(`SELECT filesChanged
|
|
398
|
+
FROM tracking_actions
|
|
399
|
+
WHERE date(timestamp) BETWEEN ? AND ? AND filesChanged IS NOT NULL`)
|
|
400
|
+
.all(monday, sunday);
|
|
401
|
+
const fileCounts = {};
|
|
402
|
+
for (const row of allFiles) {
|
|
403
|
+
const files = JSON.parse(row.filesChanged);
|
|
404
|
+
for (const f of files) {
|
|
405
|
+
fileCounts[f] = (fileCounts[f] ?? 0) + 1;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
const topFiles = Object.entries(fileCounts)
|
|
409
|
+
.sort((a, b) => b[1] - a[1])
|
|
410
|
+
.slice(0, 10)
|
|
411
|
+
.map(([file, count]) => ({ file, count }));
|
|
412
|
+
return {
|
|
413
|
+
weekStart: monday,
|
|
414
|
+
weekEnd: sunday,
|
|
415
|
+
totalActions: totalActions.count,
|
|
416
|
+
byCategory: Object.fromEntries(byCategory.map((r) => [r.category, r.count])),
|
|
417
|
+
byImpact: Object.fromEntries(byImpact.map((r) => [r.impactLevel, r.count])),
|
|
418
|
+
milestones,
|
|
419
|
+
topFiles,
|
|
420
|
+
};
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
// ─── 6. get_monthly_report ───────────────────────────────────────
|
|
424
|
+
{
|
|
425
|
+
name: "get_monthly_report",
|
|
426
|
+
description: "Monthly rollup of actions: weekly breakdown, trending categories, velocity (actions/day), milestone timeline.",
|
|
427
|
+
inputSchema: {
|
|
428
|
+
type: "object",
|
|
429
|
+
properties: {
|
|
430
|
+
month: {
|
|
431
|
+
type: "string",
|
|
432
|
+
description: "Month in YYYY-MM format (defaults to current month)",
|
|
433
|
+
},
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
annotations: { readOnlyHint: true },
|
|
437
|
+
handler: async (args) => {
|
|
438
|
+
ensureSchema();
|
|
439
|
+
const db = getDb();
|
|
440
|
+
const month = args.month ?? new Date().toISOString().slice(0, 7);
|
|
441
|
+
const byCategory = db
|
|
442
|
+
.prepare(`SELECT category, COUNT(*) as count
|
|
443
|
+
FROM tracking_actions WHERE month = ?
|
|
444
|
+
GROUP BY category ORDER BY count DESC`)
|
|
445
|
+
.all(month);
|
|
446
|
+
const byWeek = db
|
|
447
|
+
.prepare(`SELECT weekNumber, COUNT(*) as count
|
|
448
|
+
FROM tracking_actions WHERE month = ?
|
|
449
|
+
GROUP BY weekNumber ORDER BY weekNumber ASC`)
|
|
450
|
+
.all(month);
|
|
451
|
+
const totalActions = db
|
|
452
|
+
.prepare(`SELECT COUNT(*) as count FROM tracking_actions WHERE month = ?`)
|
|
453
|
+
.get(month);
|
|
454
|
+
const milestones = db
|
|
455
|
+
.prepare(`SELECT milestoneId, timestamp, title, category, metrics
|
|
456
|
+
FROM tracking_milestones WHERE month = ?
|
|
457
|
+
ORDER BY timestamp ASC`)
|
|
458
|
+
.all(month);
|
|
459
|
+
const byImpact = db
|
|
460
|
+
.prepare(`SELECT impactLevel, COUNT(*) as count
|
|
461
|
+
FROM tracking_actions WHERE month = ?
|
|
462
|
+
GROUP BY impactLevel ORDER BY count DESC`)
|
|
463
|
+
.all(month);
|
|
464
|
+
// Distinct days with actions for velocity
|
|
465
|
+
const activeDays = db
|
|
466
|
+
.prepare(`SELECT COUNT(DISTINCT date(timestamp)) as days
|
|
467
|
+
FROM tracking_actions WHERE month = ?`)
|
|
468
|
+
.get(month);
|
|
469
|
+
const velocity = activeDays.days > 0
|
|
470
|
+
? Math.round((totalActions.count / activeDays.days) * 10) / 10
|
|
471
|
+
: 0;
|
|
472
|
+
return {
|
|
473
|
+
month,
|
|
474
|
+
totalActions: totalActions.count,
|
|
475
|
+
activeDays: activeDays.days,
|
|
476
|
+
velocityPerDay: velocity,
|
|
477
|
+
byCategory: Object.fromEntries(byCategory.map((r) => [r.category, r.count])),
|
|
478
|
+
byImpact: Object.fromEntries(byImpact.map((r) => [r.impactLevel, r.count])),
|
|
479
|
+
byWeek: byWeek.map((r) => ({
|
|
480
|
+
week: r.weekNumber,
|
|
481
|
+
actions: r.count,
|
|
482
|
+
})),
|
|
483
|
+
milestones: milestones.map((m) => ({
|
|
484
|
+
...m,
|
|
485
|
+
metrics: m.metrics ? JSON.parse(m.metrics) : null,
|
|
486
|
+
})),
|
|
487
|
+
};
|
|
488
|
+
},
|
|
489
|
+
},
|
|
490
|
+
// ─── 7. get_quarterly_review ─────────────────────────────────────
|
|
491
|
+
{
|
|
492
|
+
name: "get_quarterly_review",
|
|
493
|
+
description: "Quarterly strategic view: monthly trends, category shifts, biggest milestones, velocity curve.",
|
|
494
|
+
inputSchema: {
|
|
495
|
+
type: "object",
|
|
496
|
+
properties: {
|
|
497
|
+
quarter: {
|
|
498
|
+
type: "string",
|
|
499
|
+
description: "Quarter in YYYY-Q1/Q2/Q3/Q4 format (defaults to current quarter)",
|
|
500
|
+
},
|
|
501
|
+
},
|
|
502
|
+
},
|
|
503
|
+
annotations: { readOnlyHint: true },
|
|
504
|
+
handler: async (args) => {
|
|
505
|
+
ensureSchema();
|
|
506
|
+
const db = getDb();
|
|
507
|
+
let quarter;
|
|
508
|
+
if (args.quarter) {
|
|
509
|
+
quarter = args.quarter;
|
|
510
|
+
}
|
|
511
|
+
else {
|
|
512
|
+
const now = new Date();
|
|
513
|
+
quarter = `${now.getFullYear()}-${deriveQuarter(now.getMonth() + 1)}`;
|
|
514
|
+
}
|
|
515
|
+
const byMonth = db
|
|
516
|
+
.prepare(`SELECT month, COUNT(*) as count
|
|
517
|
+
FROM tracking_actions WHERE quarter = ?
|
|
518
|
+
GROUP BY month ORDER BY month ASC`)
|
|
519
|
+
.all(quarter);
|
|
520
|
+
const byCategory = db
|
|
521
|
+
.prepare(`SELECT category, COUNT(*) as count
|
|
522
|
+
FROM tracking_actions WHERE quarter = ?
|
|
523
|
+
GROUP BY category ORDER BY count DESC`)
|
|
524
|
+
.all(quarter);
|
|
525
|
+
const totalActions = db
|
|
526
|
+
.prepare(`SELECT COUNT(*) as count FROM tracking_actions WHERE quarter = ?`)
|
|
527
|
+
.get(quarter);
|
|
528
|
+
const milestones = db
|
|
529
|
+
.prepare(`SELECT milestoneId, timestamp, title, category, metrics
|
|
530
|
+
FROM tracking_milestones WHERE quarter = ?
|
|
531
|
+
ORDER BY timestamp ASC`)
|
|
532
|
+
.all(quarter);
|
|
533
|
+
const activeDays = db
|
|
534
|
+
.prepare(`SELECT COUNT(DISTINCT date(timestamp)) as days
|
|
535
|
+
FROM tracking_actions WHERE quarter = ?`)
|
|
536
|
+
.get(quarter);
|
|
537
|
+
const velocity = activeDays.days > 0
|
|
538
|
+
? Math.round((totalActions.count / activeDays.days) * 10) / 10
|
|
539
|
+
: 0;
|
|
540
|
+
// First and last action timestamps for state span
|
|
541
|
+
const firstAction = db
|
|
542
|
+
.prepare(`SELECT timestamp, action FROM tracking_actions WHERE quarter = ? ORDER BY timestamp ASC LIMIT 1`)
|
|
543
|
+
.get(quarter);
|
|
544
|
+
const lastAction = db
|
|
545
|
+
.prepare(`SELECT timestamp, action FROM tracking_actions WHERE quarter = ? ORDER BY timestamp DESC LIMIT 1`)
|
|
546
|
+
.get(quarter);
|
|
547
|
+
return {
|
|
548
|
+
quarter,
|
|
549
|
+
totalActions: totalActions.count,
|
|
550
|
+
activeDays: activeDays.days,
|
|
551
|
+
velocityPerDay: velocity,
|
|
552
|
+
byMonth: byMonth.map((r) => ({
|
|
553
|
+
month: r.month,
|
|
554
|
+
actions: r.count,
|
|
555
|
+
})),
|
|
556
|
+
byCategory: Object.fromEntries(byCategory.map((r) => [r.category, r.count])),
|
|
557
|
+
milestones: milestones.map((m) => ({
|
|
558
|
+
...m,
|
|
559
|
+
metrics: m.metrics ? JSON.parse(m.metrics) : null,
|
|
560
|
+
})),
|
|
561
|
+
stateSpan: {
|
|
562
|
+
firstAction: firstAction ?? null,
|
|
563
|
+
lastAction: lastAction ?? null,
|
|
564
|
+
},
|
|
565
|
+
};
|
|
566
|
+
},
|
|
567
|
+
},
|
|
568
|
+
// ─── 8. get_annual_retrospective ─────────────────────────────────
|
|
569
|
+
{
|
|
570
|
+
name: "get_annual_retrospective",
|
|
571
|
+
description: "Full year view: quarterly summaries, yearly milestones, category distribution, growth metrics.",
|
|
572
|
+
inputSchema: {
|
|
573
|
+
type: "object",
|
|
574
|
+
properties: {
|
|
575
|
+
year: {
|
|
576
|
+
type: "number",
|
|
577
|
+
description: "Year to review (defaults to current year)",
|
|
578
|
+
},
|
|
579
|
+
},
|
|
580
|
+
},
|
|
581
|
+
annotations: { readOnlyHint: true },
|
|
582
|
+
handler: async (args) => {
|
|
583
|
+
ensureSchema();
|
|
584
|
+
const db = getDb();
|
|
585
|
+
const year = args.year ?? new Date().getFullYear();
|
|
586
|
+
const byQuarter = db
|
|
587
|
+
.prepare(`SELECT quarter, COUNT(*) as count
|
|
588
|
+
FROM tracking_actions WHERE year = ?
|
|
589
|
+
GROUP BY quarter ORDER BY quarter ASC`)
|
|
590
|
+
.all(year);
|
|
591
|
+
const byCategory = db
|
|
592
|
+
.prepare(`SELECT category, COUNT(*) as count
|
|
593
|
+
FROM tracking_actions WHERE year = ?
|
|
594
|
+
GROUP BY category ORDER BY count DESC`)
|
|
595
|
+
.all(year);
|
|
596
|
+
const byMonth = db
|
|
597
|
+
.prepare(`SELECT month, COUNT(*) as count
|
|
598
|
+
FROM tracking_actions WHERE year = ?
|
|
599
|
+
GROUP BY month ORDER BY month ASC`)
|
|
600
|
+
.all(year);
|
|
601
|
+
const totalActions = db
|
|
602
|
+
.prepare(`SELECT COUNT(*) as count FROM tracking_actions WHERE year = ?`)
|
|
603
|
+
.get(year);
|
|
604
|
+
const milestones = db
|
|
605
|
+
.prepare(`SELECT milestoneId, timestamp, title, category, metrics
|
|
606
|
+
FROM tracking_milestones WHERE year = ?
|
|
607
|
+
ORDER BY timestamp ASC`)
|
|
608
|
+
.all(year);
|
|
609
|
+
const activeDays = db
|
|
610
|
+
.prepare(`SELECT COUNT(DISTINCT date(timestamp)) as days
|
|
611
|
+
FROM tracking_actions WHERE year = ?`)
|
|
612
|
+
.get(year);
|
|
613
|
+
const totalSessions = db
|
|
614
|
+
.prepare(`SELECT COUNT(DISTINCT sessionId) as count
|
|
615
|
+
FROM tracking_actions WHERE year = ?`)
|
|
616
|
+
.get(year);
|
|
617
|
+
const velocity = activeDays.days > 0
|
|
618
|
+
? Math.round((totalActions.count / activeDays.days) * 10) / 10
|
|
619
|
+
: 0;
|
|
620
|
+
return {
|
|
621
|
+
year,
|
|
622
|
+
totalActions: totalActions.count,
|
|
623
|
+
totalSessions: totalSessions.count,
|
|
624
|
+
totalMilestones: milestones.length,
|
|
625
|
+
activeDays: activeDays.days,
|
|
626
|
+
velocityPerDay: velocity,
|
|
627
|
+
byQuarter: byQuarter.map((r) => ({
|
|
628
|
+
quarter: r.quarter,
|
|
629
|
+
actions: r.count,
|
|
630
|
+
})),
|
|
631
|
+
byMonth: byMonth.map((r) => ({
|
|
632
|
+
month: r.month,
|
|
633
|
+
actions: r.count,
|
|
634
|
+
})),
|
|
635
|
+
byCategory: Object.fromEntries(byCategory.map((r) => [r.category, r.count])),
|
|
636
|
+
milestones: milestones.map((m) => ({
|
|
637
|
+
...m,
|
|
638
|
+
metrics: m.metrics ? JSON.parse(m.metrics) : null,
|
|
639
|
+
})),
|
|
640
|
+
};
|
|
641
|
+
},
|
|
642
|
+
},
|
|
643
|
+
];
|
|
644
|
+
//# sourceMappingURL=founderTrackingTools.js.map
|