eklavya 1.14.0 → 1.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/tutor/SKILL.md +180 -0
- package/dist/assets/tutor/references/focus-and-level.md +156 -0
- package/dist/assets/tutor/references/grading.md +113 -0
- package/dist/assets/tutor/references/writing-mcq.md +133 -0
- package/dist/cli.js +66 -17
- package/dist/cli.js.map +1 -1
- package/dist/eval/extract-json.js +81 -0
- package/dist/eval/extract-json.js.map +1 -0
- package/dist/eval/extraction-score.js +115 -0
- package/dist/eval/extraction-score.js.map +1 -0
- package/dist/eval/history-stats.js +230 -0
- package/dist/eval/history-stats.js.map +1 -0
- package/dist/eval/question-checks.js +220 -0
- package/dist/eval/question-checks.js.map +1 -0
- package/dist/hooks/checkpoint-quiz.js +1 -1
- package/dist/hooks/lib.js +38 -13
- package/dist/hooks/lib.js.map +1 -1
- package/dist/hooks/prompt-submit-nudge.js +155 -0
- package/dist/hooks/prompt-submit-nudge.js.map +1 -0
- package/dist/hooks/session-start.js +32 -14
- package/dist/hooks/session-start.js.map +1 -1
- package/dist/hooks/stop-quiz-check.js +4 -3
- package/dist/hooks/stop-quiz-check.js.map +1 -1
- package/dist/plugin/.claude-plugin/plugin.json +1 -1
- package/dist/plugin/agents/tutor.md +19 -5
- package/dist/plugin/hooks/CLAUDE.md +83 -2
- package/dist/plugin/hooks/hooks.json +12 -0
- package/dist/plugin/hooks/run.mjs +3 -3
- package/dist/plugin/skills/CLAUDE.md +107 -15
- package/dist/plugin/skills/setup/SKILL.md +1 -1
- package/dist/plugin/skills/tutor/SKILL.md +119 -304
- package/dist/plugin/skills/tutor/references/focus-and-level.md +156 -0
- package/dist/plugin/skills/tutor/references/grading.md +113 -0
- package/dist/plugin/skills/tutor/references/writing-mcq.md +133 -0
- package/dist/slug.js +63 -0
- package/dist/slug.js.map +1 -1
- package/dist/stdin.js +131 -0
- package/dist/stdin.js.map +1 -0
- package/dist/store.js +16 -2
- package/dist/store.js.map +1 -1
- package/dist/tools/get_session_quiz_plan.js +1 -1
- package/dist/tools/get_session_quiz_plan.js.map +1 -1
- package/dist/tools/record_attempt.js +15 -3
- package/dist/tools/record_attempt.js.map +1 -1
- package/package.json +1 -1
- package/dist/assets/tutor-skill.md +0 -365
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The deterministic half of the question-quality eval.
|
|
3
|
+
*
|
|
4
|
+
* Eklavya's claim is that the developer learns, and until now nothing measured
|
|
5
|
+
* it: the 426 tests next door cover SM-2 arithmetic, plan sizing, gate maths and
|
|
6
|
+
* migrations -- the machinery -- while the product is a question, and whether a
|
|
7
|
+
* question is any good was checked by nobody.
|
|
8
|
+
*
|
|
9
|
+
* These are the checks that need no model. That matters more than it sounds:
|
|
10
|
+
* an LLM judge is soft, costs money, disagrees with itself between runs, and is
|
|
11
|
+
* biased toward output from its own family. Everything that can be settled by
|
|
12
|
+
* counting is settled here instead, for free and reproducibly, and the judge is
|
|
13
|
+
* left with only the three questions that genuinely need reading comprehension
|
|
14
|
+
* (see `eval/README.md`).
|
|
15
|
+
*
|
|
16
|
+
* Pure, like `srs.ts`: no database, no clock, no file reads. A check returns
|
|
17
|
+
* what it found, never a fix.
|
|
18
|
+
*/
|
|
19
|
+
/** Words, for length checks. Punctuation-only tokens do not count. */
|
|
20
|
+
function words(text) {
|
|
21
|
+
return text.split(/\s+/).filter((w) => /[\p{L}\p{N}]/u.test(w));
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Lowercased words, with identifiers kept whole.
|
|
25
|
+
*
|
|
26
|
+
* `_`, `-`, `.` and `/` stay inside a token on purpose. Splitting on them turns
|
|
27
|
+
* `max_questions_per_task` into four words and `mcp/src/eval/question-checks.ts`
|
|
28
|
+
* into six, so a stem and an answer that merely name the same identifier -- the
|
|
29
|
+
* ordinary way to write these questions -- would share a four-word run and trip
|
|
30
|
+
* `answer_not_in_stem` on its own. The fixtures here are this repo's own code,
|
|
31
|
+
* so that is the common case, not the corner one.
|
|
32
|
+
*/
|
|
33
|
+
function normalized(text) {
|
|
34
|
+
return words(text.toLowerCase().replace(/[^\p{L}\p{N}_\-./]+/gu, ' '));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The longest run of consecutive words the two strings share.
|
|
38
|
+
*
|
|
39
|
+
* Used for "the answer is not restated in the stem". A bag-of-words overlap
|
|
40
|
+
* would fire on every question, because a stem is *supposed* to share its
|
|
41
|
+
* subject with the answer -- "httpOnly" appears in both by design. A shared
|
|
42
|
+
* four-word run is a different thing: that is the sentence, not the subject.
|
|
43
|
+
*/
|
|
44
|
+
export function longestSharedRun(a, b) {
|
|
45
|
+
const x = normalized(a);
|
|
46
|
+
const y = normalized(b);
|
|
47
|
+
if (x.length === 0 || y.length === 0)
|
|
48
|
+
return 0;
|
|
49
|
+
// Classic LCS-of-substrings table. Both inputs are one sentence, so the
|
|
50
|
+
// quadratic cost is a few thousand operations.
|
|
51
|
+
let best = 0;
|
|
52
|
+
const prev = new Array(y.length + 1).fill(0);
|
|
53
|
+
for (let i = 1; i <= x.length; i++) {
|
|
54
|
+
let diagonal = 0;
|
|
55
|
+
for (let j = 1; j <= y.length; j++) {
|
|
56
|
+
const above = prev[j] ?? 0;
|
|
57
|
+
const run = x[i - 1] === y[j - 1] ? diagonal + 1 : 0;
|
|
58
|
+
diagonal = above;
|
|
59
|
+
prev[j] = run;
|
|
60
|
+
if (run > best)
|
|
61
|
+
best = run;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return best;
|
|
65
|
+
}
|
|
66
|
+
/** Stems this long stop being read and start being pattern-matched. */
|
|
67
|
+
export const STEM_WORD_LIMIT = 25;
|
|
68
|
+
/** A shared run this long means the stem contains the answer's sentence. */
|
|
69
|
+
export const ANSWER_RUN_LIMIT = 4;
|
|
70
|
+
/**
|
|
71
|
+
* Ratio between the longest and shortest option. Four options of visibly
|
|
72
|
+
* different length leak the answer through shape alone, whichever one is
|
|
73
|
+
* correct.
|
|
74
|
+
*/
|
|
75
|
+
export const OPTION_RATIO_LIMIT = 2.5;
|
|
76
|
+
/**
|
|
77
|
+
* Below this many words in the longest option, the ratio says nothing: four
|
|
78
|
+
* answers like `O(1)` / `O(n log n)` sit at 3.0 and are perfectly matched to a
|
|
79
|
+
* reader. Shape is a tell in sentences, not in tokens.
|
|
80
|
+
*/
|
|
81
|
+
export const PARITY_MIN_WORDS = 6;
|
|
82
|
+
/**
|
|
83
|
+
* How many words longer the correct option may be than the next-longest before
|
|
84
|
+
* the length itself is a hint. Below this it is noise; a reader does not count
|
|
85
|
+
* words, they see a shape.
|
|
86
|
+
*/
|
|
87
|
+
export const LENGTH_MARGIN_WORDS = 3;
|
|
88
|
+
/**
|
|
89
|
+
* Negation that makes a stem a reading test.
|
|
90
|
+
*
|
|
91
|
+
* Deliberately narrow. A plain "not" is often legitimate and contrastive --
|
|
92
|
+
* "why is this set on the refresh cookie but not on the access token" is a good
|
|
93
|
+
* stem -- so flagging every "not" would flag good questions and train whoever
|
|
94
|
+
* reads the report to ignore it. These three forms are the ones that turn the
|
|
95
|
+
* question itself inside out.
|
|
96
|
+
*/
|
|
97
|
+
const NEGATED_STEM = [
|
|
98
|
+
// "Which of these is not / will not / can't / is never ..."
|
|
99
|
+
/\bwhich\b(?:\s+of\s+(?:these|the\s+following))?\s+\w*\s*\b(?:is|are|does|do|will|would|can|could)(?:n't|\s+not|\s+never)\b/i,
|
|
100
|
+
// "... except which of these", but not the ordinary prose "except a corrupt file"
|
|
101
|
+
/\bexcept\b\s+(?:for\s+)?(?:which|one|these|the\s+following)\b/i,
|
|
102
|
+
/\b(?:is|are)\s+(?:not|never)\s+true\b/i,
|
|
103
|
+
/\b(?:incorrect|least\s+likely)\b/i,
|
|
104
|
+
/\bnone\s+of\s+(?:these|the\s+above)\b/i,
|
|
105
|
+
];
|
|
106
|
+
/**
|
|
107
|
+
* The tool renders the labels; a stem that numbers them is showing them twice.
|
|
108
|
+
*
|
|
109
|
+
* Two markers, not one. A single `b)` is `retry(db, b)` and a single `3.` is
|
|
110
|
+
* "the plan asked for tier 3. Why..." -- both ordinary, and both were flagged by
|
|
111
|
+
* the first version of this. An actual enumeration always has at least two.
|
|
112
|
+
*/
|
|
113
|
+
const OPTION_MARKER = /(^|\s)(?:[a-d]\)|\(?[1-4][).]\s)/gim;
|
|
114
|
+
function numberedOptions(stem) {
|
|
115
|
+
return (stem.match(OPTION_MARKER) ?? []).length >= 2;
|
|
116
|
+
}
|
|
117
|
+
export function checkQuestion(q) {
|
|
118
|
+
const checks = [];
|
|
119
|
+
const push = (id, ok, detail) => checks.push({ id, ok, detail });
|
|
120
|
+
const labels = q.options.map((o) => o.trim()).filter((o) => o.length > 0);
|
|
121
|
+
const unique = new Set(labels.map((o) => o.toLowerCase()));
|
|
122
|
+
push('four_options', labels.length === 4 && unique.size === 4, `${labels.length} non-empty option(s), ${unique.size} distinct`);
|
|
123
|
+
// The obedience check, and the reason the whole harness drives the real
|
|
124
|
+
// planner rather than inventing plan items: `answer_position` is assigned
|
|
125
|
+
// server-side by a hash so the answer cannot cluster, and that only works if
|
|
126
|
+
// the model actually puts it there.
|
|
127
|
+
const obeyed = q.correct === q.answer_position;
|
|
128
|
+
push('answer_at_position', obeyed, obeyed
|
|
129
|
+
? `correct option at ${q.answer_position}, as asked`
|
|
130
|
+
: `plan said ${q.answer_position}, model used ${q.correct}`);
|
|
131
|
+
const stemWords = words(q.stem).length;
|
|
132
|
+
push('stem_length', stemWords <= STEM_WORD_LIMIT, `${stemWords} words (limit ${STEM_WORD_LIMIT})`);
|
|
133
|
+
// Indexed into `q.options`, never `labels`. `labels` drops empty options, so
|
|
134
|
+
// one blank before the answer shifts every later index by one -- and the
|
|
135
|
+
// conspicuousness check below then compared the correct option against
|
|
136
|
+
// itself and always passed, on exactly the malformed question it should have
|
|
137
|
+
// been loudest about.
|
|
138
|
+
const inRange = Number.isInteger(q.correct) && q.correct >= 1 && q.correct <= q.options.length;
|
|
139
|
+
push('correct_in_range', inRange, inRange ? `correct = ${q.correct}` : `correct = ${JSON.stringify(q.correct)}, options = ${q.options.length}`);
|
|
140
|
+
const correct = inRange ? (q.options[q.correct - 1] ?? '') : '';
|
|
141
|
+
const run = longestSharedRun(q.stem, correct);
|
|
142
|
+
push('answer_not_in_stem', run < ANSWER_RUN_LIMIT, `longest run shared with the correct option: ${run} word(s)`);
|
|
143
|
+
const lengths = q.options.map((o) => words(o).length);
|
|
144
|
+
const longest = Math.max(...lengths, 0);
|
|
145
|
+
const shortest = Math.min(...lengths, Infinity);
|
|
146
|
+
const ratio = shortest > 0 ? longest / shortest : Infinity;
|
|
147
|
+
const shortEnoughToNotMatter = longest <= PARITY_MIN_WORDS;
|
|
148
|
+
push('option_parity', shortEnoughToNotMatter || ratio <= OPTION_RATIO_LIMIT, shortEnoughToNotMatter
|
|
149
|
+
? `all four options are ${longest} words or fewer, so shape carries nothing`
|
|
150
|
+
: `longest/shortest = ${ratio.toFixed(2)} (limit ${OPTION_RATIO_LIMIT})`);
|
|
151
|
+
// Separate from parity on purpose. Four options can sit inside the ratio and
|
|
152
|
+
// the correct one still stand out, which is the tell a learner notices first:
|
|
153
|
+
// pick the most careful-sounding answer without reading it.
|
|
154
|
+
//
|
|
155
|
+
// The margin is why this is not simply "is it the longest". The first live
|
|
156
|
+
// run flagged all three of its questions, and one of the three was the
|
|
157
|
+
// correct option beating the next by two words -- 17 against 15, which nobody
|
|
158
|
+
// can see. The other two were four-word margins and still flag today. A check
|
|
159
|
+
// that fires on noise is a check whose report gets skipped. Being the longest
|
|
160
|
+
// at all is still worth counting, but as a rate across the run
|
|
161
|
+
// (`correctLongest` below), not as a verdict on one question.
|
|
162
|
+
const correctLength = words(correct).length;
|
|
163
|
+
const others = q.options.filter((_, i) => i !== q.correct - 1).map((o) => words(o).length);
|
|
164
|
+
const runnerUp = Math.max(...others, 0);
|
|
165
|
+
const margin = correctLength - runnerUp;
|
|
166
|
+
push('correct_not_conspicuous', margin < LENGTH_MARGIN_WORDS, `correct option is ${correctLength} words against a next-longest ${runnerUp} (margin ${margin}, limit ${LENGTH_MARGIN_WORDS})`);
|
|
167
|
+
const negated = NEGATED_STEM.find((re) => re.test(q.stem));
|
|
168
|
+
push('positive_form', !negated, negated ? `stem is negated: ${negated}` : 'stem asks the positive form');
|
|
169
|
+
const numbered = numberedOptions(q.stem);
|
|
170
|
+
push('options_not_numbered', !numbered, numbered ? 'stem enumerates the options itself' : 'stem does not letter or number the options');
|
|
171
|
+
// 1.14 removed the settings line from the stem. A stem that opens with a
|
|
172
|
+
// bracketed readout is the model composing one anyway, and it would change
|
|
173
|
+
// the fingerprint the repeat check depends on.
|
|
174
|
+
push('no_settings_line', !q.stem.trimStart().startsWith('['), 'stem opens with the question');
|
|
175
|
+
return checks;
|
|
176
|
+
}
|
|
177
|
+
export function scoreAll(questions) {
|
|
178
|
+
const scored = questions.map((question) => {
|
|
179
|
+
const checks = checkQuestion(question);
|
|
180
|
+
return { question, checks, passed: checks.every((c) => c.ok) };
|
|
181
|
+
});
|
|
182
|
+
const byCheck = {};
|
|
183
|
+
for (const { checks } of scored) {
|
|
184
|
+
for (const c of checks) {
|
|
185
|
+
const row = (byCheck[c.id] ??= { passed: 0, failed: 0 });
|
|
186
|
+
if (c.ok)
|
|
187
|
+
row.passed++;
|
|
188
|
+
else
|
|
189
|
+
row.failed++;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const slots = [0, 0, 0, 0];
|
|
193
|
+
let correctLongest = 0;
|
|
194
|
+
for (const { question } of scored) {
|
|
195
|
+
const i = question.correct - 1;
|
|
196
|
+
if (i === 0 || i === 1 || i === 2 || i === 3)
|
|
197
|
+
slots[i] += 1;
|
|
198
|
+
// Inside the same guard as `slots`: an out-of-range `correct` is a
|
|
199
|
+
// malformed question, and counting it as "not the longest" reports a
|
|
200
|
+
// property of a question that does not exist.
|
|
201
|
+
if (i === 0 || i === 1 || i === 2 || i === 3) {
|
|
202
|
+
const lengths = question.options.map((o) => words(o).length);
|
|
203
|
+
const mine = lengths[i] ?? 0;
|
|
204
|
+
if (mine === Math.max(...lengths, 0) && lengths.filter((l) => l === mine).length === 1) {
|
|
205
|
+
correctLongest++;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
scored,
|
|
211
|
+
summary: {
|
|
212
|
+
questions: scored.length,
|
|
213
|
+
clean: scored.filter((s) => s.passed).length,
|
|
214
|
+
byCheck,
|
|
215
|
+
slots,
|
|
216
|
+
correctLongest,
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
//# sourceMappingURL=question-checks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"question-checks.js","sourceRoot":"","sources":["../../src/eval/question-checks.ts"],"names":[],"mappings":"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"}
|
|
@@ -129,7 +129,7 @@ Concept: ${row.concept}
|
|
|
129
129
|
Do exactly this, then get straight back to the task:
|
|
130
130
|
1. get_session_quiz_plan with max: 1 and ignore_cooldown: true (the pacing is already decided -- this hook is the cooldown).
|
|
131
131
|
2. Ask that ONE question with AskUserQuestion: four options, one correct, three plausible, and put the correct one in the slot answer_position names. Header "Eklavya", so it is clear who is asking.
|
|
132
|
-
Ask the stem on its own -- the dials that chose this question live in the status bar, so
|
|
132
|
+
Ask the stem on its own -- the dials that chose this question live in the status bar, so what you ask is the question and nothing else.
|
|
133
133
|
3. Grade it with record_attempt: format "mcq", the labels in "options", the stem alone in "question".
|
|
134
134
|
4. Resume the task exactly where you left off. Do not summarise, do not re-plan, do not ask a second question.
|
|
135
135
|
|
package/dist/hooks/lib.js
CHANGED
|
@@ -19,22 +19,23 @@ import fs from 'node:fs';
|
|
|
19
19
|
import Database from 'better-sqlite3';
|
|
20
20
|
import { dbPath } from '../paths.js';
|
|
21
21
|
import { loadConfig } from '../config.js';
|
|
22
|
+
import { readStdinBounded, stripBom, HOOK_STDIN } from '../stdin.js';
|
|
22
23
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
24
|
+
* The JSON the host piped in, or `{}`.
|
|
25
|
+
*
|
|
26
|
+
* Hooks are always given JSON, but "always" is doing a lot of work on a
|
|
27
|
+
* critical path -- a hook invoked by hand, or by a harness version that changes
|
|
28
|
+
* its mind, gets an empty object rather than an exception.
|
|
29
|
+
*
|
|
30
|
+
* The read is bounded (`readStdinBounded`). It used to be `for await (const
|
|
31
|
+
* chunk of process.stdin)`, which waits for EOF and has no other exit: on
|
|
32
|
+
* Windows the host may run a hook through a PowerShell block that swallows the
|
|
33
|
+
* pipe, so `end` never fires and the hook blocks the session on every tool call
|
|
34
|
+
* that triggers it. A hook that throws is survivable; a hook that waits is not.
|
|
26
35
|
*/
|
|
27
|
-
async function readStdin() {
|
|
28
|
-
if (process.stdin.isTTY)
|
|
29
|
-
return '';
|
|
30
|
-
const chunks = [];
|
|
31
|
-
for await (const chunk of process.stdin)
|
|
32
|
-
chunks.push(chunk);
|
|
33
|
-
return Buffer.concat(chunks).toString('utf8');
|
|
34
|
-
}
|
|
35
36
|
export async function readInput() {
|
|
36
37
|
try {
|
|
37
|
-
const raw = await
|
|
38
|
+
const raw = stripBom(await readStdinBounded(HOOK_STDIN));
|
|
38
39
|
if (!raw.trim())
|
|
39
40
|
return {};
|
|
40
41
|
const parsed = JSON.parse(raw);
|
|
@@ -49,7 +50,7 @@ export async function readInput() {
|
|
|
49
50
|
*
|
|
50
51
|
* `openDb()` is the server's entry point and does both, which is right for a
|
|
51
52
|
* server and wrong here: a hook is not the thing that should be migrating a
|
|
52
|
-
* schema, and
|
|
53
|
+
* schema, and several hooks racing a migration on session start is a corruption
|
|
53
54
|
* story rather than a feature. A hook that finds no database has nothing to say,
|
|
54
55
|
* which is the same answer `eklavya_have_deps` gave.
|
|
55
56
|
*/
|
|
@@ -67,6 +68,30 @@ export function openExisting() {
|
|
|
67
68
|
return null;
|
|
68
69
|
}
|
|
69
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* `meta` key holding the UserPromptSubmit nudge's per-session bookkeeping.
|
|
73
|
+
* Shared here rather than exported from the hook, because `session-start`
|
|
74
|
+
* clears it and two hooks spelling the same key differently would be a bug that
|
|
75
|
+
* only shows up as a nudge that never stops.
|
|
76
|
+
*/
|
|
77
|
+
export const NUDGE_KEY_PREFIX = 'prompt_nudge:';
|
|
78
|
+
/**
|
|
79
|
+
* Forget what the nudge knows about this session.
|
|
80
|
+
*
|
|
81
|
+
* Called by `session-start`, which fires on resume and after a compaction as
|
|
82
|
+
* well as at startup, and reprints the standing directive every time. Without
|
|
83
|
+
* this the nudge's row survives with its original first-seen stamp, the grace
|
|
84
|
+
* window is already spent, and the first prompt of a resumed session restates a
|
|
85
|
+
* directive printed seconds earlier.
|
|
86
|
+
*/
|
|
87
|
+
export function clearNudgeState(db, sessionId) {
|
|
88
|
+
try {
|
|
89
|
+
db.prepare('DELETE FROM meta WHERE key = ?').run(`${NUDGE_KEY_PREFIX}${sessionId}`);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
/* Bookkeeping. A session that keeps its old row gets one extra nudge. */
|
|
93
|
+
}
|
|
94
|
+
}
|
|
70
95
|
export function config(cwd) {
|
|
71
96
|
return loadConfig(cwd);
|
|
72
97
|
}
|
package/dist/hooks/lib.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lib.js","sourceRoot":"","sources":["../../src/hooks/lib.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,UAAU,EAAuB,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"lib.js","sourceRoot":"","sources":["../../src/hooks/lib.ts"],"names":[],"mappings":"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"}
|
|
@@ -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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
103
|
-
|
|
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 (
|
|
121
|
-
out.push(`[Eklavya] This repo overrides your global setting for: ${
|
|
141
|
+
if (overrides.length > 0) {
|
|
142
|
+
out.push(`[Eklavya] This repo overrides your global setting for: ${overrides.join(' ')} (.eklavya.json wins).`);
|
|
122
143
|
}
|
|
123
|
-
|
|
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":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAW,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"session-start.js","sourceRoot":"","sources":["../../src/hooks/session-start.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,eAAe,EAAW,MAAM,UAAU,CAAC;AAEjG;;;;;;;;;GASG;AACH,MAAM,SAAS,GAAG;;;;;;;;;;;kEAWgD,CAAC;AAEnE;;;;GAIG;AACH,SAAS,aAAa,CAAC,EAAM;IAC3B,MAAM,IAAI,GAAG,EAAE;SACZ,OAAO,CACN;;;;;;;;gBAQU,CACX;SACA,GAAG,EAA6D,CAAC;IAEpE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC/E,CAAC;AAED;;;;GAIG;AACH,SAAS,OAAO,CAAC,EAAM;IACrB,MAAM,IAAI,GAAG,EAAE;SACZ,OAAO,CACN;;;;gBAIU,CACX;SACA,GAAG,EAA6B,CAAC;IAEpC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC5C,CAAC;AAED,SAAS,QAAQ,CAAC,EAAM;IACtB,MAAM,GAAG,GAAG,EAAE;SACX,OAAO,CACN;;kEAE4D,CAC7D;SACA,GAAG,EAA+B,CAAC;IACtC,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;AACrB,CAAC;AAED,MAAM,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;IACxB,MAAM,EAAE,GAAG,YAAY,EAAE,CAAC;IAC1B,IAAI,CAAC,EAAE;QAAE,OAAO,CAAC,CAAC;IAElB,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;IACzB,MAAM,GAAG,GAAG,SAAS,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAEjC,8EAA8E;IAC9E,kEAAkE;IAClE,IAAI,GAAG;QAAE,iBAAiB,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;IAEpC,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IAC7B,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,QAAQ,CAAC,MAAM,CAAC;IACjF,IAAI,IAAI,KAAK,KAAK;QAAE,OAAO,CAAC,CAAC;IAE7B,MAAM,UAAU,GAAG,KAAK,KAAK,OAAO,IAAI,WAAW,CAAC,CAAC,CAAC,UAAU,WAAW,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC;IAEvF,6EAA6E;IAC7E,4EAA4E;IAC5E,+EAA+E;IAC/E,0DAA0D;IAC1D,EAAE;IACF,2EAA2E;IAC3E,6EAA6E;IAC7E,iDAAiD;IACjD,MAAM,GAAG,GAAa,EAAE,CAAC;IAEzB,8EAA8E;IAC9E,0EAA0E;IAC1E,6EAA6E;IAC7E,0EAA0E;IAC1E,6EAA6E;IAC7E,4EAA4E;IAC5E,wEAAwE;IACxE,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,IAAI,UAAU,GAAG,GAAG,UAAU,WAAW,CAAC;QAC1C,IAAI,UAAU,KAAK,MAAM,EAAE,CAAC;YAC1B,MAAM,QAAQ,GAAG,aAAa,CAAC,EAAE,EAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;YACvE,UAAU,GAAG,GAAG,QAAQ,CAAC,KAAK,KAAK,QAAQ,CAAC,MAAM,CAAC,MAAM,IAAI,QAAQ,CAAC,MAAM,CAAC,OAAO,mBAAmB,CAAC;QAC1G,CAAC;QACD,MAAM,CAAC,EAAE,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC;IAC5F,CAAC;IAED,4EAA4E;IAC5E,6EAA6E;IAC7E,6EAA6E;IAC7E,yCAAyC;IACzC,IAAI,GAAG;QAAE,eAAe,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;IAElC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACpB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5C,OAAO,CAAC,CAAC;AACX,CAAC,CAAC,CAAC;AAUH,kEAAkE;AAClE,SAAS,MAAM,CAAC,EAAM,EAAE,GAAa,EAAE,KAAkB;IACvD,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,KAAK,CAAC;IACnE,MAAM,OAAO,GAAG,aAAa,CAAC,EAAE,CAAC,CAAC;IAElC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,6EAA6E;QAC7E,GAAG,CAAC,IAAI,CACN,4CAA4C,IAAI,YAAY,UAAU,cAAc,OAAO,YAAY,UAAU,GAAG,CACrH,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,IAAI,IAAI,GAAG,8BAA8B,OAAO,GAAG,CAAC;QACpD,MAAM,IAAI,GAAG,OAAO,CAAC,EAAE,CAAC,CAAC;QACzB,IAAI,IAAI;YAAE,IAAI,IAAI,UAAU,IAAI,GAAG,CAAC;QACpC,MAAM,GAAG,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;QACzB,IAAI,GAAG,GAAG,CAAC;YAAE,IAAI,IAAI,IAAI,GAAG,6BAA6B,CAAC;QAC1D,GAAG,CAAC,IAAI,CACN,GAAG,IAAI,UAAU,IAAI,YAAY,UAAU,cAAc,OAAO,YAAY,UAAU,GAAG,CAC1F,CAAC;IACJ,CAAC;IAED,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzB,GAAG,CAAC,IAAI,CACN,0DAA0D,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,wBAAwB,CACtG,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -158,12 +158,13 @@ Concepts: ${concepts}
|
|
|
158
158
|
${ask}
|
|
159
159
|
|
|
160
160
|
Ask each question as MULTIPLE CHOICE via the AskUserQuestion tool: four options,
|
|
161
|
-
one correct and three plausible,
|
|
161
|
+
one correct and three plausible, the correct one in the slot answer_position
|
|
162
|
+
names, header "Eklavya" so it is clear who is asking.
|
|
162
163
|
The tool's own "Other" choice is the escape hatch for "I don't know". They are
|
|
163
164
|
mid-task -- a blank prompt gets skipped whether or not they knew the answer.
|
|
164
165
|
Ask the stem on its own. The dials that pitched it -- mode, focus, cadence,
|
|
165
|
-
level -- are in the status bar, so
|
|
166
|
-
|
|
166
|
+
level -- are in the status bar, so what you ask is the question and nothing
|
|
167
|
+
else.
|
|
167
168
|
Record with format "mcq", the labels in "options", and only the stem in
|
|
168
169
|
"question".
|
|
169
170
|
${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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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eklavya",
|
|
3
3
|
"displayName": "Eklavya",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.15.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"
|