eklavya 1.27.0 → 1.28.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.
@@ -1,304 +1,112 @@
1
- # Editing a skill
1
+ # Editing skills and agent instructions
2
2
 
3
- `skills/` is where Eklavya's behaviour actually lives. The MCP server holds the
4
- data and the arithmetic; these files hold everything the model does with it.
5
- A wrong sentence here is a wrong product, and nothing fails loudly when it is.
3
+ Skills define model behavior; the runtime owns data and arithmetic. A stale
4
+ sentence can change the product without raising an error. Read the root
5
+ `CLAUDE.md`, then verify instructions against the tool implementation.
6
6
 
7
- ## What a skill is, mechanically
7
+ ## Entry points and ownership
8
8
 
9
- One directory, one `SKILL.md`, YAML frontmatter with `name` and `description`.
10
- **`disable-model-invocation: true` is what turns a skill into a
11
- `/eklavya:<name>` slash command** — the model can no longer load it on its own,
12
- and the developer types it. Without that line the skill is model-invocable only.
13
-
14
- Nine have it, and they are the nine slash commands:
15
-
16
- `gate`, `learn`, `level`, `memory`, `mode`, `pack`, `progress`, `quiz`, `setup`.
17
-
18
- `skills/tutor/SKILL.md` deliberately does not. It is the pedagogy — one
19
- question at a time, honest grading, never the same question twice — and every
20
- other skill defers to it rather than restating it. `quiz` says "follow the
21
- `tutor` skill for how to ask and grade"; keep it that way. Pedagogy duplicated
22
- into a command skill is pedagogy that drifts from the one place Cursor reads.
23
-
24
- ## The description is a trigger, not a summary
25
-
26
- For a model-invocable skill the `description` is the only part always in
27
- context; the body is read only once the model has decided to load it. So a
28
- description that summarises the workflow becomes a shortcut the model takes
29
- *instead of* reading the body — it answers from the summary and never opens the
30
- file. The body becomes documentation nobody reads.
31
-
32
- `skills/tutor/SKILL.md` had exactly that shape. It read "Use while implementing
33
- any non-trivial task (to log the concepts it touches), and whenever quizzing,
34
- grading, or explaining" — three steps named, and nowhere in it the words *one
35
- question, mid-task*. Which is the failure the interleaved cadence had to fix:
36
- questions arriving in a pile at the end of the work.
37
-
38
- So, for `tutor` and anything else without `disable-model-invocation`:
39
- **triggering conditions only**, third person, opening "Use when". Never the
40
- number of questions, never the order of the tool calls, never the grading.
41
- Those live in the body, which is where the model has to go to get them.
42
-
43
- The nine slash commands are exempt, and it is not a technicality:
44
- `disable-model-invocation: true` means the model never matches on their
45
- description at all. The developer types the command and the description is its
46
- one line of help, so those should say what they do. `agents/tutor.md` keeps one
47
- identity clause for the same kind of reason — a subagent is picked from a roster
48
- by what it is, not loaded by trigger.
49
-
50
- ## Three directories, and why they are not one
51
-
52
- - **`skills/`** ships inside the plugin. Slash commands plus `tutor`.
53
- - **`user-skill/eklavya/`** is copied to `~/.claude/skills/eklavya/` by
54
- `npx eklavya install` (`installSkill()` in `mcp/src/install.ts`). It is
55
- model-invocable, so plain chat — "Eklavya is quizzing me too much" — reaches
56
- it, and it works where the plugin is not loaded. **Never move it under
57
- `skills/`**: it would then register twice, once per surface. It drives the
58
- `eklavya` CLI and the read/write config tools; it does not teach or quiz.
59
- - **`user-skill/eklavya-artifacts/`** travels the same way (`USER_SKILLS` in
60
- `install.ts`). It writes a page under `~/.eklavya/artifacts/<project>/`
61
- through `eklavya artifacts new`, which owns the path, the metadata and the
62
- template — the skill never writes a page from scratch. Its design rules and
63
- `agents/explainer.md`'s summary of them describe one thing; change both.
64
- - **`agents/explainer.md`** is the background page-writer `record_attempt`'s
65
- `explain` block names. No Eklavya MCP tools at all: it cannot grade or log.
66
- - **`agents/tutor.md`** is the subagent. It has the Eklavya MCP tools and
67
- read-only file access — and **no `AskUserQuestion`** — so it renders the four
68
- options as lettered text. Any change to
69
- `skills/tutor/references/writing-mcq.md` has to hold for a plain-text
70
- renderer too.
71
-
72
- ## A skill is a prompt, but it is also an API client
73
-
74
- Twenty tools, all in `mcp/src/tools/`. Nine for learning:
75
- `get_learner_profile`, `log_session_concepts`, `get_session_quiz_plan`,
76
- `record_attempt`, `get_gate_status`, `upsert_concepts`, `get_concept_graph`,
77
- `get_config`, `set_config`. Eleven for memory: `memory_search`, `memory_get`,
78
- `memory_timeline`, `memory_file_history`, `memory_status`, `memory_write`,
79
- `memory_correct`, `memory_delete`, `memory_collections`, `code_outline`,
80
- `code_find_symbol`.
81
-
82
- The memory tools are two-stage on purpose: the index tools return identifiers
83
- and titles, and `memory_get` is the only one that returns a narrative. A skill
84
- that tells the model to hydrate everything a search returned spends exactly
85
- the context the feature exists to save. Say "search, choose, then get".
86
-
87
- **Before you write "call `X` with `Y`", open `mcp/src/tools/<X>.ts`.** Check
88
- `Y` is in the `inputSchema`, and check the field you are telling the model to
89
- read is in what the handler returns. A skill that names a field the server
90
- never returns fails silently — the model improvises a plausible value and the
91
- developer sees a confident number nobody computed.
92
-
93
- `config_tools.ts` holds `get_config` and `set_config`, and the memory tools are
94
- grouped the same way — `memory_read_tools.ts`, `memory_write_tools.ts`,
95
- `code_tools.ts`, `collection_tools.ts`. The nine learning tools are one file
96
- per tool name.
97
-
98
- ### Not every tool takes `session_id`
9
+ Each skill has a directory and `SKILL.md` with `name` and `description`.
10
+ `disable-model-invocation: true` makes the entry user-invoked only. The shipped
11
+ slash commands are `gate`, `learn`, `level`, `memory`, `mode`, `pack`, `progress`,
12
+ `quiz` and `setup`. Recount from frontmatter when changing this inventory.
99
13
 
14
+ | Location | Responsibility |
15
+ |---|---|
16
+ | `skills/tutor/` | Shared pedagogy; commands defer to it rather than duplicate it |
17
+ | `user-skill/eklavya/` | Plain-chat configuration and CLI help, installed to `~/.claude/skills/`; does not teach |
18
+ | `user-skill/eklavya-artifacts/` | Create pages through `eklavya artifacts new`, which owns paths, metadata and template |
19
+ | `agents/tutor.md` | Read-only file access and selected learning/memory tools; teaches builder-logged concepts and uses lettered text without `AskUserQuestion` |
20
+ | `agents/explainer.md` | Background artifact writer; no Eklavya MCP tools, grading or concept logging |
21
+
22
+ Never move `user-skill/` entries into `skills/`: they would register twice.
23
+ Keep artifact design instructions and the explainer's summary aligned.
24
+
25
+ Model-invocable descriptions should state triggering conditions, in third person,
26
+ starting “Use when”; put workflow, tool order and grading in the body. A summary
27
+ in the description invites the model to skip reading the rules. User-invoked
28
+ command descriptions instead serve as concise help. Agent descriptions also
29
+ need their role so they can be selected from a roster.
30
+
31
+ ## Check the API before writing instructions
32
+
33
+ Open the relevant file in `mcp/src/tools/` before saying “call X with Y”. Verify
34
+ the input schema and every returned field the instruction uses. Learning tools
35
+ mostly have individual files; config, memory read/write, code and collections
36
+ tools are grouped. `tools/index.ts` is the advertised inventory.
37
+
38
+ Normally omit `session_id`: tools that support it resolve the current session.
39
+ Use an explicit ID only when supplied by the host or required by the task.
100
40
  `log_session_concepts`, `upsert_concepts`, `get_session_quiz_plan`,
101
- `record_attempt` and `get_gate_status` take it, optionally.
102
- `get_learner_profile`, `get_concept_graph`, `get_config` and `set_config` have
103
- no such argument at all. **Omit it everywhere.** The server resolves the
104
- current session itself, which is what lets the subagent's answers count toward
105
- the same gate. Only pass one if a hook handed you an id.
106
-
107
- ## The facts that have drifted before
108
-
109
- Check every one of these against the code when you touch a skill.
110
-
111
- - **`focus` defaults to `concept`, not `project`** (`DEFAULT_CONFIG` in
112
- `mcp/src/config.ts`). This was wrong in three skills at once. Grep before you
113
- write it: `grep -rn 'focus' skills/ user-skill/ agents/`.
114
- - **The `interleaved` one-question cap has exemptions.** In
115
- `mcp/src/tools/get_session_quiz_plan.ts`:
116
- `capped = cadence === 'interleaved' && !quiz.enforced && !explicitTopic`,
117
- and then `max = args.max ?? (capped ? 1 : max_questions_per_task)`.
118
- So `quiz.enforced` is exempt, an explicit `domain` or `slugs` is exempt, and
119
- an explicit `max` wins outright because it is read first. Read that code
120
- rather than trusting prose about it — including this paragraph.
121
- - **The Stop hook blocks when unenforced too.** `mcp/src/hooks/stop-quiz-check.ts`
122
- blocks either way; what `quiz.enforced` changes is that it skips the
123
- `min_minutes_between_quizzes` cooldown, takes the whole remaining budget
124
- instead of one question, and gates commits. Do not write "it never
125
- interrupts unless enforced".
126
- - **`get_learner_profile`'s lists are capped, and `known` is ordered by score.**
127
- `LIST_CAP = 8` covers `weak`, `due_for_review`, `projects`,
128
- `recent_concepts` and `skipped`; `KNOWN_CAP = 30` covers `known`, with the
129
- real count in `known_total`. `known` is sorted strongest-first, **not** by
130
- date. `get_concept_graph` caps at 200 nodes and sets `truncated`.
131
- - **The seed catalogue holds 87 concepts today** — 33 `web-auth`, 19 `git`, 18
132
- `react`, 17 `node-backend`, in `mcp/src/seed/*.json`. If you state that
133
- number in a skill, recount it first; a seed file gains concepts and the
134
- sentence does not.
135
-
136
- ## The dials are in the status bar, not above the stem
137
-
138
- Until 1.14 every plan item carried `ask_header` and the tutor printed it above
139
- the question: `[mode: ambient · focus: concept · level: easy · tier: 2
140
- mechanism]` — in the vocabulary of the day, when a single `mode` dial still
141
- existed. It existed for a real reason — on `concept` focus a deliberately
142
- transferable question reads as a vague one, and on `easy` a tier-2 question
143
- reads as shallow rather than as a runway — but it spent four settings' worth of
144
- screen above *every* stem to say something that is true for the whole session.
145
-
146
- Ambient state belongs somewhere ambient. `statusLine` in `mcp/src/statusline.ts`
147
- composes `[EKLAVYA concept · interleaved · easy]` for `eklavya statusline`,
148
- which the host's status bar runs — with `enforced` prepended only when it is
149
- set, since a segment that is always there is a segment nobody reads. `askHeader` is deleted,
150
- the plan no longer carries `ask_header`, and both hooks now say *ask the stem on
151
- its own*.
152
-
153
- Two consequences for anyone editing a skill:
154
-
155
- - **Never tell the model to compose a settings line.** Not the dials, not the
156
- tier, not `question: 2 of 3`. A line a skill assembles is a line the server
157
- cannot keep consistent, which is the reason this was centralised in the first
158
- place.
159
- - **`stripAskHeader` stays, and must.** Every attempt recorded while the line
160
- existed still has it inside the stem, and `questionFingerprint` (`store.ts`)
161
- hashes that text. Delete the stripper and the entire back catalogue changes
162
- fingerprint at once, so *never the same question twice* breaks for every
163
- question ever asked. It is now a guard for history, plus a model that invents
164
- a line anyway — plus the one thing that is still allowed above a stem, below.
165
-
166
- ### The one exception: who is asking
167
-
168
- The status bar carries the dials, and the `header` chip carries the
169
- attribution. Both are terminal paint. Claude Desktop draws a question card with
170
- no chip in it and has no status bar to run `eklavya statusline` in, so a
171
- question there arrived signed by nobody — which is the thing the chip existed
172
- to prevent, failing silently on a whole host.
173
-
174
- So `[Eklavya]` goes back above the stem, **on those hosts only**, and only that.
175
- `needsInlineAttribution` in `mcp/src/surface.ts` decides, `attributionRule`
176
- composes the sentence, and the plan returns it as `ask_attribution`. That field
177
- is why this is not a re-run of `ask_header`: the rule is composed once on the
178
- server, where the host is visible, rather than assembled by a skill that cannot
179
- see one.
180
-
181
- A third consequence follows from the two above:
182
-
183
- - **A skill file must not state the rule itself.** `writing-mcq.md` and
184
- `SKILL.md` point at `ask_attribution` and stop. A skill is static and a host
185
- is not, so a skill that spells out "set `header` and nothing else" is correct
186
- in a terminal and wrong in Claude Desktop, with no way to tell which it is
187
- being read in.
188
-
189
- The tier is deliberately nowhere on screen. A status bar refreshes on the host's
190
- cadence, so a tier there would sometimes name the previous question's
191
- difficulty, and a stale readout is worse than none. `level` covers what the tier
192
- was explaining: `easy` already means tiers 1-2.
193
-
194
- ## The tutor skill is an entry point plus references
195
-
196
- `skills/tutor/SKILL.md` was 5,296 words in one file, loaded whole whenever the
197
- model decided a task was non-trivial. It is now the part that decides *whether
198
- to act* — the log loop, checkpoint versus sweep, the shared budget, the tier
199
- ladder, the plan's authoritative fields, and a Red Flags table of the
200
- rationalizations that have each shipped a worse session — with the craft in
201
- three siblings:
202
-
203
- | File | Holds |
41
+ `record_attempt`, `get_gate_status`, `get_config` and `set_config` accept it for
42
+ session resolution. `memory_timeline` also accepts it, but as a filter: omitting
43
+ it shows the whole project. Check each schema instead of assuming all tools
44
+ accept the same fields.
45
+
46
+ Memory follows “search, choose, then get”. Search returns a small index;
47
+ `memory_get` returns the chosen narrative. Hydrating every result defeats the
48
+ context-saving design.
49
+
50
+ ## Preserve the tutoring contract
51
+
52
+ - Read defaults from `mcp/src/config.ts`: focus is `concept`. Search every skill,
53
+ user skill, agent and manual page when changing a shared setting.
54
+ - Planner output is authoritative. Interleaved questions are capped at one only
55
+ when unenforced and without an explicit topic; explicit `max` wins. Unenforced
56
+ Stop hooks can still ask questions.
57
+ - Settings belong in `eklavya statusline`, not a hand-composed question header.
58
+ Follow the plan's `ask_attribution` for host-specific `[Eklavya]` attribution;
59
+ never hardcode a terminal-only rendering rule. Do not display tier/counter
60
+ lines above a stem.
61
+ - `stripAskHeader` in `ask.ts` remains necessary for old recorded questions and
62
+ stable fingerprints. Removing it changes duplicate detection for history.
63
+ - Honor list caps and truncation. Profile `known` is strongest-first, not most
64
+ recent; graph results can be incomplete. Recount seed totals before quoting.
65
+ - Keep `declined` distinct from `dont_know`, MCQ grading capped as the runtime
66
+ requires, and one-question/resume behavior intact.
67
+
68
+ The tutor entry point chooses whether to act and points to required references:
69
+
70
+ | Reference | Read before |
204
71
  |---|---|
205
- | `references/writing-mcq.md` | the four-option shape, `answer_position`, distractors, plain language, the second question about a concept, `prereqs_unmet`, and how to record a stem |
206
- | `references/grading.md` | both scales, the mcq cap, feedback length, the four-step sequence a blank earns, `already_taught` |
207
- | `references/focus-and-level.md` | the three focuses, the earned level bands, the cadence contract, the enforced-mode gate retry |
208
-
209
- Two rules keep that split working.
210
-
211
- **Mark a reference REQUIRED at the point of use, never as an `@`-link.** An
212
- `@`-path is resolved eagerly by the host, which pulls the whole file into
213
- context and undoes the split. "Read `references/grading.md` before you grade",
214
- written where grading comes up, is what makes the model open it exactly when it
215
- needs it.
216
-
217
- **The entry point does not grow back, and the pointers stay honest.**
218
- `test/packaging.test.ts` fails above 2,000 whitespace tokens, and asserts the
219
- set of files named in SKILL.md is *equal* to the set on disk. Both directions
220
- matter. A pointer with no file is the worse half — the model is told the rules
221
- are elsewhere, cannot find them, and improvises, while nothing errors. A file
222
- with no pointer is the quieter half: it ships, `export-rules` inlines it, and
223
- Claude Code is never told to read it, so the same pedagogy differs by surface.
224
- An earlier version of that test harvested pointers from SKILL.md *and*
225
- `agents/tutor.md` into one list and asserted the list was non-empty — which
226
- passed with no pointers in SKILL.md at all, the exact state it was written to
227
- catch.
228
-
229
- ## Match the form to the failure
230
-
231
- Two failures need opposite wording, and using the wrong form measurably makes
232
- things worse. superpowers A/B tested this on their own dispatch-prompt
233
- guidance: the "don't do X" version produced **more** of the unwanted content
234
- than the "here is the shape" version — the distributions fully separated — and
235
- it did worse than giving no guidance at all.
236
-
237
- - **The model knows the rule and breaks it under pressure.** Discipline. Ban
238
- it, and name the excuse next to it: that is what the Red Flags table at the
239
- top of `SKILL.md` is, and what the shared budget, one-question and
240
- spent-question rules live in.
241
- - **The model complies and produces the wrong shape.** Craft. Bans backfire
242
- here. Describe the shape you want, in build order, and let the prohibitions
243
- fall out of it as properties of the finished thing.
244
-
245
- Writing a good multiple-choice question is the second kind, and
246
- `references/writing-mcq.md` was written as the first kind — *never restate the
247
- answer, no double negatives, avoid "which is NOT", do not number the options*.
248
- It is now a six-part recipe in build order followed by a checklist of
249
- properties, so the same rules arrive as "the answer appears among the options
250
- and nowhere in the stem" rather than as separate bans to weigh.
251
-
252
- **No nuance clauses in the recipe.** superpowers measured this separately: one
253
- appended "unless it matters" turns a reliable recipe into a noisy one, because
254
- it reopens the negotiation the recipe had settled. `writing-mcq.md` carried
255
- exactly one — *"Save the precise term for when the precision is the point"* —
256
- and it is gone. If an exception is real, it belongs in the plan's `framing`,
257
- which is server-side and authoritative, not in a hedge the model gets to weigh.
258
-
259
- `references/grading.md` keeps its prohibitions on purpose. Inflating a grade
260
- and offering to stop because someone is blanking are pressure failures, not
261
- shape failures: the model knows what honest grading is.
262
-
263
- ## The tutor skill has two readers
264
-
265
- `mcp/scripts/copy-assets.mjs` bundles the whole `skills/tutor/` directory to
266
- `mcp/dist/assets/tutor/`, and `eklavya export-rules` (`mcp/src/cli.ts`) strips
267
- the frontmatter and wraps it as a Cursor rules file. So the pedagogy is
268
- consumed by two editors.
269
-
270
- **Cursor has no progressive disclosure**, and that is the reason `export-rules`
271
- concatenates SKILL.md with every `references/*.md` in alphabetical order and
272
- says so in its preamble. A rules file is one document with `alwaysApply: true`,
273
- so "read `references/grading.md`" there is a pointer to nothing. Had the split
274
- shipped without the inlining, Cursor would have got the dispatch logic and none
275
- of the craft — and every test would still have passed. `test/cli.test.ts` now
276
- asserts a line from each reference reaches the output.
277
-
278
- Alphabetical rather than a hand-kept order: in an always-apply document the
279
- whole thing is in context at once, so order carries no meaning, and a listed
280
- order is one more place a new reference gets forgotten.
281
-
282
- **A missing reference is a hard failure there, not a warning.** `export-rules`
283
- reads the pointers out of SKILL.md and refuses to emit anything if one of them
284
- did not bundle, naming the file. It has to: the preamble promises the material
285
- is further down the document, so a half-bundled export is worse than none — the
286
- model is assured the rules are present and hunts for them instead of falling
287
- back on what it has. `copy-assets.mjs` only warns when a copy fails, so that
288
- state is reachable rather than hypothetical.
289
-
290
- Consequence of the two readers: **no Claude-Code-only instructions in any of
291
- the four files.** Slash-command names, plugin paths and hook mechanics belong
292
- in the command skills, not in the pedagogy. `AskUserQuestion` is the one
293
- unavoidable exception, and `agents/tutor.md` already carries the fallback for
294
- renderers that lack it.
295
-
296
- ## Consistency
297
-
298
- The same behaviour described in two skills has drifted apart before — that is
299
- how `focus: project` got into three files. The dials appear in
300
- `skills/mode/SKILL.md` and `user-skill/eklavya/SKILL.md`; the level bands
301
- appear in `skills/level/SKILL.md` and `tutor/references/focus-and-level.md`;
302
- the cadence cap appears in `mode`, `quiz` and that same reference. **When you
303
- change one, grep the others for the same claim.** Where any of them disagrees with the code,
304
- `mcp/src/config.ts` and the tool file are right and the skill is wrong.
72
+ | `tutor/references/writing-mcq.md` | Writing the stem, options and recorded question |
73
+ | `tutor/references/grading.md` | Grading, feedback, blanks and repeated teaching |
74
+ | `tutor/references/focus-and-level.md` | Applying focus, earned levels, cadence and gate retries |
75
+
76
+ Mark references REQUIRED at their point of use. Do not use eager `@` imports.
77
+ Keep the entry point within the packaging test's 2,000-whitespace-token limit.
78
+ Every referenced file must exist and every reference file must be reachable from
79
+ the entry point; the packaging test checks both directions.
80
+
81
+ For craft, describe the desired shape in build order, followed by a checklist.
82
+ For pressure failures such as grade inflation, retain explicit prohibitions and
83
+ the rationalization they prevent. Avoid vague exceptions that let the model
84
+ renegotiate a recipe; genuine framing exceptions belong in planner output.
85
+
86
+ ## Bundling and host limits
87
+
88
+ `copy-assets.mjs` bundles the tutor directory. `eklavya export-rules` strips
89
+ frontmatter and inlines reference files alphabetically for consumers that do
90
+ not load them on demand. Missing references must fail export, not emit partial
91
+ pedagogy. Keep shared tutor material independent of plugin paths, hook mechanics
92
+ and slash commands; the tutor agent supplies the plain-text fallback for
93
+ `AskUserQuestion`.
94
+
95
+ Rule export does not provide another editor with Claude Code hooks. Do not
96
+ advertise equivalent ambient learning on a host without that integration.
97
+ Delegation policy lives in `docs/subagent-policy.md`; the tutor exemption from
98
+ the implementer's “do not ask” directive must remain deliberate.
99
+
100
+ ## Documentation and checks
101
+
102
+ Update the corresponding manual page in the same PR: commands in `commands`,
103
+ settings in `dials`/`configuration`, grading in `grading-engine`, levels in
104
+ `levels-and-tiers`, artifacts in `dashboard`, and setup in `first-run`.
105
+ Update the landing command list if the public command set changes. Search other
106
+ skills for duplicated claims rather than fixing only the entry you touched.
107
+
108
+ Run the relevant packaging, CLI and tool tests from `mcp/` through `npm test`
109
+ so bundled assets are rebuilt. Tutor behavior changes also require before/after
110
+ evaluation (`eval/README.md`) and the live checkpoint acceptance check
111
+ (`CONTRIBUTING.md`). A successful build alone cannot verify prompt behavior.
112
+ Build `web/` for documentation edits and record any check not run.
@@ -77,10 +77,29 @@ not.
77
77
 
78
78
  **Stay on the design system.** Components already in the template: `.eyebrow`,
79
79
  `.lede`, `.card`, `.grid`, `.chip` (`.warn`, `.bad`), `.stat` (`.k`, `.v`,
80
- `.d`), `.callout`, `td.num`. Any new CSS names a role — `--bg --panel --mass
81
- --ink --dim --faint --line --line-2 --spot --spot-soft --warning --error` —
82
- never a raw hex, and keeps `border-radius: 0`. One accent, verdigris `--spot`,
83
- spent sparingly. No emoji.
80
+ `.d`), `.callout`, `td.num`. Reuse them before adding CSS. Any CSS you add
81
+ follows Eklavya's rules:
82
+
83
+ - **Colour names a role**, never a raw hex, `rgba()` or `--vd-*` step. Grounds
84
+ `--bg`, `--panel` (cards), `--mass` (wells, inline code), `--code-bg`; text
85
+ `--ink`, `--dim` (body), `--faint` (captions, labels); hairlines `--line`,
86
+ `--line-2`; accent `--spot`, `--spot-soft` (tints), `--spot-ink` (text on a
87
+ `--spot` fill); state `--warning`, `--error`. `--faint-2` is for disabled
88
+ marks only, never readable text.
89
+ - **One accent**, verdigris `--spot`, spent a handful of times: the node that
90
+ matters, a key number, a link. No other hues. `--warning` and `--error` mean
91
+ a real warning or mistake, and always carry a word or mark too — never
92
+ colour alone.
93
+ - **Square and flat.** `border-radius: 0` on every box, chip and table; circles
94
+ only for dots. Separate with 1px hairlines, not shadows.
95
+ - **Type through the variables**: `--font-disp` (Archivo) for headings,
96
+ `--font-body` (Inter) for text, `--font-mono` (JetBrains Mono) for code,
97
+ commands, concept slugs and small uppercase labels. Sentence case.
98
+ - **Icons** are inline Lucide-style line SVG: `stroke="currentColor"`, width 2,
99
+ round caps and joins. No emoji, icon fonts or filled icons.
100
+ - **Both grounds.** Every text colour reads at 4.5:1 on ink and on paper —
101
+ check with `data-mode="paper"` on `<html>`. Motion, if any, is short
102
+ (120–320ms on `--ease`) and switched off under `prefers-reduced-motion`.
84
103
 
85
104
  **Keep it one file.** Everything inline. An external stylesheet, script or
86
105
  image breaks the HTML download, which hands over the page itself.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eklavya",
3
- "version": "1.27.0",
3
+ "version": "1.28.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": {