eklavya-mcp 1.0.0 → 1.1.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 CHANGED
@@ -34,7 +34,9 @@ State lives in `~/.eklavya/knowledge.db` (SQLite, WAL). Override with `EKLAVYA_H
34
34
  ```bash
35
35
  eklavya doctor # check the install
36
36
  eklavya config get
37
- eklavya config set mode enforced [--repo]
37
+ eklavya config set mode enforced [--repo] # how hard it pushes
38
+ eklavya config set focus concept # what it teaches
39
+ eklavya config set focus learn --topic caching
38
40
  eklavya export-rules --out .cursor/rules/eklavya.md
39
41
  eklavya db-path
40
42
  ```
@@ -12,7 +12,7 @@
12
12
  # Keep PINNED_VERSION in step with .claude-plugin/plugin.json and
13
13
  # mcp/package.json. A test asserts all three match.
14
14
 
15
- PINNED_VERSION="1.0.0"
15
+ PINNED_VERSION="1.1.0"
16
16
 
17
17
  MCP_DIR=$(cd "$(dirname "$0")/.." && pwd)
18
18
  LOCAL_SERVER="$MCP_DIR/dist/server.js"
@@ -22,7 +22,7 @@ Each concept needs a `context`: one line naming the actual decision, in the actu
22
22
  - Good: `"set httpOnly on the refresh cookie in auth.ts:42"`
23
23
  - Useless: `"used cookies"`
24
24
 
25
- If a concept has no slug yet, just log it — unknown slugs are created. Use kebab-case and check the response: `matched` tells you the concept already existed under a canonical slug, and you should use that slug from then on.
25
+ If a concept has no slug yet, just log it — unknown slugs are created. Use kebab-case and check the response: `matched` tells you the concept already existed under a canonical slug, and you should use that slug from then on; `created` tells you a bare placeholder was made, and you owe it a `upsert_concepts` call (see *Growing the graph*).
26
26
 
27
27
  Never interrupt the implementation to teach. Logging is silent. Teaching happens after the work is done, or when the developer asks.
28
28
 
@@ -35,7 +35,9 @@ error, and do not mention hooks or exit codes. Just run the quiz below, then
35
35
  finish your turn normally.
36
36
 
37
37
  It fires at most once per batch of work, so this is your one chance to teach
38
- what the task covered. If the developer declines, record the skip and let it go.
38
+ what the task covered. If the developer declines outright, record it and let it
39
+ go. If they say they do not know, that is not a decline — that is the whole
40
+ reason you are here. Teach it.
39
41
 
40
42
  ## Before you teach or quiz
41
43
 
@@ -47,9 +49,44 @@ Call `get_learner_profile` first. Always. It tells you:
47
49
  - `due_for_review` — what spaced repetition says is ready to resurface
48
50
  - `suggested_tier` — roughly where to pitch
49
51
 
50
- Then call `get_session_quiz_plan`. It returns the concepts worth asking about *and the tier to ask each one at*. Respect `tier_to_ask` that number is how a concept gets harder as they get better.
52
+ Then call `get_session_quiz_plan`. It returns the concepts worth asking about *and the tier to ask each one at*, plus everything you need to write a question that has not been asked before:
51
53
 
52
- If `questions_needed` is 0, say nothing and move on. `reason: "cooldown"` means they were quizzed recently; `reason: "mode_off"` means Eklavya is dormant.
54
+ | Field | What it is for |
55
+ |---|---|
56
+ | `tier_to_ask` | The difficulty to pitch at. This is how a concept gets harder as they get better — not the concept's own tier. |
57
+ | `description` | The canonical one-line meaning of the concept. Anchor the question to *this*, so tier-3 questions stay on the concept instead of drifting into whatever the diff happened to contain. |
58
+ | `context` | The real decision in the real file. This is what makes the question grounded. |
59
+ | `asked_before` | Questions this developer has **already been asked** about this concept, with the tier, grade and `outcome` each got. |
60
+ | `already_taught` | They blanked on this before and you explained it. The next question is a follow-up, not a first encounter. |
61
+ | `prereqs_unmet` | Prerequisites they have not mastered yet. |
62
+ | `last_grade` | How the last attempt went. |
63
+ | `framing` | What this focus requires of the question. Authoritative — see *Focus*. |
64
+ | `bridge_context` | `learn` focus: the session's work touched this topic concept, and here is the code. |
65
+
66
+ If `questions_needed` is 0, say nothing and move on. `reason: "cooldown"` means they were quizzed recently; `reason: "already_covered"` means every candidate was already asked about in this session; `reason: "mode_off"` means Eklavya is dormant.
67
+
68
+ When the developer explicitly asked to be quizzed, pass `ignore_cooldown: true` — the cadence limit exists to stop you nagging, not to refuse a request. For a named topic rather than this session's work, pass `domain` (or `slugs`) and the same engine plans it, with the same tiers and the same repeat protection.
69
+
70
+ ## Never the same question twice
71
+
72
+ This is the promise the whole tool rests on, and `asked_before` is how you keep it.
73
+
74
+ - **A question in `asked_before` is spent.** Not "reword it" — spent. Ask a different thing about the same concept.
75
+ - If they scored 4 or 5 on it, `tier_to_ask` has already moved up: the new question should be asking for something the old one did not (mechanism → judgement → failure mode).
76
+ - If they scored 1 or 2 on it, come at the *same* level from a different angle. Same tier, different door — a concrete scenario instead of an abstraction, or their own code instead of a hypothetical.
77
+ - If they scored 0 with `outcome: "dont_know"`, you already taught this. Ask the thing your explanation set up, and say so — the question should sound like the second half of a conversation.
78
+ - If `asked_before` is empty, you have a clean slate; use `tier_to_ask` and `description`.
79
+ - `record_attempt` returns `repeat_question: true` if you asked something already on record. Treat it as a mistake you just made, and do not do it again in the same quiz.
80
+
81
+ The `known` list in the profile is the other half: **never ask about a slug in `known`** unless it also appears in `due_for_review`. Spaced repetition is the only reason a mastered concept comes back, and when it does it comes back harder.
82
+
83
+ ## Unmet prerequisites
84
+
85
+ `prereqs_unmet` is a warning that a question would be unfair, not hard. If a concept has unmet prerequisites:
86
+
87
+ - Ask about the prerequisite instead, if it is in the plan — the plan already orders foundations first.
88
+ - Otherwise drop the question a tier and make it mechanism-level. "Why this rather than the alternative" is not answerable by someone who does not yet have the alternative.
89
+ - Say the dependency out loud in your feedback. Knowing *what to learn next* is half of what the graph is for.
53
90
 
54
91
  ## Asking
55
92
 
@@ -60,6 +97,8 @@ If `questions_needed` is 0, say nothing and move on. `reason: "cooldown"` means
60
97
  - Grounded: *"I set `httpOnly: true` on the refresh cookie in `auth.ts` but left the access token in memory. What attack is that split defending against, and what does it cost us?"*
61
98
  - Textbook, avoid: *"What is an httpOnly cookie?"*
62
99
 
100
+ **…unless the focus says otherwise.** See *Focus* below. The rule above is `project` focus, which is the default and most of the time. `concept` focus changes what "grounded" means, and getting that wrong in either direction is the most likely way to ask a bad question.
101
+
63
102
  **Match the tier.** This is the difference between a quiz that teaches and one that annoys:
64
103
 
65
104
  | Tier | Asks for | Shape |
@@ -74,11 +113,11 @@ Definitions are tier 1 **only**. If you find yourself asking "what is X" at tier
74
113
 
75
114
  ## Grading
76
115
 
77
- Call `record_attempt` for **every** answer, including skips. Grade honestly on SM-2's 0–5:
116
+ Call `record_attempt` for **every** answer, including blanks and skips — pass `question` verbatim, because that text is what stops the same question coming back later. Pass `outcome` as well: `answered`, `dont_know`, or `declined`. Grade honestly on SM-2's 0–5:
78
117
 
79
118
  | Grade | Means |
80
119
  |---|---|
81
- | 0 | skipped, or no attempt |
120
+ | 0 | no answer — either a blank ("I don't know") or a decline. Pass `outcome` to say which |
82
121
  | 1 | wrong, and the misconception is load-bearing |
83
122
  | 2 | wrong, but the shape of the idea is there |
84
123
  | 3 | correct, but hesitant or incomplete — got there slowly |
@@ -87,18 +126,80 @@ Call `record_attempt` for **every** answer, including skips. Grade honestly on S
87
126
 
88
127
  Before you pick a number, state to yourself what in their answer justifies it. A gate built on inflated grades teaches nothing and the developer knows it. Being generous here is not kindness — it is the one failure mode that makes this whole tool pointless.
89
128
 
90
- Then give feedback in **four sentences or fewer**. Correct the specific thing they got wrong; don't re-teach the whole topic.
129
+ Then give feedback. **Four sentences or fewer for a grade of 2 or better** — correct the specific thing they got wrong and stop; don't re-teach a topic they mostly have. A blank is a different job, and has its own section below.
130
+
131
+ If they answer and get it wrong, do not immediately give the answer. Ask one narrower question that isolates the gap. If they miss that too, then teach it as below.
132
+
133
+ ## When they say "I don't know"
134
+
135
+ **This is the most important thing in this file.** A blank is not a skip. A skip says *leave me alone*; "I don't know" says *teach me*, and it is the single clearest request for teaching you will ever get. Answering it with a three-sentence correction and moving on is the failure this tool exists to prevent — the developer who understood least got taught least.
136
+
137
+ Both record as grade 0. What separates them is `outcome`, and what you do next.
138
+
139
+ **Teach it. Properly, in this order:**
140
+
141
+ 1. **Name the mechanism** in one sentence — the thing that is actually true, stated plainly.
142
+ 2. **Show the code.** Quote the two or three real lines from the diff that make it true. They are looking at a file they have never read; the lines are the whole lesson.
143
+ 3. **Say what it generalises to** — the rule they can carry to the next codebase, not just this one.
144
+ 4. **One-line takeaway.** What to remember if they forget everything else.
145
+
146
+ Six to ten sentences. The four-sentence cap above is for near-misses, where you are correcting a detail. Here there is no detail to correct: the topic *is* the gap.
147
+
148
+ **Then record and move on.** `grade: 0`, `outcome: "dont_know"`, and put the explanation you just gave in `feedback`. Do not re-ask the same concept in the same breath — grade 0 pins mastery at the floor, so it resurfaces on its own tomorrow, and `asked_before` will force a *different* question about a concept you have now taught. The spaced re-check is free and it is better than an immediate one, which only tests whether they can repeat a paragraph they just read.
149
+
150
+ **Never offer to stop because they are blanking.** Two blanks in a row is not a hint that they want out — it is evidence you are pitching too high. Drop a tier and keep going. Tier-1 recall on something you have just explained is fair, and it rebuilds footing. If they want to stop, they will say so; wait to be told.
91
151
 
92
- If they get it wrong, do not immediately give the answer. Ask one narrower question that isolates the gap. If they miss that too, then explain.
152
+ **Never dump the remaining answers as a list.** If the quiz ends early, it ends. A wall of four explanations at the door is not teaching, it is a receipt.
153
+
154
+ **When `already_taught` is true** on a plan item, they blanked on this before and you explained it. Open the next question as a follow-up to that explanation — *"last time I showed you that `_work_section()` can return an empty string; so what happens to the nav link when it does?"* — not as a first encounter. Building on a lesson is what makes it stick; asking cold throws it away.
93
155
 
94
156
  ## Growing the graph
95
157
 
96
- When work touches a concept with no slug and no near match, call `upsert_concepts` with a sensible `domain`, a `tier`, and at least one `prerequisite_of` edge to something that already exists. An isolated node teaches nothing about what to learn next.
158
+ `log_session_concepts` creates any slug it does not recognise, which is what keeps logging cheap but it creates it bare: tier 2, domain `general`, no edges. That is a placeholder, not a concept.
159
+
160
+ **When the response reports `created` slugs, follow it with `upsert_concepts`** giving each one a real `domain`, an honest `tier`, and at least one `prerequisite_of` edge to something that already exists. The response says so itself, in `next_action`, naming the slugs — that field exists because this instruction lives in a skill that may never have loaded, and the debt has to reach you either way.
161
+
162
+ This is not tidiness. `prereqs_unmet` is computed from those edges, so a concept with none can never be reported as unfair to ask about, however far out of its depth the learner is. Skip this step and every concept in a new domain arrives as a bare tier-2 node, the fairness check silently passes, and the developer gets mechanism questions about code they have never read. An isolated node teaches nothing about what to learn next, and worse, it tells the planner nothing about what not to ask yet.
163
+
164
+ ## Focus
165
+
166
+ Eklavya has two dials and they are not the same thing. **Mode** is how hard to push. **Focus** is what to teach. Every plan returns `focus` and a `framing` line; the framing is authoritative — follow it over your instincts, and over the grounding rule above where they differ.
167
+
168
+ ### project (the default)
169
+
170
+ What the sections above describe. The code is the subject. Name the file, the line, the decision.
171
+
172
+ ### concept
173
+
174
+ The same subject matter, asked so the answer transfers to a different codebase. **This does not mean textbook questions.** The diff stops being the *subject* and becomes the *motivation*: open from what was just written, then ask for the general rule, the class of problem, or where else it applies.
175
+
176
+ - Right: *"We gave the profile cache a 60s TTL in `profile.ts`. TTL is one answer to cache invalidation — what problem is it actually solving, and what kind of data makes it the wrong answer?"*
177
+ - Wrong, because it is `project` focus wearing a hat: *"Why did we pick 60s rather than 30s here?"* — a fine question, but the answer is about this file and dies with it.
178
+ - Also wrong, and the failure this focus invites: *"What is a TTL?"* That is tier-1 recall. Generalisation is not the same as vagueness, and a definition question is not the general version of anything.
179
+
180
+ The test: **could a correct answer be reused on a different project?** If not, you have written a `project` question. Plan items in this focus arrive with `context: null` on purpose — the code is deliberately withheld so you reach for the idea instead.
181
+
182
+ Items with `reason: "concept_widening"` are prerequisites and domain siblings the task did not touch directly. They are the ideas the diff is an instance of. Ask about them on their own terms.
183
+
184
+ ### learn
185
+
186
+ The developer named a topic. Teach that topic, in the prerequisite order the plan gives you, whether or not today's work touches it.
187
+
188
+ - When an item carries **`bridge_context`**, the session's work *did* touch that concept, and that string is the real code. Use it as the worked example — a topic taught through code they watched get written beats a hypothetical every time.
189
+ - When it does not, teach it on its own terms. **Do not force a link to unrelated work.** A strained bridge from a CSS bug to cache invalidation is worse than no bridge; it teaches that the connection is arbitrary.
190
+
191
+ `reason: "no_topic"` means the focus is `learn` but nothing was set — ask what they want to learn and set it before quizzing. `reason: "topic_unknown"` means the graph has nothing matching; offer the closest domain from `get_concept_graph`, or teach from first principles and `upsert_concepts` as you go. Do not invent questions about concepts that do not exist.
192
+
193
+ **Focus never changes when you interrupt.** The Stop hook still fires on real work, and `learn` focus does not license teaching an unrelated topic mid-task. Topic study on demand is `/eklavya:learn`, which the developer asks for.
97
194
 
98
195
  ## Mode
99
196
 
100
- - **ambient** — offer. If they say skip, record it (grade 0, feedback `"skipped"`) and drop it immediately. Do not ask twice. Do not guilt them.
197
+ - **ambient** — offer. If they *decline*, record it (grade 0, `outcome: "declined"`) and drop it immediately. Do not ask twice. Do not guilt them. A decline is not the same as "I don't know" — see below.
101
198
  - **enforced** — the quiz is required before committing. Say so plainly and once: the gate exists, here is what it needs, let's get through it. Supportive, not punitive. Never imply they are being punished.
199
+
200
+ A blank grades 0, and 0 never passes the gate — so a session answered entirely with "I don't know" would leave nothing to ask and a commit that can never go through. When that happens the plan comes back with `reason: "gate_retry"`: the concepts you just taught, offered again a tier lower, with `already_taught` set and `asked_before` holding the question that produced the blank. **This is a second lap, not a re-ask.** Open it as the follow-up to your own explanation — *"I showed you why the refresh cookie is httpOnly; so which of the two tokens survives an XSS payload?"* — and ask something the first question did not. It is the only route out of the gate, so do not skip past it, and do not treat it as the tool repeating itself.
201
+
202
+ A concept they explicitly **declined** is not offered again. That is deliberate: the gate holding against a decline is enforcement working. If they are stuck behind it, the honest thing to say is that answering the retry questions is the way through, not that the tool is broken.
102
203
  - **off** — do nothing at all.
103
204
 
104
205
  ## The bar
package/dist/cli.js CHANGED
@@ -17,11 +17,14 @@ Usage:
17
17
  eklavya export-rules [--out <file>] Write the tutor pedagogy as a Cursor rules file
18
18
  eklavya config get Show the effective configuration
19
19
  eklavya config set <key> <value> Change a setting (add --repo to scope it to this repo)
20
+ e.g. mode ambient|enforced|off, focus project|concept|learn
21
+ add --topic <topic> when setting focus to "learn"
20
22
  eklavya doctor Check that everything is wired up
21
23
  eklavya db-path Print the database location
22
24
 
23
25
  Config keys: mode, pass_threshold, max_questions_per_task,
24
- min_minutes_between_quizzes, max_new_concepts_per_session, quiet
26
+ min_minutes_between_quizzes, max_new_concepts_per_session,
27
+ max_stop_blocks_per_session, quiet
25
28
  `;
26
29
  function fail(message) {
27
30
  process.stderr.write(`${message}\n`);
@@ -75,11 +78,31 @@ function configCommand(args) {
75
78
  if (!(key in DEFAULT_CONFIG)) {
76
79
  fail(`Unknown setting "${key}". Known: ${Object.keys(DEFAULT_CONFIG).join(', ')}`);
77
80
  }
81
+ // `focus learn` is useless without a topic, so let one call say both rather
82
+ // than leaving a state that the planner will only refuse later.
83
+ const topicIndex = args.indexOf('--topic');
84
+ const topic = topicIndex === -1 ? undefined : args[topicIndex + 1];
85
+ if (topicIndex !== -1 && !topic)
86
+ fail('--topic needs a value.');
87
+ if (topic !== undefined && key !== 'focus' && key !== 'focus_topic') {
88
+ fail('--topic only applies when setting focus.');
89
+ }
90
+ if (key === 'focus' && value === 'learn' && topic === undefined) {
91
+ fail('focus "learn" needs a topic: eklavya config set focus learn --topic <topic>');
92
+ }
93
+ // A topic is always a string. Number-parsing it would turn a topic like "html5"
94
+ // -- or worse, "2" -- into something `coerce` then silently drops.
95
+ const isTopicKey = key === 'focus_topic';
78
96
  let parsed = value;
79
- if (value === 'true' || value === 'false')
97
+ if (isTopicKey)
98
+ parsed = value;
99
+ else if (value === 'true' || value === 'false')
80
100
  parsed = value === 'true';
81
101
  else if (value !== '' && !Number.isNaN(Number(value)))
82
102
  parsed = Number(value);
103
+ const patch = { [key]: parsed };
104
+ if (topic !== undefined && key === 'focus')
105
+ patch.focus_topic = topic;
83
106
  let target;
84
107
  if (scopeRepo) {
85
108
  if (!resolved.repoRoot)
@@ -89,8 +112,10 @@ function configCommand(args) {
89
112
  else {
90
113
  target = resolved.globalPath;
91
114
  }
92
- writeConfigFile(target, { [key]: parsed });
93
- process.stdout.write(`${key} = ${JSON.stringify(parsed)} -> ${target}\n`);
115
+ writeConfigFile(target, patch);
116
+ for (const [k, v] of Object.entries(patch)) {
117
+ process.stdout.write(`${k} = ${JSON.stringify(v)} -> ${target}\n`);
118
+ }
94
119
  }
95
120
  function doctor() {
96
121
  const file = dbPath();
@@ -114,7 +139,12 @@ function doctor() {
114
139
  lines.push(`database error: ${err instanceof Error ? err.message : String(err)}`);
115
140
  }
116
141
  const resolved = loadConfig();
117
- lines.push(`mode: ${resolved.config.mode}${resolved.repoPath ? ' (from this repo)' : ''}`);
142
+ const fromRepo = resolved.repoPath ? ' (from this repo)' : '';
143
+ lines.push(`mode: ${resolved.config.mode}${fromRepo}`);
144
+ lines.push(`focus: ${resolved.config.focus}${resolved.config.focus === 'learn' ? ` (${resolved.config.focus_topic ?? 'no topic set'})` : ''}${fromRepo}`);
145
+ if (resolved.overrides.length > 0) {
146
+ lines.push(`overridden by repo: ${resolved.overrides.join(', ')}`);
147
+ }
118
148
  process.stdout.write(`${lines.join('\n')}\n`);
119
149
  if (!ok)
120
150
  process.exit(1);
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;GAIG;AACH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE5F,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE/D,MAAM,KAAK,GAAG;;;;;;;;;;;;;;CAcb,CAAC;AAEF,SAAS,IAAI,CAAC,OAAe;IAC3B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC;IACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,SAAS,WAAW,CAAC,IAAc;IACjC,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,EAAE,gBAAgB,CAAC,CAAC;IACnE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC9B,IAAI,CAAC,gFAAgF,CAAC,CAAC;IACzF,CAAC;IAED,MAAM,KAAK,GAAG,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,uBAAuB,EAAE,EAAE,CAAC,CAAC;IAEtF,MAAM,KAAK,GAAG;;;;;;;;;;;EAWd,KAAK,CAAC,IAAI,EAAE;CACb,CAAC;IAEA,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACvC,IAAI,QAAQ,IAAI,CAAC,EAAE,CAAC;QAClB,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC;QAC/B,IAAI,CAAC,GAAG;YAAE,IAAI,CAAC,0BAA0B,CAAC,CAAC;QAC3C,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACnE,EAAE,CAAC,aAAa,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;QACrC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC;QACvC,OAAO;IACT,CAAC;IAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;AAC9B,CAAC;AAED,SAAS,aAAa,CAAC,IAAc;IACnC,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,IAAI,CAAC;IAClC,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC1C,MAAM,QAAQ,GAAG,UAAU,EAAE,CAAC;IAE9B,IAAI,CAAC,MAAM,IAAI,MAAM,KAAK,KAAK,EAAE,CAAC;QAChC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;QACtE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,QAAQ,CAAC,UAAU,IAAI,CAAC,CAAC;QAC3D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,QAAQ,CAAC,QAAQ,IAAI,QAAQ,IAAI,CAAC,CAAC;QACnE,OAAO;IACT,CAAC;IAED,IAAI,MAAM,KAAK,KAAK;QAAE,IAAI,CAAC,0BAA0B,MAAM,IAAI,CAAC,CAAC;IACjE,IAAI,CAAC,GAAG,IAAI,KAAK,KAAK,SAAS;QAAE,IAAI,CAAC,yCAAyC,CAAC,CAAC;IACjF,IAAI,CAAC,CAAC,GAAG,IAAI,cAAc,CAAC,EAAE,CAAC;QAC7B,IAAI,CAAC,oBAAoB,GAAG,aAAa,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACrF,CAAC;IAED,4EAA4E;IAC5E,gEAAgE;IAChE,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAC3C,MAAM,KAAK,GAAG,UAAU,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC;IACnE,IAAI,UAAU,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK;QAAE,IAAI,CAAC,wBAAwB,CAAC,CAAC;IAChE,IAAI,KAAK,KAAK,SAAS,IAAI,GAAG,KAAK,OAAO,IAAI,GAAG,KAAK,aAAa,EAAE,CAAC;QACpE,IAAI,CAAC,0CAA0C,CAAC,CAAC;IACnD,CAAC;IACD,IAAI,GAAG,KAAK,OAAO,IAAI,KAAK,KAAK,OAAO,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QAChE,IAAI,CAAC,6EAA6E,CAAC,CAAC;IACtF,CAAC;IAED,gFAAgF;IAChF,mEAAmE;IACnE,MAAM,UAAU,GAAG,GAAG,KAAK,aAAa,CAAC;IACzC,IAAI,MAAM,GAAY,KAAK,CAAC;IAC5B,IAAI,UAAU;QAAE,MAAM,GAAG,KAAK,CAAC;SAC1B,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,OAAO;QAAE,MAAM,GAAG,KAAK,KAAK,MAAM,CAAC;SACrE,IAAI,KAAK,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAAE,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAE9E,MAAM,KAAK,GAA4B,EAAE,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;IACzD,IAAI,KAAK,KAAK,SAAS,IAAI,GAAG,KAAK,OAAO;QAAE,KAAK,CAAC,WAAW,GAAG,KAAK,CAAC;IAEtE,IAAI,MAAc,CAAC;IACnB,IAAI,SAAS,EAAE,CAAC;QACd,IAAI,CAAC,QAAQ,CAAC,QAAQ;YAAE,IAAI,CAAC,0EAA0E,CAAC,CAAC;QACzG,MAAM,GAAG,QAAQ,CAAC,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC,CAAC;IAC/E,CAAC;SAAM,CAAC;QACN,MAAM,GAAG,QAAQ,CAAC,UAAU,CAAC;IAC/B,CAAC;IAED,eAAe,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC/B,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,MAAM,IAAI,CAAC,CAAC;IACvE,CAAC;AACH,CAAC;AAED,SAAS,MAAM;IACb,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC;IACtB,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,EAAE,GAAG,IAAI,CAAC;IAEd,KAAK,CAAC,IAAI,CAAC,aAAa,WAAW,EAAE,EAAE,CAAC,CAAC;IACzC,KAAK,CAAC,IAAI,CAAC,aAAa,IAAI,GAAG,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,sBAAsB,EAAE,CAAC,CAAC;IAEpF,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;QACxB,MAAM,QAAQ,GAAI,EAAE,CAAC,OAAO,CAAC,iCAAiC,CAAC,CAAC,GAAG,EAAoB,CAAC,CAAC,CAAC;QAC1F,MAAM,QAAQ,GAAI,EAAE,CAAC,OAAO,CAAC,iCAAiC,CAAC,CAAC,GAAG,EAAoB,CAAC,CAAC,CAAC;QAC1F,MAAM,KAAK,GACT,EAAE,CAAC,OAAO,CAAC,iEAAiE,CAAC,CAAC,GAAG,EAClF,CAAC,CAAC,CAAC;QACJ,KAAK,CAAC,IAAI,CAAC,aAAa,QAAQ,EAAE,CAAC,CAAC;QACpC,KAAK,CAAC,IAAI,CAAC,aAAa,QAAQ,EAAE,CAAC,CAAC;QACpC,KAAK,CAAC,IAAI,CAAC,aAAa,KAAK,EAAE,CAAC,CAAC;QACjC,KAAK,CAAC,IAAI,CAAC,aAAa,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,cAAc,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;QAC/E,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,EAAE,GAAG,KAAK,CAAC;QACX,KAAK,CAAC,IAAI,CAAC,mBAAmB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACpF,CAAC;IAED,MAAM,QAAQ,GAAG,UAAU,EAAE,CAAC;IAC9B,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9D,KAAK,CAAC,IAAI,CAAC,aAAa,QAAQ,CAAC,MAAM,CAAC,IAAI,GAAG,QAAQ,EAAE,CAAC,CAAC;IAC3D,KAAK,CAAC,IAAI,CACR,aAAa,QAAQ,CAAC,MAAM,CAAC,KAAK,GAChC,QAAQ,CAAC,MAAM,CAAC,KAAK,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,MAAM,CAAC,WAAW,IAAI,cAAc,GAAG,CAAC,CAAC,CAAC,EAC9F,GAAG,QAAQ,EAAE,CACd,CAAC;IACF,IAAI,QAAQ,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClC,KAAK,CAAC,IAAI,CAAC,uBAAuB,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACrE,CAAC;IAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC9C,IAAI,CAAC,EAAE;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAC3B,CAAC;AAED,SAAS,IAAI;IACX,MAAM,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAEjD,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,cAAc;YACjB,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC;QAC3B,KAAK,QAAQ;YACX,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC;QAC7B,KAAK,QAAQ;YACX,OAAO,MAAM,EAAE,CAAC;QAClB,KAAK,SAAS;YACZ,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,MAAM,EAAE,IAAI,CAAC,CAAC;YACtC,OAAO;QACT,KAAK,SAAS,CAAC;QACf,KAAK,IAAI,CAAC;QACV,KAAK,QAAQ;YACX,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAC5B,OAAO;QACT;YACE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAC5B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC"}
package/dist/config.js CHANGED
@@ -4,12 +4,15 @@ import path from 'node:path';
4
4
  import { globalConfigPath } from './paths.js';
5
5
  export const DEFAULT_CONFIG = {
6
6
  mode: 'ambient',
7
+ focus: 'project',
8
+ focus_topic: null,
7
9
  pass_threshold: 0.7,
8
10
  max_questions_per_task: 4,
9
11
  min_minutes_between_quizzes: 20,
10
12
  domains_enabled: ['*'],
11
13
  quiet: false,
12
14
  max_new_concepts_per_session: 8,
15
+ max_stop_blocks_per_session: 3,
13
16
  };
14
17
  export const REPO_CONFIG_FILE = '.eklavya.json';
15
18
  function realPath(p) {
@@ -65,6 +68,14 @@ function coerce(raw, base) {
65
68
  const out = { ...base };
66
69
  if (raw.mode === 'ambient' || raw.mode === 'enforced' || raw.mode === 'off')
67
70
  out.mode = raw.mode;
71
+ if (raw.focus === 'project' || raw.focus === 'concept' || raw.focus === 'learn')
72
+ out.focus = raw.focus;
73
+ if (typeof raw.focus_topic === 'string' && raw.focus_topic.trim()) {
74
+ out.focus_topic = raw.focus_topic.trim();
75
+ }
76
+ else if (raw.focus_topic === null) {
77
+ out.focus_topic = null;
78
+ }
68
79
  if (typeof raw.pass_threshold === 'number' && raw.pass_threshold >= 0 && raw.pass_threshold <= 1) {
69
80
  out.pass_threshold = raw.pass_threshold;
70
81
  }
@@ -83,6 +94,10 @@ function coerce(raw, base) {
83
94
  raw.max_new_concepts_per_session >= 0) {
84
95
  out.max_new_concepts_per_session = Math.floor(raw.max_new_concepts_per_session);
85
96
  }
97
+ if (typeof raw.max_stop_blocks_per_session === 'number' &&
98
+ raw.max_stop_blocks_per_session >= 0) {
99
+ out.max_stop_blocks_per_session = Math.floor(raw.max_stop_blocks_per_session);
100
+ }
86
101
  return out;
87
102
  }
88
103
  /** Global config merged with the repo's, repo winning (PRD §11). */
@@ -92,12 +107,14 @@ export function loadConfig(cwd = process.cwd()) {
92
107
  const globalRaw = readJson(globalPath) ?? {};
93
108
  const repoRaw = repoPath ? (readJson(repoPath) ?? {}) : {};
94
109
  const raw = { ...globalRaw, ...repoRaw };
110
+ const overrides = Object.keys(repoRaw).filter((key) => key in globalRaw && JSON.stringify(globalRaw[key]) !== JSON.stringify(repoRaw[key]));
95
111
  return {
96
112
  config: coerce(raw, DEFAULT_CONFIG),
97
113
  raw,
98
114
  globalPath,
99
115
  repoPath,
100
116
  repoRoot,
117
+ overrides,
101
118
  };
102
119
  }
103
120
  /** Writes via temp file + rename: the git hook may be reading mid-write. */
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ -- Grade 0 was carrying two opposite meanings: "I don't know, teach me" and
2
+ -- "not now, leave me alone". The tutor skill collapsed them into one row of its
3
+ -- grading table, so a learner asking to be taught was dropped exactly like one
4
+ -- asking to be left alone (observed: four blanks in one session, nothing
5
+ -- taught). SM-2 wants both at 0 -- neither is a recall success -- so the
6
+ -- distinction cannot live in `grade`.
7
+ --
8
+ -- It has to be recorded rather than inferred, because the useful question comes
9
+ -- a day later: "did I already teach this, or has it never been explained?"
10
+ -- `feedback` text cannot answer that reliably.
11
+ --
12
+ -- NULL means "recorded before this column existed"; readers must treat it as
13
+ -- unknown, not as any particular outcome.
14
+ ALTER TABLE attempts ADD COLUMN outcome TEXT
15
+ CHECK (outcome IN ('answered','dont_know','declined'));
@@ -0,0 +1,24 @@
1
+ -- `required` is derived from the concepts the session's *work* touched, but
2
+ -- `passed` was satisfiable by any concept in `session_concepts` -- and
3
+ -- `record_attempt` inserts a row there for anything it is asked about, so that
4
+ -- review debt is not free. The two ends never matched: answering concepts the
5
+ -- planner pulled in from elsewhere raised `passedCount` while leaving `required`
6
+ -- frozen, so a gate whose bar was set by today's diff could be cleared without
7
+ -- answering a single question about today's diff.
8
+ --
9
+ -- Focus modes make that the main path rather than a trickle. `concept` focus
10
+ -- widens selection to domain siblings and prerequisites on purpose, and `learn`
11
+ -- focus selects from a topic that may have nothing to do with the session at
12
+ -- all.
13
+ --
14
+ -- So the origin has to be recorded. 'work' is a concept the implementer logged
15
+ -- as part of the task; 'review' is one the tutor pulled in. Both still record
16
+ -- attempts, update mastery and count as `answered` -- review debt stays real --
17
+ -- but only 'work' can satisfy the bar the work itself set.
18
+ --
19
+ -- NULL means "recorded before this column existed". Those rows are read as
20
+ -- 'work', which is what they were in every case that mattered: before focus
21
+ -- modes there was nothing to widen with except same-domain review, and treating
22
+ -- them as 'review' would retroactively unpass gates that have already passed.
23
+ ALTER TABLE session_concepts ADD COLUMN origin TEXT
24
+ CHECK (origin IN ('work','review'));
package/dist/server.js CHANGED
@@ -1,9 +1,22 @@
1
1
  #!/usr/bin/env node
2
+ import fs from 'node:fs';
2
3
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
4
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
5
  import { openDb } from './db.js';
5
6
  import { dbPath } from './paths.js';
6
7
  import { registerTools } from './tools/index.js';
8
+ // Read the version rather than hardcoding it: a literal here silently reports a
9
+ // stale version forever, since nothing about publishing touches this file.
10
+ // `../package.json` resolves the same from src/ under tsx and from dist/ built.
11
+ function serverVersion() {
12
+ try {
13
+ const pkg = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
14
+ return typeof pkg.version === 'string' ? pkg.version : '0.0.0';
15
+ }
16
+ catch {
17
+ return '0.0.0';
18
+ }
19
+ }
7
20
  // stdout is the MCP transport. Anything diagnostic goes to stderr or it corrupts
8
21
  // the protocol stream.
9
22
  function log(message) {
@@ -12,7 +25,7 @@ function log(message) {
12
25
  async function main() {
13
26
  const db = openDb();
14
27
  log(`db ready at ${dbPath()}`);
15
- const server = new McpServer({ name: 'eklavya', version: '0.1.0' }, {
28
+ const server = new McpServer({ name: 'eklavya', version: serverVersion() }, {
16
29
  capabilities: { tools: {} },
17
30
  instructions: 'Eklavya tracks what this developer has actually learned. Log the concepts your work touches, ' +
18
31
  'and quiz from the learner profile rather than from scratch — never ask about a concept already mastered.',
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,iFAAiF;AACjF,uBAAuB;AACvB,SAAS,GAAG,CAAC,OAAe;IAC1B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iBAAiB,OAAO,IAAI,CAAC,CAAC;AACrD,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;IACpB,GAAG,CAAC,eAAe,MAAM,EAAE,EAAE,CAAC,CAAC;IAE/B,MAAM,MAAM,GAAG,IAAI,SAAS,CAC1B,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,EACrC;QACE,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE;QAC3B,YAAY,EACV,+FAA+F;YAC/F,0GAA0G;KAC7G,CACF,CAAC;IAEF,aAAa,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAE1B,MAAM,QAAQ,GAAG,GAAG,EAAE;QACpB,IAAI,CAAC;YACH,EAAE,CAAC,KAAK,EAAE,CAAC;QACb,CAAC;QAAC,MAAM,CAAC;YACP,qEAAqE;QACvE,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC;IACF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IAEhC,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC;IACjD,GAAG,CAAC,6BAA6B,CAAC,CAAC;AACrC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IAC5B,GAAG,CAAC,UAAU,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC/E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,gFAAgF;AAChF,2EAA2E;AAC3E,gFAAgF;AAChF,SAAS,aAAa;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,GAAG,CAAC,iBAAiB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QAC7F,OAAO,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;IACjE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC;IACjB,CAAC;AACH,CAAC;AAED,iFAAiF;AACjF,uBAAuB;AACvB,SAAS,GAAG,CAAC,OAAe;IAC1B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iBAAiB,OAAO,IAAI,CAAC,CAAC;AACrD,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;IACpB,GAAG,CAAC,eAAe,MAAM,EAAE,EAAE,CAAC,CAAC;IAE/B,MAAM,MAAM,GAAG,IAAI,SAAS,CAC1B,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,aAAa,EAAE,EAAE,EAC7C;QACE,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE;QAC3B,YAAY,EACV,+FAA+F;YAC/F,0GAA0G;KAC7G,CACF,CAAC;IAEF,aAAa,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAE1B,MAAM,QAAQ,GAAG,GAAG,EAAE;QACpB,IAAI,CAAC;YACH,EAAE,CAAC,KAAK,EAAE,CAAC;QACb,CAAC;QAAC,MAAM,CAAC;YACP,qEAAqE;QACvE,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC;IACF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IAEhC,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC;IACjD,GAAG,CAAC,6BAA6B,CAAC,CAAC;AACrC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IAC5B,GAAG,CAAC,UAAU,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC/E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}