eklavya 1.59.0 → 1.61.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 (33) hide show
  1. package/README.md +2 -2
  2. package/dist/assets/dashboard.html +131 -56
  3. package/dist/assets/tutor/SKILL.md +49 -44
  4. package/dist/assets/tutor/references/focus-and-level.md +2 -1
  5. package/dist/assets/tutor/references/grading.md +101 -104
  6. package/dist/assets/tutor/references/panel.md +5 -2
  7. package/dist/assets/tutor/references/writing-mcq.md +28 -20
  8. package/dist/eval/conversation-score.js +98 -0
  9. package/dist/eval/conversation-score.js.map +1 -0
  10. package/dist/eval/question-checks.js +31 -0
  11. package/dist/eval/question-checks.js.map +1 -1
  12. package/dist/hooks/ask-label.js +37 -14
  13. package/dist/hooks/ask-label.js.map +1 -1
  14. package/dist/migrations/027_invalid_questions.sql +19 -0
  15. package/dist/panel.js +17 -0
  16. package/dist/panel.js.map +1 -1
  17. package/dist/plugin/.claude-plugin/plugin.json +1 -1
  18. package/dist/plugin/agents/tutor.md +26 -23
  19. package/dist/plugin/hooks/CLAUDE.md +1 -1
  20. package/dist/plugin/skills/CLAUDE.md +1 -1
  21. package/dist/plugin/skills/tutor/SKILL.md +49 -44
  22. package/dist/plugin/skills/tutor/references/focus-and-level.md +2 -1
  23. package/dist/plugin/skills/tutor/references/grading.md +101 -104
  24. package/dist/plugin/skills/tutor/references/panel.md +5 -2
  25. package/dist/plugin/skills/tutor/references/writing-mcq.md +28 -20
  26. package/dist/store.js +15 -1
  27. package/dist/store.js.map +1 -1
  28. package/dist/tools/panel_tools.js +1 -1
  29. package/dist/tools/panel_tools.js.map +1 -1
  30. package/dist/tools/record_attempt.js +26 -8
  31. package/dist/tools/record_attempt.js.map +1 -1
  32. package/dist/user-skill/eklavya/SKILL.md +1 -1
  33. package/package.json +1 -1
@@ -1,15 +1,34 @@
1
1
  # Grading, and what to do with a blank
2
2
 
3
- Required reading before you grade. Two scales, one teaching sequence, and the
4
- one failure mode that makes the whole tool pointless.
3
+ Required reading before you grade. Call `record_attempt` for **every** answer,
4
+ blanks and skips included, with `question` verbatim (that text is what stops the
5
+ same question coming back) and an `outcome`: `answered`, `dont_know`, `declined`
6
+ or `invalid`. A question that was itself at fault is not a grade at all: see
7
+ *When the question is at fault*.
5
8
 
6
- ## Every answer gets recorded
9
+ ## What every response earns
7
10
 
8
- Call `record_attempt` for **every** answer, including blanks and skips — pass
9
- `question` verbatim, because that text is what stops the same question coming
10
- back later. Pass `outcome` as well: `answered`, `dont_know`, or `declined`.
11
+ Find the row for who asked and what the learner did. The sections below say how
12
+ to do each cell.
11
13
 
12
- Grade honestly on SM-2's 0–5:
14
+ | Learner | Automatic question: checkpoint, sweep, while-waiting | Panel | Parallel tutor | Asked-for quiz or lesson |
15
+ |---|---|---|---|---|
16
+ | Right | record; verdict | the panel records and judges; do nothing | record; verdict | record; verdict |
17
+ | Wrong | record; verdict, right answer, one line of why; no follow-up | as left | as left, then the handoff block | do not give the answer yet: one narrower question, then teach |
18
+ | Blank ("I don't know") | `dont_know`; answer and one line of why | as left | as left | `dont_know`; the full four-step teaching |
19
+ | Skip | `declined`, nothing said | the Skip button records it | `declined` | `declined` |
20
+ | Question at fault | `invalid`; own it, give the answer, no replacement this turn | n/a: nothing to press | `invalid` | `invalid`; own it, then ask a fixed question now |
21
+ | Silence | nothing | nothing | nothing, do not chase | wait |
22
+
23
+ Then **straight back to the task** in every automatic case: no summary, no
24
+ second question, no re-plan. A verdict is *right*, or *wrong plus the right
25
+ answer and one line of why*, and it always comes first.
26
+
27
+ ## Grading honestly
28
+
29
+ Grade on SM-2's 0–5. Before you pick a number, state to yourself what in their
30
+ answer justifies it: a gate built on inflated grades teaches nothing, and being
31
+ generous is the one failure that makes this tool pointless.
13
32
 
14
33
  | Grade | Means |
15
34
  |---|---|
@@ -20,18 +39,9 @@ Grade honestly on SM-2's 0–5:
20
39
  | 4 | correct and clean |
21
40
  | 5 | correct, and explained *why*, or caught a nuance you didn't ask for |
22
41
 
23
- Before you pick a number, state to yourself what in their answer justifies it. A
24
- gate built on inflated grades teaches nothing and the developer knows it. Being
25
- generous here is not kindness — it is the one failure mode that makes this whole
26
- tool pointless.
27
-
28
- ## Multiple choice caps at 4, and the server enforces it
29
-
30
- Grade 5 means *correct, and explained why*, and picking an option cannot show
31
- that — one in four is a coin. `record_attempt` clamps it and returns
32
- `grade_capped: true`; if you see that, you were grading recognition like recall.
33
-
34
- Within the cap, still grade honestly:
42
+ **Multiple choice caps at 4, and the server enforces it** (`grade_capped: true`
43
+ means you graded recognition like recall). Picking one of four cannot show *why*.
44
+ Within the cap:
35
45
 
36
46
  | Grade | Means |
37
47
  |---|---|
@@ -39,104 +49,91 @@ Within the cap, still grade honestly:
39
49
  | 3 | right option, but their "Other" text or follow-up showed it was a guess |
40
50
  | 2 | picked a distractor that is the shape of the idea |
41
51
  | 1 | picked a distractor built on a misconception |
42
- | 0 | "Other" with *I don't know* (`outcome: dont_know` — **teach it**), or a decline (`outcome: declined`) |
43
-
44
- **A defensible pick is a correct answer.** If the option they chose also
45
- answers the stem — or they argue convincingly that it does — the question was
46
- flawed, not the learner. Grade it 4 as if it were the key, say plainly that the
47
- question had two right answers, and never grade them down for your own
48
- ambiguity.
52
+ | 0 | "Other" with *I don't know* (`dont_know`, teach it), or a decline (`declined`) |
49
53
 
50
- **If they want to explain, let them, and say so.** Someone who picks "Other" and
51
- types a real answer has just given you better evidence than the multiple choice
52
- could. Grade that as the free answer it is — **omit `format`**, and the cap does
53
- not apply.
54
+ **A defensible pick is a correct answer.** If their option also answers the
55
+ stem, or they argue convincingly that it does, the question was flawed, not the
56
+ learner: grade it 4, say plainly it had two right answers, and never mark them
57
+ down for your ambiguity. If they did not pick but said two are right, that is
58
+ `invalid`.
54
59
 
55
- ## Feedback
60
+ **If they want to explain, let them.** Someone who picks "Other" and types a real
61
+ answer has given better evidence than the choice could. Grade it as free recall:
62
+ omit `format`, and the cap does not apply.
56
63
 
57
- **Four sentences or fewer for a grade of 2 or better** — correct the specific
58
- thing they got wrong and stop; don't re-teach a topic they mostly have.
59
-
60
- If they answer and get it wrong, do not immediately give the answer. Ask one
61
- narrower question that isolates the gap. If they miss that too, teach it as
62
- below.
63
-
64
- **Always say whether they were right first.** At an `[Eklavya checkpoint]`
65
- there is no narrower question — the checkpoint is one question — so a miss
66
- gets the right answer and one line of why, then the task resumes.
64
+ Feedback for a grade of 2 or better is four sentences or fewer: correct the
65
+ specific thing they got wrong and stop.
67
66
 
68
67
  ## When they say "I don't know"
69
68
 
70
- **This is the most important thing in this skill.** A blank is not a skip. A
71
- skip says *leave me alone*; "I don't know" says *teach me*, and it is the single
72
- clearest request for teaching you will ever get. Answering it with a
73
- three-sentence correction and moving on is the failure this tool exists to
74
- prevent — the developer who understood least got taught least.
75
-
76
- Both record as grade 0. What separates them is `outcome`, and what you do next.
77
-
78
- **They are not interchangeable, and the asymmetry is worth knowing.** A concept
79
- recorded as `declined` is never offered again — deliberately, because "leave me
80
- alone" is a choice. So labelling a blank as a decline removes that concept from
81
- the gate-retry path, which when enforced is the only route out of a blocked
82
- commit. If you explained it, it was a blank: `dont_know`. `record_attempt`
83
- returns `outcome_conflict` when it is given `declined` together with feedback,
84
- because a decline you dropped immediately has nothing to explain. It rejects
85
- `declined` or `dont_know` with a grade of 3 or more (`outcome_grade_conflict`)
86
- and records nothing: call again with the pair that is true.
87
-
88
- **Teach it. Properly, in this order:**
89
-
90
- 1. **Name the mechanism** in one sentence — the thing that is actually true,
91
- stated plainly.
92
- 2. **Show the code.** Quote the two or three real lines from the diff that make
93
- it true. They are looking at a file they have never read; the lines are the
94
- whole lesson.
95
- 3. **Say what it generalises to** — the rule they can carry to the next
96
- codebase, not just this one.
69
+ **The most important thing in this skill.** A blank is not a skip. A skip says
70
+ *leave me alone*; "I don't know" says *teach me*, the clearest request for teaching you will ever get.
71
+ Answering it with a three-sentence correction is the failure this tool exists to
72
+ prevent. Both record grade 0; `outcome` separates them.
73
+
74
+ They are not interchangeable. A `declined` concept is not offered again in this
75
+ session's gate retry (a later session's project review may bring it back once it
76
+ is due), so labelling a blank as a decline removes it from the only route out of
77
+ a blocked commit. If you explained it, it was a blank: `dont_know`.
78
+ `record_attempt` returns `outcome_conflict` for `declined` with feedback (a
79
+ decline you dropped has nothing to explain), and rejects `declined` or
80
+ `dont_know` with a grade of 3 or more (`outcome_grade_conflict`) without
81
+ recording anything.
82
+
83
+ **Who teaches, and how much.** The full sequence is for a quiz or lesson the
84
+ developer asked for. For an automatic question the answer and one line of why is
85
+ the whole inline reply, and when `record_attempt` returns `explain` the
86
+ background page carries the long version: teach in one place, never both.
87
+
88
+ 1. **Name the mechanism** in one sentence, plainly.
89
+ 2. **Show the code, or a small example.** With a `context` from the plan, quote
90
+ the two or three lines from the diff that make it true, only lines you have
91
+ read. With `context: null` (`concept` focus, a review, a topic lesson) write a
92
+ three-to-six-line worked example and say it is one. Never present invented
93
+ lines as theirs.
94
+ 3. **Say what it generalises to**: the rule they carry to the next codebase.
97
95
  4. **One-line takeaway.** What to remember if they forget everything else.
98
96
 
99
- Six to ten sentences. The four-sentence cap above is for near-misses, where you
100
- are correcting a detail. Here there is no detail to correct: the topic *is* the
101
- gap.
102
-
103
- **Then record and move on.** `grade: 0`, `outcome: "dont_know"`, and put the
104
- explanation you just gave in `feedback`. Do not re-ask the same concept in the
105
- same breath — grade 0 pins mastery at the floor, so it resurfaces on its own
106
- tomorrow, and `asked_before` will force a *different* question about a concept
107
- you have now taught. The spaced re-check is free and it is better than an
108
- immediate one, which only tests whether they can repeat a paragraph they just
109
- read.
97
+ Six to ten sentences. Then record: `grade: 0`, `outcome: "dont_know"`, your
98
+ explanation in `feedback`. Do not re-ask in the same breath: grade 0 pins mastery
99
+ at the floor, so it resurfaces tomorrow, and `asked_before` forces a different
100
+ question. Never offer to stop because they are blanking: two blanks mean the
101
+ pitch was too high, the planner already lowers `tier_to_ask`, so ask at the tier
102
+ the plan gives and wait to be told to stop. Never dump the remaining answers as a
103
+ list.
110
104
 
111
- **Never offer to stop because they are blanking.** Two blanks in a row is not a
112
- hint that they want out — it is evidence you are pitching too high. Drop a tier
113
- and keep going. Tier-1 recall on something you have just explained is fair, and
114
- it rebuilds footing. If they want to stop, they will say so; wait to be told.
105
+ ## When the question is at fault
115
106
 
116
- **Never dump the remaining answers as a list.** If the quiz ends early, it ends.
117
- A wall of four explanations at the door is not teaching, it is a receipt.
107
+ "This question does not have enough context", or "two of these are right" with
108
+ no pick made, is a verdict on the question, not a decline and not a blank. The
109
+ miss is yours and must cost them nothing:
118
110
 
119
- ## When they say the question lacks context
120
-
121
- "This question does not have enough context" is a verdict on the question, not
122
- a decline. The question assumed something they never saw, so the miss is
123
- yours. Never drop it untaught:
124
-
125
- 1. **Own it** in a few words — the question leaned on something you had not
126
- shown them.
111
+ 1. **Own it** in a few words.
127
112
  2. **Give the answer and one line of why**, defining the name or situation the
128
113
  stem skipped.
129
- 3. **Record it as a blank**: `grade: 0`, `outcome: "dont_know"`, with that
130
- answer in `feedback`. It is not `declined` — a decline is never offered
131
- again, and they did not ask to be left alone.
132
-
133
- The next question about it, now or later, starts with the situation sentence
134
- the first one was missing (`writing-mcq.md`, *Write for a cold reader*).
114
+ 3. **Record `invalid`**: `outcome: "invalid"`, `grade: 0` (ignored), what was
115
+ wrong in `feedback`. Nothing is graded: no mastery, level, accuracy, review
116
+ date or gate changes, no `attempt_id` comes back, and an enforced gate does not
117
+ count it, so the concept is still asked. The stem is marked spent.
118
+ 4. **Replace it** where the pacing allows (the table above). The replacement
119
+ opens with the situation sentence the first one lacked (`writing-mcq.md`,
120
+ *Write for a cold reader*).
121
+
122
+ A learner who really blanked on a clear question is `dont_know`: only the
123
+ question's own defect makes it `invalid`.
124
+
125
+ ## Skip, stop and opting out
126
+
127
+ - **Skip this question**: `declined`, no teaching, no second ask.
128
+ - **Stop for now** ("enough"): stop asking this session and record nothing for
129
+ questions never shown. Nothing remembers it past the session, so for a lasting
130
+ stop tell them `eklavya config set quiz.enabled false` (the dashboard Settings
131
+ page does the same).
132
+ - **Opt out of a topic**: there is no per-topic switch; do not promise one.
135
133
 
136
134
  ## `already_taught`
137
135
 
138
- When it is true on a plan item, they blanked on this before and you explained
139
- it. Open the next question as a follow-up to that explanation — *"last time I
140
- showed you that `_work_section()` can return an empty string; so what happens to
141
- the nav link when it does?"* — not as a first encounter. Building on a lesson is
142
- what makes it stick; asking cold throws it away.
136
+ When true on a plan item, they blanked on this before and you explained it. Open
137
+ the next question as a follow-up to that explanation — *"last time I showed you
138
+ that `_work_section()` can return an empty string; so what happens to the nav
139
+ link when it does?"* — not as a first encounter.
@@ -18,7 +18,7 @@ Make one call to `present_question` instead:
18
18
  - `options`: four, in display order, each `{label, description, grade}`. The
19
19
  `label` is the option itself, a complete claim (what `writing-mcq.md` calls the
20
20
  option), never `A` or `1`: the panel numbers them, and a bare marker is rejected.
21
- The `description` is the one clause from part 4. Mark the right one
21
+ The `description` is the neutral clause from part 4: what the option claims, with no verdict and no "tempting because". Mark the right one
22
22
  `correct: true`, **in the `answer_position` slot** (the call is rejected with
23
23
  `wrong_answer_position` otherwise, and nothing is stored).
24
24
  - Each option's `grade` is what a learner who picks it has shown, on the scale in
@@ -32,7 +32,10 @@ Make one call to `present_question` instead:
32
32
  `` `shouldRepaint` ``) and nothing else: no headings, lists or emphasis. Option `label`s are drawn as
33
33
  plain text, so write them in words and leave the backticks out of them.
34
34
  - `explanation`: one line saying why the right option is right. The panel shows
35
- it after the answer, so it is the whole of your feedback.
35
+ it after the answer, so it is the whole of your feedback. A call whose options
36
+ are lopsided in visible length, or whose descriptions explain themselves, is
37
+ rejected once with `conspicuous_options` and nothing stored: rewrite and call
38
+ again with the same stem.
36
39
 
37
40
  It returns at once. Do not wait for the answer, do not call `record_attempt`
38
41
  (the panel records it, once), do not give a verdict, and go straight back to
@@ -95,18 +95,25 @@ Before asking, read each distractor as if it were the key: if a senior reviewer
95
95
  could argue for it, narrow the stem ("why *here*", "what does *this line*
96
96
  prevent") until it cannot, or replace it with a different row.
97
97
 
98
- **4. One clause of `description` per option.** This is where a near-miss earns
99
- its place — the sentence that makes the wrong answer tempting.
100
-
101
- The parity rule from step 2 holds here too, and this is where it usually
102
- breaks: the correct option gets the careful sentence that explains the
103
- mechanism, and each distractor gets a few throwaway words. A learner reads that
104
- as the answer without reading the claim. Give every description the same
105
- weight — one clause of similar length, none standing out as the longest or the
106
- shortest — and spend the effort on the distractors: each one says why it is
107
- tempting, as specifically as the correct one says why it holds. Overcorrecting
108
- is a tell too: if the right answer's note is never the longest, a learner
109
- learns to rule the longest one out.
98
+ **4. One neutral clause of `description` per option.** The learner reads it
99
+ before answering, so it says what the option *does or claims*, never whether it
100
+ is right, why it is tempting or which misconception it is. "Tempting because…",
101
+ "a common mistake" and "this is why it works" are feedback, and feedback comes
102
+ after the answer.
103
+
104
+ Judge parity on what is on screen: the label and its description together. Give
105
+ every option the same grammar, the same number of claims and the same kind of
106
+ detail. Do not keep the mechanism, the example or the caveat for the correct
107
+ one. Bring the short ones up with real content, never filler; if a distractor
108
+ cannot reach the length honestly, the stem is too thin to have a near miss. The
109
+ correct option should not be the longest, and it should not be systematically
110
+ the shortest either: that is a tell in the other direction. The server and the
111
+ `ask-label` hook send back an option that outruns the next by a few words, or
112
+ whose description explains itself, once; rewrite it and ask again.
113
+
114
+ The reasoning behind each option goes in `option_notes` when you record the
115
+ attempt (the card path) and in the panel's `explanation` (the panel path), both
116
+ shown only after the answer.
110
117
 
111
118
  **5. Placement.** The plan gives each question an `answer_position`, 1 to 4. Put
112
119
  the correct option in that slot. Left to your own judgement you will put the
@@ -157,9 +164,8 @@ telling you which part to rebuild.
157
164
  - All four options are within a few words of the same length and use the same
158
165
  grammar. A visibly longer or more careful option reads as the correct one, and
159
166
  gets picked without engaging — the same leak as always answering first.
160
- - The four descriptions are within a few words of each other too, and the
161
- correct option's is no more careful than the rest. Each distractor's says why
162
- it is tempting, not just that it is wrong.
167
+ - Each option, label plus description, is within a few words of the others, and
168
+ no description says why an option is right, wrong or tempting.
163
169
  - Each of the three wrong options came from a different row of the table above.
164
170
  - Only the correct option answers the stem; no expert could argue for another.
165
171
  - The correct option sits at `answer_position`.
@@ -194,9 +200,10 @@ concept has unmet prerequisites:
194
200
 
195
201
  - Ask about the prerequisite instead, if it is in the plan — the plan already
196
202
  orders foundations first.
197
- - Otherwise drop the question a tier and make it mechanism-level. "Why this
198
- rather than the alternative" is not answerable by someone who does not yet
199
- have the alternative.
203
+ - Otherwise ask at `tier_to_ask` (the planner owns the tier) and write the
204
+ question so it needs no alternative: about what this thing does, not why it
205
+ beats another. "Why this rather than the alternative" is not answerable by
206
+ someone who does not yet have the alternative.
200
207
  - Say the dependency out loud in your feedback. Knowing *what to learn next* is
201
208
  half of what the graph is for.
202
209
 
@@ -204,8 +211,9 @@ concept has unmet prerequisites:
204
211
 
205
212
  `record_attempt` with `format: "mcq"`, `options` as the labels you offered,
206
213
  `answer` as the one they picked, `correct` as the right option's label
207
- verbatim, `option_notes` as the `description` you wrote under each option (same
208
- order as `options`), and `question` as the **stem only**.
214
+ verbatim, `option_notes` as what you want shown under each option after the answer (same
215
+ order as `options`; the reasoning each option earns, which the neutral
216
+ `description` left out), and `question` as the **stem only**.
209
217
 
210
218
  `correct` and `option_notes` are what let the learner correct a missed answer
211
219
  from its explainer page: the dashboard grades the new pick against `correct`
@@ -0,0 +1,98 @@
1
+ /**
2
+ * The deterministic half of the conversation eval.
3
+ *
4
+ * `question-checks.ts` scores one question. The rules that decide whether a
5
+ * session goes well live elsewhere: what a blank earns, what a decline earns,
6
+ * whether a checkpoint stays one question, whether a parallel tutor hands the
7
+ * explainer everything it needs. A scenario freezes one moment (the plan, the
8
+ * question asked, what the learner said) and the model replies with the tool
9
+ * calls and message it would produce next; this module judges that reply
10
+ * against rules written as data.
11
+ *
12
+ * Pure like `question-checks.ts`: no model, no files, no clock.
13
+ */
14
+ /** `mcp__plugin_eklavya_eklavya__record_attempt` and `record_attempt` are one tool. */
15
+ export function toolName(raw) {
16
+ return raw.replace(/^mcp__.*?__/, '');
17
+ }
18
+ const ASKING = new Set(['AskUserQuestion', 'present_question']);
19
+ /** Four lettered lines is a question in a renderer that has no tool for it. */
20
+ function lettered(message) {
21
+ return (message.match(/^\s*\(?[A-D][).]\s+\S/gm) ?? []).length >= 4;
22
+ }
23
+ export function questionCount(turn) {
24
+ const calls = turn.tool_calls.filter((c) => ASKING.has(toolName(c.tool))).length;
25
+ return calls + (lettered(turn.message) ? 1 : 0);
26
+ }
27
+ function sentences(text) {
28
+ return text.split(/(?<=[.!?])\s+/).filter((s) => /\w/.test(s)).length;
29
+ }
30
+ function dig(obj, path) {
31
+ return path.split('.').reduce((o, k) => (o && typeof o === 'object' ? o[k] : undefined), obj);
32
+ }
33
+ function matches(value, m) {
34
+ if (typeof m !== 'object')
35
+ return value === m;
36
+ if (m.absent)
37
+ return value === undefined || value === null || value === '';
38
+ if (m.oneOf)
39
+ return m.oneOf.includes(value);
40
+ if (m.regex !== undefined)
41
+ return typeof value === 'string' && new RegExp(m.regex, 'i').test(value);
42
+ if (m.lengthMin !== undefined || m.lengthMax !== undefined) {
43
+ const n = typeof value === 'string' ? value.trim().length : Array.isArray(value) ? value.length : -1;
44
+ return n >= (m.lengthMin ?? 0) && n <= (m.lengthMax ?? Infinity);
45
+ }
46
+ return typeof value === 'number' && value >= (m.min ?? -Infinity) && value <= (m.max ?? Infinity);
47
+ }
48
+ export function checkTurn(turn, rules) {
49
+ return rules.map((rule) => {
50
+ if (rule.kind === 'call') {
51
+ const where = Object.entries(rule.where ?? {});
52
+ const hits = turn.tool_calls.filter((c) => toolName(c.tool) === rule.tool && where.every(([k, m]) => matches(dig(c.args, k), m))).length;
53
+ // A rule that only sets `max: 0` is a prohibition; it must not also demand a call.
54
+ const min = rule.min ?? (rule.max === 0 ? 0 : 1);
55
+ const ok = hits >= min && hits <= (rule.max ?? Infinity);
56
+ return { id: rule.id, ok, detail: `${hits} matching ${rule.tool} call(s), wanted ${min}-${rule.max ?? '∞'}` };
57
+ }
58
+ if (rule.kind === 'questions') {
59
+ const n = questionCount(turn);
60
+ return { id: rule.id, ok: n <= rule.max, detail: `${n} question(s) asked, limit ${rule.max}` };
61
+ }
62
+ const problems = [];
63
+ const text = turn.message.trim();
64
+ if (rule.maxSentences !== undefined && sentences(text) > rule.maxSentences)
65
+ problems.push(`${sentences(text)} sentences, limit ${rule.maxSentences}`);
66
+ if (rule.minChars !== undefined && text.length < rule.minChars)
67
+ problems.push(`${text.length} characters, wanted ${rule.minChars}+`);
68
+ if (rule.match !== undefined && !new RegExp(rule.match, 'is').test(text))
69
+ problems.push(`does not match /${rule.match}/`);
70
+ if (rule.notMatch !== undefined && new RegExp(rule.notMatch, 'is').test(text))
71
+ problems.push(`matches /${rule.notMatch}/`);
72
+ if (rule.notEndsWithQuestion && text.endsWith('?'))
73
+ problems.push('ends on a question');
74
+ return { id: rule.id, ok: problems.length === 0, detail: problems.length ? problems.join('; ') : 'message ok' };
75
+ });
76
+ }
77
+ /** Per-rule and per-scenario pass rates; an unparsed reply counts against every rule. */
78
+ export function summarise(results) {
79
+ const byScenario = {};
80
+ for (const r of results) {
81
+ const row = (byScenario[r.scenario] ??= { trials: 0, clean: 0, unparsed: 0, failed: {} });
82
+ row.trials++;
83
+ if (r.unparsed) {
84
+ row.unparsed++;
85
+ continue;
86
+ }
87
+ const bad = r.verdicts.filter((v) => !v.ok);
88
+ if (bad.length === 0)
89
+ row.clean++;
90
+ for (const v of bad)
91
+ row.failed[v.id] = (row.failed[v.id] ?? 0) + 1;
92
+ }
93
+ const trials = results.length;
94
+ const clean = Object.values(byScenario).reduce((n, r) => n + r.clean, 0);
95
+ const unparsed = Object.values(byScenario).reduce((n, r) => n + r.unparsed, 0);
96
+ return { trials, clean, unparsed, byScenario };
97
+ }
98
+ //# sourceMappingURL=conversation-score.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conversation-score.js","sourceRoot":"","sources":["../../src/eval/conversation-score.ts"],"names":[],"mappings":"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"}
@@ -222,6 +222,37 @@ export function checkQuestion(q) {
222
222
  push('no_settings_line', !q.stem.trimStart().startsWith('['), 'stem opens with the question');
223
223
  return checks;
224
224
  }
225
+ /**
226
+ * Phrasing that explains an option before the learner has answered. A
227
+ * description that says why a distractor is "tempting" or names the
228
+ * misconception is feedback, and feedback belongs after the answer.
229
+ */
230
+ const PRE_ANSWER_RATIONALE = /\b(?:tempting|common (?:mistake|misconception)|misconception|is the (?:right|correct) answer|(?:is|are) (?:wrong|incorrect) because|looks (?:right|correct) but)\b/i;
231
+ /**
232
+ * Why a question should not be shown as written, or null when it can be.
233
+ *
234
+ * Judged on what is on screen -- label plus description -- and on no knowledge
235
+ * of the key, so it works where the key is not known (the `AskUserQuestion`
236
+ * card) as well as where it is (the panel). An option that outruns the next by
237
+ * `LENGTH_MARGIN_WORDS` is conspicuous whichever one is correct: if it is the
238
+ * key it leaks, and if it is not, the learner is being steered off the key.
239
+ * Only the outlier is named, so the rewrite is aimed at one option.
240
+ */
241
+ export function visibleOptionProblem(options) {
242
+ for (const [i, o] of options.entries()) {
243
+ const phrase = PRE_ANSWER_RATIONALE.exec(o.description ?? '');
244
+ if (phrase) {
245
+ return `option ${i + 1}'s description explains the option before the answer (${phrase[0]}). Keep descriptions neutral and put the reasoning in the explanation shown after the answer`;
246
+ }
247
+ }
248
+ const lengths = options.map((o) => words(`${o.label} ${o.description ?? ''}`).length);
249
+ const longest = Math.max(...lengths, 0);
250
+ const runnerUp = Math.max(...lengths.filter((_, i) => i !== lengths.indexOf(longest)), 0);
251
+ if (longest > PARITY_MIN_WORDS && longest - runnerUp >= LENGTH_MARGIN_WORDS) {
252
+ return `option ${lengths.indexOf(longest) + 1} is ${longest} visible words (label plus description) against ${runnerUp} for the next longest, so it stands out. Shorten it or give the others the same number of claims, without padding`;
253
+ }
254
+ return null;
255
+ }
225
256
  export function scoreAll(questions) {
226
257
  const scored = questions.map((question) => {
227
258
  const checks = checkQuestion(question);
@@ -1 +1 @@
1
- {"version":3,"file":"question-checks.js","sourceRoot":"","sources":["../../src/eval/question-checks.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"question-checks.js","sourceRoot":"","sources":["../../src/eval/question-checks.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * PreToolUse(`AskUserQuestion`): an Eklavya question on a card host must carry
3
- * its `[Eklavya]` line.
3
+ * its `[Eklavya]` line, and on any host its options must not give the answer
4
+ * away (`visibleOptionProblem`, sent back once per stem).
4
5
  *
5
6
  * The plan's `ask_attribution` asks for it, and a model can read past an
6
7
  * instruction. This is the check behind the instruction: a question headed
@@ -12,27 +13,49 @@
12
13
  * Fails open like every hook, and stays on the lightest imports.
13
14
  */
14
15
  import { run } from './lib.js';
16
+ import { createHash } from 'node:crypto';
17
+ import fs from 'node:fs';
18
+ import os from 'node:os';
19
+ import path from 'node:path';
15
20
  import { needsInlineAttribution } from '../surface.js';
21
+ import { visibleOptionProblem } from '../eval/question-checks.js';
16
22
  const PREFIX = /^\s*\[eklavya\]/i;
23
+ function deny(reason) {
24
+ process.stdout.write(`${JSON.stringify({ hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: reason } })}\n`);
25
+ }
26
+ /**
27
+ * True the first time a stem is seen, false after: the rewrite is asked for
28
+ * once, and a question that comes back with the same stem is shown as written.
29
+ * A marker file in the temp directory is the whole state; losing it only means
30
+ * one more request for a rewrite.
31
+ */
32
+ function firstSight(sessionId, stem) {
33
+ const key = createHash('sha1').update(`${String(sessionId)}\n${stem}`).digest('hex').slice(0, 16);
34
+ const marker = path.join(os.tmpdir(), `eklavya-ask-${key}`);
35
+ if (fs.existsSync(marker))
36
+ return false;
37
+ fs.writeFileSync(marker, '');
38
+ return true;
39
+ }
17
40
  await run(async (input) => {
18
- if (input.tool_name !== 'AskUserQuestion' || input.agent_id || !needsInlineAttribution())
41
+ if (input.tool_name !== 'AskUserQuestion' || input.agent_id)
19
42
  return 0;
20
43
  const questions = input.tool_input?.questions;
21
44
  if (!Array.isArray(questions))
22
45
  return 0;
23
- const unsigned = questions.some((q) => typeof q?.header === 'string' &&
24
- q.header.trim().toLowerCase() === 'eklavya' &&
25
- typeof q.question === 'string' &&
26
- !PREFIX.test(q.question));
27
- if (!unsigned)
46
+ const ours = questions.filter((q) => typeof q?.header === 'string' && q.header.trim().toLowerCase() === 'eklavya' && typeof q.question === 'string');
47
+ if (needsInlineAttribution() && ours.some((q) => !PREFIX.test(q.question))) {
48
+ deny('This host draws no header chip, so an Eklavya question must open with "[Eklavya]" on its own line, then the stem. Ask the same question again with that line added; change nothing else.');
28
49
  return 0;
29
- process.stdout.write(`${JSON.stringify({
30
- hookSpecificOutput: {
31
- hookEventName: 'PreToolUse',
32
- permissionDecision: 'deny',
33
- permissionDecisionReason: 'This host draws no header chip, so an Eklavya question must open with "[Eklavya]" on its own line, then the stem. Ask the same question again with that line added; change nothing else.',
34
- },
35
- })}\n`);
50
+ }
51
+ for (const q of ours) {
52
+ const options = Array.isArray(q.options) ? q.options.filter((o) => typeof o?.label === 'string') : [];
53
+ const problem = visibleOptionProblem(options);
54
+ if (problem && firstSight(input.session_id, q.question)) {
55
+ deny(`Rewrite the options and ask again with the same question: ${problem}.`);
56
+ return 0;
57
+ }
58
+ }
36
59
  return 0;
37
60
  });
38
61
  //# sourceMappingURL=ask-label.js.map