eklavya 1.14.0 → 1.16.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 (56) hide show
  1. package/dist/assets/tutor/SKILL.md +180 -0
  2. package/dist/assets/tutor/references/focus-and-level.md +156 -0
  3. package/dist/assets/tutor/references/grading.md +113 -0
  4. package/dist/assets/tutor/references/writing-mcq.md +133 -0
  5. package/dist/cli.js +99 -18
  6. package/dist/cli.js.map +1 -1
  7. package/dist/db.js +5 -0
  8. package/dist/db.js.map +1 -1
  9. package/dist/eval/extract-json.js +81 -0
  10. package/dist/eval/extract-json.js.map +1 -0
  11. package/dist/eval/extraction-score.js +115 -0
  12. package/dist/eval/extraction-score.js.map +1 -0
  13. package/dist/eval/history-stats.js +230 -0
  14. package/dist/eval/history-stats.js.map +1 -0
  15. package/dist/eval/question-checks.js +220 -0
  16. package/dist/eval/question-checks.js.map +1 -0
  17. package/dist/hooks/checkpoint-quiz.js +1 -1
  18. package/dist/hooks/lib.js +38 -13
  19. package/dist/hooks/lib.js.map +1 -1
  20. package/dist/hooks/prompt-submit-nudge.js +155 -0
  21. package/dist/hooks/prompt-submit-nudge.js.map +1 -0
  22. package/dist/hooks/session-start.js +32 -14
  23. package/dist/hooks/session-start.js.map +1 -1
  24. package/dist/hooks/stop-quiz-check.js +14 -3
  25. package/dist/hooks/stop-quiz-check.js.map +1 -1
  26. package/dist/hooks/subagent-start.js +87 -0
  27. package/dist/hooks/subagent-start.js.map +1 -0
  28. package/dist/packs.js +177 -0
  29. package/dist/packs.js.map +1 -0
  30. package/dist/plugin/.claude-plugin/plugin.json +1 -1
  31. package/dist/plugin/agents/tutor.md +19 -5
  32. package/dist/plugin/hooks/CLAUDE.md +114 -2
  33. package/dist/plugin/hooks/hooks.json +24 -0
  34. package/dist/plugin/hooks/run.mjs +3 -3
  35. package/dist/plugin/skills/CLAUDE.md +110 -18
  36. package/dist/plugin/skills/pack/SKILL.md +61 -0
  37. package/dist/plugin/skills/setup/SKILL.md +1 -1
  38. package/dist/plugin/skills/tutor/SKILL.md +119 -304
  39. package/dist/plugin/skills/tutor/references/focus-and-level.md +156 -0
  40. package/dist/plugin/skills/tutor/references/grading.md +113 -0
  41. package/dist/plugin/skills/tutor/references/writing-mcq.md +133 -0
  42. package/dist/seed.js +29 -8
  43. package/dist/seed.js.map +1 -1
  44. package/dist/slug.js +63 -0
  45. package/dist/slug.js.map +1 -1
  46. package/dist/stdin.js +131 -0
  47. package/dist/stdin.js.map +1 -0
  48. package/dist/store.js +16 -2
  49. package/dist/store.js.map +1 -1
  50. package/dist/tools/get_session_quiz_plan.js +1 -1
  51. package/dist/tools/get_session_quiz_plan.js.map +1 -1
  52. package/dist/tools/record_attempt.js +15 -3
  53. package/dist/tools/record_attempt.js.map +1 -1
  54. package/dist/user-skill/eklavya/SKILL.md +1 -0
  55. package/package.json +1 -1
  56. package/dist/assets/tutor-skill.md +0 -365
@@ -0,0 +1,155 @@
1
+ /**
2
+ * UserPromptSubmit: say the standing instruction again, once, when a session
3
+ * has plainly stopped hearing it.
4
+ *
5
+ * `session-start.ts` injects the directive that starts the whole loop -- call
6
+ * `log_session_concepts` with what this task exercises -- and its own comment
7
+ * explains why it lives in a hook rather than only in the tutor skill: the skill
8
+ * is model-invocable, it competes with every other skill on the machine, and it
9
+ * may never load. The same argument applies one level up. A directive injected
10
+ * once is the oldest thing in the context window by turn forty, competing with
11
+ * everything that arrived after it.
12
+ *
13
+ * That failure is the worst-shaped one Eklavya has, because nothing errors.
14
+ * Nothing gets logged, so `session_concepts` stays empty, so the Stop hook finds
15
+ * no candidates and exits silently. From the outside it reads as *Eklavya is
16
+ * broken* -- which is unreportable, and the reason both comparable plugins
17
+ * (ponytail, claude-mem) re-inject on every prompt rather than once per session.
18
+ *
19
+ * Where this deliberately differs from them: they re-emit their whole ruleset
20
+ * every turn. This emits ONE line, and only when it has been earned. A hook that
21
+ * spends tokens on every prompt to repeat something the model is already doing
22
+ * is a hook that makes every session more expensive to no end -- and a nudge
23
+ * that arrives while logging is working teaches the model to ignore nudges.
24
+ *
25
+ * So the predicate is narrow, and it is the one case worth catching: this
26
+ * session has logged **nothing at all**, and it has been going long enough that
27
+ * the session-start directive can no longer be blamed on not having had a chance.
28
+ * A session that logged once and then went quiet for a while is *not* nudged --
29
+ * concepts are logged per task and a task can legitimately run long, so that
30
+ * signal is noise.
31
+ *
32
+ * State lives in `meta` rather than a table of its own: one row per session,
33
+ * `prompt_nudge:<sid>` -> `<first-seen>|<last-nudge>|<count>`, pipe-delimited so
34
+ * the prune below is one statement and needs no JSON parsing in SQL. A
35
+ * migration for a rate limiter would be a schema change for a comment.
36
+ * `session-start` deletes the row when it reprints the directive, which is what
37
+ * re-arms the grace window on a resume or after a compaction.
38
+ *
39
+ * Failure is silent, as everywhere here: exit 0, no output. This runs on every
40
+ * prompt the developer types, so it is also the hook with the least right to be
41
+ * slow -- the common case (a session that is logging) is one COUNT and out.
42
+ */
43
+ import { run, openExisting, config, cwdOf, sessionId, minutesSince, NUDGE_KEY_PREFIX, } from './lib.js';
44
+ /**
45
+ * Long enough that the session-start directive has had a fair chance, short
46
+ * enough to still be the same piece of work. Not configurable on purpose: a
47
+ * knob for this would be a config key, a schema entry, a CLI line and four doc
48
+ * updates to tune something nobody can feel.
49
+ */
50
+ const GRACE_MINUTES = 12;
51
+ /** And having said it once, do not say it again this soon. */
52
+ const COOLDOWN_MINUTES = 25;
53
+ /**
54
+ * And having said it three times, stop.
55
+ *
56
+ * The cooldown alone is not a bound. "This session has logged nothing at all"
57
+ * is not only the signature of a model that forgot the directive -- it is also
58
+ * exactly what a dead MCP server looks like from here, and that state never
59
+ * resolves. Without a cap an eight-hour session against an unreachable server
60
+ * gets nineteen injections telling the model to call a tool that is not
61
+ * registered, each one likely drawing a visible failure in the transcript.
62
+ *
63
+ * `max_stop_blocks_per_session` is the same kind of backstop for the Stop hook,
64
+ * for the same reason. A constant rather than a config key: the difference
65
+ * between three nudges and four is not something anyone needs to tune.
66
+ */
67
+ const MAX_NUDGES = 3;
68
+ /**
69
+ * `<first-seen ISO>|<last-nudge ISO or empty>|<nudges so far>`
70
+ *
71
+ * The count was appended rather than inserted, so a row written by the previous
72
+ * shape reads back as count 0 instead of throwing. It buys such a row at most
73
+ * MAX_NUDGES extra nudges, once, which is cheaper than a migration.
74
+ */
75
+ function readState(db, key) {
76
+ const row = db.prepare('SELECT value FROM meta WHERE key = ?').get(key);
77
+ if (!row)
78
+ return null;
79
+ const [first, nudged, count] = row.value.split('|');
80
+ if (!first)
81
+ return null;
82
+ return { first, nudged: nudged || null, count: Number(count) || 0 };
83
+ }
84
+ function writeState(db, key, first, nudged, count) {
85
+ db.prepare('INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value').run(key, `${first}|${nudged ?? ''}|${count}`);
86
+ }
87
+ /**
88
+ * One row per session would otherwise be one row per session forever. The value
89
+ * starts with an ISO date, which compares lexicographically, so a week's cutoff
90
+ * is a substring comparison rather than a parse.
91
+ */
92
+ function prune(db) {
93
+ db.prepare(`DELETE FROM meta
94
+ WHERE key LIKE ?
95
+ AND substr(value, 1, 10) < date('now', '-7 day')`).run(`${NUDGE_KEY_PREFIX}%`);
96
+ }
97
+ await run(async (input) => {
98
+ // A subagent's prompts are not the developer's, and the parent is the thread
99
+ // that logs. Nudging here would spend context inside a transcript nobody is
100
+ // reading. Same reasoning as checkpoint-quiz.ts.
101
+ if (input.agent_id)
102
+ return 0;
103
+ // Config before the database, unlike the other hooks: this one runs on every
104
+ // prompt the developer types, so a dormant or quiet session should not pay to
105
+ // open SQLite before finding out it had nothing to do. Reading one or two
106
+ // small JSON files is the cheaper question, so it is asked first.
107
+ const { mode } = config(cwdOf(input)).config;
108
+ if (mode === 'off')
109
+ return 0;
110
+ // `quiet` is deliberately NOT consulted. It suppresses the banner and the
111
+ // status bar -- things the developer looks at -- and this is an
112
+ // additionalContext line the model reads, exactly like the session-start
113
+ // directive it restates. Honouring it here used to be defended as consistency
114
+ // with that directive, which was itself being dropped under `quiet`: between
115
+ // them they turned a preference about greetings into a silent off switch.
116
+ // `mode: off` is the off switch.
117
+ const db = openExisting();
118
+ if (!db)
119
+ return 0;
120
+ const sid = sessionId(input, db);
121
+ if (!sid)
122
+ return 0;
123
+ // The fast path, and the one that runs on almost every prompt: a session that
124
+ // is logging needs nothing said to it.
125
+ const logged = db
126
+ .prepare('SELECT count(*) AS n FROM session_concepts WHERE session_id = ?')
127
+ .get(sid);
128
+ if ((logged?.n ?? 0) > 0)
129
+ return 0;
130
+ const key = `${NUDGE_KEY_PREFIX}${sid}`;
131
+ const now = new Date().toISOString();
132
+ const state = readState(db, key);
133
+ // First prompt we have seen for this session. The directive was injected
134
+ // seconds ago; repeating it now would be noise. Start the clock instead.
135
+ if (!state) {
136
+ prune(db);
137
+ writeState(db, key, now, null, 0);
138
+ return 0;
139
+ }
140
+ if (state.count >= MAX_NUDGES)
141
+ return 0;
142
+ if (minutesSince(state.first) < GRACE_MINUTES)
143
+ return 0;
144
+ if (state.nudged && minutesSince(state.nudged) < COOLDOWN_MINUTES)
145
+ return 0;
146
+ writeState(db, key, state.first, now, state.count + 1);
147
+ process.stdout.write(`${JSON.stringify({
148
+ hookSpecificOutput: {
149
+ hookEventName: 'UserPromptSubmit',
150
+ additionalContext: '[Eklavya] Nothing logged this session. Once you know what the current task involves, call log_session_concepts with the 3-8 concepts it genuinely exercises, each with a context line naming the real code — without it there is nothing to quiz on.',
151
+ },
152
+ })}\n`);
153
+ return 0;
154
+ });
155
+ //# sourceMappingURL=prompt-submit-nudge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-submit-nudge.js","sourceRoot":"","sources":["../../src/hooks/prompt-submit-nudge.ts"],"names":[],"mappings":"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"}
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import { setCurrentSession } from '../session.js';
8
8
  import { levelStanding } from '../store.js';
9
- import { run, openExisting, config, cwdOf, sessionId } from './lib.js';
9
+ import { run, openExisting, config, cwdOf, sessionId, clearNudgeState } from './lib.js';
10
10
  /**
11
11
  * The whole tutoring loop starts at log_session_concepts: it is the only writer
12
12
  * of session_concepts, and the Stop hook quizzes from nothing else. An unlogged
@@ -94,15 +94,36 @@ await run(async (input) => {
94
94
  // `levelStanding` is the server's own function, so the banner cannot drift
95
95
  // from what the quiz planner actually does — the shell version reimplemented
96
96
  // this query and had to keep it in step by hand.
97
- let levelLabel = `${difficulty} (pinned)`;
98
- if (difficulty === 'auto') {
99
- const standing = levelStanding(db, resolved.config, resolved.repoRoot);
100
- levelLabel = `${standing.level} (${standing.counts.passed}/${standing.needed.answers} on this project)`;
97
+ const out = [];
98
+ // `quiet` suppresses the banner, and only the banner. It used to return here,
99
+ // which also dropped the directive below -- so a developer who turned the
100
+ // greeting off silently turned the whole product off: nothing was logged, so
101
+ // the Stop hook found no candidates, so no question was ever asked, while
102
+ // `get_config` went on reporting `mode: ambient`. The manual has always said
103
+ // this key "suppresses the session-start banner and the statusline output",
104
+ // and `mode: off` is the documented way to stop Eklavya doing anything.
105
+ if (!quiet) {
106
+ let levelLabel = `${difficulty} (pinned)`;
107
+ if (difficulty === 'auto') {
108
+ const standing = levelStanding(db, resolved.config, resolved.repoRoot);
109
+ levelLabel = `${standing.level} (${standing.counts.passed}/${standing.needed.answers} on this project)`;
110
+ }
111
+ banner(db, out, { mode, focusLabel, cadence, levelLabel, overrides: resolved.overrides });
101
112
  }
102
- if (quiet)
103
- return 0;
113
+ // The directive is on screen again, so the nudge's clock starts again. This
114
+ // hook fires on resume and after a compaction, not only at startup, and both
115
+ // reuse the session id -- without this the first prompt of a resumed session
116
+ // restates what was printed seconds ago.
117
+ if (sid)
118
+ clearNudgeState(db, sid);
119
+ out.push(DIRECTIVE);
120
+ process.stdout.write(`${out.join('\n')}\n`);
121
+ return 0;
122
+ });
123
+ /** The developer-facing greeting. Everything `quiet` is about. */
124
+ function banner(db, out, parts) {
125
+ const { mode, focusLabel, cadence, levelLabel, overrides } = parts;
104
126
  const domains = domainSummary(db);
105
- const out = [];
106
127
  if (!domains) {
107
128
  // Nothing learned yet — say the useful thing instead of an empty scoreboard.
108
129
  out.push(`[Eklavya] No learning history yet. Mode: ${mode}. Focus: ${focusLabel}. Cadence: ${cadence}. Level: ${levelLabel}.`);
@@ -117,11 +138,8 @@ await run(async (input) => {
117
138
  line += ` ${due} concept(s) due for review.`;
118
139
  out.push(`${line} Mode: ${mode}. Focus: ${focusLabel}. Cadence: ${cadence}. Level: ${levelLabel}.`);
119
140
  }
120
- if (resolved.overrides.length > 0) {
121
- out.push(`[Eklavya] This repo overrides your global setting for: ${resolved.overrides.join(' ')} (.eklavya.json wins).`);
141
+ if (overrides.length > 0) {
142
+ out.push(`[Eklavya] This repo overrides your global setting for: ${overrides.join(' ')} (.eklavya.json wins).`);
122
143
  }
123
- out.push(DIRECTIVE);
124
- process.stdout.write(`${out.join('\n')}\n`);
125
- return 0;
126
- });
144
+ }
127
145
  //# sourceMappingURL=session-start.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"session-start.js","sourceRoot":"","sources":["../../src/hooks/session-start.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"session-start.js","sourceRoot":"","sources":["../../src/hooks/session-start.ts"],"names":[],"mappings":"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"}
@@ -27,6 +27,16 @@
27
27
  */
28
28
  import { run, openExisting, config, cwdOf, sessionId, minutesSince, framingFor } from './lib.js';
29
29
  await run(async (input) => {
30
+ // Same fast path as checkpoint-quiz.ts, and for a stronger reason: this hook
31
+ // blocks with exit 2. `Stop` is believed to be parent-only -- `SubagentStop`
32
+ // is a separate event -- but nothing in this repo has verified that, and the
33
+ // cost of being wrong is a subagent told to run a quiz it has no
34
+ // AskUserQuestion to ask, in a transcript nobody reads, up to
35
+ // max_stop_blocks_per_session times. subagent-start.ts is what made that
36
+ // reachable: before it, a subagent logged nothing, so the `logged > last_logged`
37
+ // predicate below could never arm.
38
+ if (input.agent_id)
39
+ return 0;
30
40
  const db = openExisting();
31
41
  if (!db)
32
42
  return 0;
@@ -158,12 +168,13 @@ Concepts: ${concepts}
158
168
  ${ask}
159
169
 
160
170
  Ask each question as MULTIPLE CHOICE via the AskUserQuestion tool: four options,
161
- one correct and three plausible, header "Eklavya" so it is clear who is asking.
171
+ one correct and three plausible, the correct one in the slot answer_position
172
+ names, header "Eklavya" so it is clear who is asking.
162
173
  The tool's own "Other" choice is the escape hatch for "I don't know". They are
163
174
  mid-task -- a blank prompt gets skipped whether or not they knew the answer.
164
175
  Ask the stem on its own. The dials that pitched it -- mode, focus, cadence,
165
- level -- are in the status bar, so never prepend a settings line to the
166
- question.
176
+ level -- are in the status bar, so what you ask is the question and nothing
177
+ else.
167
178
  Record with format "mcq", the labels in "options", and only the stem in
168
179
  "question".
169
180
  ${framing}
@@ -1 +1 @@
1
- {"version":3,"file":"stop-quiz-check.js","sourceRoot":"","sources":["../../src/hooks/stop-quiz-check.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"stop-quiz-check.js","sourceRoot":"","sources":["../../src/hooks/stop-quiz-check.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * SubagentStart: tell a delegated agent to log the concepts it exercises.
3
+ *
4
+ * `SessionStart` fires once, in the parent thread, and its standing directive
5
+ * never reaches a subagent — a subagent opens with a fresh context and the
6
+ * agent file it was defined by. So the agent that most often knows what the
7
+ * code touched is the one agent never told to say so, and a session that
8
+ * delegates its implementation logs nothing at all. From the outside that is
9
+ * indistinguishable from Eklavya being broken: nothing is logged, so the Stop
10
+ * hook finds no candidates and exits without a word.
11
+ *
12
+ * Two things make this different from `session-start.ts` rather than a copy of
13
+ * it.
14
+ *
15
+ * **The output form.** `SessionStart` accepts raw stdout as context.
16
+ * `SubagentStart` does not — it needs the `hookSpecificOutput.additionalContext`
17
+ * JSON, and text printed here is dropped silently, which is the failure this
18
+ * comment exists to stop someone rediscovering.
19
+ *
20
+ * **Not every subagent should hear it.** `eklavya-tutor` (`agents/tutor.md`) is
21
+ * deliberately not given `log_session_concepts` — it teaches what the builder
22
+ * logged. Telling it to log would be an instruction to call a tool it does not
23
+ * have. `docs/subagent-policy.md` is the whole policy: who logs, who quizzes,
24
+ * who stays silent.
25
+ *
26
+ * Nothing here asks a question. A subagent has no `AskUserQuestion` and no
27
+ * audience, and `checkpoint-quiz.ts` already returns on `agent_id` for that
28
+ * reason; this hook must not undo it by asking for one in prose.
29
+ */
30
+ import { run, config, cwdOf } from './lib.js';
31
+ /**
32
+ * Two sentences, because a subagent's context is its whole budget and this is
33
+ * spent before it has read its task.
34
+ *
35
+ * `session_id` is not mentioned: `log_session_concepts` resolves it from
36
+ * `meta.current_session`, which `session-start.ts` stamped, so a subagent lands
37
+ * in the parent's session without being told how. Nor is there an "if the tool
38
+ * is available" hedge — `skills/CLAUDE.md` measured that shape of clause
39
+ * turning a reliable recipe into a noisy one, and the exemption that matters is
40
+ * the guard's job below, not a condition the model is invited to weigh.
41
+ */
42
+ const DIRECTIVE = `[Eklavya] Call log_session_concepts once you know what this task involves — the 3-8 concepts the code genuinely exercises, each with a context line naming the real code you wrote.
43
+ Do not ask the developer anything here: nobody is watching this transcript, and the parent session is what asks the questions.`;
44
+ /**
45
+ * The tutor, whichever way it was installed.
46
+ *
47
+ * Not because it lacks `log_session_concepts` — so do `Explore` and `Plan`, and
48
+ * they are told anyway; a subagent without the tool simply does not call it.
49
+ * The harm is the *second* sentence. `agents/tutor.md` exists to quiz from
50
+ * inside a subagent (`docs/parallel-tutoring.md`, Option A), and "do not ask
51
+ * the developer anything here" is a direct order not to do the only thing it
52
+ * does. Delivering it silently disables parallel tutoring.
53
+ *
54
+ * Substring rather than equality: an agent is namespaced `<plugin>:<name>` when
55
+ * installed through `/plugin` and bare when it is a user-level agent, so both
56
+ * `eklavya-tutor` and `eklavya:eklavya-tutor` have to match.
57
+ */
58
+ function isTutor(agentType) {
59
+ return typeof agentType === 'string' && agentType.includes('eklavya-tutor');
60
+ }
61
+ await run(async (input) => {
62
+ // No database read at all. Whether to speak depends only on `mode`, and a
63
+ // subagent may well be the first thing in a session to touch Eklavya — a hook
64
+ // that bailed on a missing database would stay silent on exactly the fresh
65
+ // install that most needs the directive. (`lib.js` still pulls in the SQLite
66
+ // driver at import, so this is one query fewer, not a free hook.)
67
+ if (config(cwdOf(input)).config.mode === 'off')
68
+ return 0;
69
+ // Fail OPEN on an absent or unrecognised agent_type: speak. A host that does
70
+ // not send the field is a host where failing closed would kill the feature
71
+ // silently — the failure this repo keeps rediscovering — while the cost of
72
+ // this direction is one tutor session that logs instead of quizzing, in a
73
+ // subagent the developer asked for by name and is watching for.
74
+ if (isTutor(input.agent_type))
75
+ return 0;
76
+ // `quiet` is not consulted, deliberately: it suppresses what the developer
77
+ // looks at — the banner and the status bar — and this is context the model
78
+ // reads. See hooks/CLAUDE.md, "`quiet` is not an off switch".
79
+ process.stdout.write(`${JSON.stringify({
80
+ hookSpecificOutput: {
81
+ hookEventName: 'SubagentStart',
82
+ additionalContext: DIRECTIVE,
83
+ },
84
+ })}\n`);
85
+ return 0;
86
+ });
87
+ //# sourceMappingURL=subagent-start.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subagent-start.js","sourceRoot":"","sources":["../../src/hooks/subagent-start.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAE9C;;;;;;;;;;GAUG;AACH,MAAM,SAAS,GAAG;+HAC6G,CAAC;AAEhI;;;;;;;;;;;;;GAaG;AACH,SAAS,OAAO,CAAC,SAA6B;IAC5C,OAAO,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC;AAC9E,CAAC;AAED,MAAM,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;IACxB,0EAA0E;IAC1E,8EAA8E;IAC9E,2EAA2E;IAC3E,6EAA6E;IAC7E,kEAAkE;IAClE,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,KAAK,KAAK;QAAE,OAAO,CAAC,CAAC;IAEzD,6EAA6E;IAC7E,2EAA2E;IAC3E,2EAA2E;IAC3E,0EAA0E;IAC1E,gEAAgE;IAChE,IAAI,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC;QAAE,OAAO,CAAC,CAAC;IAExC,2EAA2E;IAC3E,2EAA2E;IAC3E,8DAA8D;IAC9D,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,GAAG,IAAI,CAAC,SAAS,CAAC;QAChB,kBAAkB,EAAE;YAClB,aAAa,EAAE,eAAe;YAC9B,iBAAiB,EAAE,SAAS;SAC7B;KACF,CAAC,IAAI,CACP,CAAC;IACF,OAAO,CAAC,CAAC;AACX,CAAC,CAAC,CAAC"}
package/dist/packs.js ADDED
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Concept packs: the graph as data someone else can write.
3
+ *
4
+ * The shipped catalogue in `src/seed/` is already JSON rather than code, but it
5
+ * is *ours*: four graphs — git, node-backend, react, web-auth — and the only way
6
+ * to add a fifth was a pull request to this repository. Everything else a
7
+ * developer actually works in arrives only as whatever `upsert_concepts` minted
8
+ * mid-session. A pack is the same file shape, loaded from two places Eklavya
9
+ * does not own:
10
+ *
11
+ * ~/.eklavya/packs/*.json the learner's own, and anything installed
12
+ * <repo>/.eklavya/packs/*.json a team's, versioned with the codebase
13
+ *
14
+ * The second is the interesting one. A repository that ships its own concepts
15
+ * and prerequisites is describing itself to a new joiner, which is onboarding
16
+ * rather than quizzing.
17
+ *
18
+ * Packs are applied AFTER the seed and merged over it, so a pack may retier or
19
+ * rename a shipped concept, and a repo pack wins over a global one. Nothing
20
+ * here touches `mastery`: the seed's rule is the pack's rule, and a learner's
21
+ * history survives any pack being installed, edited or deleted.
22
+ */
23
+ import fs from 'node:fs';
24
+ import path from 'node:path';
25
+ import crypto from 'node:crypto';
26
+ import { eklavyaHome } from './paths.js';
27
+ import { findRepoConfig } from './config.js';
28
+ import { applySeedGraph, validateSeedGraph, SEED_VERSION } from './seed.js';
29
+ /**
30
+ * One row per set of pack directories, not one row for the install.
31
+ *
32
+ * The pack set depends on the working directory, so a single global row made
33
+ * two repositories fight over it: every `openDb()` in repo A saw repo B's hash,
34
+ * re-applied, and stored its own — a write on every CLI invocation for anyone
35
+ * with a repo pack and more than one project.
36
+ */
37
+ export function packFingerprintKey(dirs) {
38
+ return `packs_fingerprint:${crypto.createHash('sha256').update(dirs.join('\n')).digest('hex').slice(0, 16)}`;
39
+ }
40
+ /**
41
+ * Global first, repo second, because later wins. `packs/` under both, so the
42
+ * rule is one sentence: packs live in a `packs/` directory next to Eklavya's
43
+ * state, whether that state is yours or the repository's.
44
+ */
45
+ export function packDirs(cwd = process.cwd()) {
46
+ const dirs = [
47
+ { dir: path.join(eklavyaHome(), 'packs'), scope: 'global' },
48
+ ];
49
+ const { repoRoot } = findRepoConfig(cwd);
50
+ if (repoRoot)
51
+ dirs.push({ dir: path.join(repoRoot, '.eklavya', 'packs'), scope: 'repo' });
52
+ return dirs;
53
+ }
54
+ function listPackFiles(dir) {
55
+ try {
56
+ return fs
57
+ .readdirSync(dir)
58
+ .filter((f) => f.endsWith('.json'))
59
+ .sort()
60
+ .map((f) => path.join(dir, f));
61
+ }
62
+ catch {
63
+ // A missing packs directory is the normal case, not an error.
64
+ return [];
65
+ }
66
+ }
67
+ /**
68
+ * Reads every pack, and never throws.
69
+ *
70
+ * A malformed pack must not take the database down with it: `openDb()` is on
71
+ * the path of every CLI command and the server's own start, so one bad file in
72
+ * `~/.eklavya/packs/` would otherwise make Eklavya unusable rather than making
73
+ * one pack unavailable. The error is carried on the result instead, and
74
+ * `eklavya doctor` is where a learner reads it.
75
+ */
76
+ export function loadPacks(cwd = process.cwd()) {
77
+ const out = [];
78
+ for (const { dir, scope } of packDirs(cwd)) {
79
+ for (const file of listPackFiles(dir)) {
80
+ try {
81
+ const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
82
+ if (typeof parsed.pack !== 'string' || !parsed.pack) {
83
+ throw new Error('missing "pack" — a pack has to say who it is, as a string');
84
+ }
85
+ // Edges may leave the file, unlike a seed graph's: a pack extending a
86
+ // shipped domain wants `prerequisite_of` pointing at a seeded concept.
87
+ // An endpoint naming nothing is dropped at apply time and counted, so a
88
+ // typo is visible in `doctor` rather than failing the whole pack.
89
+ validateSeedGraph(parsed, path.basename(file), { allowExternalEdges: true });
90
+ out.push({ file, scope, pack: parsed });
91
+ }
92
+ catch (err) {
93
+ out.push({ file, scope, error: err instanceof Error ? err.message : String(err) });
94
+ }
95
+ }
96
+ }
97
+ return out;
98
+ }
99
+ /**
100
+ * Identity of the pack set as it is on disk right now.
101
+ *
102
+ * Path, size and mtime rather than content: applying packs is a write, and
103
+ * doing it on every `openDb()` would churn the WAL on every CLI invocation for
104
+ * a set of files that changes about never. Editing a pack changes its mtime and
105
+ * its size in almost every real case, and the one that slips through is fixed
106
+ * by the next edit or by `eklavya doctor`, which applies unconditionally.
107
+ */
108
+ export function packFingerprint(files, seedVersion = SEED_VERSION) {
109
+ const h = crypto.createHash('sha256');
110
+ // The seed version is part of a pack's identity, because a re-seed upserts
111
+ // every shipped concept back to its shipped tier and undoes whatever a pack
112
+ // had merged over it. Folding it in here means a bumped SEED_VERSION marks
113
+ // EVERY scope stale, so each repository re-applies its own packs the next
114
+ // time it is opened — not only the one the learner happened to be in when
115
+ // the re-seed ran.
116
+ h.update(`seed:${seedVersion}\n`);
117
+ for (const file of files) {
118
+ let stat = null;
119
+ try {
120
+ stat = fs.statSync(file);
121
+ }
122
+ catch {
123
+ /* raced with a delete; the path alone still changes the hash */
124
+ }
125
+ h.update(`${file}:${stat?.size ?? -1}:${stat?.mtimeMs ?? -1}\n`);
126
+ }
127
+ return h.digest('hex');
128
+ }
129
+ /** Applies every readable pack, global then repo, so the repo's wins. */
130
+ export function applyPacks(db, cwd = process.cwd()) {
131
+ return applyLoaded(db, loadPacks(cwd), packDirs(cwd).map((d) => d.dir));
132
+ }
133
+ function applyLoaded(db, loaded, dirs) {
134
+ const summary = { applied: 0, concepts: 0, edges: 0, edgesDropped: 0, errors: [] };
135
+ for (const entry of loaded) {
136
+ if (!entry.pack) {
137
+ summary.errors.push({ file: entry.file, error: entry.error ?? 'unreadable' });
138
+ continue;
139
+ }
140
+ const s = applySeedGraph(db, entry.pack, 'pack');
141
+ summary.applied += 1;
142
+ summary.concepts += s.concepts;
143
+ summary.edges += s.edges;
144
+ summary.edgesDropped += (entry.pack.edges?.length ?? 0) - s.edges;
145
+ }
146
+ db.prepare(`INSERT INTO meta (key, value) VALUES (?, ?)
147
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(packFingerprintKey(dirs), packFingerprint(loaded.map((l) => l.file)));
148
+ return summary;
149
+ }
150
+ /**
151
+ * Applies packs when the set on disk has changed since this scope last saw it.
152
+ *
153
+ * "Changed" includes a bumped `SEED_VERSION`, which is folded into the hash:
154
+ * seeding upserts every shipped concept back to its shipped name and tier, so a
155
+ * pack that had merged over one is undone by a re-seed and has to land again.
156
+ * That is why there is no separate `force` — the seed version *is* part of what
157
+ * the fingerprint is fingerprinting.
158
+ *
159
+ * The write on the way out matters even when nothing was applied. It is what
160
+ * records "this scope has seen this state", and dropping it for the empty set
161
+ * loses a pack's override for good: a re-seed that happens while the learner is
162
+ * in some other repository would otherwise never be noticed by the repository
163
+ * whose pack it undid.
164
+ *
165
+ * Removing a pack does NOT remove its concepts. `mastery` and `attempts`
166
+ * reference them, so deleting the rows would delete the learner's history along
167
+ * with them — the same rule that keeps seeding away from `mastery`.
168
+ */
169
+ export function applyPacksIfNeeded(db, cwd = process.cwd()) {
170
+ const dirs = packDirs(cwd).map((d) => d.dir);
171
+ const loaded = loadPacks(cwd);
172
+ const row = db.prepare('SELECT value FROM meta WHERE key = ?').get(packFingerprintKey(dirs));
173
+ if (row?.value === packFingerprint(loaded.map((l) => l.file)))
174
+ return null;
175
+ return applyLoaded(db, loaded, dirs);
176
+ }
177
+ //# sourceMappingURL=packs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"packs.js","sourceRoot":"","sources":["../src/packs.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "eklavya",
3
3
  "displayName": "Eklavya",
4
- "version": "1.14.0",
4
+ "version": "1.16.0",
5
5
  "description": "Learn while your agent works. Turns coding-agent generation time into adaptive, Socratic learning grounded in the code being written.",
6
6
  "author": {
7
7
  "name": "Ajay Kumar"
@@ -30,15 +30,29 @@ The main session asks quiz questions as multiple choice through the `AskUserQues
30
30
  ```
31
31
  Which of these does `httpOnly` actually prevent?
32
32
 
33
- A) JavaScript reading the cookie via document.cookie
34
- B) The cookie being sent to a different origin
35
- C) The cookie surviving a browser restart
33
+ A) The cookie being sent to a different origin
34
+ B) The cookie surviving a browser restart
35
+ C) JavaScript reading the cookie via document.cookie
36
36
  D) The cookie being read over plain HTTP
37
37
 
38
38
  Reply with a letter, or "teach me" if you'd rather I explain it.
39
39
  ```
40
40
 
41
- Same rules as the skill's *Multiple choice* section: four options, three plausible distractors, the stem alone in `record_attempt`'s `question`, the labels in `options`, `format: "mcq"`, and the grade capped at 4. The only thing that changes is who draws the box.
41
+ The answer is at C there because the plan said `answer_position: 3`, and
42
+ **`answer_position` matters more here than anywhere else**: in the main session
43
+ a tool draws the options, and here you letter them yourself, so nothing but you
44
+ enforces the slot. Left to instinct the right answer lands at A nearly every
45
+ time, and a learner needs only a handful of questions to notice that and start
46
+ replying "A" without reading.
47
+
48
+ "teach me" is this renderer's version of the tool's "Other" choice. Treat it as
49
+ a blank, not a decline: grade 0 with `outcome: "dont_know"`, then teach.
50
+
51
+ Same rules as the tutor skill's `skills/tutor/references/writing-mcq.md`: four
52
+ options, three plausible distractors, the correct one at `answer_position`, the
53
+ stem alone in `record_attempt`'s `question`, the labels in `options`,
54
+ `format: "mcq"`, and the grade capped at 4. The only thing that changes is who
55
+ draws the box.
42
56
 
43
57
  ## You do not write code
44
58
 
@@ -60,4 +74,4 @@ You are competing for attention with an agent that is producing code. Ask one qu
60
74
 
61
75
  Never record a grade for a question that was not answered. Record a decline (`outcome: "declined"`) only when they actually say so.
62
76
 
63
- "I don't know" is not silence and not a decline — it is a request. Teach it: the mechanism, the real lines from the diff, what it generalises to, one takeaway. Then record grade 0 with `outcome: "dont_know"` and move on. See the `tutor` skill for the full shape.
77
+ "I don't know" is not silence and not a decline — it is a request. Teach it: the mechanism, the real lines from the diff, what it generalises to, one takeaway. Then record grade 0 with `outcome: "dont_know"` and move on. The full shape is in the tutor skill's `skills/tutor/references/grading.md`.