eklavya 1.52.4 → 1.53.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.
@@ -27,6 +27,10 @@ Make one call to `present_question` instead:
27
27
  misconception (rows one and three). Grade 3 is not available to a pick: only
28
28
  typed words can show a hesitant "right".
29
29
  - `more: true` only in an explicit round (the plan has more items after this one, as for `/eklavya:quiz`): the panel then offers a Next button. Leave it out otherwise. Once a round is planned the server tracks it: the last question's `more` is set false for you, and Next arrives as a prompt telling you to call `get_session_quiz_plan` with `resume_round: true`.
30
+ - **Mark the code.** The panel draws the stem, each `description` and the `explanation` as Markdown, so
31
+ put every function, call, class, file and flag in backticks (`` `tester.pumpWidget(widget)` ``,
32
+ `` `shouldRepaint` ``) and nothing else: no headings, lists or emphasis. Option `label`s are drawn as
33
+ plain text, so write them in words and leave the backticks out of them.
30
34
  - `explanation`: one line saying why the right option is right. The panel shows
31
35
  it after the answer, so it is the whole of your feedback.
32
36
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "eklavya",
3
3
  "displayName": "Eklavya",
4
- "version": "1.52.4",
4
+ "version": "1.53.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"
@@ -18,14 +18,96 @@ export const EMPTY: PanelState = {
18
18
  /** Tier names as the dashboard shows them (`TIER` in `assets/dashboard.html`; a test keeps the two equal). */
19
19
  export const TIER: Record<number, string> = { 1: 'recall', 2: 'mechanism', 3: 'judgement', 4: 'failure modes', 5: 'design' }
20
20
 
21
+ /** Brand verdigris (`--vd-300`): the selected card's border and the name. */
22
+ export const BRAND = '#79D5C4'
23
+
24
+ /**
25
+ * The logo: the Claude mascot (orange) holding an Eklavya bow and arrow (white), drawn on a
26
+ * grid where one cell is a quarter of a text character. Designed in the logo maker; a letter
27
+ * names its colour in LOGO_PALETTE and a dot is empty.
28
+ */
29
+ export const LOGO_GRID = [
30
+ '.................dd.......',
31
+ '................d..d......',
32
+ '................d...d.....',
33
+ '...aaaaaaaaaaaa.d....d....',
34
+ '...aa.aaaaaa.aa.d.....d...',
35
+ '.aaaaaaaaaaaaaaadddddddddd',
36
+ '...aaaaaaaaaaaa.d.....d...',
37
+ '....a.a....a.a..d....d....',
38
+ '................d...d.....',
39
+ '................d..d......',
40
+ '.................dd.......',
41
+ '..........................',
42
+ ] as const
43
+ export const LOGO_PALETTE: Record<string, string> = { a: '#D97757', d: '#FFFFFF' }
44
+
45
+ /** One stretch of text cells that share a colour pair. A text character holds two colours at most. */
46
+ export type LogoRun = { text: string; fg?: string; bg?: string }
47
+
48
+ /** Quadrant glyphs by which of the four cells (upper left 1, upper right 2, lower left 4, lower right 8) are filled. */
49
+ const QUADRANT = ' ▘▝▀▖▌▞▛▗▚▐▜▄▙▟█'
50
+
51
+ /**
52
+ * The grid as rows of text runs: each 2 by 2 block becomes one quadrant character, the
53
+ * first colour as its foreground and a second, if any, as its background. A third colour
54
+ * in one block is dropped, so the logo degrades and never throws.
55
+ */
56
+ export function logoRows(grid: readonly string[] = LOGO_GRID, palette: Record<string, string> = LOGO_PALETTE): LogoRun[][] {
57
+ const out: LogoRun[][] = []
58
+ for (let cy = 0; cy < Math.ceil(grid.length / 2); cy++) {
59
+ const runs: LogoRun[] = []
60
+ for (let cx = 0; cx < Math.ceil((grid[0]?.length ?? 0) / 2); cx++) {
61
+ const quad = [grid[cy * 2]?.[cx * 2], grid[cy * 2]?.[cx * 2 + 1], grid[cy * 2 + 1]?.[cx * 2], grid[cy * 2 + 1]?.[cx * 2 + 1]].map(c => (c && c !== '.' ? c : ''))
62
+ const [first, second] = [...new Set(quad.filter(Boolean))]
63
+ let run: LogoRun = { text: ' ' }
64
+ if (first) {
65
+ const mask = quad.reduce((n, c, i) => (c === first ? n | (1 << i) : n), 0)
66
+ run = { text: QUADRANT[mask], fg: palette[first], bg: second ? palette[second] : undefined }
67
+ }
68
+ const last = runs[runs.length - 1]
69
+ if (last && last.fg === run.fg && last.bg === run.bg) last.text += run.text
70
+ else runs.push(run)
71
+ }
72
+ out.push(runs)
73
+ }
74
+ return out
75
+ }
76
+
77
+ /** The name as the header sets it: spaced capitals, the nearest a terminal gets to a larger type size. */
78
+ /** A call such as `tester.tap(find.byType(X))`, nested up to three deep, a lowerCamelCase name or a snake_case name. */
79
+ const CODE_LIKE = /[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*\((?:[^()\n]|\((?:[^()\n]|\([^()\n]*\))*\))*\)|\b[a-z]{2,}(?:[A-Z][a-z0-9]+)+\b|\b[a-z0-9]+(?:_[a-z0-9]+)+\b/g
80
+
81
+ /**
82
+ * Wraps code-looking words in backticks so the pane's Markdown draws them as code. A question
83
+ * is plain prose unless its author marked the code, and an old or careless one often is not.
84
+ * Text already in backticks is left as written.
85
+ */
86
+ export function codify(text: string): string {
87
+ return text
88
+ .split(/(`[^`]*`)/)
89
+ .map((part, i) => (i % 2 ? part : part.replace(CODE_LIKE, '`$&`')))
90
+ .join('')
91
+ }
92
+
93
+ /** What a Button label can show of Markdown: it keeps the words and drops the code and bold marks. */
94
+ export function plainLabel(text: string): string {
95
+ return text.replace(/`|\*\*/g, '')
96
+ }
97
+
98
+ export const WORDMARK = 'EKLAVYA'.split('').join(' ')
99
+
100
+ /** How long a finished result stays before it closes itself, when no Next is waiting. */
101
+ export const CLOSE_MS = 15_000
102
+
21
103
  /** Every sentence the learner reads, in one place. The handoff's table, verbatim. */
22
104
  export const STR = {
23
105
  brand: 'Eklavya',
24
- powered: 'Powered by Claude',
25
106
  submit: 'Submit answer',
26
107
  skip: 'Skip question',
27
108
  next: 'Next question',
28
109
  done: 'Done',
110
+ autoClose: 'Closes by itself in 15 seconds.',
29
111
  retry: 'Retry',
30
112
  close: 'Close',
31
113
  other: 'Other — explain in your own words',
@@ -41,6 +123,8 @@ export const STR = {
41
123
  nothingTyped: 'Write your answer first, or pick one of the options.',
42
124
  graderFailed: "We couldn't check that answer. Your text is saved here — Retry.",
43
125
  explainer: 'A page explaining this is being written.',
126
+ focus: 'Ctrl+X then Tab moves the keys here.',
127
+ keys: '1–4 pick · o other · s submit · k skip · Esc back to prompt',
44
128
  off: 'The quiz panel is off. Turn it on with: eklavya config set quiz.panel true',
45
129
  } as const
46
130
 
@@ -2,15 +2,21 @@ import { atom, read, update } from 'claude-code'
2
2
  import type { EngineInterface, Register } from 'claude-code'
3
3
 
4
4
  import {
5
+ BRAND,
5
6
  EMPTY,
6
7
  EXPLAINER,
7
8
  NEXT_PROMPT,
8
9
  PANE,
9
10
  REOPEN,
10
11
  STR,
12
+ WORDMARK,
13
+ CLOSE_MS,
11
14
  answerWas,
12
15
  explainerBrief,
16
+ codify,
13
17
  gradingRequest,
18
+ logoRows,
19
+ plainLabel,
14
20
  levelLine,
15
21
  parseVerdict,
16
22
  payloadOf,
@@ -49,6 +55,8 @@ let busy = false
49
55
  /** Bumped when the session ends: a submit that began before it is stale and must write nothing. */
50
56
  let epoch = 0
51
57
  let explainedAttempt = 0
58
+ /** The result closes itself after CLOSE_MS when nothing else is pending (no Next). */
59
+ let closer: { cancel: () => void } | null = null
52
60
 
53
61
  /**
54
62
  * One call to an Eklavya tool through the host's own connection, as the parsed
@@ -219,14 +227,22 @@ async function submit($: EngineInterface, wanted: PanelPending | null): Promise<
219
227
  message: null,
220
228
  }))
221
229
 
230
+ // The last question of a round has no Next: the result closes itself, as Done would.
231
+ if (!q.more) arm($, mine, q.question_id)
232
+
222
233
  // A miss with explain_on_wrong on: the panel starts the explainer itself in
223
234
  // the background, so no prompt shows in the transcript and nothing waits.
224
- // A refused spawn falls back to the queued prompt the model acts on.
235
+ // Auto mode judges every spawn (it drops Agent allow rules), and its
236
+ // classifier can be briefly unavailable, so a refusal is asked once more.
237
+ // A second refusal falls back to the queued prompt the model acts on.
225
238
  if (out.explain?.instruction && explainedAttempt !== out.attempt_id) {
226
239
  explainedAttempt = out.attempt_id
227
- const spawned = await $.agent
228
- .spawn({ subagentType: EXPLAINER, description: 'Eklavya explainer', prompt: explainerBrief(out.explain) })
229
- .catch(() => null)
240
+ let spawned = null
241
+ for (let tries = 0; tries < 2 && (!spawned || spawned.deny); tries += 1) {
242
+ spawned = await $.agent
243
+ .spawn({ subagentType: EXPLAINER, description: 'Eklavya explainer', prompt: explainerBrief(out.explain) })
244
+ .catch(() => null)
245
+ }
230
246
  if (!spawned || spawned.deny) await $.prompt.submit({ text: String(out.explain.instruction) })
231
247
  }
232
248
  } catch {
@@ -265,6 +281,7 @@ export const register: Register = on => {
265
281
  on('session.end', async ($, e, next) => {
266
282
  try {
267
283
  epoch += 1
284
+ disarm()
268
285
  openedFor = null
269
286
  busy = false
270
287
  await update($, quiz, () => EMPTY)
@@ -327,22 +344,61 @@ export const register: Register = on => {
327
344
  on('ui.render', { component: 'Pane', requestId: 'eklavya-quiz' }, async ($, e, next) => {
328
345
  // Only our pane: every other pane, and everything else, is the host's.
329
346
  if (e.requestId !== PANE) return next(e)
330
- const { Box, Text, Button, Input } = $.ui.resolve(e) as any
347
+ const { Box, Text, Button, Input, Markdown } = $.ui.resolve(e) as any
331
348
  const s: PanelState = await read($, quiz)
332
349
  const q = s.question
333
350
 
334
- const head = (
335
- <Box flexDirection="row" justifyContent="space-between">
336
- <Text bold color="suggestion">
337
- {STR.brand}
338
- </Text>
339
- <Text dimColor>{STR.powered}</Text>
351
+ // The mark is drawn in text, as Claude Code draws its own, so it shows in every terminal.
352
+ const logo = (
353
+ <Box flexDirection="column" flexShrink={0}>
354
+ {logoRows().map((runs, y) => (
355
+ <Box key={`logo-${y}`} flexDirection="row" flexShrink={0}>
356
+ {runs.map((run, x) => (
357
+ <Text key={`logo-${y}-${x}`} color={run.fg} backgroundColor={run.bg} wrap="truncate">
358
+ {run.text}
359
+ </Text>
360
+ ))}
361
+ </Box>
362
+ ))}
363
+ </Box>
364
+ )
365
+ // A list tile: the mark on the left; the name over the topic beside it.
366
+ const head = (subtitle?: string) => (
367
+ <Box flexDirection="row" alignItems="center" gap={2}>
368
+ {logo}
369
+ <Box flexDirection="column" flexShrink={1}>
370
+ <Text bold color={BRAND}>
371
+ {WORDMARK}
372
+ </Text>
373
+ {subtitle ? <Text>{subtitle}</Text> : null}
374
+ </Box>
375
+ </Box>
376
+ )
377
+ const hint = (
378
+ <Box flexDirection="column">
379
+ <Text dimColor>{STR.focus}</Text>
380
+ <Text dimColor>{STR.keys}</Text>
381
+ </Box>
382
+ )
383
+ // One answer: a bordered block. The Button holds the whole answer text, so a press anywhere on it
384
+ // selects; the border and a tick show the pick, and nothing implies it is right.
385
+ const card = (id: string, selected: boolean, button: unknown, body: unknown[], roomy = false) => (
386
+ <Box
387
+ key={`card-${id}`}
388
+ flexDirection="column"
389
+ paddingX={1}
390
+ paddingY={roomy ? 1 : 0}
391
+ gap={roomy ? 1 : 0}
392
+ borderStyle="single"
393
+ borderColor={selected ? BRAND : 'inactive'}
394
+ >
395
+ {button}
396
+ {body}
340
397
  </Box>
341
398
  )
342
- const hint = <Text dimColor>Ctrl+X then Tab moves focus here. Enter presses a button. Esc returns to the prompt.</Text>
343
399
  const frame = (...body: unknown[]) => (
344
400
  <Box flexDirection="column" paddingX={1} gap={1}>
345
- {head}
401
+ {head(q ? topicLabel(q) : undefined)}
346
402
  {body}
347
403
  {hint}
348
404
  </Box>
@@ -359,25 +415,24 @@ export const register: Register = on => {
359
415
  )
360
416
  }
361
417
 
362
- const topic = <Text dimColor>{topicLabel(q)}</Text>
363
- const stem = <Text bold>{q.stem}</Text>
418
+ const stem = <Markdown text={codify(q.stem)} />
364
419
 
365
420
  if (s.step === 'feedback' || s.step === 'skipped') {
366
421
  const r = s.result
367
422
  if (s.step === 'skipped' || !r) {
368
- return frame(topic, <Text>{STR.skipped}</Text>, <Button key="done" label={STR.done} variant="primary" hotkey="d" onPress={() => done($)} />)
423
+ return frame(<Text>{STR.skipped}</Text>, q.more ? null : <Text dimColor>{STR.autoClose}</Text>, <Button key="done" label={STR.done} variant="primary" hotkey="d" onPress={() => done($)} />)
369
424
  }
370
425
  const right = r.correct === true
371
426
  return frame(
372
- topic,
373
427
  stem,
374
428
  <Text bold color={right ? 'suggestion' : 'error'}>
375
429
  {right ? STR.correct : STR.wrong}
376
430
  </Text>,
377
431
  right || !r.correct_label ? null : <Text>{answerWas(r.correct_label)}</Text>,
378
- r.explanation ? <Text>{r.explanation}</Text> : null,
432
+ r.explanation ? <Markdown text={codify(r.explanation)} /> : null,
379
433
  r.level_up ? <Text color="suggestion">{levelLine(r.level_up.from, projectName(q.repo))}</Text> : null,
380
434
  r.explain ? <Text dimColor>{STR.explainer}</Text> : null,
435
+ q.more ? null : <Text dimColor>{STR.autoClose}</Text>,
381
436
  <Box flexDirection="row" gap={2}>
382
437
  {q.more ? <Button key="next" label={STR.next} hotkey="n" onPress={() => askNext($)} /> : null}
383
438
  <Button key="done" label={STR.done} variant="primary" hotkey="d" onPress={() => done($)} />
@@ -388,45 +443,51 @@ export const register: Register = on => {
388
443
  const locked = s.step === 'grading'
389
444
  // Submit looks disabled until there is something to send; pressing it then does nothing.
390
445
  const hasDraft = s.draft.other ? s.draft.text.trim().length > 0 : s.draft.picked !== null
446
+ // Markdown marks cannot show inside a Button's label, so the code and bold marks are dropped there; the note keeps them.
447
+ const answer = (id: string, key: string, hotkey: string, label: string, chosen: boolean) => (
448
+ <Button
449
+ key={key}
450
+ plain
451
+ hotkey={hotkey}
452
+ label={`${chosen ? '✓ ' : ''}${label}`}
453
+ onPress={() => (locked ? undefined : id === 'other' ? chooseOther($) : pick($, id))}
454
+ />
455
+ )
391
456
  const options = q.options.map((o, i) => {
392
457
  const chosen = !s.draft.other && s.draft.picked === o.id
393
- return (
394
- <Box key={`row-${o.id}`} flexDirection="column">
395
- <Button
396
- key={`opt-${o.id}`}
397
- plain
398
- label={`${chosen ? '●' : '○'} ${o.label}`}
399
- hotkey={String(i + 1)}
400
- onPress={() => (locked ? undefined : pick($, o.id))}
401
- />
402
- {o.note ? <Text dimColor>{` ${o.note}`}</Text> : null}
403
- </Box>
458
+ return card(
459
+ o.id,
460
+ chosen,
461
+ answer(o.id, `opt-${o.id}`, String(i + 1), plainLabel(o.label), chosen),
462
+ [o.note ? <Box key={`note-${o.id}`} paddingLeft={3}><Markdown text={codify(o.note)} dimColor /></Box> : null],
404
463
  )
405
464
  })
406
465
 
407
466
  return frame(
408
- topic,
409
467
  stem,
410
- <Box flexDirection="column" gap={1}>
468
+ <Box flexDirection="column">
411
469
  {options}
412
- <Button
413
- key="opt-other"
414
- plain
415
- label={`${s.draft.other ? '●' : '○'} ${STR.other}`}
416
- hotkey="o"
417
- onPress={() => (locked ? undefined : chooseOther($))}
418
- />
419
- {s.draft.other ? (
420
- <Input
421
- key="other-text"
422
- autoFocus
423
- placeholder={STR.otherPlaceholder}
424
- value={s.draft.text}
425
- submitLabel="keep"
426
- onInput={(value: string) => (locked ? undefined : typeOther($, value))}
427
- onSubmit={(value: string) => (locked ? undefined : typeOther($, value))}
428
- />
429
- ) : null}
470
+ {card(
471
+ 'other',
472
+ s.draft.other,
473
+ answer('other', 'opt-other', 'o', STR.other, s.draft.other),
474
+ [
475
+ s.draft.other ? (
476
+ <Box key="other-box" paddingLeft={3}>
477
+ <Input
478
+ key="other-text"
479
+ autoFocus
480
+ placeholder={STR.otherPlaceholder}
481
+ value={s.draft.text}
482
+ submitLabel="keep"
483
+ onInput={(value: string) => (locked ? undefined : typeOther($, value))}
484
+ onSubmit={(value: string) => (locked ? undefined : typeOther($, value))}
485
+ />
486
+ </Box>
487
+ ) : null,
488
+ ],
489
+ s.draft.other,
490
+ )}
430
491
  </Box>,
431
492
  locked ? <Text dimColor>{STR.grading}</Text> : null,
432
493
  s.step === 'error' && s.message ? <Text color="error">{s.message}</Text> : null,
@@ -447,6 +508,12 @@ export const register: Register = on => {
447
508
  /** Selecting an option records a draft and nothing else: it never submits, grades or skips. */
448
509
  async function pick($: EngineInterface, id: string): Promise<void> {
449
510
  await update($, quiz, (s: PanelState) => (s.step === 'awaiting' || s.step === 'error' ? { ...s, draft: { ...s.draft, picked: id, other: false }, message: null } : s))
511
+ try {
512
+ // The host's focus mark follows the pick, so the highlighted answer is the chosen one even after a hotkey.
513
+ await $.ui.focus({ requestId: PANE, key: `opt-${id}` })
514
+ } catch {
515
+ /* Fail open: the pick stands without it. */
516
+ }
450
517
  }
451
518
 
452
519
  async function chooseOther($: EngineInterface): Promise<void> {
@@ -461,8 +528,34 @@ async function skip($: EngineInterface): Promise<void> {
461
528
  await submit($, { kind: 'skip' })
462
529
  }
463
530
 
531
+ /** Starts the auto-close for this result; any later Done, Next or new question cancels it. */
532
+ function arm($: EngineInterface, mine: number, id: string): void {
533
+ try {
534
+ closer?.cancel()
535
+ closer = $.clock.after(CLOSE_MS, async () => {
536
+ try {
537
+ closer = null
538
+ if (epoch !== mine) return
539
+ const s: PanelState = (await $.state.get(REF)).value ?? EMPTY
540
+ // Only the result it was started for: an answer-in-progress or a newer question stays.
541
+ if ((s.step === 'feedback' || s.step === 'skipped') && s.question?.question_id === id) await done($)
542
+ } catch {
543
+ /* Fail open. */
544
+ }
545
+ })
546
+ } catch {
547
+ /* No clock: the learner presses Done. */
548
+ }
549
+ }
550
+
551
+ function disarm(): void {
552
+ closer?.cancel()
553
+ closer = null
554
+ }
555
+
464
556
  /** Done closes the pane; another question may already be waiting, and then the pane comes straight back. */
465
557
  async function done($: EngineInterface): Promise<void> {
558
+ disarm()
466
559
  try {
467
560
  openedFor = null
468
561
  await update($, quiz, () => EMPTY)
@@ -475,6 +568,7 @@ async function done($: EngineInterface): Promise<void> {
475
568
 
476
569
  /** Next asks the model, by a queued prompt, for the next question of an explicit round. */
477
570
  async function askNext($: EngineInterface): Promise<void> {
571
+ disarm()
478
572
  try {
479
573
  openedFor = null
480
574
  await update($, quiz, () => ({ ...EMPTY, step: 'loading' }))
@@ -27,6 +27,10 @@ Make one call to `present_question` instead:
27
27
  misconception (rows one and three). Grade 3 is not available to a pick: only
28
28
  typed words can show a hesitant "right".
29
29
  - `more: true` only in an explicit round (the plan has more items after this one, as for `/eklavya:quiz`): the panel then offers a Next button. Leave it out otherwise. Once a round is planned the server tracks it: the last question's `more` is set false for you, and Next arrives as a prompt telling you to call `get_session_quiz_plan` with `resume_round: true`.
30
+ - **Mark the code.** The panel draws the stem, each `description` and the `explanation` as Markdown, so
31
+ put every function, call, class, file and flag in backticks (`` `tester.pumpWidget(widget)` ``,
32
+ `` `shouldRepaint` ``) and nothing else: no headings, lists or emphasis. Option `label`s are drawn as
33
+ plain text, so write them in words and leave the backticks out of them.
30
34
  - `explanation`: one line saying why the right option is right. The panel shows
31
35
  it after the answer, so it is the whole of your feedback.
32
36
 
@@ -10,7 +10,7 @@ export const presentQuestionTool = {
10
10
  session_id: z.string().max(LIMITS.sessionId).optional().describe(SESSION_HINT),
11
11
  cwd: z.string().max(LIMITS.cwd).optional().describe(CWD_HINT),
12
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.'),
13
+ question: z.string().min(1).max(LIMITS.question).describe('The question stem only: no options, no attribution, no settings line. Markdown: put code names and calls in backticks.'),
14
14
  options: z
15
15
  .array(z.object({
16
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.'),
@@ -1 +1 @@
1
- {"version":3,"file":"panel_tools.js","sourceRoot":"","sources":["../../src/tools/panel_tools.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"panel_tools.js","sourceRoot":"","sources":["../../src/tools/panel_tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,eAAe,EAAuD,MAAM,aAAa,CAAC;AAC3H,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAgB,MAAM,YAAY,CAAC;AAE1E,MAAM,YAAY,GAAG,CAAC,CAAC;AAEvB,MAAM,CAAC,MAAM,mBAAmB,GAAY;IAC1C,IAAI,EAAE,kBAAkB;IACxB,KAAK,EAAE,2CAA2C;IAClD,WAAW,EACT,k0BAAk0B;IACp0B,WAAW,EAAE;QACX,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC;QAC9E,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAC7D,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,0BAA0B,CAAC;QACtE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC,wHAAwH,CAAC;QACnL,OAAO,EAAE,CAAC;aACP,KAAK,CACJ,CAAC,CAAC,MAAM,CAAC;YACP,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,+IAA+I,CAAC;YACrM,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,kDAAkD,CAAC;YACvG,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,gFAAgF,CAAC;YAChI,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,+CAA+C,CAAC;SAC1F,CAAC,CACH;aACA,MAAM,CAAC,YAAY,CAAC;aACpB,QAAQ,CAAC,wCAAwC,CAAC;QACrD,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC,2CAA2C,CAAC;QACzG,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,mDAAmD,CAAC;QACxG,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,iLAAiL,CAAC;KACzN;IACD,OAAO,EAAE,CAAC,IAAkB,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,eAAe,CAAC,EAAE,EAAE,IAAI,CAAC;CACnE,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAY;IACpC,IAAI,EAAE,YAAY;IAClB,KAAK,EAAE,8BAA8B;IACrC,WAAW,EACT,ugBAAugB;IACzgB,WAAW,EAAE;QACX,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,sBAAsB,CAAC;QACpF,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,gCAAgC,CAAC;QAC1E,IAAI,EAAE,CAAC;aACJ,MAAM,CAAC;YACN,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,gFAAgF,CAAC;YAC7H,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE;YACtC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE;SACxD,CAAC;aACD,QAAQ,CAAC,wFAAwF,CAAC;QACrG,MAAM,EAAE,CAAC;aACN,MAAM,CAAC;YACN,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC;YAC7C,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE;YACf,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE;SACjD,CAAC;aACD,QAAQ,EAAE;aACV,QAAQ,CAAC,gDAAgD,CAAC;KAC9D;IACD,OAAO,EAAE,CAAC,IAAe,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,EAAE,IAAI,CAAC;CAC1D,CAAC;AAEF,MAAM,CAAC,MAAM,eAAe,GAAY;IACtC,IAAI,EAAE,cAAc;IACpB,KAAK,EAAE,0BAA0B;IACjC,WAAW,EACT,qhBAAqhB;IACvhB,WAAW,EAAE;QACX,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC;QAC7C,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC;QACnD,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,iDAAiD,CAAC;QAC5F,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,yEAAyE,CAAC;QACnH,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;QACxC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE;QACxC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE;QAC9C,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;QAChD,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC,CAAC,QAAQ,EAAE;QACrD,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,QAAQ,EAAE;KACrD;IACD,OAAO,EAAE,CAAC,IAAiB,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,WAAW,CAAC,EAAE,EAAE,IAAI,CAAC;CAC9D,CAAC"}
@@ -270,6 +270,10 @@ it is missing, and leave the file valid JSON. Do not allow the whole
270
270
  `mcp__plugin_eklavya_eklavya` server instead: that would also let
271
271
  `memory_delete` and `set_config` run unasked. Show them the entries you added.
272
272
  If the denial continues, a restart of Claude Code reloads the settings.
273
+ `eklavya:eklavya-explainer denied by auto mode` needs no fix and has none: auto
274
+ mode drops `Agent` allow rules, so do not add one. The panel asks once more,
275
+ then queues a prompt for you to start the explainer; do that and tell them
276
+ the page is on its way.
273
277
 
274
278
  **Updates are automatic.** Eklavya checks npm at session start, at most hourly,
275
279
  and installs a newer release on its own, settings untouched. So "update
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eklavya",
3
- "version": "1.52.4",
3
+ "version": "1.53.0",
4
4
  "description": "Learn while your agent works — local knowledge graph, spaced repetition, and quiz gating for agent-assisted development.",
5
5
  "license": "MIT",
6
6
  "repository": {