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,7 +1,7 @@
1
1
  {
2
2
  "name": "eklavya",
3
3
  "displayName": "Eklavya",
4
- "version": "1.27.0",
4
+ "version": "1.28.0",
5
5
  "description": "Learn while your agent works. Turns coding-agent generation time into adaptive, Socratic learning grounded in the code being written.",
6
6
  "author": {
7
7
  "name": "Ajay Kumar"
@@ -47,9 +47,11 @@ In this order, each under its own `<h2>`:
47
47
  - **The rule that transfers.** When this applies in a different codebase, and
48
48
  the one gotcha to watch for.
49
49
 
50
- Plain English, short sentences, no jargon without a gloss. Role tokens only in
51
- any CSS you add (`--ink --dim --line --spot …`), square corners, no emoji, one
52
- self-contained file.
50
+ Plain English, short sentences, no jargon without a gloss. Any CSS you add
51
+ follows the design rules in the `eklavya-artifacts` skill: role tokens only
52
+ (`--ink --dim --faint --line --spot …`, never a raw hex or `--vd-*` step), one
53
+ verdigris accent, square corners, hairlines instead of shadows, the three font
54
+ variables, readable on both ink and paper, no emoji. One self-contained file.
53
55
 
54
56
  Never reveal or restate the answer to a *different* question, and never grade
55
57
  anything: the session already recorded the answer.
@@ -1,110 +1,62 @@
1
1
  # Working in `cli/`
2
2
 
3
- **This directory is not the `eklavya` binary.** That is `mcp/src/cli.ts`,
4
- published as `dist/cli.js` and the package's only `bin`. `cli/` holds exactly one
5
- file: `eklavya-gate`, the POSIX commit-gate script. The name invites the
6
- confusion, so check which one a request means before you edit anything —
7
- `eklavya config`, `eklavya doctor`, `eklavya dashboard` and `eklavya install` all
8
- live in `mcp/src/cli.ts`.
9
-
10
- It is a shell script and not Node on purpose: it runs as a git `pre-commit` hook
11
- on every commit, and a git hook must not pay Node's startup cost. Keep it POSIX
12
- `sh` — the installed hook execs it with `#!/bin/sh`, and `mcp/test/gate.test.ts`
13
- drives it through `/bin/sh`.
14
-
15
- ## It is opt-in, twice over
16
-
17
- Nothing in `eklavya install` puts this on a repo. `scripts/install-git-hook.sh`
18
- does, as a separate step the user runs per repo; it writes `pre-commit` in the
19
- **common** git directory's `hooks/` (so a linked worktree gets the main
20
- checkout's hook, which is the one git runs), between the `# >>> eklavya gate >>>`
21
- markers, and if a `pre-commit` already existed it moves it to `pre-commit.local`
22
- and chains it first. `--uninstall` restores it. Re-running it rewrites an older
23
- Eklavya hook in place. With `core.hooksPath` set (husky, lefthook) it installs
24
- nothing, prints the line to add to the manager's hook, and exits 2.
25
-
26
- The installed hook does not `exec` this file at a fixed path any more. It runs
27
- the first that exists of `~/.eklavya/runtime/node_modules/eklavya/dist/plugin/cli/eklavya-gate`
28
- (honouring `EKLAVYA_RUNTIME` and `EKLAVYA_HOME`) and the path the installer ran
29
- from, with `sh` so a lost executable bit cannot block. If neither exists the
30
- commit goes through with one stderr line. The old fixed-path `exec` meant a
31
- plugin update that moved the directory made every commit fail — the one failure
32
- this gate must never have. `eklavya uninstall` warns about the hook and prints
33
- the command to remove it; it never deletes it.
34
-
35
- Then the script itself only acts on a project whose config sets `quiz.enforced`
36
- (or the retired `"mode": "enforced"`, which it still reads). That config is at
37
- `~/.eklavya/projects/<slug>/config.json` — the script rebuilds the slug itself,
38
- in shell, from `git rev-parse --show-toplevel` folded to the main checkout via
39
- `--git-common-dir`, because a worktree shares its parent's settings. Note that it
40
- reads enforcement from **only** the project file — not `~/.eklavya/config.json` —
41
- so a globally set gate does not hold a bare terminal. The global file is read for
42
- one thing: a `quiz.enabled: false` (or `mode: off`) there, not overridden by the
43
- project, releases the gate, exactly as `coerce()` does — otherwise a terminal
44
- commit is held by a gate no quiz will ever run to clear. The slug path is built
45
- with `pwd -P` so a checkout reached through a symlink still finds its file.
46
-
47
- It reads `<repo>/.eklavya.json` as a fallback and does **not** migrate it. The
48
- node half moves that file out automatically; a git pre-commit hook is the wrong
49
- place to start rewriting somebody's working tree, so it reads the old location
50
- and lets the next ordinary session do the move. Any doc claiming a terminal commit is gated has to attach the installer
51
- step and the repo config; `web/src/content/docs/docs/commit-gate.mdx` and
52
- `skills/setup/SKILL.md` are where that lives.
53
-
54
- ## It fails open, always
55
-
56
- A learning tool that bricks commits gets uninstalled, and an unpassable gate
57
- teaches nothing. Every one of these exits 0:
58
-
59
- - not inside a git repo, or `git rev-parse` fails;
60
- - no config for this project, at either the current or the legacy location;
61
- - `jq` not on PATH (warns on stderr) — **or** `sqlite3` not on PATH (warns too).
62
- It needs both, not just `jq`;
63
- - the config does not ask for enforcement — `quiz.enforced` false or absent with
64
- no `mode: enforced` behind it, or `quiz.enabled` false — in the project file,
65
- or in the global one when the project does not say;
66
- - the database file does not exist;
67
- - the `sqlite3` query errors, or returns no gate row for this repo.
68
-
69
- Exit 1 happens on exactly one condition: a gate row for this repo whose `passed`
70
- is not `1`. Adding a new failure mode means adding a new `exit 0` path.
71
-
72
- ## It reads the database directly
73
-
74
- There is no server in a git hook, so the script talks to `knowledge.db` in raw
75
- SQL and duplicates things it cannot import. Keep it in step with:
76
-
77
- - the `gates` table shape — it selects `passed, required, answered` and picks the
78
- repo's most recent row by `updated_at`. `syncGate` in `mcp/src/store.ts` is the
79
- only writer; a renamed or added column lands here in the same commit.
80
- - `DEFAULT_CONFIG.quiz` in `mcp/src/config.ts` — the script defaults a config
81
- with neither `quiz` nor `mode` to unenforced.
82
- - `projectSlug` in `mcp/src/paths.ts` — the script rebuilds it with
83
- `tr '/\\:' '-'`, and `test/gate.test.ts` runs both parsers over one set of
84
- configs so the two cannot drift apart silently.
85
- - the `mode` → `quiz` alias in `normalizeLegacyKeys`, and the rule that
86
- `quiz.enabled: false` forces `enforced` off. Both are duplicated here in jq,
87
- spelled out with `if`/`elif` rather than `//`: jq's `//` is an alternative
88
- operator, so `.quiz.enforced // (.mode == "enforced")` reads an explicit
89
- `false` as unset and falls back to a stale `mode` in the same file — a gate
90
- that keeps holding commits after somebody switched it off.
91
- `test/gate.test.ts` runs both parsers over the same five configs.
92
-
93
- The pass/fail arithmetic itself (`PASSING_GRADE`, `pass_threshold`,
94
- `ceil(required * pass_threshold)`) is **not** duplicated here — the script trusts
95
- the persisted `passed` column. Keep it that way: a change to the gate's rule
96
- belongs in `syncGate` alone. What is duplicated is the lookup key, and it differs
97
- from the in-session hook on purpose: `mcp/src/hooks/pre-tool-gate.ts` looks the
98
- gate up by `session_id`, this script by `repo`, because a terminal commit has no
99
- session.
100
-
101
- ## Tests
102
-
103
- `mcp/test/gate.test.ts` runs the real script and the real installer against a
104
- temp repo, and ends with an actual `git commit`. From `mcp/`:
105
-
106
- ```sh
107
- npm test -- gate
108
- ```
109
-
110
- `pretest` builds `dist/`, which `pre-tool-gate.js` in the same file needs.
3
+ This directory contains the POSIX `eklavya-gate` script. The `eklavya` binary
4
+ (`config`, `doctor`, `install`, `dashboard`, etc.) lives in `mcp/src/cli.ts`.
5
+ Read the root `CLAUDE.md` for the repository contract.
6
+
7
+ ## Installation and scope
8
+
9
+ The terminal gate requires both a per-repository installation and project
10
+ `quiz.enforced: true`. `eklavya install` does not install a git hook.
11
+
12
+ `scripts/install-git-hook.sh` writes into the common Git directory so linked
13
+ worktrees use the same hook. It preserves an existing hook as `pre-commit.local`
14
+ and runs that first; `--uninstall` restores it. With `core.hooksPath` configured,
15
+ it prints the integration line and exits 2 without installing anything.
16
+
17
+ The installed wrapper tries the managed runtime, then the original installation
18
+ path, and invokes the script with `sh`. Missing both paths warns and allows the
19
+ commit. Preserve this fallback: updates can move plugin directories.
20
+ `eklavya uninstall` warns about the hook and prints removal instructions; it
21
+ does not remove the hook itself.
22
+
23
+ Project config lives in `~/.eklavya/projects/<slug>/config.json`; worktrees fold
24
+ to their main checkout. The script reads the old `<repo>/.eklavya.json` only as
25
+ a fallback and never migrates it. Global enforcement alone does not activate
26
+ this gate, but a global `quiz.enabled: false` releases it unless overridden by
27
+ the project. Keep legacy `mode` compatibility and explicit `false` values.
28
+
29
+ ## Fail-open contract
30
+
31
+ Use POSIX `sh`; the installed hook runs through `/bin/sh`. Missing repository,
32
+ config, `jq`, `sqlite3`, database or gate row allows the commit. Database query
33
+ errors also allow it. Missing dependencies warn on stderr.
34
+
35
+ Exit 1 only when a gate row exists for this repository and `passed` is not `1`.
36
+ Do not add an operational error that prevents a commit.
37
+
38
+ ## Keep duplicated logic aligned
39
+
40
+ | Script responsibility | Source and verification |
41
+ |---|---|
42
+ | Config merge and legacy `mode` alias | `mcp/src/config.ts`; shared fixtures in `mcp/test/gate.test.ts` |
43
+ | Project slug and worktree folding | `mcp/src/paths.ts`; physical paths must also handle symlinks |
44
+ | Latest repository gate lookup | `gates` schema and `syncGate` in `mcp/src/store.ts` |
45
+ | In-session commit detection | `mcp/src/hooks/pre-tool-gate.ts` and `commit-lib.ts` |
46
+
47
+ The script trusts persisted `passed`; it must not duplicate passing-grade or
48
+ threshold arithmetic. It looks up the latest gate by repository, whereas the
49
+ in-session hook uses a session ID. In jq, use explicit `if`/`elif` for booleans:
50
+ `//` treats `false` as absent and can re-enable a disabled legacy gate.
51
+
52
+ ## Documentation and verification
53
+
54
+ In the same PR, update the manual `commit-gate.mdx` for installer, prerequisite,
55
+ scope, bypass or failure behavior changes. Check `configuration.mdx`,
56
+ `skills/setup/SKILL.md` and the commit-gate acceptance check in `CONTRIBUTING.md`
57
+ when their claims change. Every claim about terminal enforcement must include
58
+ both installation and project enforcement.
59
+
60
+ From `mcp/`, run `npm test -- gate`. It builds the runtime and exercises the real
61
+ script and installer in temporary repositories, including an actual commit.
62
+ Build `web/` after documentation changes; report any live acceptance test not run.