eklavya-mcp 1.0.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 (55) hide show
  1. package/README.md +42 -0
  2. package/bin/eklavya-mcp.sh +25 -0
  3. package/dist/assets/tutor-skill.md +106 -0
  4. package/dist/cli.js +145 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/concurrency.js +41 -0
  7. package/dist/concurrency.js.map +1 -0
  8. package/dist/config.js +116 -0
  9. package/dist/config.js.map +1 -0
  10. package/dist/db.js +26 -0
  11. package/dist/db.js.map +1 -0
  12. package/dist/migrate.js +45 -0
  13. package/dist/migrate.js.map +1 -0
  14. package/dist/migrations/001_init.sql +71 -0
  15. package/dist/migrations/002_stop_markers.sql +14 -0
  16. package/dist/migrations/003_gate_repo.sql +5 -0
  17. package/dist/paths.js +25 -0
  18. package/dist/paths.js.map +1 -0
  19. package/dist/seed/git.json +49 -0
  20. package/dist/seed/node-backend.json +43 -0
  21. package/dist/seed/react.json +46 -0
  22. package/dist/seed/web-auth.json +78 -0
  23. package/dist/seed.js +112 -0
  24. package/dist/seed.js.map +1 -0
  25. package/dist/server.js +39 -0
  26. package/dist/server.js.map +1 -0
  27. package/dist/session.js +24 -0
  28. package/dist/session.js.map +1 -0
  29. package/dist/slug.js +75 -0
  30. package/dist/slug.js.map +1 -0
  31. package/dist/srs.js +130 -0
  32. package/dist/srs.js.map +1 -0
  33. package/dist/store.js +122 -0
  34. package/dist/store.js.map +1 -0
  35. package/dist/tools/config_tools.js +73 -0
  36. package/dist/tools/config_tools.js.map +1 -0
  37. package/dist/tools/get_concept_graph.js +104 -0
  38. package/dist/tools/get_concept_graph.js.map +1 -0
  39. package/dist/tools/get_gate_status.js +20 -0
  40. package/dist/tools/get_gate_status.js.map +1 -0
  41. package/dist/tools/get_learner_profile.js +78 -0
  42. package/dist/tools/get_learner_profile.js.map +1 -0
  43. package/dist/tools/get_session_quiz_plan.js +107 -0
  44. package/dist/tools/get_session_quiz_plan.js.map +1 -0
  45. package/dist/tools/index.js +45 -0
  46. package/dist/tools/index.js.map +1 -0
  47. package/dist/tools/log_session_concepts.js +83 -0
  48. package/dist/tools/log_session_concepts.js.map +1 -0
  49. package/dist/tools/record_attempt.js +69 -0
  50. package/dist/tools/record_attempt.js.map +1 -0
  51. package/dist/tools/types.js +4 -0
  52. package/dist/tools/types.js.map +1 -0
  53. package/dist/tools/upsert_concepts.js +89 -0
  54. package/dist/tools/upsert_concepts.js.map +1 -0
  55. package/package.json +68 -0
@@ -0,0 +1,107 @@
1
+ import { z } from 'zod';
2
+ import { loadConfig } from '../config.js';
3
+ import { decayedScore, isDue, isKnown, nextTierToAsk } from '../srs.js';
4
+ import { resolveSessionId } from '../session.js';
5
+ import { lastAttempt, lastAttemptAt, masteryFor, sessionConcepts } from '../store.js';
6
+ import { CWD_HINT, SESSION_HINT } from './types.js';
7
+ function minutesSince(iso, now) {
8
+ if (!iso)
9
+ return Number.POSITIVE_INFINITY;
10
+ // SQLite datetime('now') is UTC without a zone marker; make that explicit.
11
+ const t = Date.parse(iso.includes('T') ? iso : `${iso.replace(' ', 'T')}Z`);
12
+ if (!Number.isFinite(t))
13
+ return Number.POSITIVE_INFINITY;
14
+ return (now.getTime() - t) / 60_000;
15
+ }
16
+ export const getSessionQuizPlan = {
17
+ name: 'get_session_quiz_plan',
18
+ title: 'Get session quiz plan',
19
+ description: 'What to quiz on right now and at what difficulty tier, chosen from this session\'s concepts and whatever is due for review. Returns questions_needed: 0 when there is nothing worth asking.',
20
+ inputSchema: {
21
+ session_id: z.string().optional().describe(SESSION_HINT),
22
+ cwd: z.string().optional().describe(CWD_HINT),
23
+ max: z.number().int().min(1).max(10).optional().describe('Cap on questions. Defaults to the configured max_questions_per_task.'),
24
+ },
25
+ handler: (args, { db }) => {
26
+ const now = new Date();
27
+ const { config } = loadConfig(args.cwd);
28
+ const sessionId = resolveSessionId(db, args.session_id);
29
+ const max = args.max ?? config.max_questions_per_task;
30
+ if (config.mode === 'off') {
31
+ return { session_id: sessionId, questions_needed: 0, concepts: [], reason: 'mode_off' };
32
+ }
33
+ // Cooldown keeps ambient mode from nagging. It must never apply in enforced
34
+ // mode, or a cooldown could make a commit gate unpassable (decision G5).
35
+ if (config.mode === 'ambient') {
36
+ const since = minutesSince(lastAttemptAt(db, sessionId), now);
37
+ if (since < config.min_minutes_between_quizzes) {
38
+ return {
39
+ session_id: sessionId,
40
+ questions_needed: 0,
41
+ concepts: [],
42
+ reason: 'cooldown',
43
+ minutes_remaining: Math.ceil(config.min_minutes_between_quizzes - since),
44
+ };
45
+ }
46
+ }
47
+ const picked = [];
48
+ const seen = new Set();
49
+ const domains = new Set();
50
+ const add = (concept, context, reason) => {
51
+ if (seen.has(concept.slug) || picked.length >= max)
52
+ return;
53
+ const m = masteryFor(db, concept.id);
54
+ const score = decayedScore(m.score, m.next_review, now);
55
+ const last = lastAttempt(db, concept.id);
56
+ seen.add(concept.slug);
57
+ picked.push({
58
+ slug: concept.slug,
59
+ name: concept.name,
60
+ domain: concept.domain,
61
+ tier_to_ask: nextTierToAsk({
62
+ conceptTier: concept.tier,
63
+ lastDifficulty: last?.difficulty ?? null,
64
+ lastGrade: last?.grade ?? null,
65
+ score,
66
+ }),
67
+ context,
68
+ last_grade: last?.grade ?? null,
69
+ reason,
70
+ });
71
+ };
72
+ const touched = sessionConcepts(db, sessionId);
73
+ for (const c of touched)
74
+ domains.add(c.domain);
75
+ // (a) concepts this session touched that the learner has not mastered
76
+ for (const c of touched) {
77
+ const m = masteryFor(db, c.id);
78
+ const score = decayedScore(m.score, m.next_review, now);
79
+ if (!isKnown({ score, reps: m.reps }))
80
+ add(c, c.context, 'unmastered');
81
+ }
82
+ // (b) concepts this session touched that are due for review
83
+ for (const c of touched) {
84
+ const m = masteryFor(db, c.id);
85
+ if (isDue(m.next_review, now))
86
+ add(c, c.context, 'due_review');
87
+ }
88
+ // (c) anything else due in the same domains, so review debt gets paid down
89
+ if (picked.length < max && domains.size > 0) {
90
+ const placeholders = [...domains].map(() => '?').join(',');
91
+ const rows = db
92
+ .prepare(`SELECT c.* FROM concepts c
93
+ JOIN mastery m ON m.concept_id = c.id
94
+ WHERE c.domain IN (${placeholders}) AND m.next_review IS NOT NULL AND m.next_review <= ?
95
+ ORDER BY m.next_review ASC`)
96
+ .all(...domains, now.toISOString());
97
+ for (const c of rows)
98
+ add(c, null, 'domain_review');
99
+ }
100
+ return {
101
+ session_id: sessionId,
102
+ questions_needed: picked.length,
103
+ concepts: picked,
104
+ };
105
+ },
106
+ };
107
+ //# sourceMappingURL=get_session_quiz_plan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get_session_quiz_plan.js","sourceRoot":"","sources":["../../src/tools/get_session_quiz_plan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ import { retryOnBusy } from '../concurrency.js';
2
+ import { getLearnerProfile } from './get_learner_profile.js';
3
+ import { logSessionConcepts } from './log_session_concepts.js';
4
+ import { getSessionQuizPlan } from './get_session_quiz_plan.js';
5
+ import { recordAttempt } from './record_attempt.js';
6
+ import { getGateStatus } from './get_gate_status.js';
7
+ import { upsertConcepts } from './upsert_concepts.js';
8
+ import { getConceptGraph } from './get_concept_graph.js';
9
+ import { getConfig, setConfig } from './config_tools.js';
10
+ export const TOOLS = [
11
+ getLearnerProfile,
12
+ logSessionConcepts,
13
+ getSessionQuizPlan,
14
+ recordAttempt,
15
+ getGateStatus,
16
+ upsertConcepts,
17
+ getConceptGraph,
18
+ getConfig,
19
+ setConfig,
20
+ ];
21
+ /** Tool results are JSON text: compact, and small enough to live in context (decision G7). */
22
+ function toResult(payload) {
23
+ const text = JSON.stringify(payload);
24
+ const isError = typeof payload === 'object' && payload !== null && 'error' in payload;
25
+ return { content: [{ type: 'text', text }], isError };
26
+ }
27
+ export function registerTools(server, db) {
28
+ for (const tool of TOOLS) {
29
+ server.registerTool(tool.name, { title: tool.title, description: tool.description, inputSchema: tool.inputSchema }, async (args) => {
30
+ try {
31
+ // Safe to retry: a busy transaction has already rolled back.
32
+ return toResult(retryOnBusy(() => tool.handler(args, { db })));
33
+ }
34
+ catch (err) {
35
+ // A tool throwing must not take the server down mid-session.
36
+ return toResult({
37
+ error: 'tool_failed',
38
+ tool: tool.name,
39
+ detail: err instanceof Error ? err.message : String(err),
40
+ });
41
+ }
42
+ });
43
+ }
44
+ }
45
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEzD,MAAM,CAAC,MAAM,KAAK,GAAc;IAC9B,iBAAiB;IACjB,kBAAkB;IAClB,kBAAkB;IAClB,aAAa;IACb,aAAa;IACb,cAAc;IACd,eAAe;IACf,SAAS;IACT,SAAS;CACV,CAAC;AAEF,8FAA8F;AAC9F,SAAS,QAAQ,CAAC,OAAgB;IAChC,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACrC,MAAM,OAAO,GAAG,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,IAAI,OAAO,CAAC;IACtF,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC;AACjE,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,MAAiB,EAAE,EAAM;IACrD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,CAAC,YAAY,CACjB,IAAI,CAAC,IAAI,EACT,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,EACnF,KAAK,EAAE,IAAa,EAAE,EAAE;YACtB,IAAI,CAAC;gBACH,6DAA6D;gBAC7D,OAAO,QAAQ,CAAC,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;YACjE,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,6DAA6D;gBAC7D,OAAO,QAAQ,CAAC;oBACd,KAAK,EAAE,aAAa;oBACpB,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,MAAM,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;iBACzD,CAAC,CAAC;YACL,CAAC;QACH,CAAC,CACF,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -0,0 +1,83 @@
1
+ import { z } from 'zod';
2
+ import { loadConfig } from '../config.js';
3
+ import { normalizeSlug, findFuzzyMatch } from '../slug.js';
4
+ import { decayedScore, isKnown } from '../srs.js';
5
+ import { resolveSessionId } from '../session.js';
6
+ import { allConceptSlugs, conceptBySlug, insertConcept, logSessionConcept, masteryFor, newConceptsThisSession, sessionConcepts, syncGate, } from '../store.js';
7
+ import { CWD_HINT, SESSION_HINT } from './types.js';
8
+ const DEFAULT_DOMAIN = 'general';
9
+ const DEFAULT_TIER = 2;
10
+ export const logSessionConcepts = {
11
+ name: 'log_session_concepts',
12
+ title: 'Log session concepts',
13
+ description: 'Record the concepts the current task genuinely exercises, each with a one-line context pointing at the real code you wrote. Call this while implementing, batched, 3-8 concepts per task. Unknown slugs are created automatically.',
14
+ inputSchema: {
15
+ session_id: z.string().optional().describe(SESSION_HINT),
16
+ cwd: z.string().optional().describe(CWD_HINT),
17
+ concepts: z
18
+ .array(z.object({
19
+ slug: z.string().describe('kebab-case, e.g. "httponly-cookies"'),
20
+ name: z.string().optional(),
21
+ domain: z.string().optional(),
22
+ tier: z.number().int().min(1).max(5).optional(),
23
+ context: z.string().optional().describe('One line naming the actual code, e.g. "set httpOnly on the refresh cookie in auth.ts".'),
24
+ }))
25
+ .min(1),
26
+ },
27
+ handler: (args, { db }) => {
28
+ const now = new Date();
29
+ const { config, repoRoot } = loadConfig(args.cwd);
30
+ const sessionId = resolveSessionId(db, args.session_id);
31
+ const created = [];
32
+ const matched = [];
33
+ const logged = [];
34
+ let capped = 0;
35
+ const apply = db.transaction(() => {
36
+ let budget = Math.max(0, config.max_new_concepts_per_session - newConceptsThisSession(db, sessionId));
37
+ for (const input of args.concepts) {
38
+ const slug = normalizeSlug(input.slug);
39
+ if (!slug)
40
+ continue;
41
+ let concept = conceptBySlug(db, slug);
42
+ if (!concept) {
43
+ const near = findFuzzyMatch(slug, allConceptSlugs(db));
44
+ if (near) {
45
+ concept = conceptBySlug(db, near.slug);
46
+ if (concept)
47
+ matched.push({ requested: slug, resolved: concept.slug });
48
+ }
49
+ }
50
+ if (!concept) {
51
+ if (budget <= 0) {
52
+ capped += 1;
53
+ continue;
54
+ }
55
+ concept = insertConcept(db, {
56
+ slug,
57
+ name: input.name ?? slug.replace(/-/g, ' '),
58
+ domain: input.domain ?? DEFAULT_DOMAIN,
59
+ tier: input.tier ?? DEFAULT_TIER,
60
+ source: 'llm',
61
+ });
62
+ created.push(concept.slug);
63
+ budget -= 1;
64
+ }
65
+ logSessionConcept(db, sessionId, concept.id, input.context ?? null);
66
+ logged.push(concept.slug);
67
+ }
68
+ });
69
+ apply();
70
+ // The gate's bar rises with the work: how many touched concepts are still
71
+ // unmastered, capped at the configured questions per task.
72
+ const unmastered = sessionConcepts(db, sessionId).filter((c) => {
73
+ const m = masteryFor(db, c.id);
74
+ return !isKnown({ score: decayedScore(m.score, m.next_review, now), reps: m.reps });
75
+ }).length;
76
+ const gate = syncGate(db, sessionId, config, {
77
+ requiredHint: Math.min(unmastered, config.max_questions_per_task),
78
+ repo: repoRoot,
79
+ });
80
+ return { session_id: sessionId, logged, created, matched, capped, gate };
81
+ },
82
+ };
83
+ //# sourceMappingURL=log_session_concepts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log_session_concepts.js","sourceRoot":"","sources":["../../src/tools/log_session_concepts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,69 @@
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, logSessionConcept, syncGate } from '../store.js';
7
+ import { CWD_HINT, SESSION_HINT } from './types.js';
8
+ export const recordAttempt = {
9
+ name: 'record_attempt',
10
+ title: 'Record a quiz attempt',
11
+ description: 'Grade one answer on the 0-5 SM-2 scale and persist it. Updates mastery, the next review date and the session gate. Record skips too, as grade 0 with feedback "skipped".',
12
+ inputSchema: {
13
+ session_id: z.string().optional().describe(SESSION_HINT),
14
+ cwd: z.string().optional().describe(CWD_HINT),
15
+ slug: z.string().describe('The concept that was asked about.'),
16
+ question: z.string().describe('The question exactly as asked.'),
17
+ answer: z.string().optional().describe('The learner\'s answer verbatim. Omit for a skip.'),
18
+ grade: z
19
+ .number()
20
+ .int()
21
+ .min(0)
22
+ .max(5)
23
+ .describe('0 no answer/skip, 1-2 wrong, 3 correct but laboured, 4 correct, 5 correct and explained the why.'),
24
+ difficulty: z.number().int().min(1).max(5).describe('The tier you actually asked at — use tier_to_ask from the plan.'),
25
+ feedback: z.string().optional().describe('The short explanation you gave back.'),
26
+ },
27
+ handler: (args, { db }) => {
28
+ const now = new Date();
29
+ const { config, repoRoot } = loadConfig(args.cwd);
30
+ const sessionId = resolveSessionId(db, args.session_id);
31
+ const slug = normalizeSlug(args.slug);
32
+ const concept = conceptBySlug(db, slug);
33
+ if (!concept) {
34
+ return {
35
+ error: 'unknown_concept',
36
+ slug,
37
+ detail: 'No concept with that slug. Call upsert_concepts or log_session_concepts first.',
38
+ };
39
+ }
40
+ const state = db.transaction(() => {
41
+ // An attempt on a concept the session never logged still counts toward the
42
+ // gate, so quizzing on review debt is not free.
43
+ logSessionConcept(db, sessionId, concept.id, null);
44
+ return gradeConcept(db, {
45
+ conceptId: concept.id,
46
+ sessionId,
47
+ question: args.question,
48
+ answer: args.answer ?? null,
49
+ grade: args.grade,
50
+ difficulty: args.difficulty,
51
+ feedback: args.feedback ?? null,
52
+ now,
53
+ });
54
+ })();
55
+ const gate = syncGate(db, sessionId, config, { repo: repoRoot });
56
+ const score = decayedScore(state.score, state.next_review, now);
57
+ return {
58
+ slug: concept.slug,
59
+ new_score: Number(state.score.toFixed(3)),
60
+ next_review: state.next_review,
61
+ interval_days: state.interval_d,
62
+ reps: state.reps,
63
+ ease: Number(state.ease.toFixed(2)),
64
+ known: isKnown({ score, reps: state.reps }),
65
+ gate,
66
+ };
67
+ },
68
+ };
69
+ //# sourceMappingURL=record_attempt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record_attempt.js","sourceRoot":"","sources":["../../src/tools/record_attempt.ts"],"names":[],"mappings":"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"}
@@ -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,68 @@
1
+ {
2
+ "name": "eklavya-mcp",
3
+ "version": "1.0.0",
4
+ "description": "Eklavya MCP server — local knowledge graph, spaced repetition, and quiz gating for agent-assisted learning.",
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-mcp": "dist/server.js",
29
+ "eklavya": "dist/cli.js"
30
+ },
31
+ "files": [
32
+ "dist",
33
+ "bin",
34
+ "README.md"
35
+ ],
36
+ "publishConfig": {
37
+ "access": "public",
38
+ "provenance": true
39
+ },
40
+ "engines": {
41
+ "node": ">=18"
42
+ },
43
+ "scripts": {
44
+ "build": "tsc -p tsconfig.json && node scripts/copy-assets.mjs",
45
+ "dev": "tsx src/server.ts",
46
+ "test": "vitest run",
47
+ "test:watch": "vitest",
48
+ "pretest": "npm run build",
49
+ "prepublishOnly": "npm run build"
50
+ },
51
+ "dependencies": {
52
+ "@modelcontextprotocol/sdk": "^1.0.0",
53
+ "better-sqlite3": "^12.0.0",
54
+ "zod": "^4.4.3"
55
+ },
56
+ "devDependencies": {
57
+ "@types/better-sqlite3": "^7.6.11",
58
+ "@types/node": "^22.0.0",
59
+ "tsx": "^4.19.0",
60
+ "typescript": "^5.6.0",
61
+ "vitest": "^4.1.11"
62
+ },
63
+ "allowScripts": {
64
+ "better-sqlite3@12.11.1": true,
65
+ "esbuild@0.28.2": true,
66
+ "esbuild@0.21.5": true
67
+ }
68
+ }