athar-mcp 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 (71) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +242 -0
  3. package/dist/cli/commands/list.d.ts +10 -0
  4. package/dist/cli/commands/list.d.ts.map +1 -0
  5. package/dist/cli/commands/list.js +73 -0
  6. package/dist/cli/commands/list.js.map +1 -0
  7. package/dist/cli/commands/review.d.ts +2 -0
  8. package/dist/cli/commands/review.d.ts.map +1 -0
  9. package/dist/cli/commands/review.js +112 -0
  10. package/dist/cli/commands/review.js.map +1 -0
  11. package/dist/cli/commands/setup.d.ts +2 -0
  12. package/dist/cli/commands/setup.d.ts.map +1 -0
  13. package/dist/cli/commands/setup.js +55 -0
  14. package/dist/cli/commands/setup.js.map +1 -0
  15. package/dist/cli/commands/status.d.ts +2 -0
  16. package/dist/cli/commands/status.d.ts.map +1 -0
  17. package/dist/cli/commands/status.js +39 -0
  18. package/dist/cli/commands/status.js.map +1 -0
  19. package/dist/cli/index.d.ts +12 -0
  20. package/dist/cli/index.d.ts.map +1 -0
  21. package/dist/cli/index.js +43 -0
  22. package/dist/cli/index.js.map +1 -0
  23. package/dist/db/connection.d.ts +11 -0
  24. package/dist/db/connection.d.ts.map +1 -0
  25. package/dist/db/connection.js +49 -0
  26. package/dist/db/connection.js.map +1 -0
  27. package/dist/db/schema.d.ts +12 -0
  28. package/dist/db/schema.d.ts.map +1 -0
  29. package/dist/db/schema.js +95 -0
  30. package/dist/db/schema.js.map +1 -0
  31. package/dist/index.d.ts +19 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +48 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/server.d.ts +7 -0
  36. package/dist/server.d.ts.map +1 -0
  37. package/dist/server.js +139 -0
  38. package/dist/server.js.map +1 -0
  39. package/dist/smoke-test.d.ts +7 -0
  40. package/dist/smoke-test.d.ts.map +1 -0
  41. package/dist/smoke-test.js +99 -0
  42. package/dist/smoke-test.js.map +1 -0
  43. package/dist/spaced-repetition/scheduler.d.ts +46 -0
  44. package/dist/spaced-repetition/scheduler.d.ts.map +1 -0
  45. package/dist/spaced-repetition/scheduler.js +98 -0
  46. package/dist/spaced-repetition/scheduler.js.map +1 -0
  47. package/dist/spaced-repetition/sm2.d.ts +36 -0
  48. package/dist/spaced-repetition/sm2.d.ts.map +1 -0
  49. package/dist/spaced-repetition/sm2.js +90 -0
  50. package/dist/spaced-repetition/sm2.js.map +1 -0
  51. package/dist/tools/memory.d.ts +25 -0
  52. package/dist/tools/memory.d.ts.map +1 -0
  53. package/dist/tools/memory.js +112 -0
  54. package/dist/tools/memory.js.map +1 -0
  55. package/dist/tools/save-lesson.d.ts +11 -0
  56. package/dist/tools/save-lesson.d.ts.map +1 -0
  57. package/dist/tools/save-lesson.js +137 -0
  58. package/dist/tools/save-lesson.js.map +1 -0
  59. package/dist/tools/validators.d.ts +82 -0
  60. package/dist/tools/validators.d.ts.map +1 -0
  61. package/dist/tools/validators.js +86 -0
  62. package/dist/tools/validators.js.map +1 -0
  63. package/dist/utils/logger.d.ts +23 -0
  64. package/dist/utils/logger.d.ts.map +1 -0
  65. package/dist/utils/logger.js +55 -0
  66. package/dist/utils/logger.js.map +1 -0
  67. package/dist/utils/paths.d.ts +17 -0
  68. package/dist/utils/paths.d.ts.map +1 -0
  69. package/dist/utils/paths.js +43 -0
  70. package/dist/utils/paths.js.map +1 -0
  71. package/package.json +60 -0
@@ -0,0 +1,98 @@
1
+ import { getDatabase } from '../db/connection.js';
2
+ import { calculateSM2 } from './sm2.js';
3
+ import { createLogger } from '../utils/logger.js';
4
+ const log = createLogger('scheduler');
5
+ /**
6
+ * Get all lessons that are due for review (next_review_at <= now).
7
+ */
8
+ export function getDueReviews() {
9
+ const db = getDatabase();
10
+ const now = new Date().toISOString();
11
+ const stmt = db.prepare(`
12
+ SELECT id, title, problem, root_cause, lesson,
13
+ bad_code, good_code, tags, language,
14
+ review_questions, repetitions, easiness_factor,
15
+ interval_days, status, review_count, created_at
16
+ FROM lessons
17
+ WHERE next_review_at <= ? OR next_review_at IS NULL
18
+ ORDER BY next_review_at ASC
19
+ `);
20
+ return stmt.all(now);
21
+ }
22
+ /**
23
+ * Update a lesson after a review session.
24
+ * Applies the SM-2 algorithm and updates the database.
25
+ */
26
+ export function recordReview(lessonId, quality) {
27
+ const db = getDatabase();
28
+ // Get current state
29
+ const lesson = db.prepare(`
30
+ SELECT repetitions, easiness_factor, interval_days
31
+ FROM lessons WHERE id = ?
32
+ `).get(lessonId);
33
+ if (!lesson) {
34
+ throw new Error(`Lesson with ID ${lessonId} not found`);
35
+ }
36
+ // Calculate SM-2
37
+ const result = calculateSM2({
38
+ repetitions: lesson.repetitions,
39
+ easinessFactor: lesson.easiness_factor,
40
+ intervalDays: lesson.interval_days,
41
+ }, quality);
42
+ // Update database
43
+ db.prepare(`
44
+ UPDATE lessons SET
45
+ repetitions = ?,
46
+ easiness_factor = ?,
47
+ interval_days = ?,
48
+ next_review_at = ?,
49
+ last_reviewed_at = datetime('now'),
50
+ status = ?,
51
+ quality_score = ?,
52
+ review_count = review_count + 1,
53
+ updated_at = datetime('now')
54
+ WHERE id = ?
55
+ `).run(result.repetitions, result.easinessFactor, result.intervalDays, result.nextReviewAt.toISOString(), result.status, quality, lessonId);
56
+ log.info(`Review recorded: lesson=${lessonId}, quality=${quality}, next_review=${result.intervalDays}d, status=${result.status}`);
57
+ return {
58
+ success: true,
59
+ nextReviewAt: result.nextReviewAt,
60
+ newStatus: result.status,
61
+ intervalDays: result.intervalDays,
62
+ };
63
+ }
64
+ /**
65
+ * Get statistics about all lessons.
66
+ */
67
+ export function getStats() {
68
+ const db = getDatabase();
69
+ const now = new Date().toISOString();
70
+ const weekFromNow = new Date();
71
+ weekFromNow.setDate(weekFromNow.getDate() + 7);
72
+ const total = db.prepare('SELECT COUNT(*) as c FROM lessons').get().c;
73
+ const dueToday = db.prepare('SELECT COUNT(*) as c FROM lessons WHERE next_review_at <= ? OR next_review_at IS NULL').get(now).c;
74
+ const dueThisWeek = db.prepare('SELECT COUNT(*) as c FROM lessons WHERE next_review_at <= ?').get(weekFromNow.toISOString()).c;
75
+ const statusRows = db.prepare('SELECT status, COUNT(*) as c FROM lessons GROUP BY status').all();
76
+ const byStatus = {};
77
+ for (const row of statusRows) {
78
+ byStatus[row.status] = row.c;
79
+ }
80
+ // Extract and count tags
81
+ const allTags = db.prepare('SELECT tags FROM lessons').all();
82
+ const tagCounts = new Map();
83
+ for (const row of allTags) {
84
+ try {
85
+ const tags = JSON.parse(row.tags || '[]');
86
+ for (const tag of tags) {
87
+ tagCounts.set(tag, (tagCounts.get(tag) || 0) + 1);
88
+ }
89
+ }
90
+ catch { /* skip malformed */ }
91
+ }
92
+ const topTags = [...tagCounts.entries()]
93
+ .sort((a, b) => b[1] - a[1])
94
+ .slice(0, 8)
95
+ .map(([tag, count]) => ({ tag, count }));
96
+ return { total, dueToday, dueThisWeek, byStatus, topTags };
97
+ }
98
+ //# sourceMappingURL=scheduler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../../src/spaced-repetition/scheduler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * SM-2 Spaced Repetition Algorithm
3
+ *
4
+ * Based on the SuperMemo-2 algorithm by Piotr Wozniak.
5
+ * Calculates optimal review intervals based on recall quality.
6
+ *
7
+ * Quality scores:
8
+ * 5 — Perfect response (instant recall)
9
+ * 4 — Correct after hesitation
10
+ * 3 — Correct with significant difficulty
11
+ * 2 — Incorrect, but felt close / recognized the answer
12
+ * 1 — Incorrect, vague memory
13
+ * 0 — Complete blackout
14
+ */
15
+ export interface SM2State {
16
+ repetitions: number;
17
+ easinessFactor: number;
18
+ intervalDays: number;
19
+ }
20
+ export interface SM2Result extends SM2State {
21
+ nextReviewAt: Date;
22
+ status: 'new' | 'learning' | 'learned' | 'mastered';
23
+ }
24
+ /**
25
+ * Calculate the next review state using the SM-2 algorithm.
26
+ *
27
+ * @param current - Current card state
28
+ * @param quality - Recall quality (0-5)
29
+ * @returns Updated state with next review date
30
+ */
31
+ export declare function calculateSM2(current: SM2State, quality: number): SM2Result;
32
+ /**
33
+ * Get a human-readable description of the quality score.
34
+ */
35
+ export declare function qualityLabel(q: number): string;
36
+ //# sourceMappingURL=sm2.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sm2.d.ts","sourceRoot":"","sources":["../../src/spaced-repetition/sm2.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,QAAQ;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,SAAU,SAAQ,QAAQ;IACzC,YAAY,EAAE,IAAI,CAAC;IACnB,MAAM,EAAE,KAAK,GAAG,UAAU,GAAG,SAAS,GAAG,UAAU,CAAC;CACrD;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,CAsD1E;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAU9C"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * SM-2 Spaced Repetition Algorithm
3
+ *
4
+ * Based on the SuperMemo-2 algorithm by Piotr Wozniak.
5
+ * Calculates optimal review intervals based on recall quality.
6
+ *
7
+ * Quality scores:
8
+ * 5 — Perfect response (instant recall)
9
+ * 4 — Correct after hesitation
10
+ * 3 — Correct with significant difficulty
11
+ * 2 — Incorrect, but felt close / recognized the answer
12
+ * 1 — Incorrect, vague memory
13
+ * 0 — Complete blackout
14
+ */
15
+ /**
16
+ * Calculate the next review state using the SM-2 algorithm.
17
+ *
18
+ * @param current - Current card state
19
+ * @param quality - Recall quality (0-5)
20
+ * @returns Updated state with next review date
21
+ */
22
+ export function calculateSM2(current, quality) {
23
+ // Clamp quality to valid range
24
+ quality = Math.max(0, Math.min(5, Math.round(quality)));
25
+ let { repetitions, easinessFactor, intervalDays } = current;
26
+ // Calculate new Easiness Factor
27
+ // EF' = EF + (0.1 - (5 - q) * (0.08 + (5 - q) * 0.02))
28
+ let newEF = easinessFactor + (0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02));
29
+ if (newEF < 1.3)
30
+ newEF = 1.3; // EF cannot drop below 1.3
31
+ let newRepetitions;
32
+ let newInterval;
33
+ if (quality < 3) {
34
+ // Failed recall → reset to beginning
35
+ newRepetitions = 0;
36
+ newInterval = 1; // Review again tomorrow
37
+ }
38
+ else {
39
+ // Successful recall → advance
40
+ newRepetitions = repetitions + 1;
41
+ if (newRepetitions === 1) {
42
+ newInterval = 1; // First success: review in 1 day
43
+ }
44
+ else if (newRepetitions === 2) {
45
+ newInterval = 6; // Second success: review in 6 days
46
+ }
47
+ else {
48
+ newInterval = Math.round(intervalDays * newEF);
49
+ }
50
+ }
51
+ // Calculate next review date
52
+ const nextReviewAt = new Date();
53
+ nextReviewAt.setDate(nextReviewAt.getDate() + newInterval);
54
+ // Determine status based on repetitions and EF
55
+ let status;
56
+ if (newRepetitions === 0) {
57
+ status = 'new';
58
+ }
59
+ else if (newRepetitions <= 2) {
60
+ status = 'learning';
61
+ }
62
+ else if (newRepetitions <= 5 || newEF < 2.5) {
63
+ status = 'learned';
64
+ }
65
+ else {
66
+ status = 'mastered';
67
+ }
68
+ return {
69
+ repetitions: newRepetitions,
70
+ easinessFactor: Math.round(newEF * 100) / 100, // Round to 2 decimals
71
+ intervalDays: newInterval,
72
+ nextReviewAt,
73
+ status,
74
+ };
75
+ }
76
+ /**
77
+ * Get a human-readable description of the quality score.
78
+ */
79
+ export function qualityLabel(q) {
80
+ switch (q) {
81
+ case 0: return '🟄 Complete blackout';
82
+ case 1: return '🟧 Incorrect, vague memory';
83
+ case 2: return '🟨 Incorrect, but recognized the answer';
84
+ case 3: return '🟩 Correct with significant difficulty';
85
+ case 4: return '🟦 Correct after hesitation';
86
+ case 5: return '🟪 Perfect recall';
87
+ default: return `Unknown (${q})`;
88
+ }
89
+ }
90
+ //# sourceMappingURL=sm2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sm2.js","sourceRoot":"","sources":["../../src/spaced-repetition/sm2.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAaH;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,OAAiB,EAAE,OAAe;IAC7D,+BAA+B;IAC/B,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAExD,IAAI,EAAE,WAAW,EAAE,cAAc,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC;IAE5D,gCAAgC;IAChC,uDAAuD;IACvD,IAAI,KAAK,GAAG,cAAc,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;IACnF,IAAI,KAAK,GAAG,GAAG;QAAE,KAAK,GAAG,GAAG,CAAC,CAAC,2BAA2B;IAEzD,IAAI,cAAsB,CAAC;IAC3B,IAAI,WAAmB,CAAC;IAExB,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;QAChB,qCAAqC;QACrC,cAAc,GAAG,CAAC,CAAC;QACnB,WAAW,GAAG,CAAC,CAAC,CAAC,wBAAwB;IAC3C,CAAC;SAAM,CAAC;QACN,8BAA8B;QAC9B,cAAc,GAAG,WAAW,GAAG,CAAC,CAAC;QAEjC,IAAI,cAAc,KAAK,CAAC,EAAE,CAAC;YACzB,WAAW,GAAG,CAAC,CAAC,CAAS,iCAAiC;QAC5D,CAAC;aAAM,IAAI,cAAc,KAAK,CAAC,EAAE,CAAC;YAChC,WAAW,GAAG,CAAC,CAAC,CAAS,mCAAmC;QAC9D,CAAC;aAAM,CAAC;YACN,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,KAAK,CAAC,CAAC;QACjD,CAAC;IACH,CAAC;IAED,6BAA6B;IAC7B,MAAM,YAAY,GAAG,IAAI,IAAI,EAAE,CAAC;IAChC,YAAY,CAAC,OAAO,CAAC,YAAY,CAAC,OAAO,EAAE,GAAG,WAAW,CAAC,CAAC;IAE3D,+CAA+C;IAC/C,IAAI,MAA2B,CAAC;IAChC,IAAI,cAAc,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,GAAG,KAAK,CAAC;IACjB,CAAC;SAAM,IAAI,cAAc,IAAI,CAAC,EAAE,CAAC;QAC/B,MAAM,GAAG,UAAU,CAAC;IACtB,CAAC;SAAM,IAAI,cAAc,IAAI,CAAC,IAAI,KAAK,GAAG,GAAG,EAAE,CAAC;QAC9C,MAAM,GAAG,SAAS,CAAC;IACrB,CAAC;SAAM,CAAC;QACN,MAAM,GAAG,UAAU,CAAC;IACtB,CAAC;IAED,OAAO;QACL,WAAW,EAAE,cAAc;QAC3B,cAAc,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,GAAG,EAAE,sBAAsB;QACrE,YAAY,EAAE,WAAW;QACzB,YAAY;QACZ,MAAM;KACP,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,CAAS;IACpC,QAAQ,CAAC,EAAE,CAAC;QACV,KAAK,CAAC,CAAC,CAAC,OAAO,sBAAsB,CAAC;QACtC,KAAK,CAAC,CAAC,CAAC,OAAO,4BAA4B,CAAC;QAC5C,KAAK,CAAC,CAAC,CAAC,OAAO,yCAAyC,CAAC;QACzD,KAAK,CAAC,CAAC,CAAC,OAAO,wCAAwC,CAAC;QACxD,KAAK,CAAC,CAAC,CAAC,OAAO,6BAA6B,CAAC;QAC7C,KAAK,CAAC,CAAC,CAAC,OAAO,mBAAmB,CAAC;QACnC,OAAO,CAAC,CAAC,OAAO,YAAY,CAAC,GAAG,CAAC;IACnC,CAAC;AACH,CAAC"}
@@ -0,0 +1,25 @@
1
+ import type { MemoryInput } from './validators.js';
2
+ interface LessonResult {
3
+ id: number;
4
+ title: string;
5
+ problem: string;
6
+ root_cause: string;
7
+ lesson: string;
8
+ bad_code: string | null;
9
+ good_code: string | null;
10
+ tags: string;
11
+ language: string | null;
12
+ status: string;
13
+ review_count: number;
14
+ created_at: string;
15
+ }
16
+ /**
17
+ * Search and retrieve past lessons from memory.
18
+ * Called by the MCP memory tool handler.
19
+ */
20
+ export declare function searchMemory(input: MemoryInput): {
21
+ results: LessonResult[];
22
+ message: string;
23
+ };
24
+ export {};
25
+ //# sourceMappingURL=memory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/tools/memory.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAInD,UAAU,YAAY;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,WAAW,GAAG;IAAE,OAAO,EAAE,YAAY,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CA4H7F"}
@@ -0,0 +1,112 @@
1
+ import { getDatabase } from '../db/connection.js';
2
+ import { createLogger } from '../utils/logger.js';
3
+ const log = createLogger('memory');
4
+ /**
5
+ * Search and retrieve past lessons from memory.
6
+ * Called by the MCP memory tool handler.
7
+ */
8
+ export function searchMemory(input) {
9
+ const db = getDatabase();
10
+ log.info(`Searching memory: query="${input.query}", limit=${input.limit}`);
11
+ let results = [];
12
+ // Strategy 1: Try FTS5 full-text search first
13
+ try {
14
+ const ftsQuery = input.query
15
+ .replace(/[^\w\s]/g, ' ') // Remove special chars
16
+ .split(/\s+/)
17
+ .filter(w => w.length > 1)
18
+ .map(w => `"${w}"`) // Exact phrase matching per word
19
+ .join(' OR ');
20
+ if (ftsQuery) {
21
+ let sql = `
22
+ SELECT l.id, l.title, l.problem, l.root_cause, l.lesson,
23
+ l.bad_code, l.good_code, l.tags, l.language,
24
+ l.status, l.review_count, l.created_at
25
+ FROM lessons_fts fts
26
+ JOIN lessons l ON l.id = fts.rowid
27
+ `;
28
+ const conditions = [];
29
+ const params = [];
30
+ conditions.push('lessons_fts MATCH ?');
31
+ params.push(ftsQuery);
32
+ if (input.language) {
33
+ conditions.push('l.language = ?');
34
+ params.push(input.language);
35
+ }
36
+ if (input.tags && input.tags.length > 0) {
37
+ // Match any tag using JSON
38
+ const tagConditions = input.tags.map(() => 'l.tags LIKE ?');
39
+ conditions.push(`(${tagConditions.join(' OR ')})`);
40
+ params.push(...input.tags.map(t => `%"${t}"%`));
41
+ }
42
+ sql += ' WHERE ' + conditions.join(' AND ');
43
+ sql += ` ORDER BY rank LIMIT ?`;
44
+ params.push(input.limit);
45
+ const stmt = db.prepare(sql);
46
+ results = stmt.all(...params);
47
+ }
48
+ }
49
+ catch (err) {
50
+ log.debug('FTS search failed, falling back to LIKE search', String(err));
51
+ }
52
+ // Strategy 2: Fallback to LIKE search if FTS returns nothing
53
+ if (results.length === 0) {
54
+ let sql = `
55
+ SELECT id, title, problem, root_cause, lesson,
56
+ bad_code, good_code, tags, language,
57
+ status, review_count, created_at
58
+ FROM lessons
59
+ WHERE (
60
+ title LIKE ? OR
61
+ problem LIKE ? OR
62
+ root_cause LIKE ? OR
63
+ lesson LIKE ? OR
64
+ tags LIKE ?
65
+ )
66
+ `;
67
+ const likeQuery = `%${input.query}%`;
68
+ const params = [likeQuery, likeQuery, likeQuery, likeQuery, likeQuery];
69
+ if (input.language) {
70
+ sql += ' AND language = ?';
71
+ params.push(input.language);
72
+ }
73
+ if (input.tags && input.tags.length > 0) {
74
+ const tagConditions = input.tags.map(() => 'tags LIKE ?');
75
+ sql += ` AND (${tagConditions.join(' OR ')})`;
76
+ params.push(...input.tags.map(t => `%"${t}"%`));
77
+ }
78
+ sql += ` ORDER BY created_at DESC LIMIT ?`;
79
+ params.push(input.limit);
80
+ const stmt = db.prepare(sql);
81
+ results = stmt.all(...params);
82
+ }
83
+ log.info(`Found ${results.length} matching lessons`);
84
+ if (results.length === 0) {
85
+ return {
86
+ results: [],
87
+ message: `No lessons found matching "${input.query}". This might be a new type of mistake — if resolved, consider saving it as a lesson.`,
88
+ };
89
+ }
90
+ // Format results for the AI assistant
91
+ const formatted = results.map((r, i) => {
92
+ const tags = JSON.parse(r.tags || '[]');
93
+ let text = `\n### ${i + 1}. ${r.title} (ID: ${r.id})\n`;
94
+ text += `**Status:** ${r.status} | **Reviews:** ${r.review_count} | **Language:** ${r.language || 'N/A'}\n`;
95
+ text += `**Tags:** ${tags.join(', ')}\n\n`;
96
+ text += `**Problem:** ${r.problem}\n\n`;
97
+ text += `**Root Cause:** ${r.root_cause}\n\n`;
98
+ text += `**Lesson:** ${r.lesson}\n`;
99
+ if (r.bad_code) {
100
+ text += `\n**Bad Code:**\n\`\`\`\n${r.bad_code}\n\`\`\`\n`;
101
+ }
102
+ if (r.good_code) {
103
+ text += `\n**Good Code:**\n\`\`\`\n${r.good_code}\n\`\`\`\n`;
104
+ }
105
+ return text;
106
+ }).join('\n---\n');
107
+ return {
108
+ results,
109
+ message: `Found ${results.length} relevant lesson(s) from memory:\n${formatted}`,
110
+ };
111
+ }
112
+ //# sourceMappingURL=memory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.js","sourceRoot":"","sources":["../../src/tools/memory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { SaveLessonInput } from './validators.js';
2
+ /**
3
+ * Save a new programming lesson to the database.
4
+ * Called by the MCP save_lesson tool handler.
5
+ */
6
+ export declare function saveLesson(input: SaveLessonInput): {
7
+ success: boolean;
8
+ message: string;
9
+ lessonId?: number;
10
+ };
11
+ //# sourceMappingURL=save-lesson.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"save-lesson.d.ts","sourceRoot":"","sources":["../../src/tools/save-lesson.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAuGvD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CA8D3G"}
@@ -0,0 +1,137 @@
1
+ import { getDatabase } from '../db/connection.js';
2
+ import { createLogger } from '../utils/logger.js';
3
+ const log = createLogger('save-lesson');
4
+ /** Patterns that indicate a trivial, non-educational change */
5
+ const TRIVIAL_PATTERNS = [
6
+ /^(fix|change|update)\s+(indent|format|spacing|whitespace)/i,
7
+ /^(add|remove)\s+(semicolon|comma|bracket|parenthes)/i,
8
+ /^(prettier|eslint|lint)\s+(fix|format|auto)/i,
9
+ /^(import|export)\s+(order|sort|organiz)/i,
10
+ /^(rename|typo|spell)/i,
11
+ ];
12
+ /**
13
+ * Validate that this lesson has real educational value.
14
+ * Returns null if valid, or an error message if rejected.
15
+ */
16
+ function validateLessonQuality(input) {
17
+ // 1. Check for trivial patterns in the title
18
+ for (const pattern of TRIVIAL_PATTERNS) {
19
+ if (pattern.test(input.title)) {
20
+ return `Rejected: "${input.title}" appears to be a trivial formatting/style change, not a genuine learning moment. Only save lessons for real mistakes with root cause analysis.`;
21
+ }
22
+ }
23
+ // 2. Problem and root_cause should not be near-identical
24
+ const problemNorm = input.problem.toLowerCase().trim();
25
+ const rootCauseNorm = input.root_cause.toLowerCase().trim();
26
+ if (problemNorm === rootCauseNorm) {
27
+ return 'Rejected: "problem" and "root_cause" are identical. The root cause should explain WHY the problem happened, not restate the problem.';
28
+ }
29
+ // 3. If bad_code provided, good_code should also be provided (and vice versa)
30
+ if (input.bad_code && !input.good_code) {
31
+ return 'Rejected: "bad_code" was provided but "good_code" is missing. Both must be provided for a meaningful comparison.';
32
+ }
33
+ if (input.good_code && !input.bad_code) {
34
+ return 'Rejected: "good_code" was provided but "bad_code" is missing. Both must be provided for a meaningful comparison.';
35
+ }
36
+ // 4. Lesson should not just restate the problem
37
+ const lessonNorm = input.lesson.toLowerCase().trim();
38
+ if (lessonNorm === problemNorm) {
39
+ return 'Rejected: The "lesson" is just a restatement of the "problem". The lesson should be an actionable takeaway.';
40
+ }
41
+ return null; // Valid
42
+ }
43
+ /**
44
+ * Check for duplicate lessons (similar title + root_cause).
45
+ */
46
+ function checkDuplicate(input) {
47
+ const db = getDatabase();
48
+ // Search FTS for similar content
49
+ const stmt = db.prepare(`
50
+ SELECT l.id, l.title
51
+ FROM lessons_fts fts
52
+ JOIN lessons l ON l.id = fts.rowid
53
+ WHERE lessons_fts MATCH ?
54
+ LIMIT 3
55
+ `);
56
+ // Build a search query from key terms in title and root_cause
57
+ const searchTerms = `${input.title} ${input.root_cause}`
58
+ .replace(/[^\w\s]/g, ' ')
59
+ .split(/\s+/)
60
+ .filter(w => w.length > 3)
61
+ .slice(0, 5)
62
+ .join(' OR ');
63
+ if (!searchTerms)
64
+ return { isDuplicate: false };
65
+ try {
66
+ const results = stmt.all(searchTerms);
67
+ // Simple similarity check: if any existing title is very close
68
+ for (const result of results) {
69
+ const similarity = calculateSimilarity(input.title.toLowerCase(), result.title.toLowerCase());
70
+ if (similarity > 0.8) {
71
+ return { isDuplicate: true, existingId: result.id, existingTitle: result.title };
72
+ }
73
+ }
74
+ }
75
+ catch {
76
+ // FTS might fail on empty table — that's fine
77
+ log.debug('FTS search failed (possibly empty table), skipping duplicate check');
78
+ }
79
+ return { isDuplicate: false };
80
+ }
81
+ /**
82
+ * Simple Jaccard similarity between two strings (word-level).
83
+ */
84
+ function calculateSimilarity(a, b) {
85
+ const wordsA = new Set(a.split(/\s+/));
86
+ const wordsB = new Set(b.split(/\s+/));
87
+ const intersection = new Set([...wordsA].filter(w => wordsB.has(w)));
88
+ const union = new Set([...wordsA, ...wordsB]);
89
+ return union.size === 0 ? 0 : intersection.size / union.size;
90
+ }
91
+ /**
92
+ * Save a new programming lesson to the database.
93
+ * Called by the MCP save_lesson tool handler.
94
+ */
95
+ export function saveLesson(input) {
96
+ // Step 1: Validate lesson quality
97
+ const qualityError = validateLessonQuality(input);
98
+ if (qualityError) {
99
+ log.warn(`Lesson rejected: ${qualityError}`);
100
+ return { success: false, message: qualityError };
101
+ }
102
+ // Step 2: Check for duplicates
103
+ const dupCheck = checkDuplicate(input);
104
+ if (dupCheck.isDuplicate) {
105
+ const msg = `Duplicate detected: A similar lesson already exists (ID: ${dupCheck.existingId}, Title: "${dupCheck.existingTitle}"). No new lesson saved.`;
106
+ log.warn(msg);
107
+ return { success: false, message: msg };
108
+ }
109
+ // Step 3: Insert into database
110
+ const db = getDatabase();
111
+ // Calculate first review: tomorrow
112
+ const tomorrow = new Date();
113
+ tomorrow.setDate(tomorrow.getDate() + 1);
114
+ const nextReviewAt = tomorrow.toISOString();
115
+ const stmt = db.prepare(`
116
+ INSERT INTO lessons (
117
+ title, problem, error_message, root_cause,
118
+ bad_code, good_code, lesson, tags, language,
119
+ file_path, git_diff, review_questions,
120
+ next_review_at, status
121
+ ) VALUES (
122
+ ?, ?, ?, ?,
123
+ ?, ?, ?, ?, ?,
124
+ ?, ?, ?,
125
+ ?, 'new'
126
+ )
127
+ `);
128
+ const result = stmt.run(input.title, input.problem, input.error_message || null, input.root_cause, input.bad_code || null, input.good_code || null, input.lesson, JSON.stringify(input.tags), input.language || null, input.file_path || null, input.git_diff || null, JSON.stringify(input.review_questions), nextReviewAt);
129
+ const lessonId = Number(result.lastInsertRowid);
130
+ log.info(`Lesson saved successfully: ID=${lessonId}, Title="${input.title}"`);
131
+ return {
132
+ success: true,
133
+ message: `āœ… Lesson saved successfully!\n\nšŸ“– **${input.title}**\nšŸ†” ID: ${lessonId}\nšŸ·ļø Tags: ${input.tags.join(', ')}\nšŸ“… First review scheduled: tomorrow\n\nThe developer will be reminded to review this lesson using spaced repetition.`,
134
+ lessonId,
135
+ };
136
+ }
137
+ //# sourceMappingURL=save-lesson.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"save-lesson.js","sourceRoot":"","sources":["../../src/tools/save-lesson.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Zod schema for the save_lesson tool input.
4
+ * Enforces structured, high-quality lesson data from the AI assistant.
5
+ */
6
+ export declare const SaveLessonSchema: z.ZodObject<{
7
+ title: z.ZodString;
8
+ problem: z.ZodString;
9
+ error_message: z.ZodOptional<z.ZodString>;
10
+ root_cause: z.ZodString;
11
+ bad_code: z.ZodOptional<z.ZodString>;
12
+ good_code: z.ZodOptional<z.ZodString>;
13
+ lesson: z.ZodString;
14
+ tags: z.ZodArray<z.ZodString, "many">;
15
+ language: z.ZodOptional<z.ZodString>;
16
+ file_path: z.ZodOptional<z.ZodString>;
17
+ git_diff: z.ZodOptional<z.ZodString>;
18
+ review_questions: z.ZodArray<z.ZodObject<{
19
+ q: z.ZodString;
20
+ a: z.ZodString;
21
+ }, "strip", z.ZodTypeAny, {
22
+ q: string;
23
+ a: string;
24
+ }, {
25
+ q: string;
26
+ a: string;
27
+ }>, "many">;
28
+ }, "strip", z.ZodTypeAny, {
29
+ title: string;
30
+ problem: string;
31
+ root_cause: string;
32
+ lesson: string;
33
+ tags: string[];
34
+ review_questions: {
35
+ q: string;
36
+ a: string;
37
+ }[];
38
+ error_message?: string | undefined;
39
+ bad_code?: string | undefined;
40
+ good_code?: string | undefined;
41
+ language?: string | undefined;
42
+ file_path?: string | undefined;
43
+ git_diff?: string | undefined;
44
+ }, {
45
+ title: string;
46
+ problem: string;
47
+ root_cause: string;
48
+ lesson: string;
49
+ tags: string[];
50
+ review_questions: {
51
+ q: string;
52
+ a: string;
53
+ }[];
54
+ error_message?: string | undefined;
55
+ bad_code?: string | undefined;
56
+ good_code?: string | undefined;
57
+ language?: string | undefined;
58
+ file_path?: string | undefined;
59
+ git_diff?: string | undefined;
60
+ }>;
61
+ export type SaveLessonInput = z.infer<typeof SaveLessonSchema>;
62
+ /**
63
+ * Zod schema for the memory (search) tool input.
64
+ */
65
+ export declare const MemorySchema: z.ZodObject<{
66
+ query: z.ZodString;
67
+ limit: z.ZodDefault<z.ZodNumber>;
68
+ language: z.ZodOptional<z.ZodString>;
69
+ tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
70
+ }, "strip", z.ZodTypeAny, {
71
+ query: string;
72
+ limit: number;
73
+ tags?: string[] | undefined;
74
+ language?: string | undefined;
75
+ }, {
76
+ query: string;
77
+ tags?: string[] | undefined;
78
+ language?: string | undefined;
79
+ limit?: number | undefined;
80
+ }>;
81
+ export type MemoryInput = z.infer<typeof MemorySchema>;
82
+ //# sourceMappingURL=validators.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validators.d.ts","sourceRoot":"","sources":["../../src/tools/validators.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;GAGG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoE3B,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE/D;;GAEG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;EAuBvB,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC"}