eklavya 1.7.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 +69 -0
- package/dist/ask.js +58 -0
- package/dist/ask.js.map +1 -0
- package/dist/assets/dashboard.html +407 -0
- package/dist/assets/tokens.css +135 -0
- package/dist/assets/tutor-skill.md +345 -0
- package/dist/cli.js +223 -0
- package/dist/cli.js.map +1 -0
- package/dist/concurrency.js +41 -0
- package/dist/concurrency.js.map +1 -0
- package/dist/config.js +158 -0
- package/dist/config.js.map +1 -0
- package/dist/dashboard.js +214 -0
- package/dist/dashboard.js.map +1 -0
- package/dist/db.js +26 -0
- package/dist/db.js.map +1 -0
- package/dist/hooks/checkpoint-quiz.js +152 -0
- package/dist/hooks/checkpoint-quiz.js.map +1 -0
- package/dist/hooks/lib.js +177 -0
- package/dist/hooks/lib.js.map +1 -0
- package/dist/hooks/pre-tool-gate.js +53 -0
- package/dist/hooks/pre-tool-gate.js.map +1 -0
- package/dist/hooks/session-start.js +128 -0
- package/dist/hooks/session-start.js.map +1 -0
- package/dist/hooks/stop-quiz-check.js +147 -0
- package/dist/hooks/stop-quiz-check.js.map +1 -0
- package/dist/install.js +313 -0
- package/dist/install.js.map +1 -0
- package/dist/mcq.js +34 -0
- package/dist/mcq.js.map +1 -0
- package/dist/migrate.js +45 -0
- package/dist/migrate.js.map +1 -0
- package/dist/migrations/001_init.sql +71 -0
- package/dist/migrations/002_stop_markers.sql +14 -0
- package/dist/migrations/003_gate_repo.sql +5 -0
- package/dist/migrations/004_attempt_outcome.sql +15 -0
- package/dist/migrations/005_session_concept_origin.sql +24 -0
- package/dist/migrations/006_attempt_format.sql +31 -0
- package/dist/migrations/007_checkpoints.sql +23 -0
- package/dist/migrations/008_difficulty_levels.sql +57 -0
- package/dist/paths.js +25 -0
- package/dist/paths.js.map +1 -0
- package/dist/plugin/.claude-plugin/marketplace.json +14 -0
- package/dist/plugin/.claude-plugin/plugin.json +17 -0
- package/dist/plugin/.mcp.json +11 -0
- package/dist/plugin/agents/tutor.md +63 -0
- package/dist/plugin/cli/eklavya-gate +67 -0
- package/dist/plugin/hooks/hooks.json +57 -0
- package/dist/plugin/hooks/run.mjs +169 -0
- package/dist/plugin/scripts/install-git-hook.sh +81 -0
- package/dist/plugin/skills/gate/SKILL.md +17 -0
- package/dist/plugin/skills/learn/SKILL.md +28 -0
- package/dist/plugin/skills/level/SKILL.md +53 -0
- package/dist/plugin/skills/mode/SKILL.md +56 -0
- package/dist/plugin/skills/progress/SKILL.md +72 -0
- package/dist/plugin/skills/quiz/SKILL.md +31 -0
- package/dist/plugin/skills/setup/SKILL.md +49 -0
- package/dist/plugin/skills/tutor/SKILL.md +345 -0
- package/dist/seed/git.json +49 -0
- package/dist/seed/node-backend.json +43 -0
- package/dist/seed/react.json +46 -0
- package/dist/seed/web-auth.json +78 -0
- package/dist/seed.js +112 -0
- package/dist/seed.js.map +1 -0
- package/dist/server.js +52 -0
- package/dist/server.js.map +1 -0
- package/dist/session.js +24 -0
- package/dist/session.js.map +1 -0
- package/dist/slug.js +75 -0
- package/dist/slug.js.map +1 -0
- package/dist/srs.js +206 -0
- package/dist/srs.js.map +1 -0
- package/dist/store.js +450 -0
- package/dist/store.js.map +1 -0
- package/dist/tools/config_tools.js +119 -0
- package/dist/tools/config_tools.js.map +1 -0
- package/dist/tools/get_concept_graph.js +104 -0
- package/dist/tools/get_concept_graph.js.map +1 -0
- package/dist/tools/get_gate_status.js +20 -0
- package/dist/tools/get_gate_status.js.map +1 -0
- package/dist/tools/get_learner_profile.js +142 -0
- package/dist/tools/get_learner_profile.js.map +1 -0
- package/dist/tools/get_session_quiz_plan.js +393 -0
- package/dist/tools/get_session_quiz_plan.js.map +1 -0
- package/dist/tools/index.js +45 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/log_session_concepts.js +103 -0
- package/dist/tools/log_session_concepts.js.map +1 -0
- package/dist/tools/record_attempt.js +158 -0
- package/dist/tools/record_attempt.js.map +1 -0
- package/dist/tools/types.js +4 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/tools/upsert_concepts.js +89 -0
- package/dist/tools/upsert_concepts.js.map +1 -0
- package/package.json +66 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* The `eklavya` CLI: the parts of Eklavya that make sense outside a Claude Code
|
|
4
|
+
* session. The commit gate has its own POSIX script (`cli/eklavya-gate`) because
|
|
5
|
+
* a git hook must not pay Node's startup cost.
|
|
6
|
+
*/
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import { openDb } from './db.js';
|
|
11
|
+
import { dbPath, eklavyaHome } from './paths.js';
|
|
12
|
+
import { loadConfig, writeConfigFile, REPO_CONFIG_FILE, DEFAULT_CONFIG } from './config.js';
|
|
13
|
+
import { levelStanding } from './store.js';
|
|
14
|
+
import { startDashboard } from './dashboard.js';
|
|
15
|
+
import { install, uninstall } from './install.js';
|
|
16
|
+
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const USAGE = `eklavya — local learning state for agent-assisted development
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
eklavya serve Run the MCP server on stdio (what Claude Code starts)
|
|
21
|
+
eklavya install Install Eklavya into Claude Code, runtime included
|
|
22
|
+
eklavya uninstall [--purge] Remove it (--purge also deletes your learning history)
|
|
23
|
+
eklavya export-rules [--out <file>] Write the tutor pedagogy as a Cursor rules file
|
|
24
|
+
eklavya config get Show the effective configuration
|
|
25
|
+
eklavya config set <key> <value> Change a setting (add --repo to scope it to this repo)
|
|
26
|
+
e.g. mode ambient|enforced|off, focus project|concept|learn,
|
|
27
|
+
cadence interleaved|end,
|
|
28
|
+
difficulty auto|easy|medium|hard
|
|
29
|
+
add --topic <topic> when setting focus to "learn"
|
|
30
|
+
eklavya dashboard [--port <n>] Serve the learning dashboard on localhost
|
|
31
|
+
eklavya doctor Check that everything is wired up
|
|
32
|
+
eklavya db-path Print the database location
|
|
33
|
+
|
|
34
|
+
Config keys: mode, focus, focus_topic, cadence, difficulty, level_up_after,
|
|
35
|
+
level_up_accuracy, pass_threshold, max_questions_per_task,
|
|
36
|
+
min_minutes_between_quizzes, min_minutes_between_checkpoints,
|
|
37
|
+
max_new_concepts_per_session, max_stop_blocks_per_session, quiet
|
|
38
|
+
`;
|
|
39
|
+
function fail(message) {
|
|
40
|
+
process.stderr.write(`${message}\n`);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
function exportRules(args) {
|
|
44
|
+
const skillPath = path.join(moduleDir, 'assets', 'tutor-skill.md');
|
|
45
|
+
if (!fs.existsSync(skillPath)) {
|
|
46
|
+
fail('The bundled tutor skill is missing. Run `npm run build` in the mcp/ directory.');
|
|
47
|
+
}
|
|
48
|
+
const skill = fs.readFileSync(skillPath, 'utf8').replace(/^---\n[\s\S]*?\n---\n/, '');
|
|
49
|
+
const rules = `---
|
|
50
|
+
description: Eklavya tutor — teach the concepts behind the code being written
|
|
51
|
+
alwaysApply: true
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
<!-- Generated by \`eklavya export-rules\`. Do not edit by hand; edit the plugin's
|
|
55
|
+
skills/tutor/SKILL.md and regenerate, so Claude Code and Cursor never drift. -->
|
|
56
|
+
|
|
57
|
+
The Eklavya MCP server is available in this editor. Its tools are the source of
|
|
58
|
+
truth for what this developer already knows.
|
|
59
|
+
|
|
60
|
+
${skill.trim()}
|
|
61
|
+
`;
|
|
62
|
+
const outIndex = args.indexOf('--out');
|
|
63
|
+
if (outIndex >= 0) {
|
|
64
|
+
const out = args[outIndex + 1];
|
|
65
|
+
if (!out)
|
|
66
|
+
fail('--out needs a file path.');
|
|
67
|
+
fs.mkdirSync(path.dirname(path.resolve(out)), { recursive: true });
|
|
68
|
+
fs.writeFileSync(out, rules, 'utf8');
|
|
69
|
+
process.stdout.write(`Wrote ${out}\n`);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
process.stdout.write(rules);
|
|
73
|
+
}
|
|
74
|
+
function configCommand(args) {
|
|
75
|
+
const [action, key, value] = args;
|
|
76
|
+
const scopeRepo = args.includes('--repo');
|
|
77
|
+
const resolved = loadConfig();
|
|
78
|
+
if (!action || action === 'get') {
|
|
79
|
+
process.stdout.write(`${JSON.stringify(resolved.config, null, 2)}\n`);
|
|
80
|
+
process.stdout.write(`\nglobal: ${resolved.globalPath}\n`);
|
|
81
|
+
process.stdout.write(`repo: ${resolved.repoPath ?? '(none)'}\n`);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (action !== 'set')
|
|
85
|
+
fail(`Unknown config action "${action}".`);
|
|
86
|
+
if (!key || value === undefined)
|
|
87
|
+
fail('Usage: eklavya config set <key> <value>');
|
|
88
|
+
if (!(key in DEFAULT_CONFIG)) {
|
|
89
|
+
fail(`Unknown setting "${key}". Known: ${Object.keys(DEFAULT_CONFIG).join(', ')}`);
|
|
90
|
+
}
|
|
91
|
+
// `focus learn` is useless without a topic, so let one call say both rather
|
|
92
|
+
// than leaving a state that the planner will only refuse later.
|
|
93
|
+
const topicIndex = args.indexOf('--topic');
|
|
94
|
+
const topic = topicIndex === -1 ? undefined : args[topicIndex + 1];
|
|
95
|
+
if (topicIndex !== -1 && !topic)
|
|
96
|
+
fail('--topic needs a value.');
|
|
97
|
+
if (topic !== undefined && key !== 'focus' && key !== 'focus_topic') {
|
|
98
|
+
fail('--topic only applies when setting focus.');
|
|
99
|
+
}
|
|
100
|
+
if (key === 'focus' && value === 'learn' && topic === undefined) {
|
|
101
|
+
fail('focus "learn" needs a topic: eklavya config set focus learn --topic <topic>');
|
|
102
|
+
}
|
|
103
|
+
// A topic is always a string. Number-parsing it would turn a topic like "html5"
|
|
104
|
+
// -- or worse, "2" -- into something `coerce` then silently drops.
|
|
105
|
+
const isTopicKey = key === 'focus_topic';
|
|
106
|
+
let parsed = value;
|
|
107
|
+
if (isTopicKey)
|
|
108
|
+
parsed = value;
|
|
109
|
+
else if (value === 'true' || value === 'false')
|
|
110
|
+
parsed = value === 'true';
|
|
111
|
+
else if (value !== '' && !Number.isNaN(Number(value)))
|
|
112
|
+
parsed = Number(value);
|
|
113
|
+
const patch = { [key]: parsed };
|
|
114
|
+
if (topic !== undefined && key === 'focus')
|
|
115
|
+
patch.focus_topic = topic;
|
|
116
|
+
let target;
|
|
117
|
+
if (scopeRepo) {
|
|
118
|
+
if (!resolved.repoRoot)
|
|
119
|
+
fail('Not inside a git repository, so there is nowhere to write .eklavya.json.');
|
|
120
|
+
target = resolved.repoPath ?? path.join(resolved.repoRoot, REPO_CONFIG_FILE);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
target = resolved.globalPath;
|
|
124
|
+
}
|
|
125
|
+
writeConfigFile(target, patch);
|
|
126
|
+
for (const [k, v] of Object.entries(patch)) {
|
|
127
|
+
process.stdout.write(`${k} = ${JSON.stringify(v)} -> ${target}\n`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function doctor() {
|
|
131
|
+
const file = dbPath();
|
|
132
|
+
const resolved = loadConfig();
|
|
133
|
+
const lines = [];
|
|
134
|
+
let ok = true;
|
|
135
|
+
lines.push(`home: ${eklavyaHome()}`);
|
|
136
|
+
lines.push(`database: ${file}${fs.existsSync(file) ? '' : ' (not created yet)'}`);
|
|
137
|
+
try {
|
|
138
|
+
const db = openDb(file);
|
|
139
|
+
const concepts = db.prepare('SELECT count(*) n FROM concepts').get().n;
|
|
140
|
+
const attempts = db.prepare('SELECT count(*) n FROM attempts').get().n;
|
|
141
|
+
const known = db.prepare('SELECT count(*) n FROM mastery WHERE score >= 0.7 AND reps >= 2').get().n;
|
|
142
|
+
lines.push(`concepts: ${concepts}`);
|
|
143
|
+
lines.push(`attempts: ${attempts}`);
|
|
144
|
+
lines.push(`mastered: ${known}`);
|
|
145
|
+
lines.push(`journal: ${String(db.pragma('journal_mode', { simple: true }))}`);
|
|
146
|
+
// The project's band, and the runway left in it. Read here because `doctor`
|
|
147
|
+
// is where someone looks when the questions feel wrong for them.
|
|
148
|
+
const standing = levelStanding(db, resolved.config, resolved.repoRoot);
|
|
149
|
+
lines.push(`level: ${standing.level}${standing.pinned
|
|
150
|
+
? ' (pinned by config — no progression)'
|
|
151
|
+
: ` (${standing.counts.passed}/${standing.needed.answers} passing answers in ${standing.repo})`}`);
|
|
152
|
+
db.close();
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
ok = false;
|
|
156
|
+
lines.push(`database error: ${err instanceof Error ? err.message : String(err)}`);
|
|
157
|
+
}
|
|
158
|
+
const fromRepo = resolved.repoPath ? ' (from this repo)' : '';
|
|
159
|
+
lines.push(`mode: ${resolved.config.mode}${fromRepo}`);
|
|
160
|
+
lines.push(`focus: ${resolved.config.focus}${resolved.config.focus === 'learn' ? ` (${resolved.config.focus_topic ?? 'no topic set'})` : ''}${fromRepo}`);
|
|
161
|
+
lines.push(`cadence: ${resolved.config.cadence}${resolved.config.cadence === 'interleaved'
|
|
162
|
+
? ` (one question mid-task, min ${resolved.config.min_minutes_between_checkpoints}m apart)`
|
|
163
|
+
: ' (all questions at the end of the task)'}${fromRepo}`);
|
|
164
|
+
if (resolved.overrides.length > 0) {
|
|
165
|
+
lines.push(`overridden by repo: ${resolved.overrides.join(', ')}`);
|
|
166
|
+
}
|
|
167
|
+
process.stdout.write(`${lines.join('\n')}\n`);
|
|
168
|
+
if (!ok)
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
function dashboardCommand(argv) {
|
|
172
|
+
const i = argv.indexOf('--port');
|
|
173
|
+
const port = i === -1 ? undefined : Number(argv[i + 1]);
|
|
174
|
+
if (port !== undefined && !Number.isInteger(port)) {
|
|
175
|
+
process.stderr.write('eklavya dashboard: --port needs a number\n');
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
startDashboard(openDb(), { port }).then(({ url }) => {
|
|
179
|
+
process.stdout.write(`Eklavya dashboard on ${url}\nReading ${dbPath()} — press Ctrl+C to stop.\n`);
|
|
180
|
+
}, (err) => {
|
|
181
|
+
process.stderr.write(`eklavya dashboard: ${err.message}\n`);
|
|
182
|
+
process.exit(1);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
function main() {
|
|
186
|
+
const [command, ...rest] = process.argv.slice(2);
|
|
187
|
+
switch (command) {
|
|
188
|
+
case 'serve':
|
|
189
|
+
// Importing the server runs it: it owns stdout from here on, which is why
|
|
190
|
+
// nothing above may print. It never returns -- the process ends when the
|
|
191
|
+
// stdio transport closes.
|
|
192
|
+
void import('./server.js').catch((err) => {
|
|
193
|
+
process.stderr.write(`eklavya serve: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
194
|
+
process.exit(1);
|
|
195
|
+
});
|
|
196
|
+
return;
|
|
197
|
+
case 'install':
|
|
198
|
+
return install(rest);
|
|
199
|
+
case 'uninstall':
|
|
200
|
+
return uninstall(rest);
|
|
201
|
+
case 'export-rules':
|
|
202
|
+
return exportRules(rest);
|
|
203
|
+
case 'config':
|
|
204
|
+
return configCommand(rest);
|
|
205
|
+
case 'dashboard':
|
|
206
|
+
return dashboardCommand(rest);
|
|
207
|
+
case 'doctor':
|
|
208
|
+
return doctor();
|
|
209
|
+
case 'db-path':
|
|
210
|
+
process.stdout.write(`${dbPath()}\n`);
|
|
211
|
+
return;
|
|
212
|
+
case undefined:
|
|
213
|
+
case '-h':
|
|
214
|
+
case '--help':
|
|
215
|
+
process.stdout.write(USAGE);
|
|
216
|
+
return;
|
|
217
|
+
default:
|
|
218
|
+
process.stderr.write(USAGE);
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
main();
|
|
223
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Several Claude Code sessions and the git pre-commit hook can hit the same
|
|
3
|
+
* SQLite file at once (PRD §8 "Concurrency"). WAL plus `busy_timeout` handles
|
|
4
|
+
* almost all of it; what remains is the case where two writers upgrade to a
|
|
5
|
+
* write transaction at the same moment and SQLite returns SQLITE_BUSY
|
|
6
|
+
* immediately rather than waiting.
|
|
7
|
+
*
|
|
8
|
+
* A busy transaction has already been rolled back, so retrying it is safe —
|
|
9
|
+
* that is what makes this wrapper correct for writes and not just for reads.
|
|
10
|
+
*/
|
|
11
|
+
const BUSY_CODES = new Set(['SQLITE_BUSY', 'SQLITE_BUSY_SNAPSHOT', 'SQLITE_LOCKED']);
|
|
12
|
+
export const MAX_BUSY_ATTEMPTS = 5;
|
|
13
|
+
function isBusy(err) {
|
|
14
|
+
if (typeof err !== 'object' || err === null)
|
|
15
|
+
return false;
|
|
16
|
+
const code = err.code;
|
|
17
|
+
return typeof code === 'string' && BUSY_CODES.has(code);
|
|
18
|
+
}
|
|
19
|
+
/** Blocking backoff — better-sqlite3 is synchronous, so there is nothing to await. */
|
|
20
|
+
function sleepSync(ms) {
|
|
21
|
+
const until = Date.now() + ms;
|
|
22
|
+
while (Date.now() < until) {
|
|
23
|
+
// busy-wait; only ever a few milliseconds, and only under real contention
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function retryOnBusy(fn, attempts = MAX_BUSY_ATTEMPTS) {
|
|
27
|
+
let lastError;
|
|
28
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
29
|
+
try {
|
|
30
|
+
return fn();
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
if (!isBusy(err))
|
|
34
|
+
throw err;
|
|
35
|
+
lastError = err;
|
|
36
|
+
sleepSync(10 * (attempt + 1));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
throw lastError;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=concurrency.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"concurrency.js","sourceRoot":"","sources":["../src/concurrency.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,CAAC,aAAa,EAAE,sBAAsB,EAAE,eAAe,CAAC,CAAC,CAAC;AAErF,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC;AAEnC,SAAS,MAAM,CAAC,GAAY;IAC1B,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC1D,MAAM,IAAI,GAAI,GAAyB,CAAC,IAAI,CAAC;IAC7C,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AAC1D,CAAC;AAED,sFAAsF;AACtF,SAAS,SAAS,CAAC,EAAU;IAC3B,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC;IAC9B,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC;QAC1B,0EAA0E;IAC5E,CAAC;AACH,CAAC;AAED,MAAM,UAAU,WAAW,CAAI,EAAW,EAAE,QAAQ,GAAG,iBAAiB;IACtE,IAAI,SAAkB,CAAC;IAEvB,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,QAAQ,EAAE,OAAO,IAAI,CAAC,EAAE,CAAC;QACvD,IAAI,CAAC;YACH,OAAO,EAAE,EAAE,CAAC;QACd,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC;gBAAE,MAAM,GAAG,CAAC;YAC5B,SAAS,GAAG,GAAG,CAAC;YAChB,SAAS,CAAC,EAAE,GAAG,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC;QAChC,CAAC;IACH,CAAC;IAED,MAAM,SAAS,CAAC;AAClB,CAAC"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { globalConfigPath } from './paths.js';
|
|
5
|
+
export const DEFAULT_CONFIG = {
|
|
6
|
+
mode: 'ambient',
|
|
7
|
+
focus: 'concept',
|
|
8
|
+
focus_topic: null,
|
|
9
|
+
cadence: 'interleaved',
|
|
10
|
+
difficulty: 'auto',
|
|
11
|
+
level_up_after: 100,
|
|
12
|
+
level_up_accuracy: 0.7,
|
|
13
|
+
pass_threshold: 0.7,
|
|
14
|
+
max_questions_per_task: 4,
|
|
15
|
+
min_minutes_between_quizzes: 20,
|
|
16
|
+
min_minutes_between_checkpoints: 4,
|
|
17
|
+
domains_enabled: ['*'],
|
|
18
|
+
quiet: false,
|
|
19
|
+
max_new_concepts_per_session: 8,
|
|
20
|
+
max_stop_blocks_per_session: 3,
|
|
21
|
+
};
|
|
22
|
+
export const REPO_CONFIG_FILE = '.eklavya.json';
|
|
23
|
+
function realPath(p) {
|
|
24
|
+
try {
|
|
25
|
+
return fs.realpathSync(p);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return p;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function readJson(file) {
|
|
32
|
+
try {
|
|
33
|
+
const text = fs.readFileSync(file, 'utf8');
|
|
34
|
+
const parsed = JSON.parse(text);
|
|
35
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
36
|
+
? parsed
|
|
37
|
+
: null;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// A missing or malformed config must never take a session down; defaults win.
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Walks up from `cwd` looking for a repo-level config, stopping at the git root
|
|
46
|
+
* or the filesystem root. Returns the directory holding it, plus the git root if
|
|
47
|
+
* one was passed on the way (Phase 3 stamps that onto gate rows).
|
|
48
|
+
*/
|
|
49
|
+
export function findRepoConfig(cwd = process.cwd()) {
|
|
50
|
+
// Resolve symlinks: `git rev-parse --show-toplevel` reports the real path, and
|
|
51
|
+
// the git pre-commit hook matches gate rows on it. On macOS /tmp is a symlink
|
|
52
|
+
// to /private/tmp, so without this the two sides silently never match.
|
|
53
|
+
let dir = realPath(path.resolve(cwd));
|
|
54
|
+
let repoPath = null;
|
|
55
|
+
let repoRoot = null;
|
|
56
|
+
for (;;) {
|
|
57
|
+
if (!repoPath && fs.existsSync(path.join(dir, REPO_CONFIG_FILE))) {
|
|
58
|
+
repoPath = path.join(dir, REPO_CONFIG_FILE);
|
|
59
|
+
}
|
|
60
|
+
if (!repoRoot && fs.existsSync(path.join(dir, '.git'))) {
|
|
61
|
+
repoRoot = dir;
|
|
62
|
+
// The git root is the boundary: a config above it belongs to another project.
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
const parent = path.dirname(dir);
|
|
66
|
+
if (parent === dir || dir === os.homedir())
|
|
67
|
+
break;
|
|
68
|
+
dir = parent;
|
|
69
|
+
}
|
|
70
|
+
return { repoPath, repoRoot };
|
|
71
|
+
}
|
|
72
|
+
function coerce(raw, base) {
|
|
73
|
+
const out = { ...base };
|
|
74
|
+
if (raw.mode === 'ambient' || raw.mode === 'enforced' || raw.mode === 'off')
|
|
75
|
+
out.mode = raw.mode;
|
|
76
|
+
if (raw.focus === 'project' || raw.focus === 'concept' || raw.focus === 'learn')
|
|
77
|
+
out.focus = raw.focus;
|
|
78
|
+
if (raw.cadence === 'interleaved' || raw.cadence === 'end')
|
|
79
|
+
out.cadence = raw.cadence;
|
|
80
|
+
if (raw.difficulty === 'auto' ||
|
|
81
|
+
raw.difficulty === 'easy' ||
|
|
82
|
+
raw.difficulty === 'medium' ||
|
|
83
|
+
raw.difficulty === 'hard') {
|
|
84
|
+
out.difficulty = raw.difficulty;
|
|
85
|
+
}
|
|
86
|
+
if (typeof raw.level_up_after === 'number' && raw.level_up_after > 0) {
|
|
87
|
+
out.level_up_after = Math.floor(raw.level_up_after);
|
|
88
|
+
}
|
|
89
|
+
if (typeof raw.level_up_accuracy === 'number' &&
|
|
90
|
+
raw.level_up_accuracy >= 0 &&
|
|
91
|
+
raw.level_up_accuracy <= 1) {
|
|
92
|
+
out.level_up_accuracy = raw.level_up_accuracy;
|
|
93
|
+
}
|
|
94
|
+
if (typeof raw.focus_topic === 'string' && raw.focus_topic.trim()) {
|
|
95
|
+
out.focus_topic = raw.focus_topic.trim();
|
|
96
|
+
}
|
|
97
|
+
else if (raw.focus_topic === null) {
|
|
98
|
+
out.focus_topic = null;
|
|
99
|
+
}
|
|
100
|
+
if (typeof raw.pass_threshold === 'number' && raw.pass_threshold >= 0 && raw.pass_threshold <= 1) {
|
|
101
|
+
out.pass_threshold = raw.pass_threshold;
|
|
102
|
+
}
|
|
103
|
+
if (typeof raw.max_questions_per_task === 'number' && raw.max_questions_per_task > 0) {
|
|
104
|
+
out.max_questions_per_task = Math.floor(raw.max_questions_per_task);
|
|
105
|
+
}
|
|
106
|
+
if (typeof raw.min_minutes_between_quizzes === 'number' && raw.min_minutes_between_quizzes >= 0) {
|
|
107
|
+
out.min_minutes_between_quizzes = Math.floor(raw.min_minutes_between_quizzes);
|
|
108
|
+
}
|
|
109
|
+
if (typeof raw.min_minutes_between_checkpoints === 'number' &&
|
|
110
|
+
raw.min_minutes_between_checkpoints >= 0) {
|
|
111
|
+
out.min_minutes_between_checkpoints = Math.floor(raw.min_minutes_between_checkpoints);
|
|
112
|
+
}
|
|
113
|
+
if (Array.isArray(raw.domains_enabled) && raw.domains_enabled.every((d) => typeof d === 'string')) {
|
|
114
|
+
out.domains_enabled = raw.domains_enabled;
|
|
115
|
+
}
|
|
116
|
+
if (typeof raw.quiet === 'boolean')
|
|
117
|
+
out.quiet = raw.quiet;
|
|
118
|
+
if (typeof raw.max_new_concepts_per_session === 'number' &&
|
|
119
|
+
raw.max_new_concepts_per_session >= 0) {
|
|
120
|
+
out.max_new_concepts_per_session = Math.floor(raw.max_new_concepts_per_session);
|
|
121
|
+
}
|
|
122
|
+
if (typeof raw.max_stop_blocks_per_session === 'number' &&
|
|
123
|
+
raw.max_stop_blocks_per_session >= 0) {
|
|
124
|
+
out.max_stop_blocks_per_session = Math.floor(raw.max_stop_blocks_per_session);
|
|
125
|
+
}
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
/** Global config merged with the repo's, repo winning (PRD §11). */
|
|
129
|
+
export function loadConfig(cwd = process.cwd()) {
|
|
130
|
+
const globalPath = globalConfigPath();
|
|
131
|
+
const { repoPath, repoRoot } = findRepoConfig(cwd);
|
|
132
|
+
const globalRaw = readJson(globalPath) ?? {};
|
|
133
|
+
const repoRaw = repoPath ? (readJson(repoPath) ?? {}) : {};
|
|
134
|
+
const raw = { ...globalRaw, ...repoRaw };
|
|
135
|
+
const overrides = Object.keys(repoRaw).filter((key) => key in globalRaw && JSON.stringify(globalRaw[key]) !== JSON.stringify(repoRaw[key]));
|
|
136
|
+
return {
|
|
137
|
+
config: coerce(raw, DEFAULT_CONFIG),
|
|
138
|
+
raw,
|
|
139
|
+
globalPath,
|
|
140
|
+
repoPath,
|
|
141
|
+
repoRoot,
|
|
142
|
+
overrides,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/** Writes via temp file + rename: the git hook may be reading mid-write. */
|
|
146
|
+
export function writeConfigFile(file, patch) {
|
|
147
|
+
const existing = readJson(file) ?? {};
|
|
148
|
+
const merged = { ...existing, ...patch };
|
|
149
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
150
|
+
const tmp = `${file}.tmp-${process.pid}`;
|
|
151
|
+
fs.writeFileSync(tmp, `${JSON.stringify(merged, null, 2)}\n`, 'utf8');
|
|
152
|
+
fs.renameSync(tmp, file);
|
|
153
|
+
return merged;
|
|
154
|
+
}
|
|
155
|
+
export function isDomainEnabled(config, domain) {
|
|
156
|
+
return config.domains_enabled.includes('*') || config.domains_enabled.includes(domain);
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|