eklavya 1.7.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 (95) hide show
  1. package/README.md +69 -0
  2. package/dist/ask.js +58 -0
  3. package/dist/ask.js.map +1 -0
  4. package/dist/assets/dashboard.html +407 -0
  5. package/dist/assets/tokens.css +135 -0
  6. package/dist/assets/tutor-skill.md +345 -0
  7. package/dist/cli.js +223 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/concurrency.js +41 -0
  10. package/dist/concurrency.js.map +1 -0
  11. package/dist/config.js +158 -0
  12. package/dist/config.js.map +1 -0
  13. package/dist/dashboard.js +214 -0
  14. package/dist/dashboard.js.map +1 -0
  15. package/dist/db.js +26 -0
  16. package/dist/db.js.map +1 -0
  17. package/dist/hooks/checkpoint-quiz.js +152 -0
  18. package/dist/hooks/checkpoint-quiz.js.map +1 -0
  19. package/dist/hooks/lib.js +177 -0
  20. package/dist/hooks/lib.js.map +1 -0
  21. package/dist/hooks/pre-tool-gate.js +53 -0
  22. package/dist/hooks/pre-tool-gate.js.map +1 -0
  23. package/dist/hooks/session-start.js +128 -0
  24. package/dist/hooks/session-start.js.map +1 -0
  25. package/dist/hooks/stop-quiz-check.js +147 -0
  26. package/dist/hooks/stop-quiz-check.js.map +1 -0
  27. package/dist/install.js +313 -0
  28. package/dist/install.js.map +1 -0
  29. package/dist/mcq.js +34 -0
  30. package/dist/mcq.js.map +1 -0
  31. package/dist/migrate.js +45 -0
  32. package/dist/migrate.js.map +1 -0
  33. package/dist/migrations/001_init.sql +71 -0
  34. package/dist/migrations/002_stop_markers.sql +14 -0
  35. package/dist/migrations/003_gate_repo.sql +5 -0
  36. package/dist/migrations/004_attempt_outcome.sql +15 -0
  37. package/dist/migrations/005_session_concept_origin.sql +24 -0
  38. package/dist/migrations/006_attempt_format.sql +31 -0
  39. package/dist/migrations/007_checkpoints.sql +23 -0
  40. package/dist/migrations/008_difficulty_levels.sql +57 -0
  41. package/dist/paths.js +25 -0
  42. package/dist/paths.js.map +1 -0
  43. package/dist/plugin/.claude-plugin/marketplace.json +14 -0
  44. package/dist/plugin/.claude-plugin/plugin.json +17 -0
  45. package/dist/plugin/.mcp.json +11 -0
  46. package/dist/plugin/agents/tutor.md +63 -0
  47. package/dist/plugin/cli/eklavya-gate +67 -0
  48. package/dist/plugin/hooks/hooks.json +57 -0
  49. package/dist/plugin/hooks/run.mjs +169 -0
  50. package/dist/plugin/scripts/install-git-hook.sh +81 -0
  51. package/dist/plugin/skills/gate/SKILL.md +17 -0
  52. package/dist/plugin/skills/learn/SKILL.md +28 -0
  53. package/dist/plugin/skills/level/SKILL.md +53 -0
  54. package/dist/plugin/skills/mode/SKILL.md +56 -0
  55. package/dist/plugin/skills/progress/SKILL.md +72 -0
  56. package/dist/plugin/skills/quiz/SKILL.md +31 -0
  57. package/dist/plugin/skills/setup/SKILL.md +49 -0
  58. package/dist/plugin/skills/tutor/SKILL.md +345 -0
  59. package/dist/seed/git.json +49 -0
  60. package/dist/seed/node-backend.json +43 -0
  61. package/dist/seed/react.json +46 -0
  62. package/dist/seed/web-auth.json +78 -0
  63. package/dist/seed.js +112 -0
  64. package/dist/seed.js.map +1 -0
  65. package/dist/server.js +52 -0
  66. package/dist/server.js.map +1 -0
  67. package/dist/session.js +24 -0
  68. package/dist/session.js.map +1 -0
  69. package/dist/slug.js +75 -0
  70. package/dist/slug.js.map +1 -0
  71. package/dist/srs.js +206 -0
  72. package/dist/srs.js.map +1 -0
  73. package/dist/store.js +450 -0
  74. package/dist/store.js.map +1 -0
  75. package/dist/tools/config_tools.js +119 -0
  76. package/dist/tools/config_tools.js.map +1 -0
  77. package/dist/tools/get_concept_graph.js +104 -0
  78. package/dist/tools/get_concept_graph.js.map +1 -0
  79. package/dist/tools/get_gate_status.js +20 -0
  80. package/dist/tools/get_gate_status.js.map +1 -0
  81. package/dist/tools/get_learner_profile.js +142 -0
  82. package/dist/tools/get_learner_profile.js.map +1 -0
  83. package/dist/tools/get_session_quiz_plan.js +393 -0
  84. package/dist/tools/get_session_quiz_plan.js.map +1 -0
  85. package/dist/tools/index.js +45 -0
  86. package/dist/tools/index.js.map +1 -0
  87. package/dist/tools/log_session_concepts.js +103 -0
  88. package/dist/tools/log_session_concepts.js.map +1 -0
  89. package/dist/tools/record_attempt.js +158 -0
  90. package/dist/tools/record_attempt.js.map +1 -0
  91. package/dist/tools/types.js +4 -0
  92. package/dist/tools/types.js.map +1 -0
  93. package/dist/tools/upsert_concepts.js +89 -0
  94. package/dist/tools/upsert_concepts.js.map +1 -0
  95. package/package.json +66 -0
@@ -0,0 +1,158 @@
1
+ import { z } from 'zod';
2
+ import { loadConfig } from '../config.js';
3
+ import { normalizeSlug } from '../slug.js';
4
+ import { decayedScore, isKnown } from '../srs.js';
5
+ import { resolveSessionId } from '../session.js';
6
+ import { conceptBySlug, gradeConcept, hasAskedQuestion, levelStanding, logSessionConcept, promoteIfEarned, syncGate, MAX_MCQ_GRADE, } from '../store.js';
7
+ import { stripAskFooter } from '../ask.js';
8
+ import { CWD_HINT, SESSION_HINT } from './types.js';
9
+ export const recordAttempt = {
10
+ name: 'record_attempt',
11
+ title: 'Record a quiz attempt',
12
+ description: 'Grade one answer on the 0-5 SM-2 scale and persist it. Updates mastery, the next review date, the session gate and this project\'s difficulty level. Record every response, including "I don\'t know" (grade 0, outcome dont_know, after you have taught it) and declines (grade 0, outcome declined). Pass format and, for multiple choice, the options you offered — put only the stem in question, never the options and never the ask_footer line, or the repeat check breaks. Multiple choice is capped at grade 4: picking one of four cannot show you know why. Returns level and level_progress, and level_up on the answer that earns a promotion — say that in one line and move on.',
13
+ inputSchema: {
14
+ session_id: z.string().optional().describe(SESSION_HINT),
15
+ cwd: z.string().optional().describe(CWD_HINT),
16
+ slug: z.string().describe('The concept that was asked about.'),
17
+ question: z
18
+ .string()
19
+ .describe('The question stem exactly as asked — WITHOUT the options and WITHOUT the ask_footer line. This text is what stops the same question coming back later, so anything baked in here that moves (shuffled options, the level in the footer) would make one question look like several.'),
20
+ answer: z
21
+ .string()
22
+ .optional()
23
+ .describe('The learner\'s answer verbatim — for multiple choice, the option they picked (or what they typed under "Other"). Omit for a skip.'),
24
+ grade: z
25
+ .number()
26
+ .int()
27
+ .min(0)
28
+ .max(5)
29
+ .describe('0 no answer/skip, 1-2 wrong, 3 correct but laboured, 4 correct, 5 correct and explained the why.'),
30
+ difficulty: z.number().int().min(1).max(5).describe('The tier you actually asked at — use tier_to_ask from the plan.'),
31
+ feedback: z.string().optional().describe('The short explanation you gave back.'),
32
+ format: z
33
+ .enum(['mcq', 'fill_blank', 'open'])
34
+ .optional()
35
+ .describe('How you put the question. "mcq" is the default shape — four options via AskUserQuestion. Say so, because a correct multiple-choice answer is weaker evidence than a correct free one and is graded accordingly.'),
36
+ options: z
37
+ .array(z.string())
38
+ .optional()
39
+ .describe('For mcq: the option labels you offered, in the order shown. Not the stem.'),
40
+ outcome: z
41
+ .enum(['answered', 'dont_know', 'declined'])
42
+ .optional()
43
+ .describe('Why the grade is what it is. "answered" they attempted it; "dont_know" they said they did not know and you taught it; "declined" they chose to skip. Grade 0 covers the last two, so this is the only thing that tells them apart later.'),
44
+ },
45
+ handler: (args, { db }) => {
46
+ const now = new Date();
47
+ const { config, repoRoot } = loadConfig(args.cwd);
48
+ const sessionId = resolveSessionId(db, args.session_id);
49
+ const slug = normalizeSlug(args.slug);
50
+ const concept = conceptBySlug(db, slug);
51
+ if (!concept) {
52
+ return {
53
+ error: 'unknown_concept',
54
+ slug,
55
+ detail: 'No concept with that slug. Call upsert_concepts or log_session_concepts first.',
56
+ };
57
+ }
58
+ // The footer is presentation. Stripped rather than rejected, because the
59
+ // tutor pasting back the block it displayed is the likely mistake and losing
60
+ // a real answer over it would be the wrong trade.
61
+ const question = stripAskFooter(args.question);
62
+ // Which band this answer was earned at, read before the write so a promotion
63
+ // triggered by this very attempt cannot relabel it.
64
+ const standing = levelStanding(db, config, repoRoot);
65
+ // Checked before the write, or the question we are recording matches itself.
66
+ const repeatQuestion = hasAskedQuestion(db, concept.id, question);
67
+ // Enforced here rather than trusted to the tutor. Grade 5 means "explained
68
+ // why", which choosing among four options cannot demonstrate; one in four is
69
+ // a coin. Capping is visible in the response so a tutor that keeps awarding
70
+ // 5s for multiple choice finds out.
71
+ const capped = args.format === 'mcq' && args.grade > MAX_MCQ_GRADE;
72
+ const grade = capped ? MAX_MCQ_GRADE : args.grade;
73
+ const graded = db.transaction(() => {
74
+ // An attempt on a concept the session never logged still counts toward
75
+ // `answered`, so quizzing on review debt is not free -- but it lands as
76
+ // 'review', so it cannot satisfy a bar that the session's actual work set.
77
+ // If the task did touch this concept, log_session_concepts has already
78
+ // marked it 'work' and that wins.
79
+ logSessionConcept(db, sessionId, concept.id, null, 'review');
80
+ const next = gradeConcept(db, {
81
+ conceptId: concept.id,
82
+ sessionId,
83
+ question,
84
+ answer: args.answer ?? null,
85
+ grade,
86
+ difficulty: args.difficulty,
87
+ feedback: args.feedback ?? null,
88
+ format: args.format ?? null,
89
+ options: args.options ?? null,
90
+ // Left NULL rather than guessed when the tutor does not say. An absent
91
+ // answer is a fair hint that nothing was attempted, but it cannot tell
92
+ // "teach me" from "leave it" -- and inventing the difference here would
93
+ // put a value in the column that nobody observed.
94
+ outcome: args.outcome ?? null,
95
+ repo: standing.repo,
96
+ level: standing.level,
97
+ now,
98
+ });
99
+ // In the same transaction as the grade: a promotion is a fact about
100
+ // attempt rows, and a crash between the two would leave a level claiming
101
+ // evidence that was rolled back.
102
+ return { state: next, promotion: promoteIfEarned(db, config, repoRoot) };
103
+ })();
104
+ const state = graded.state;
105
+ const after = levelStanding(db, config, repoRoot);
106
+ const gate = syncGate(db, sessionId, config, { repo: repoRoot });
107
+ const score = decayedScore(state.score, state.next_review, now);
108
+ return {
109
+ slug: concept.slug,
110
+ recorded_grade: grade,
111
+ // Silence here would let the tutor keep miscalibrating; say what was
112
+ // changed and why.
113
+ ...(capped
114
+ ? {
115
+ grade_capped: true,
116
+ detail: `Multiple choice is capped at ${MAX_MCQ_GRADE}: recognising the right option does not show you can explain it. Recorded ${grade} instead of ${args.grade}.`,
117
+ }
118
+ : {}),
119
+ new_score: Number(state.score.toFixed(3)),
120
+ next_review: state.next_review,
121
+ interval_days: state.interval_d,
122
+ reps: state.reps,
123
+ ease: Number(state.ease.toFixed(2)),
124
+ known: isKnown({ score, reps: state.reps }),
125
+ // PRD goal 2. Recorded either way — refusing the write would lose a real
126
+ // answer — but the tutor is told, so the next question can be a new one.
127
+ repeat_question: repeatQuestion,
128
+ gate,
129
+ level: after.level,
130
+ // The runway, said in numbers. A level nobody can see the progress toward
131
+ // is a level that feels identical in week one and week ten.
132
+ level_progress: {
133
+ passed: after.counts.passed,
134
+ needed: after.needed.answers,
135
+ answered: after.counts.answered,
136
+ accuracy: after.accuracy,
137
+ min_accuracy: after.needed.accuracy,
138
+ concepts: after.counts.concepts,
139
+ needed_concepts: after.needed.concepts,
140
+ next: after.next,
141
+ ...(after.pinned ? { pinned: true } : {}),
142
+ ...(after.unmet.length > 0 ? { unmet: after.unmet } : {}),
143
+ },
144
+ ...(graded.promotion
145
+ ? {
146
+ level_up: {
147
+ from: graded.promotion.from,
148
+ to: graded.promotion.to,
149
+ passed: graded.promotion.counts.passed,
150
+ accuracy: Number((graded.promotion.counts.passed / Math.max(1, graded.promotion.counts.answered)).toFixed(3)),
151
+ detail: `Say this in one line and go back to the task: they have cleared ${graded.promotion.from} on this project, and questions now come from the ${graded.promotion.to} band.`,
152
+ },
153
+ }
154
+ : {}),
155
+ };
156
+ },
157
+ };
158
+ //# sourceMappingURL=record_attempt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record_attempt.js","sourceRoot":"","sources":["../../src/tools/record_attempt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EACL,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,QAAQ,EACR,aAAa,GAGd,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAgB,MAAM,YAAY,CAAC;AAElE,MAAM,CAAC,MAAM,aAAa,GAAY;IACpC,IAAI,EAAE,gBAAgB;IACtB,KAAK,EAAE,uBAAuB;IAC9B,WAAW,EACT,gqBAAgqB;IAClqB,WAAW,EAAE;QACX,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC;QACxD,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAC7C,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,mCAAmC,CAAC;QAC9D,QAAQ,EAAE,CAAC;aACR,MAAM,EAAE;aACR,QAAQ,CACP,oRAAoR,CACrR;QACH,MAAM,EAAE,CAAC;aACN,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CACP,mIAAmI,CACpI;QACH,KAAK,EAAE,CAAC;aACL,MAAM,EAAE;aACR,GAAG,EAAE;aACL,GAAG,CAAC,CAAC,CAAC;aACN,GAAG,CAAC,CAAC,CAAC;aACN,QAAQ,CAAC,kGAAkG,CAAC;QAC/G,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,iEAAiE,CAAC;QACtH,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sCAAsC,CAAC;QAChF,MAAM,EAAE,CAAC;aACN,IAAI,CAAC,CAAC,KAAK,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC;aACnC,QAAQ,EAAE;aACV,QAAQ,CACP,iNAAiN,CAClN;QACH,OAAO,EAAE,CAAC;aACP,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;aACjB,QAAQ,EAAE;aACV,QAAQ,CAAC,2EAA2E,CAAC;QACxF,OAAO,EAAE,CAAC;aACP,IAAI,CAAC,CAAC,UAAU,EAAE,WAAW,EAAE,UAAU,CAAC,CAAC;aAC3C,QAAQ,EAAE;aACV,QAAQ,CACP,0OAA0O,CAC3O;KACJ;IACD,OAAO,EAAE,CACP,IAYC,EACD,EAAE,EAAE,EAAE,EACN,EAAE;QACF,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAClD,MAAM,SAAS,GAAG,gBAAgB,CAAC,EAAE,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;QAExD,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtC,MAAM,OAAO,GAAG,aAAa,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QACxC,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO;gBACL,KAAK,EAAE,iBAAiB;gBACxB,IAAI;gBACJ,MAAM,EAAE,gFAAgF;aACzF,CAAC;QACJ,CAAC;QAED,yEAAyE;QACzE,6EAA6E;QAC7E,kDAAkD;QAClD,MAAM,QAAQ,GAAG,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAE/C,6EAA6E;QAC7E,oDAAoD;QACpD,MAAM,QAAQ,GAAG,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;QAErD,6EAA6E;QAC7E,MAAM,cAAc,GAAG,gBAAgB,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;QAElE,2EAA2E;QAC3E,6EAA6E;QAC7E,4EAA4E;QAC5E,oCAAoC;QACpC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,GAAG,aAAa,CAAC;QACnE,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC;QAElD,MAAM,MAAM,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE;YACjC,uEAAuE;YACvE,wEAAwE;YACxE,2EAA2E;YAC3E,uEAAuE;YACvE,kCAAkC;YAClC,iBAAiB,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;YAC7D,MAAM,IAAI,GAAG,YAAY,CAAC,EAAE,EAAE;gBAC5B,SAAS,EAAE,OAAO,CAAC,EAAE;gBACrB,SAAS;gBACT,QAAQ;gBACR,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,IAAI;gBAC3B,KAAK;gBACL,UAAU,EAAE,IAAI,CAAC,UAAU;gBAC3B,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,IAAI;gBAC/B,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,IAAI;gBAC3B,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,IAAI;gBAC7B,uEAAuE;gBACvE,uEAAuE;gBACvE,wEAAwE;gBACxE,kDAAkD;gBAClD,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,IAAI;gBAC7B,IAAI,EAAE,QAAQ,CAAC,IAAI;gBACnB,KAAK,EAAE,QAAQ,CAAC,KAAK;gBACrB,GAAG;aACJ,CAAC,CAAC;YAEH,oEAAoE;YACpE,yEAAyE;YACzE,iCAAiC;YACjC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,eAAe,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC;QAC3E,CAAC,CAAC,EAAE,CAAC;QAEL,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;QAC3B,MAAM,KAAK,GAAG,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;QAClD,MAAM,IAAI,GAAG,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;QACjE,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;QAEhE,OAAO;YACL,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,cAAc,EAAE,KAAK;YACrB,qEAAqE;YACrE,mBAAmB;YACnB,GAAG,CAAC,MAAM;gBACR,CAAC,CAAC;oBACE,YAAY,EAAE,IAAI;oBAClB,MAAM,EAAE,gCAAgC,aAAa,6EAA6E,KAAK,eAAe,IAAI,CAAC,KAAK,GAAG;iBACpK;gBACH,CAAC,CAAC,EAAE,CAAC;YACP,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YACzC,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,aAAa,EAAE,KAAK,CAAC,UAAU;YAC/B,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YACnC,KAAK,EAAE,OAAO,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC;YAC3C,yEAAyE;YACzE,yEAAyE;YACzE,eAAe,EAAE,cAAc;YAC/B,IAAI;YACJ,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,0EAA0E;YAC1E,4DAA4D;YAC5D,cAAc,EAAE;gBACd,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM;gBAC3B,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,OAAO;gBAC5B,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,QAAQ;gBAC/B,QAAQ,EAAE,KAAK,CAAC,QAAQ;gBACxB,YAAY,EAAE,KAAK,CAAC,MAAM,CAAC,QAAQ;gBACnC,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,QAAQ;gBAC/B,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC,QAAQ;gBACtC,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACzC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC1D;YACD,GAAG,CAAC,MAAM,CAAC,SAAS;gBAClB,CAAC,CAAC;oBACE,QAAQ,EAAE;wBACR,IAAI,EAAE,MAAM,CAAC,SAAS,CAAC,IAAI;wBAC3B,EAAE,EAAE,MAAM,CAAC,SAAS,CAAC,EAAE;wBACvB,MAAM,EAAE,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM;wBACtC,QAAQ,EAAE,MAAM,CACd,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAC5F;wBACD,MAAM,EAAE,mEAAmE,MAAM,CAAC,SAAS,CAAC,IAAI,qDAAqD,MAAM,CAAC,SAAS,CAAC,EAAE,QAAQ;qBACjL;iBACF;gBACH,CAAC,CAAC,EAAE,CAAC;SACR,CAAC;IACJ,CAAC;CACF,CAAC"}
@@ -0,0 +1,4 @@
1
+ /** Shared arg shapes so every tool speaks the same dialect. */
2
+ export const CWD_HINT = 'Working directory, so the repo-level .eklavya.json is found. Defaults to the server cwd.';
3
+ export const SESSION_HINT = 'Claude Code session id. Optional — omit it and the server resolves the current session itself.';
4
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/tools/types.ts"],"names":[],"mappings":"AAeA,+DAA+D;AAC/D,MAAM,CAAC,MAAM,QAAQ,GAAG,0FAA0F,CAAC;AACnH,MAAM,CAAC,MAAM,YAAY,GACvB,gGAAgG,CAAC"}
@@ -0,0 +1,89 @@
1
+ import { z } from 'zod';
2
+ import { loadConfig } from '../config.js';
3
+ import { normalizeSlug, isValidSlug, findFuzzyMatch } from '../slug.js';
4
+ import { resolveSessionId } from '../session.js';
5
+ import { allConceptSlugs, conceptBySlug, insertConcept, newConceptsThisSession } from '../store.js';
6
+ import { CWD_HINT, SESSION_HINT } from './types.js';
7
+ export const upsertConcepts = {
8
+ name: 'upsert_concepts',
9
+ title: 'Upsert concepts',
10
+ description: 'Grow the knowledge graph when work touches a concept that has no slug yet. Slugs are normalized and near-duplicates are folded into the existing concept, so check the returned canonical slug before using it.',
11
+ inputSchema: {
12
+ session_id: z.string().optional().describe(SESSION_HINT),
13
+ cwd: z.string().optional().describe(CWD_HINT),
14
+ concepts: z
15
+ .array(z.object({
16
+ slug: z.string(),
17
+ name: z.string().optional(),
18
+ domain: z.string().optional(),
19
+ tier: z.number().int().min(1).max(5).optional(),
20
+ description: z.string().optional(),
21
+ }))
22
+ .min(1),
23
+ edges: z
24
+ .array(z.object({
25
+ from: z.string(),
26
+ to: z.string(),
27
+ relation: z.enum(['prerequisite_of', 'related_to', 'part_of']),
28
+ }))
29
+ .optional()
30
+ .describe('Prefer prerequisite_of edges — they are what orders a teaching session.'),
31
+ },
32
+ handler: (args, { db }) => {
33
+ const { config } = loadConfig(args.cwd);
34
+ const sessionId = resolveSessionId(db, args.session_id);
35
+ const created = [];
36
+ const matched = [];
37
+ const rejected = [];
38
+ const resolvedSlug = new Map();
39
+ let edgesAdded = 0;
40
+ db.transaction(() => {
41
+ let budget = Math.max(0, config.max_new_concepts_per_session - newConceptsThisSession(db, sessionId));
42
+ for (const input of args.concepts) {
43
+ const slug = normalizeSlug(input.slug);
44
+ if (!isValidSlug(slug)) {
45
+ rejected.push({ slug: input.slug, reason: 'invalid_slug' });
46
+ continue;
47
+ }
48
+ const existing = conceptBySlug(db, slug);
49
+ if (existing) {
50
+ resolvedSlug.set(input.slug, existing.slug);
51
+ matched.push({ requested: slug, resolved: existing.slug });
52
+ continue;
53
+ }
54
+ const near = findFuzzyMatch(slug, allConceptSlugs(db));
55
+ if (near) {
56
+ resolvedSlug.set(input.slug, near.slug);
57
+ matched.push({ requested: slug, resolved: near.slug });
58
+ continue;
59
+ }
60
+ if (budget <= 0) {
61
+ rejected.push({ slug, reason: 'session_cap_reached' });
62
+ continue;
63
+ }
64
+ const concept = insertConcept(db, {
65
+ slug,
66
+ name: input.name ?? slug.replace(/-/g, ' '),
67
+ domain: input.domain ?? 'general',
68
+ description: input.description ?? null,
69
+ tier: input.tier ?? 2,
70
+ source: 'llm',
71
+ });
72
+ resolvedSlug.set(input.slug, concept.slug);
73
+ created.push(concept.slug);
74
+ budget -= 1;
75
+ }
76
+ const insertEdge = db.prepare('INSERT OR IGNORE INTO edges (from_concept, to_concept, relation) VALUES (?, ?, ?)');
77
+ for (const edge of args.edges ?? []) {
78
+ const from = conceptBySlug(db, resolvedSlug.get(edge.from) ?? normalizeSlug(edge.from));
79
+ const to = conceptBySlug(db, resolvedSlug.get(edge.to) ?? normalizeSlug(edge.to));
80
+ if (!from || !to || from.id === to.id)
81
+ continue;
82
+ const result = insertEdge.run(from.id, to.id, edge.relation);
83
+ edgesAdded += result.changes;
84
+ }
85
+ })();
86
+ return { created, matched, rejected, edges_added: edgesAdded };
87
+ },
88
+ };
89
+ //# sourceMappingURL=upsert_concepts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upsert_concepts.js","sourceRoot":"","sources":["../../src/tools/upsert_concepts.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "eklavya",
3
+ "version": "1.7.0",
4
+ "description": "Learn while your agent works \u2014 local knowledge graph, spaced repetition, and quiz gating for agent-assisted development.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/ProjectAJ14/eklavya.git",
9
+ "directory": "mcp"
10
+ },
11
+ "homepage": "https://github.com/ProjectAJ14/eklavya#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/ProjectAJ14/eklavya/issues"
14
+ },
15
+ "author": "Ajay Kumar",
16
+ "keywords": [
17
+ "mcp",
18
+ "claude-code",
19
+ "learning",
20
+ "spaced-repetition",
21
+ "sm-2",
22
+ "knowledge-graph",
23
+ "tutor",
24
+ "mentorship"
25
+ ],
26
+ "type": "module",
27
+ "bin": {
28
+ "eklavya": "dist/cli.js"
29
+ },
30
+ "files": [
31
+ "dist",
32
+ "README.md"
33
+ ],
34
+ "publishConfig": {
35
+ "access": "public",
36
+ "provenance": true
37
+ },
38
+ "engines": {
39
+ "node": ">=22"
40
+ },
41
+ "scripts": {
42
+ "build": "tsc -p tsconfig.json && node scripts/copy-assets.mjs",
43
+ "dev": "tsx src/server.ts",
44
+ "test": "vitest run",
45
+ "test:watch": "vitest",
46
+ "pretest": "npm run build",
47
+ "prepublishOnly": "npm run build"
48
+ },
49
+ "dependencies": {
50
+ "@modelcontextprotocol/sdk": "^1.0.0",
51
+ "better-sqlite3": "^12.0.0",
52
+ "zod": "^4.4.3"
53
+ },
54
+ "devDependencies": {
55
+ "@types/better-sqlite3": "^7.6.11",
56
+ "@types/node": "^22.0.0",
57
+ "tsx": "^4.19.0",
58
+ "typescript": "^5.6.0",
59
+ "vitest": "^4.1.11"
60
+ },
61
+ "allowScripts": {
62
+ "better-sqlite3@12.11.1": true,
63
+ "esbuild@0.28.2": true,
64
+ "esbuild@0.21.5": true
65
+ }
66
+ }