eklavya 1.51.0 → 1.52.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/README.md +13 -0
- package/dist/assets/dashboard.html +1 -1
- package/dist/assets/tutor/SKILL.md +18 -16
- package/dist/assets/tutor/references/panel.md +40 -0
- package/dist/cli.js +6 -3
- package/dist/cli.js.map +1 -1
- package/dist/config-path.js +1 -0
- package/dist/config-path.js.map +1 -1
- package/dist/config.js +3 -1
- package/dist/config.js.map +1 -1
- package/dist/dashboard.js +2 -0
- package/dist/dashboard.js.map +1 -1
- package/dist/hooks/checkpoint-quiz.js +27 -7
- package/dist/hooks/checkpoint-quiz.js.map +1 -1
- package/dist/hooks/delegate-nudge.js +1 -1
- package/dist/hooks/delegate-nudge.js.map +1 -1
- package/dist/hooks/delegation-lib.js +14 -9
- package/dist/hooks/delegation-lib.js.map +1 -1
- package/dist/hooks/prompt-submit-nudge.js +1 -1
- package/dist/hooks/prompt-submit-nudge.js.map +1 -1
- package/dist/hooks/session-start.js +1 -1
- package/dist/hooks/session-start.js.map +1 -1
- package/dist/hooks/stop-quiz-check.js +6 -0
- package/dist/hooks/stop-quiz-check.js.map +1 -1
- package/dist/migrations/025_panel_questions.sql +43 -0
- package/dist/panel-state.js +117 -0
- package/dist/panel-state.js.map +1 -0
- package/dist/panel.js +241 -0
- package/dist/panel.js.map +1 -0
- package/dist/plugin/.claude-plugin/plugin.json +3 -2
- package/dist/plugin/agents/tutor.md +3 -1
- package/dist/plugin/hooks/CLAUDE.md +18 -0
- package/dist/plugin/hooks/hooks.json +44 -11
- package/dist/plugin/hooks/panel/model.ts +132 -0
- package/dist/plugin/hooks/panel/register.tsx +479 -0
- package/dist/plugin/hooks/panel/types/index.d.ts +61 -0
- package/dist/plugin/skills/CLAUDE.md +6 -0
- package/dist/plugin/skills/quiz/SKILL.md +3 -0
- package/dist/plugin/skills/tutor/SKILL.md +18 -16
- package/dist/plugin/skills/tutor/references/panel.md +40 -0
- package/dist/store.js +6 -0
- package/dist/store.js.map +1 -1
- package/dist/tools/config_tools.js +2 -1
- package/dist/tools/config_tools.js.map +1 -1
- package/dist/tools/get_session_quiz_plan.js +54 -8
- package/dist/tools/get_session_quiz_plan.js.map +1 -1
- package/dist/tools/index.js +4 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/panel_tools.js +72 -0
- package/dist/tools/panel_tools.js.map +1 -0
- package/dist/tools/record_attempt.js +191 -185
- package/dist/tools/record_attempt.js.map +1 -1
- package/dist/user-skill/eklavya/SKILL.md +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { panelAnswer, panelSync, presentQuestion } from '../panel.js';
|
|
3
|
+
import { CWD_HINT, LIMITS, SESSION_HINT } from './types.js';
|
|
4
|
+
const OPTION_COUNT = 4;
|
|
5
|
+
export const presentQuestionTool = {
|
|
6
|
+
name: 'present_question',
|
|
7
|
+
title: 'Present a quiz question in the side panel',
|
|
8
|
+
description: 'Hand one multiple-choice question to the Eklavya side panel and return at once. Use it instead of AskUserQuestion only when the plan says presentation "panel"; it is rejected with panel_disabled or panel_unavailable otherwise, and then you ask with AskUserQuestion. Give the stem alone, four options each with its one-line description and a grade (the right one 4, a near miss 2, one built on a misconception 1) with the right one in the plan\'s answer_position slot, and a one-line explanation shown after the answer. The panel shows it, grades it from those grades and records the attempt itself: do not wait, do not ask anything else, do not call record_attempt, carry on with the task. One question per session may be open; a second is rejected with question_open. Nothing is written to the learner\'s history until they answer.',
|
|
9
|
+
inputSchema: {
|
|
10
|
+
session_id: z.string().max(LIMITS.sessionId).optional().describe(SESSION_HINT),
|
|
11
|
+
cwd: z.string().max(LIMITS.cwd).optional().describe(CWD_HINT),
|
|
12
|
+
slug: z.string().max(LIMITS.slug).describe('The concept asked about.'),
|
|
13
|
+
question: z.string().min(1).max(LIMITS.question).describe('The question stem only: no options, no attribution, no settings line.'),
|
|
14
|
+
options: z
|
|
15
|
+
.array(z.object({
|
|
16
|
+
label: z.string().min(1).max(LIMITS.option).describe('The option text itself, a complete claim in the option\'s own words: never a letter or a number (the panel numbers them). Labels must differ.'),
|
|
17
|
+
description: z.string().max(LIMITS.option).describe('The one-line description shown under the option.'),
|
|
18
|
+
grade: z.number().int().min(1).max(4).describe('4 for the right option, 2 for a near miss, 1 for one built on a misconception.'),
|
|
19
|
+
correct: z.boolean().optional().describe('true on exactly one option, the one graded 4.'),
|
|
20
|
+
}))
|
|
21
|
+
.length(OPTION_COUNT)
|
|
22
|
+
.describe('Exactly four options in display order.'),
|
|
23
|
+
explanation: z.string().min(1).max(LIMITS.feedback).describe('The one-line why, shown after the answer.'),
|
|
24
|
+
difficulty: z.number().int().min(1).max(5).describe('The tier you asked at: tier_to_ask from the plan.'),
|
|
25
|
+
more: z.boolean().optional().describe('true when more questions of this round follow (an explicit quiz): the panel then offers a Next question button. Leave it out for a single question and for the last of a round.'),
|
|
26
|
+
},
|
|
27
|
+
handler: (args, { db }) => presentQuestion(db, args),
|
|
28
|
+
};
|
|
29
|
+
export const panelSyncTool = {
|
|
30
|
+
name: 'panel_sync',
|
|
31
|
+
title: 'Panel: is a question waiting',
|
|
32
|
+
description: 'Called only by the Eklavya panel, never by the model. Returns the question waiting for this session and project (its repo, stem, options with ids and notes, phase, concept name, tier, more), or {none: true}; {disabled: true} when quiz.panel is off. Each call is the heartbeat of the mod: the planner chooses the panel only while one is fresh. Never returns the answer key. Reports whether the host could seat the pane (placed) so an invisible question is never counted as shown. Safe to call on every lifecycle event.',
|
|
33
|
+
inputSchema: {
|
|
34
|
+
session_id: z.string().min(1).max(LIMITS.sessionId).describe('The host session id.'),
|
|
35
|
+
cwd: z.string().max(LIMITS.cwd).describe('The session working directory.'),
|
|
36
|
+
host: z
|
|
37
|
+
.object({
|
|
38
|
+
surface: z.string().min(1).max(32).describe('The Claude Code surface drawing the pane: terminal, desktop, vscode or mobile.'),
|
|
39
|
+
version: z.string().max(64).optional(),
|
|
40
|
+
columns: z.number().int().min(0).max(100000).optional(),
|
|
41
|
+
})
|
|
42
|
+
.describe('Where the mod is running. Stamps the heartbeat that lets the planner choose the panel.'),
|
|
43
|
+
placed: z
|
|
44
|
+
.object({
|
|
45
|
+
question_id: z.string().max(LIMITS.sessionId),
|
|
46
|
+
ok: z.boolean(),
|
|
47
|
+
reason: z.string().max(LIMITS.option).optional(),
|
|
48
|
+
})
|
|
49
|
+
.optional()
|
|
50
|
+
.describe('Whether the pane for this question was seated.'),
|
|
51
|
+
},
|
|
52
|
+
handler: (args, { db }) => panelSync(db, args),
|
|
53
|
+
};
|
|
54
|
+
export const panelAnswerTool = {
|
|
55
|
+
name: 'panel_answer',
|
|
56
|
+
title: 'Panel: record the answer',
|
|
57
|
+
description: 'Called only by the Eklavya panel, never by the model. Grades and records one answer exactly once: kind "choice" with option_id, kind "text" with the typed text, first alone (it replies {phase: "grading", correct_label} so the panel\'s grader can judge), then with the grade (0-5), outcome (answered or dont_know, which needs grade 0) and feedback the grader produced, or kind "skip" (a decline). A second call for the same question returns the first reply and writes nothing. A mismatched session or project returns stale_question.',
|
|
58
|
+
inputSchema: {
|
|
59
|
+
question_id: z.string().max(LIMITS.sessionId),
|
|
60
|
+
session_id: z.string().min(1).max(LIMITS.sessionId),
|
|
61
|
+
repo: z.string().max(LIMITS.cwd).describe('The repo panel_sync returned with the question.'),
|
|
62
|
+
cwd: z.string().max(LIMITS.cwd).describe('The session working directory, where this project\'s settings are read.'),
|
|
63
|
+
kind: z.enum(['choice', 'text', 'skip']),
|
|
64
|
+
option_id: z.string().max(16).optional(),
|
|
65
|
+
text: z.string().max(LIMITS.answer).optional(),
|
|
66
|
+
grade: z.number().int().min(0).max(5).optional(),
|
|
67
|
+
outcome: z.enum(['answered', 'dont_know']).optional(),
|
|
68
|
+
feedback: z.string().max(LIMITS.feedback).optional(),
|
|
69
|
+
},
|
|
70
|
+
handler: (args, { db }) => panelAnswer(db, args),
|
|
71
|
+
};
|
|
72
|
+
//# sourceMappingURL=panel_tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"panel_tools.js","sourceRoot":"","sources":["../../src/tools/panel_tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -75,192 +75,198 @@ export const recordAttempt = {
|
|
|
75
75
|
.optional()
|
|
76
76
|
.describe('Why the grade is what it is. "answered" they attempted it; "dont_know" they said they did not know and you taught it; "declined" they chose to skip and you dropped it without explaining. Grade 0 covers the last two, so this is the only thing that tells them apart later -- and they are not interchangeable: a declined concept is never offered again, so labelling a blank as a decline removes it from the only route out of a blocked commit gate. If you taught it, it is "dont_know".'),
|
|
77
77
|
},
|
|
78
|
-
handler: (args, { db }) =>
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
78
|
+
handler: (args, { db }) => recordAttemptCore(db, args),
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Everything `record_attempt` does, as one function. The panel's `panel_answer`
|
|
82
|
+
* calls it too, inside its own transaction, so the grade cap, the outcome
|
|
83
|
+
* conflicts, promotion, the gate and the explain block exist exactly once.
|
|
84
|
+
*/
|
|
85
|
+
export function recordAttemptCore(db, args) {
|
|
86
|
+
const now = new Date();
|
|
87
|
+
const { config, repoRoot } = loadConfig(args.cwd);
|
|
88
|
+
const sessionId = resolveSessionId(db, args.session_id, args.cwd);
|
|
89
|
+
// A skip that claims a pass. "declined" and "dont_know" both say nothing was
|
|
90
|
+
// answered, and a grade of 3 or more says it was answered correctly; one of
|
|
91
|
+
// the two is wrong and nothing here can tell which. Rejected before any
|
|
92
|
+
// write rather than corrected: keeping the grade would grant mastery and
|
|
93
|
+
// clear a gate on a question nobody answered, and keeping the outcome would
|
|
94
|
+
// throw away a grade that may have been real.
|
|
95
|
+
if ((args.outcome === 'declined' || args.outcome === 'dont_know') && args.grade >= PASSING_GRADE) {
|
|
96
|
+
return {
|
|
97
|
+
error: 'outcome_grade_conflict',
|
|
98
|
+
outcome: args.outcome,
|
|
99
|
+
grade: args.grade,
|
|
100
|
+
detail: `Outcome "${args.outcome}" means nothing was answered, so its grade is 0; a grade of ${PASSING_GRADE} or more means a correct answer, whose outcome is "answered". Nothing was recorded. Call again with whichever pair is true.`,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
const slug = normalizeSlug(args.slug);
|
|
104
|
+
const concept = conceptBySlug(db, slug);
|
|
105
|
+
if (!concept) {
|
|
106
|
+
return {
|
|
107
|
+
error: 'unknown_concept',
|
|
108
|
+
slug,
|
|
109
|
+
detail: 'No concept with that slug. Call upsert_concepts or log_session_concepts first.',
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
// The settings line is presentation. Stripped rather than rejected, because the
|
|
113
|
+
// tutor pasting back the block it displayed is the likely mistake and losing
|
|
114
|
+
// a real answer over it would be the wrong trade.
|
|
115
|
+
const question = stripAskHeader(args.question);
|
|
116
|
+
// Which band this answer was earned at, read before the write so a promotion
|
|
117
|
+
// triggered by this very attempt cannot relabel it.
|
|
118
|
+
const standing = levelStanding(db, config, repoRoot);
|
|
119
|
+
// Checked before the write, or the question we are recording matches itself.
|
|
120
|
+
const repeatQuestion = hasAskedQuestion(db, concept.id, question);
|
|
121
|
+
// Enforced here rather than trusted to the tutor. Grade 5 means "explained
|
|
122
|
+
// why", which choosing among four options cannot demonstrate; one in four is
|
|
123
|
+
// a coin. Capping is visible in the response so a tutor that keeps awarding
|
|
124
|
+
// 5s for multiple choice finds out.
|
|
125
|
+
// A decline you explained is a contradiction, and the likelier reading is a
|
|
126
|
+
// mislabelled blank: the rule for a decline is to drop it immediately, so
|
|
127
|
+
// there is nothing to write feedback about. Reported rather than corrected
|
|
128
|
+
// -- rewriting a stated outcome would be guessing at what happened -- but
|
|
129
|
+
// reported loudly, because silence here is what let 11 of 16 declines in a
|
|
130
|
+
// real history carry an explanation with nobody noticing.
|
|
131
|
+
const outcomeConflict = args.outcome === 'declined' && typeof args.feedback === 'string' && args.feedback.trim().length > 0;
|
|
132
|
+
// A key that does not line up with the options cannot grade a correction,
|
|
133
|
+
// so it is stored as NULL -- but the answer is real, and losing it would be
|
|
134
|
+
// the worse trade, so the attempt is recorded and the tutor told.
|
|
135
|
+
const options = args.options ?? null;
|
|
136
|
+
const correctOk = args.correct === undefined || (options?.includes(args.correct) ?? false);
|
|
137
|
+
const correct = correctOk ? (args.correct ?? null) : null;
|
|
138
|
+
const notesOk = args.option_notes === undefined || args.option_notes.length === (options?.length ?? 0);
|
|
139
|
+
const optionNotes = notesOk ? (args.option_notes ?? null) : null;
|
|
140
|
+
const capped = args.format === 'mcq' && args.grade > MAX_MCQ_GRADE;
|
|
141
|
+
const grade = capped ? MAX_MCQ_GRADE : args.grade;
|
|
142
|
+
const graded = db.transaction(() => {
|
|
143
|
+
// An attempt on a concept the session never logged still counts toward
|
|
144
|
+
// `answered`, so quizzing on review debt is not free -- but it lands as
|
|
145
|
+
// 'review', so it cannot satisfy a bar that the session's actual work set.
|
|
146
|
+
// If the task did touch this concept, log_session_concepts has already
|
|
147
|
+
// marked it 'work' and that wins.
|
|
148
|
+
logSessionConcept(db, sessionId, concept.id, null, 'review');
|
|
149
|
+
const next = gradeConcept(db, {
|
|
150
|
+
conceptId: concept.id,
|
|
151
|
+
sessionId,
|
|
152
|
+
question,
|
|
153
|
+
answer: args.answer ?? null,
|
|
154
|
+
grade,
|
|
155
|
+
difficulty: args.difficulty,
|
|
156
|
+
feedback: args.feedback ?? null,
|
|
157
|
+
format: args.format ?? null,
|
|
158
|
+
options,
|
|
159
|
+
correct,
|
|
160
|
+
optionNotes,
|
|
161
|
+
// Left NULL rather than guessed when the tutor does not say. An absent
|
|
162
|
+
// answer is a fair hint that nothing was attempted, but it cannot tell
|
|
163
|
+
// "teach me" from "leave it" -- and inventing the difference here would
|
|
164
|
+
// put a value in the column that nobody observed.
|
|
165
|
+
outcome: args.outcome ?? null,
|
|
166
|
+
repo: standing.repo,
|
|
167
|
+
level: standing.level,
|
|
168
|
+
now,
|
|
169
|
+
});
|
|
170
|
+
// In the same transaction as the grade: a promotion is a fact about
|
|
171
|
+
// attempt rows, and a crash between the two would leave a level claiming
|
|
172
|
+
// evidence that was rolled back.
|
|
173
|
+
const promotion = promoteIfEarned(db, config, repoRoot);
|
|
174
|
+
// The gate too. `registerTools` retries the whole handler on SQLITE_BUSY,
|
|
175
|
+
// which is only safe when every write here rolls back together: with the
|
|
176
|
+
// gate written after the commit, a lock on it retried a grade that had
|
|
177
|
+
// already landed, and one answer became two attempt rows and a rung of
|
|
178
|
+
// the SM-2 ladder the learner never climbed.
|
|
179
|
+
const gate = syncGate(db, sessionId, config, { repo: repoRoot });
|
|
180
|
+
return { state: next, promotion, gate };
|
|
181
|
+
})();
|
|
182
|
+
const state = graded.state;
|
|
183
|
+
const gate = graded.gate;
|
|
184
|
+
// A missed answer, when the developer asked for pages on those. Decided
|
|
185
|
+
// here rather than left to a line in the tutor skill: the model follows a
|
|
186
|
+
// field it was handed far more reliably than a rule it has to remember, and
|
|
187
|
+
// the instruction is composed once, where the config is visible. A decline
|
|
188
|
+
// and a bare skip get nothing -- they asked to move on.
|
|
189
|
+
const missed = isMissed(grade, args.outcome ?? null);
|
|
190
|
+
const explain = config.explain_on_wrong && missed
|
|
191
|
+
? {
|
|
192
|
+
concept: concept.slug,
|
|
193
|
+
name: concept.name,
|
|
194
|
+
question,
|
|
195
|
+
options,
|
|
196
|
+
option_notes: optionNotes,
|
|
197
|
+
answer: args.answer ?? null,
|
|
198
|
+
correct,
|
|
199
|
+
attempt_id: state.attemptId,
|
|
200
|
+
instruction: explainInstruction(concept.name, options, state.attemptId, optionNotes),
|
|
104
201
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
// why", which choosing among four options cannot demonstrate; one in four is
|
|
116
|
-
// a coin. Capping is visible in the response so a tutor that keeps awarding
|
|
117
|
-
// 5s for multiple choice finds out.
|
|
118
|
-
// A decline you explained is a contradiction, and the likelier reading is a
|
|
119
|
-
// mislabelled blank: the rule for a decline is to drop it immediately, so
|
|
120
|
-
// there is nothing to write feedback about. Reported rather than corrected
|
|
121
|
-
// -- rewriting a stated outcome would be guessing at what happened -- but
|
|
122
|
-
// reported loudly, because silence here is what let 11 of 16 declines in a
|
|
123
|
-
// real history carry an explanation with nobody noticing.
|
|
124
|
-
const outcomeConflict = args.outcome === 'declined' && typeof args.feedback === 'string' && args.feedback.trim().length > 0;
|
|
125
|
-
// A key that does not line up with the options cannot grade a correction,
|
|
126
|
-
// so it is stored as NULL -- but the answer is real, and losing it would be
|
|
127
|
-
// the worse trade, so the attempt is recorded and the tutor told.
|
|
128
|
-
const options = args.options ?? null;
|
|
129
|
-
const correctOk = args.correct === undefined || (options?.includes(args.correct) ?? false);
|
|
130
|
-
const correct = correctOk ? (args.correct ?? null) : null;
|
|
131
|
-
const notesOk = args.option_notes === undefined || args.option_notes.length === (options?.length ?? 0);
|
|
132
|
-
const optionNotes = notesOk ? (args.option_notes ?? null) : null;
|
|
133
|
-
const capped = args.format === 'mcq' && args.grade > MAX_MCQ_GRADE;
|
|
134
|
-
const grade = capped ? MAX_MCQ_GRADE : args.grade;
|
|
135
|
-
const graded = db.transaction(() => {
|
|
136
|
-
// An attempt on a concept the session never logged still counts toward
|
|
137
|
-
// `answered`, so quizzing on review debt is not free -- but it lands as
|
|
138
|
-
// 'review', so it cannot satisfy a bar that the session's actual work set.
|
|
139
|
-
// If the task did touch this concept, log_session_concepts has already
|
|
140
|
-
// marked it 'work' and that wins.
|
|
141
|
-
logSessionConcept(db, sessionId, concept.id, null, 'review');
|
|
142
|
-
const next = gradeConcept(db, {
|
|
143
|
-
conceptId: concept.id,
|
|
144
|
-
sessionId,
|
|
145
|
-
question,
|
|
146
|
-
answer: args.answer ?? null,
|
|
147
|
-
grade,
|
|
148
|
-
difficulty: args.difficulty,
|
|
149
|
-
feedback: args.feedback ?? null,
|
|
150
|
-
format: args.format ?? null,
|
|
151
|
-
options,
|
|
152
|
-
correct,
|
|
153
|
-
optionNotes,
|
|
154
|
-
// Left NULL rather than guessed when the tutor does not say. An absent
|
|
155
|
-
// answer is a fair hint that nothing was attempted, but it cannot tell
|
|
156
|
-
// "teach me" from "leave it" -- and inventing the difference here would
|
|
157
|
-
// put a value in the column that nobody observed.
|
|
158
|
-
outcome: args.outcome ?? null,
|
|
159
|
-
repo: standing.repo,
|
|
160
|
-
level: standing.level,
|
|
161
|
-
now,
|
|
162
|
-
});
|
|
163
|
-
// In the same transaction as the grade: a promotion is a fact about
|
|
164
|
-
// attempt rows, and a crash between the two would leave a level claiming
|
|
165
|
-
// evidence that was rolled back.
|
|
166
|
-
const promotion = promoteIfEarned(db, config, repoRoot);
|
|
167
|
-
// The gate too. `registerTools` retries the whole handler on SQLITE_BUSY,
|
|
168
|
-
// which is only safe when every write here rolls back together: with the
|
|
169
|
-
// gate written after the commit, a lock on it retried a grade that had
|
|
170
|
-
// already landed, and one answer became two attempt rows and a rung of
|
|
171
|
-
// the SM-2 ladder the learner never climbed.
|
|
172
|
-
const gate = syncGate(db, sessionId, config, { repo: repoRoot });
|
|
173
|
-
return { state: next, promotion, gate };
|
|
174
|
-
})();
|
|
175
|
-
const state = graded.state;
|
|
176
|
-
const gate = graded.gate;
|
|
177
|
-
// A missed answer, when the developer asked for pages on those. Decided
|
|
178
|
-
// here rather than left to a line in the tutor skill: the model follows a
|
|
179
|
-
// field it was handed far more reliably than a rule it has to remember, and
|
|
180
|
-
// the instruction is composed once, where the config is visible. A decline
|
|
181
|
-
// and a bare skip get nothing -- they asked to move on.
|
|
182
|
-
const missed = isMissed(grade, args.outcome ?? null);
|
|
183
|
-
const explain = config.explain_on_wrong && missed
|
|
202
|
+
: null;
|
|
203
|
+
const after = levelStanding(db, config, repoRoot);
|
|
204
|
+
const score = decayedScore(state.score, state.next_review, now);
|
|
205
|
+
return {
|
|
206
|
+
slug: concept.slug,
|
|
207
|
+
attempt_id: state.attemptId,
|
|
208
|
+
recorded_grade: grade,
|
|
209
|
+
// Silence here would let the tutor keep miscalibrating; say what was
|
|
210
|
+
// changed and why.
|
|
211
|
+
...(capped
|
|
184
212
|
? {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
question,
|
|
188
|
-
options,
|
|
189
|
-
option_notes: optionNotes,
|
|
190
|
-
answer: args.answer ?? null,
|
|
191
|
-
correct,
|
|
192
|
-
attempt_id: state.attemptId,
|
|
193
|
-
instruction: explainInstruction(concept.name, options, state.attemptId, optionNotes),
|
|
213
|
+
grade_capped: true,
|
|
214
|
+
detail: `Multiple choice is capped at ${MAX_MCQ_GRADE}: recognising the right option does not show you can explain it. Recorded ${grade} instead of ${args.grade}.`,
|
|
194
215
|
}
|
|
195
|
-
:
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
},
|
|
252
|
-
...(graded.promotion
|
|
253
|
-
? {
|
|
254
|
-
level_up: {
|
|
255
|
-
from: graded.promotion.from,
|
|
256
|
-
to: graded.promotion.to,
|
|
257
|
-
passed: graded.promotion.counts.passed,
|
|
258
|
-
accuracy: Number((graded.promotion.counts.passed / Math.max(1, graded.promotion.counts.answered)).toFixed(3)),
|
|
259
|
-
detail: `Say this in one line and go back to the task: they have cleared ${graded.promotion.from} on this project, and questions now come from the ${graded.promotion.to} band.`,
|
|
260
|
-
},
|
|
261
|
-
}
|
|
262
|
-
: {}),
|
|
263
|
-
};
|
|
264
|
-
},
|
|
265
|
-
};
|
|
216
|
+
: {}),
|
|
217
|
+
new_score: Number(state.score.toFixed(3)),
|
|
218
|
+
next_review: state.next_review,
|
|
219
|
+
interval_days: state.interval_d,
|
|
220
|
+
reps: state.reps,
|
|
221
|
+
ease: Number(state.ease.toFixed(2)),
|
|
222
|
+
known: isKnown({ score, reps: state.reps }),
|
|
223
|
+
// Never the same question twice. Recorded either way — refusing the
|
|
224
|
+
// write would lose a real answer — but the tutor is told, so the next
|
|
225
|
+
// question can be a new one.
|
|
226
|
+
repeat_question: repeatQuestion,
|
|
227
|
+
...(explain ? { explain } : {}),
|
|
228
|
+
...(correctOk
|
|
229
|
+
? {}
|
|
230
|
+
: {
|
|
231
|
+
correct_mismatch: 'correct was not one of the options, so this answer cannot be corrected later. Pass the right option\'s label verbatim, exactly as it appears in options.',
|
|
232
|
+
}),
|
|
233
|
+
...(notesOk
|
|
234
|
+
? {}
|
|
235
|
+
: {
|
|
236
|
+
option_notes_mismatch: 'option_notes needs one note per option, in the same order as options. They were not stored.',
|
|
237
|
+
}),
|
|
238
|
+
...(outcomeConflict
|
|
239
|
+
? {
|
|
240
|
+
outcome_conflict: 'You passed outcome "declined" and also feedback. A decline is dropped without explanation, so if you taught this concept it was a blank: record outcome "dont_know". It matters — a declined concept is never offered again, and in enforced mode that is the only way out of a blocked commit.',
|
|
241
|
+
}
|
|
242
|
+
: {}),
|
|
243
|
+
gate,
|
|
244
|
+
level: after.level,
|
|
245
|
+
// The runway, said in numbers. A level nobody can see the progress toward
|
|
246
|
+
// is a level that feels identical in week one and week ten.
|
|
247
|
+
level_progress: {
|
|
248
|
+
passed: after.counts.passed,
|
|
249
|
+
needed: after.needed.answers,
|
|
250
|
+
answered: after.counts.answered,
|
|
251
|
+
accuracy: after.accuracy,
|
|
252
|
+
min_accuracy: after.needed.accuracy,
|
|
253
|
+
concepts: after.counts.concepts,
|
|
254
|
+
needed_concepts: after.needed.concepts,
|
|
255
|
+
next: after.next,
|
|
256
|
+
...(after.pinned ? { pinned: true } : {}),
|
|
257
|
+
...(after.unmet.length > 0 ? { unmet: after.unmet } : {}),
|
|
258
|
+
},
|
|
259
|
+
...(graded.promotion
|
|
260
|
+
? {
|
|
261
|
+
level_up: {
|
|
262
|
+
from: graded.promotion.from,
|
|
263
|
+
to: graded.promotion.to,
|
|
264
|
+
passed: graded.promotion.counts.passed,
|
|
265
|
+
accuracy: Number((graded.promotion.counts.passed / Math.max(1, graded.promotion.counts.answered)).toFixed(3)),
|
|
266
|
+
detail: `Say this in one line and go back to the task: they have cleared ${graded.promotion.from} on this project, and questions now come from the ${graded.promotion.to} band.`,
|
|
267
|
+
},
|
|
268
|
+
}
|
|
269
|
+
: {}),
|
|
270
|
+
};
|
|
271
|
+
}
|
|
266
272
|
//# sourceMappingURL=record_attempt.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"record_attempt.js","sourceRoot":"","sources":["../../src/tools/record_attempt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"record_attempt.js","sourceRoot":"","sources":["../../src/tools/record_attempt.ts"],"names":[],"mappings":"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"}
|
|
@@ -71,6 +71,7 @@ changing.
|
|
|
71
71
|
| `quiz.enabled` | Does it ask at all? | `true`, `false` | `true` |
|
|
72
72
|
| `quiz.enforced` | Do questions hold commits? | `true`, `false` | `false` |
|
|
73
73
|
| `quiz.only_on_changes` | Only ask once the session has changed code in a git repository? | `true`, `false` | `true` |
|
|
74
|
+
| `quiz.panel` | Show questions in the experimental side panel instead of Claude's question card? | `true`, `false` | `false` |
|
|
74
75
|
| `focus` | What does it teach? | `project`, `concept`, `learn` | `concept` |
|
|
75
76
|
| `cadence` | When do the questions land? | `as-you-go`, `end` | `as-you-go` |
|
|
76
77
|
| `difficulty` | How hard may they get? | `auto`, `easy`, `medium`, `hard` | `auto` |
|
package/package.json
CHANGED