eklavya 1.24.2 → 1.25.1
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/dist/assets/dashboard.html +1012 -255
- package/dist/assets/tutor/references/focus-and-level.md +7 -0
- package/dist/claude-mem.js +9 -7
- package/dist/claude-mem.js.map +1 -1
- package/dist/cli-memory.js +786 -0
- package/dist/cli-memory.js.map +1 -0
- package/dist/cli.js +95 -773
- package/dist/cli.js.map +1 -1
- package/dist/config.js +79 -13
- package/dist/config.js.map +1 -1
- package/dist/dashboard.js +289 -15
- package/dist/dashboard.js.map +1 -1
- package/dist/db.js +33 -6
- package/dist/db.js.map +1 -1
- package/dist/hooks/capture-lib.js +55 -0
- package/dist/hooks/capture-lib.js.map +1 -0
- package/dist/hooks/capture-tool.js +1 -1
- package/dist/hooks/capture-tool.js.map +1 -1
- package/dist/hooks/commit-lib.js +255 -0
- package/dist/hooks/commit-lib.js.map +1 -0
- package/dist/hooks/lib.js +37 -5
- package/dist/hooks/lib.js.map +1 -1
- package/dist/hooks/memory-lib.js +93 -78
- package/dist/hooks/memory-lib.js.map +1 -1
- package/dist/hooks/pre-tool-gate.js +5 -10
- package/dist/hooks/pre-tool-gate.js.map +1 -1
- package/dist/hooks/prompt-submit-nudge.js +26 -1
- package/dist/hooks/prompt-submit-nudge.js.map +1 -1
- package/dist/hooks/session-start.js +13 -4
- package/dist/hooks/session-start.js.map +1 -1
- package/dist/install.js +280 -67
- package/dist/install.js.map +1 -1
- package/dist/memory/capture.js +25 -5
- package/dist/memory/capture.js.map +1 -1
- package/dist/memory/privacy.js +105 -7
- package/dist/memory/privacy.js.map +1 -1
- package/dist/memory/provider.js +13 -3
- package/dist/memory/provider.js.map +1 -1
- package/dist/memory/recall.js +18 -6
- package/dist/memory/recall.js.map +1 -1
- package/dist/memory/spool.js +105 -23
- package/dist/memory/spool.js.map +1 -1
- package/dist/memory/store.js +32 -2
- package/dist/memory/store.js.map +1 -1
- package/dist/memory/summarize.js +5 -5
- package/dist/memory/summarize.js.map +1 -1
- package/dist/memory/worker.js +71 -10
- package/dist/memory/worker.js.map +1 -1
- package/dist/migrate.js +60 -14
- package/dist/migrate.js.map +1 -1
- package/dist/paths.js +49 -0
- package/dist/paths.js.map +1 -1
- package/dist/plugin/.claude-plugin/plugin.json +1 -1
- package/dist/plugin/cli/CLAUDE.md +16 -4
- package/dist/plugin/hooks/CLAUDE.md +33 -3
- package/dist/plugin/hooks/run.mjs +69 -5
- package/dist/plugin/scripts/install-git-hook.sh +114 -24
- package/dist/plugin/skills/CLAUDE.md +1 -1
- package/dist/plugin/skills/setup/SKILL.md +9 -2
- package/dist/plugin/skills/tutor/references/focus-and-level.md +7 -0
- package/dist/safe-write.js +146 -0
- package/dist/safe-write.js.map +1 -0
- package/dist/slug.js +4 -1
- package/dist/slug.js.map +1 -1
- package/dist/srs.js +33 -1
- package/dist/srs.js.map +1 -1
- package/dist/store.js +29 -19
- package/dist/store.js.map +1 -1
- package/dist/tools/config_tools.js +23 -1
- package/dist/tools/config_tools.js.map +1 -1
- package/dist/tools/get_session_quiz_plan.js +51 -30
- package/dist/tools/get_session_quiz_plan.js.map +1 -1
- package/dist/tools/log_session_concepts.js +30 -23
- package/dist/tools/log_session_concepts.js.map +1 -1
- package/dist/tools/record_attempt.js +18 -8
- package/dist/tools/record_attempt.js.map +1 -1
- package/dist/tools/types.js +29 -0
- package/dist/tools/types.js.map +1 -1
- package/dist/tools/upsert_concepts.js +12 -10
- package/dist/tools/upsert_concepts.js.map +1 -1
- package/dist/user-skill/eklavya/SKILL.md +46 -21
- package/package.json +2 -1
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Does this Bash command create a commit? The PreToolUse gate's one question.
|
|
3
|
+
*
|
|
4
|
+
* It used to be a single regex anchored at the start of the line or after `;`,
|
|
5
|
+
* `&` or `|`. That missed a commit on its own line (`npm test\ngit commit`),
|
|
6
|
+
* inside `( )`, `{ }`, `$( )` or backticks, behind `env`, `command`, `time`,
|
|
7
|
+
* `nice` or `VAR=x`, through `/usr/bin/git` or `bash -c "…"`, and after
|
|
8
|
+
* `git --no-pager` or `git -C "my dir"`. Each extra case made the regex less
|
|
9
|
+
* readable, so this is a small shell lexer instead: split the command into
|
|
10
|
+
* simple commands the way the shell would, peel off the prefixes that run their
|
|
11
|
+
* argument as a command, and look at what git is asked to do.
|
|
12
|
+
*
|
|
13
|
+
* What it deliberately still lets through, because none of it runs a commit:
|
|
14
|
+
* a quoted string (`echo "git commit"`), a comment, a here-document body, and
|
|
15
|
+
* other subcommands that merely contain the word (`git commit-tree`,
|
|
16
|
+
* `git log --grep commit`).
|
|
17
|
+
*
|
|
18
|
+
* Scope matches the git pre-commit hook (`cli/eklavya-gate`), checked against
|
|
19
|
+
* git 2.50: `git commit` and `git merge --continue` run pre-commit; a clean
|
|
20
|
+
* `git merge`, `cherry-pick`, `revert`, `am` and `rebase --continue` do not. Two
|
|
21
|
+
* enforcement paths disagreeing about what a commit is would be a gate one of
|
|
22
|
+
* them waves through.
|
|
23
|
+
*
|
|
24
|
+
* Known misses, accepted: git aliases (`git ci`), a variable holding the command
|
|
25
|
+
* (`$GIT commit`), and scripts on disk. This is a speed bump for an agent that
|
|
26
|
+
* forgot the quiz, not a sandbox; the git hook is the backstop.
|
|
27
|
+
*/
|
|
28
|
+
/** Every simple command in `src`, as its words with quotes resolved. */
|
|
29
|
+
export function simpleCommands(src) {
|
|
30
|
+
const out = [];
|
|
31
|
+
let i = 0;
|
|
32
|
+
const heredocs = [];
|
|
33
|
+
// Skip here-document bodies queued on the line just ended. `i` sits just
|
|
34
|
+
// past the newline.
|
|
35
|
+
const skipHeredocs = () => {
|
|
36
|
+
while (heredocs.length) {
|
|
37
|
+
const { delim, strip } = heredocs.shift();
|
|
38
|
+
while (i < src.length) {
|
|
39
|
+
const nl = src.indexOf('\n', i);
|
|
40
|
+
const end = nl === -1 ? src.length : nl;
|
|
41
|
+
let line = src.slice(i, end);
|
|
42
|
+
if (strip)
|
|
43
|
+
line = line.replace(/^\t+/, '');
|
|
44
|
+
i = end + 1;
|
|
45
|
+
if (line === delim)
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
// One command list, up to `stop` (`)` or a backtick) or the end of input.
|
|
51
|
+
const list = (stop) => {
|
|
52
|
+
let words = [];
|
|
53
|
+
let word = '';
|
|
54
|
+
let inWord = false;
|
|
55
|
+
const endWord = () => {
|
|
56
|
+
if (inWord)
|
|
57
|
+
words.push(word);
|
|
58
|
+
word = '';
|
|
59
|
+
inWord = false;
|
|
60
|
+
};
|
|
61
|
+
const endCmd = () => {
|
|
62
|
+
endWord();
|
|
63
|
+
if (words.length)
|
|
64
|
+
out.push(words);
|
|
65
|
+
words = [];
|
|
66
|
+
};
|
|
67
|
+
while (i < src.length) {
|
|
68
|
+
const c = src[i];
|
|
69
|
+
if (c === stop) {
|
|
70
|
+
i += 1;
|
|
71
|
+
endCmd();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (c === ' ' || c === '\t') {
|
|
75
|
+
endWord();
|
|
76
|
+
i += 1;
|
|
77
|
+
}
|
|
78
|
+
else if (c === '\n') {
|
|
79
|
+
endCmd();
|
|
80
|
+
i += 1;
|
|
81
|
+
skipHeredocs();
|
|
82
|
+
}
|
|
83
|
+
else if (c === ';' || c === '&' || c === '|' || c === ')') {
|
|
84
|
+
endCmd();
|
|
85
|
+
i += 1;
|
|
86
|
+
}
|
|
87
|
+
else if (c === '(') {
|
|
88
|
+
endCmd();
|
|
89
|
+
i += 1;
|
|
90
|
+
list(')');
|
|
91
|
+
}
|
|
92
|
+
else if (c === '`') {
|
|
93
|
+
i += 1;
|
|
94
|
+
inWord = true;
|
|
95
|
+
list('`');
|
|
96
|
+
}
|
|
97
|
+
else if (c === '$' && src[i + 1] === '(') {
|
|
98
|
+
i += 2;
|
|
99
|
+
inWord = true;
|
|
100
|
+
list(')');
|
|
101
|
+
}
|
|
102
|
+
else if (c === '#' && !inWord) {
|
|
103
|
+
const nl = src.indexOf('\n', i);
|
|
104
|
+
i = nl === -1 ? src.length : nl;
|
|
105
|
+
}
|
|
106
|
+
else if (c === '\\') {
|
|
107
|
+
// A backslash-newline is a line continuation; anything else is literal.
|
|
108
|
+
if (src[i + 1] !== '\n') {
|
|
109
|
+
word += src[i + 1] ?? '';
|
|
110
|
+
inWord = true;
|
|
111
|
+
}
|
|
112
|
+
i += 2;
|
|
113
|
+
}
|
|
114
|
+
else if (c === "'") {
|
|
115
|
+
const close = src.indexOf("'", i + 1);
|
|
116
|
+
const end = close === -1 ? src.length : close;
|
|
117
|
+
word += src.slice(i + 1, end);
|
|
118
|
+
inWord = true;
|
|
119
|
+
i = end + 1;
|
|
120
|
+
}
|
|
121
|
+
else if (c === '"') {
|
|
122
|
+
i += 1;
|
|
123
|
+
inWord = true;
|
|
124
|
+
while (i < src.length && src[i] !== '"') {
|
|
125
|
+
if (src[i] === '\\') {
|
|
126
|
+
word += src[i + 1] ?? '';
|
|
127
|
+
i += 2;
|
|
128
|
+
}
|
|
129
|
+
else if (src[i] === '$' && src[i + 1] === '(') {
|
|
130
|
+
i += 2;
|
|
131
|
+
list(')');
|
|
132
|
+
}
|
|
133
|
+
else if (src[i] === '`') {
|
|
134
|
+
i += 1;
|
|
135
|
+
list('`');
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
word += src[i];
|
|
139
|
+
i += 1;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
i += 1;
|
|
143
|
+
}
|
|
144
|
+
else if (c === '<' && src.startsWith('<<', i) && !src.startsWith('<<<', i)) {
|
|
145
|
+
endWord();
|
|
146
|
+
i += 2;
|
|
147
|
+
const strip = src[i] === '-';
|
|
148
|
+
if (strip)
|
|
149
|
+
i += 1;
|
|
150
|
+
while (src[i] === ' ' || src[i] === '\t')
|
|
151
|
+
i += 1;
|
|
152
|
+
const m = /^[^\s;&|()<>]+/.exec(src.slice(i));
|
|
153
|
+
if (m) {
|
|
154
|
+
heredocs.push({ delim: m[0].replace(/["'\\]/g, ''), strip });
|
|
155
|
+
i += m[0].length;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
word += c;
|
|
160
|
+
inWord = true;
|
|
161
|
+
i += 1;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
endCmd();
|
|
165
|
+
};
|
|
166
|
+
list(null);
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
169
|
+
const ASSIGNMENT = /^[A-Za-z_][A-Za-z0-9_]*=/;
|
|
170
|
+
/** Words that sit in front of a command without changing which one runs. */
|
|
171
|
+
const KEYWORDS = new Set(['{', '}', '!', 'if', 'then', 'else', 'elif', 'do', 'while', 'until', 'exec', 'nohup', 'builtin']);
|
|
172
|
+
/**
|
|
173
|
+
* Wrappers that run their argument as a command, and which of their options
|
|
174
|
+
* take a separate value. Anything else starting with `-` is a flag.
|
|
175
|
+
*/
|
|
176
|
+
const WRAPPERS = {
|
|
177
|
+
sudo: new Set(['-u', '-g', '-C', '-D', '-h', '-p', '-r', '-t', '-U']),
|
|
178
|
+
doas: new Set(['-u', '-C']),
|
|
179
|
+
env: new Set(['-u', '-C', '-S']),
|
|
180
|
+
command: new Set(),
|
|
181
|
+
time: new Set(['-f', '-o']),
|
|
182
|
+
nice: new Set(['-n']),
|
|
183
|
+
xargs: new Set(['-I', '-n', '-L', '-P', '-d', '-E', '-s', '-a']),
|
|
184
|
+
timeout: new Set(['-s', '-k']),
|
|
185
|
+
};
|
|
186
|
+
/** `git` options that take a separate value, before the subcommand. */
|
|
187
|
+
const GIT_VALUE_OPTS = new Set(['-C', '-c', '--git-dir', '--work-tree', '--namespace', '--config-env', '--super-prefix']);
|
|
188
|
+
const SHELLS = new Set(['sh', 'bash', 'zsh', 'dash', 'ksh']);
|
|
189
|
+
const base = (word) => word.replace(/^.*[/\\]/, '').replace(/\.exe$/i, '');
|
|
190
|
+
function commits(words, depth) {
|
|
191
|
+
let k = 0;
|
|
192
|
+
// Peel prefixes until the word that actually runs.
|
|
193
|
+
for (;;) {
|
|
194
|
+
const w = words[k];
|
|
195
|
+
if (w === undefined)
|
|
196
|
+
return false;
|
|
197
|
+
if (ASSIGNMENT.test(w) || KEYWORDS.has(w)) {
|
|
198
|
+
k += 1;
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const wrapper = WRAPPERS[base(w)];
|
|
202
|
+
if (!wrapper)
|
|
203
|
+
break;
|
|
204
|
+
k += 1;
|
|
205
|
+
// `timeout 10 git commit`: the duration is its first bare argument.
|
|
206
|
+
let needsDuration = base(w) === 'timeout';
|
|
207
|
+
for (let a = words[k]; a !== undefined; a = words[k]) {
|
|
208
|
+
if (a.startsWith('-') && a !== '-')
|
|
209
|
+
k += wrapper.has(a) ? 2 : 1;
|
|
210
|
+
else if (ASSIGNMENT.test(a))
|
|
211
|
+
k += 1;
|
|
212
|
+
else if (needsDuration) {
|
|
213
|
+
needsDuration = false;
|
|
214
|
+
k += 1;
|
|
215
|
+
}
|
|
216
|
+
else
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const cmd = base(words[k] ?? '');
|
|
221
|
+
const args = words.slice(k + 1);
|
|
222
|
+
if ((SHELLS.has(cmd) || cmd === 'eval') && depth < 4) {
|
|
223
|
+
let script;
|
|
224
|
+
if (cmd === 'eval')
|
|
225
|
+
script = args.join(' ');
|
|
226
|
+
else {
|
|
227
|
+
const flag = args.findIndex((a) => /^-[a-z]*c[a-z]*$/.test(a));
|
|
228
|
+
if (flag !== -1)
|
|
229
|
+
script = args.slice(flag + 1).find((a) => !a.startsWith('-'));
|
|
230
|
+
}
|
|
231
|
+
return script !== undefined && isCommitCommand(script, depth + 1);
|
|
232
|
+
}
|
|
233
|
+
if (cmd !== 'git')
|
|
234
|
+
return false;
|
|
235
|
+
let j = 0;
|
|
236
|
+
for (let a = args[j]; a?.startsWith('-'); a = args[j])
|
|
237
|
+
j += GIT_VALUE_OPTS.has(a) ? 2 : 1;
|
|
238
|
+
const sub = args[j];
|
|
239
|
+
if (sub === 'commit')
|
|
240
|
+
return true;
|
|
241
|
+
return sub === 'merge' && args.slice(j + 1).includes('--continue');
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* True when running `command` would create a commit. Never throws: any
|
|
245
|
+
* internal failure answers false, so the gate fails open.
|
|
246
|
+
*/
|
|
247
|
+
export function isCommitCommand(command, depth = 0) {
|
|
248
|
+
try {
|
|
249
|
+
return simpleCommands(command).some((words) => commits(words, depth));
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
//# sourceMappingURL=commit-lib.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commit-lib.js","sourceRoot":"","sources":["../../src/hooks/commit-lib.ts"],"names":[],"mappings":"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"}
|
package/dist/hooks/lib.js
CHANGED
|
@@ -62,19 +62,51 @@ export async function readInput() {
|
|
|
62
62
|
* which is the same answer `eklavya_have_deps` gave.
|
|
63
63
|
*/
|
|
64
64
|
export function openExisting() {
|
|
65
|
+
return openOrDiagnose().db;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* `openExisting`, plus the reason when the answer is no.
|
|
69
|
+
*
|
|
70
|
+
* No file is not a problem: it is a first session, and the MCP server creates
|
|
71
|
+
* the database. A file that is there and cannot be opened or read is, and it
|
|
72
|
+
* used to be indistinguishable -- every hook returned in silence and Eklavya
|
|
73
|
+
* just stopped. `new Database` succeeds on a file full of garbage, so the header
|
|
74
|
+
* is read (`schema_version`) before calling it healthy; an empty file reads as
|
|
75
|
+
* version 0, which is what the server's own first open looks like mid-creation.
|
|
76
|
+
* A busy database is somebody else's write in progress, not a fault. Never throws.
|
|
77
|
+
*/
|
|
78
|
+
export function openOrDiagnose() {
|
|
65
79
|
const file = dbPath();
|
|
66
80
|
if (!fs.existsSync(file))
|
|
67
|
-
return null;
|
|
81
|
+
return { db: null, problem: null };
|
|
82
|
+
let db;
|
|
83
|
+
try {
|
|
84
|
+
db = new Database(file);
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
return { db: null, problem: isNativeLoadError(err) ? 'native' : 'unreadable' };
|
|
88
|
+
}
|
|
68
89
|
try {
|
|
69
|
-
const db = new Database(file);
|
|
70
90
|
db.pragma('busy_timeout = 2000');
|
|
71
91
|
db.pragma('foreign_keys = ON');
|
|
72
|
-
|
|
92
|
+
db.pragma('schema_version');
|
|
93
|
+
return { db, problem: null };
|
|
73
94
|
}
|
|
74
|
-
catch {
|
|
75
|
-
|
|
95
|
+
catch (err) {
|
|
96
|
+
try {
|
|
97
|
+
db.close();
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
/* already unusable */
|
|
101
|
+
}
|
|
102
|
+
const code = err?.code ?? '';
|
|
103
|
+
return { db: null, problem: /^SQLITE_(BUSY|LOCKED)/.test(code) ? null : 'unreadable' };
|
|
76
104
|
}
|
|
77
105
|
}
|
|
106
|
+
function isNativeLoadError(err) {
|
|
107
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
108
|
+
return /NODE_MODULE_VERSION|better_sqlite3\.node|bindings file|dlopen|ERR_DLOPEN_FAILED/i.test(message);
|
|
109
|
+
}
|
|
78
110
|
/**
|
|
79
111
|
* `meta` key holding the UserPromptSubmit nudge's per-session bookkeeping.
|
|
80
112
|
* Shared here rather than exported from the hook, because `session-start`
|
package/dist/hooks/lib.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lib.js","sourceRoot":"","sources":["../../src/hooks/lib.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,UAAU,EAAuB,MAAM,cAAc,CAAC;AAC/D,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACrE,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAE9D,8EAA8E;AAC9E,4CAA4C;AAC5C,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AAqBvC;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS;IAC7B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,gBAAgB,CAAC,UAAU,CAAC,CAAC,CAAC;QACzD,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE;YAAE,OAAO,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACxC,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,CAAC,CAAC,CAAE,MAAoB,CAAC,CAAC,CAAC,EAAE,CAAC;IACpF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,YAAY;IAC1B,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC;IACtB,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"lib.js","sourceRoot":"","sources":["../../src/hooks/lib.ts"],"names":[],"mappings":"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"}
|
package/dist/hooks/memory-lib.js
CHANGED
|
@@ -1,70 +1,86 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { recall, recallForPrompt } from '../memory/recall.js';
|
|
1
|
+
import { drainSpool } from '../memory/capture.js';
|
|
2
|
+
import { batchSession, hasClaimableJob, openSessions } from '../memory/store.js';
|
|
3
|
+
import { processPending, pruneIfDue, writeSessionSummary } from '../memory/worker.js';
|
|
4
|
+
import { parseStamp } from '../time.js';
|
|
5
|
+
import { recall } from '../memory/recall.js';
|
|
7
6
|
import { notify, queuePausedAlert, sessionWrapUp } from '../memory/notify.js';
|
|
8
7
|
import { countEntries } from '../memory/store.js';
|
|
9
8
|
import { queueDepth } from '../memory/worker.js';
|
|
10
9
|
import { isInternalObserver, launchWorker, reserveWorker } from '../memory/reservation.js';
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
host: 'claude-code',
|
|
17
|
-
});
|
|
18
|
-
// `identityFor` resolves the project from the cwd; `projectKey` is the
|
|
19
|
-
// learning half's spelling of the same thing, and the two must not diverge or
|
|
20
|
-
// a memory and a mastery row disagree about which codebase they belong to.
|
|
21
|
-
return { ...identity, project: projectKey(identity.checkout) };
|
|
22
|
-
}
|
|
23
|
-
/** Records one event. Returns false on any failure, and never throws. */
|
|
24
|
-
export function record(db, resolved, identity, event) {
|
|
25
|
-
try {
|
|
26
|
-
return capture(db, resolved.config, identity, event) === 'stored';
|
|
27
|
-
}
|
|
28
|
-
catch {
|
|
29
|
-
return false;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
10
|
+
// The per-call capture helpers live in `capture-lib.ts`, so the two hooks that
|
|
11
|
+
// run on every tool call and every prompt do not load this module's worker,
|
|
12
|
+
// provider, recall and notify graph. Re-exported so the seam hooks and tests
|
|
13
|
+
// keep one import.
|
|
14
|
+
export { batchIfFull, identityOf, record } from './capture-lib.js';
|
|
32
15
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
16
|
+
* A Stop seam closes a session's open evidence into a batch only past one of
|
|
17
|
+
* these. The Stop hook fires at the end of every turn, and with an observer
|
|
18
|
+
* configured every closed batch is a `claude -p` call — so closing on every turn
|
|
19
|
+
* made a two-event "yes, go ahead" turn cost a model call. Eight events is
|
|
20
|
+
* roughly one real task (a prompt, a few reads, an edit or two, a test run);
|
|
21
|
+
* twenty minutes is long enough that a pause for thought does not split a task,
|
|
22
|
+
* and short enough that an abandoned session's tail is summarised the same
|
|
23
|
+
* afternoon. `batchIfFull` still closes at `memory.batch_max_events` per tool
|
|
24
|
+
* call, and a session start (`all`) closes everything whatever its size.
|
|
35
25
|
*/
|
|
36
|
-
export
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
26
|
+
export const SEAM_MIN_EVENTS = 8;
|
|
27
|
+
export const SEAM_MAX_AGE_MS = 20 * 60_000;
|
|
28
|
+
/**
|
|
29
|
+
* Closes the project's open evidence that is worth a summary now.
|
|
30
|
+
*
|
|
31
|
+
* Every session in the project, not only this one: a session that ended with
|
|
32
|
+
* two events after its last closed batch has no seam of its own left, so its
|
|
33
|
+
* tail is closed here — at once with `all`, otherwise once it is old enough.
|
|
34
|
+
* That is what keeps the thresholds from stranding anything.
|
|
35
|
+
*/
|
|
36
|
+
function closeOpenBatches(db, resolved, project, all) {
|
|
37
|
+
const now = Date.now();
|
|
38
|
+
for (const open of openSessions(db, project)) {
|
|
39
|
+
const oldest = parseStamp(open.oldest);
|
|
40
|
+
const due = all ||
|
|
41
|
+
open.events >= SEAM_MIN_EVENTS ||
|
|
42
|
+
// An unreadable stamp is closed rather than kept open for ever.
|
|
43
|
+
oldest === null ||
|
|
44
|
+
now - oldest >= SEAM_MAX_AGE_MS;
|
|
45
|
+
if (!due)
|
|
46
|
+
continue;
|
|
41
47
|
batchSession(db, {
|
|
42
|
-
project
|
|
43
|
-
sessionId:
|
|
44
|
-
reason: '
|
|
45
|
-
maxEvents:
|
|
48
|
+
project,
|
|
49
|
+
sessionId: open.sessionId,
|
|
50
|
+
reason: 'session_seam',
|
|
51
|
+
maxEvents: resolved.config.memory.batch_max_events,
|
|
46
52
|
});
|
|
47
53
|
}
|
|
48
|
-
catch {
|
|
49
|
-
/* A batch that did not close is one that closes at the seam instead. */
|
|
50
|
-
}
|
|
51
54
|
}
|
|
52
55
|
/**
|
|
53
|
-
* The session seam: close what is
|
|
56
|
+
* The session seam: replay the spool, close what is worth closing, summarise
|
|
57
|
+
* it, and run the retention sweep when it is due.
|
|
58
|
+
*
|
|
59
|
+
* `all` is for a session start, where whatever the last session left open is
|
|
60
|
+
* closed however small; a Stop seam leaves a short fresh turn open to join the
|
|
61
|
+
* next (`SEAM_MIN_EVENTS`, `SEAM_MAX_AGE_MS`).
|
|
54
62
|
*
|
|
55
63
|
* With a provider configured the hook never summarises itself: waiting on a
|
|
56
64
|
* model inside a Stop hook would make the developer wait for it to finish a
|
|
57
65
|
* turn, which PRD LRN-04 forbids. It hands the queue to a detached worker
|
|
58
66
|
* instead and returns.
|
|
59
67
|
*/
|
|
60
|
-
export async function flushAtSeam(db, resolved, identity) {
|
|
68
|
+
export async function flushAtSeam(db, resolved, identity, opts = {}) {
|
|
69
|
+
// Before batching, so a spooled event joins the batch it belongs to.
|
|
70
|
+
replaySpool(db);
|
|
71
|
+
try {
|
|
72
|
+
closeOpenBatches(db, resolved, identity.project, opts.all ?? false);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
/* Evidence left open is closed at the next seam. */
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
pruneIfDue(db, resolved.config);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
/* Retention runs again at the next seam; nothing is lost by waiting. */
|
|
82
|
+
}
|
|
61
83
|
try {
|
|
62
|
-
batchSession(db, {
|
|
63
|
-
project: identity.project,
|
|
64
|
-
sessionId: identity.sessionId,
|
|
65
|
-
reason: 'session_seam',
|
|
66
|
-
maxEvents: resolved.config.memory.batch_max_events,
|
|
67
|
-
});
|
|
68
84
|
if (resolved.config.providers.observer) {
|
|
69
85
|
// A paused queue is waiting on the developer (a login, a usage limit, a
|
|
70
86
|
// missing `claude`). Launching per seam would relearn that once a turn;
|
|
@@ -132,27 +148,12 @@ export function recallBlock(db, resolved, identity, scope) {
|
|
|
132
148
|
}
|
|
133
149
|
}
|
|
134
150
|
/**
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
* recall on every turn is a tax on every turn.
|
|
151
|
+
* The most the Stop hook spends on notifications, all of them together. Under
|
|
152
|
+
* the 4-second bound each sink already has plus one second of slack, and a
|
|
153
|
+
* third of the hook's 15-second timeout, which leaves the rest for batching,
|
|
154
|
+
* summarising and the quiz decision.
|
|
140
155
|
*/
|
|
141
|
-
export
|
|
142
|
-
try {
|
|
143
|
-
if (!resolved.config.memory.enabled)
|
|
144
|
-
return null;
|
|
145
|
-
const result = recallForPrompt(db, resolved.config, {
|
|
146
|
-
project: identity.project,
|
|
147
|
-
sessionId: identity.sessionId,
|
|
148
|
-
prompt,
|
|
149
|
-
});
|
|
150
|
-
return result?.block ?? null;
|
|
151
|
-
}
|
|
152
|
-
catch {
|
|
153
|
-
return null;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
+
export const NOTIFY_BUDGET_MS = 5_000;
|
|
156
157
|
/**
|
|
157
158
|
* Everything that happens once the seam's work has been flushed: the session
|
|
158
159
|
* summary, the wrap-up, and the one alert worth interrupting for.
|
|
@@ -177,13 +178,15 @@ export async function wrapUpAtSeam(db, resolved, identity) {
|
|
|
177
178
|
const attempts = db
|
|
178
179
|
.prepare('SELECT COUNT(*) AS n, COALESCE(SUM(grade >= 3), 0) AS passed FROM attempts WHERE session_id = ?')
|
|
179
180
|
.get(identity.sessionId);
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
181
|
+
const sends = [
|
|
182
|
+
notify(db, resolved.config, sessionWrapUp({
|
|
183
|
+
project: identity.project,
|
|
184
|
+
sessionId: identity.sessionId,
|
|
185
|
+
entries: countEntries(db, identity.project),
|
|
186
|
+
questions: attempts.n,
|
|
187
|
+
passed: attempts.passed,
|
|
188
|
+
})),
|
|
189
|
+
];
|
|
187
190
|
// A paused queue is capture that has stopped and will not restart by
|
|
188
191
|
// itself. Everything else about memory degrades quietly on purpose; this
|
|
189
192
|
// one is worth saying out loud, once per reason.
|
|
@@ -192,12 +195,24 @@ export async function wrapUpAtSeam(db, resolved, identity) {
|
|
|
192
195
|
const reason = db
|
|
193
196
|
.prepare("SELECT error_class FROM memory_jobs WHERE status = 'paused' ORDER BY updated_at DESC LIMIT 1")
|
|
194
197
|
.get();
|
|
195
|
-
|
|
198
|
+
sends.push(notify(db, resolved.config, queuePausedAlert({
|
|
196
199
|
project: identity.project,
|
|
197
200
|
errorClass: reason?.error_class ?? 'unknown',
|
|
198
201
|
failed: queue.paused + queue.failed,
|
|
199
|
-
}));
|
|
202
|
+
})));
|
|
200
203
|
}
|
|
204
|
+
// Together, not one after the other, and never past the budget: each sink
|
|
205
|
+
// is bounded on its own, but two notifications in turn to the same dead
|
|
206
|
+
// sinks was twice that bound inside a hook with 15 seconds for everything.
|
|
207
|
+
// A send still running at the deadline is abandoned; its ledger row says an
|
|
208
|
+
// attempt started, so a later seam retries it.
|
|
209
|
+
let timer;
|
|
210
|
+
const deadline = new Promise((resolve) => {
|
|
211
|
+
timer = setTimeout(resolve, NOTIFY_BUDGET_MS);
|
|
212
|
+
timer.unref?.();
|
|
213
|
+
});
|
|
214
|
+
await Promise.race([Promise.allSettled(sends), deadline]);
|
|
215
|
+
clearTimeout(timer);
|
|
201
216
|
}
|
|
202
217
|
catch {
|
|
203
218
|
/* A wrap-up nobody received is a wrap-up. A thrown one is a broken session. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory-lib.js","sourceRoot":"","sources":["../../src/hooks/memory-lib.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"memory-lib.js","sourceRoot":"","sources":["../../src/hooks/memory-lib.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,20 +6,15 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { run, openExisting, config, cwdOf, sessionId } from './lib.js';
|
|
8
8
|
import { isSessionOff } from '../session.js';
|
|
9
|
-
|
|
10
|
-
* `git commit` only counts at a command position: after the start of the line or
|
|
11
|
-
* a shell separator. This is what keeps `echo "git commit"` from being blocked,
|
|
12
|
-
* while `npm test && git commit -m x` is still caught. The option group allows a
|
|
13
|
-
* value after a flag, so `git -C . commit` and `git -c user.name=x commit` are
|
|
14
|
-
* caught alongside plain `git commit`.
|
|
15
|
-
*/
|
|
16
|
-
const COMMIT = /(^|[;&|])\s*(sudo\s+)?git(\s+-\S+(\s+[^-\s]\S*)?)*\s+commit(\s|$)/;
|
|
9
|
+
import { isCommitCommand } from './commit-lib.js';
|
|
17
10
|
await run(async (input) => {
|
|
18
11
|
// Fast path: no string match, no work. PreToolUse fires on every Bash call.
|
|
12
|
+
// Every command the gate holds (`git commit`, `git merge --continue`) spells
|
|
13
|
+
// one of these two words; `commit-lib.ts` says what counts and why.
|
|
19
14
|
const command = input.tool_input?.command;
|
|
20
|
-
if (
|
|
15
|
+
if (typeof command !== 'string' || !/commit|continue/.test(command))
|
|
21
16
|
return 0;
|
|
22
|
-
if (!
|
|
17
|
+
if (!isCommitCommand(command))
|
|
23
18
|
return 0;
|
|
24
19
|
const cwd = cwdOf(input);
|
|
25
20
|
if (!config(cwd).config.quiz.enforced)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pre-tool-gate.js","sourceRoot":"","sources":["../../src/hooks/pre-tool-gate.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"pre-tool-gate.js","sourceRoot":"","sources":["../../src/hooks/pre-tool-gate.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAElD,MAAM,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;IACxB,4EAA4E;IAC5E,6EAA6E;IAC7E,oEAAoE;IACpE,MAAM,OAAO,GAAG,KAAK,CAAC,UAAU,EAAE,OAAO,CAAC;IAC1C,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,CAAC,CAAC;IAC9E,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC;QAAE,OAAO,CAAC,CAAC;IAExC,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;IACzB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ;QAAE,OAAO,CAAC,CAAC;IAEhD,MAAM,EAAE,GAAG,YAAY,EAAE,CAAC;IAC1B,IAAI,CAAC,EAAE;QAAE,OAAO,CAAC,CAAC;IAElB,MAAM,GAAG,GAAG,SAAS,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACjC,IAAI,CAAC,GAAG;QAAE,OAAO,CAAC,CAAC;IAEnB,MAAM,IAAI,GAAG,EAAE;SACZ,OAAO,CAAC,mEAAmE,CAAC;SAC5E,GAAG,CAAC,GAAG,CAAuE,CAAC;IAElF,4EAA4E;IAC5E,wDAAwD;IACxD,IAAI,CAAC,IAAI;QAAE,OAAO,CAAC,CAAC;IACpB,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAEhC,2EAA2E;IAC3E,2EAA2E;IAC3E,qEAAqE;IACrE,2EAA2E;IAC3E,0CAA0C;IAC1C,MAAM,QAAQ,GAAG,YAAY,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;IAEvC,MAAM,MAAM,GACV,8DAA8D,IAAI,CAAC,QAAQ,OAAO,IAAI,CAAC,QAAQ,uBAAuB;QACtH,CAAC,QAAQ;YACP,CAAC,CAAC,mKAAmK;YACrK,CAAC,CAAC,uBAAuB,CAAC;QAC5B,6FAA6F;QAC7F,iDAAiD,CAAC;IAEpD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,GAAG,IAAI,CAAC,SAAS,CAAC;QAChB,kBAAkB,EAAE;YAClB,aAAa,EAAE,YAAY;YAC3B,kBAAkB,EAAE,MAAM;YAC1B,wBAAwB,EAAE,MAAM;SACjC;KACF,CAAC,IAAI,CACP,CAAC;IACF,OAAO,CAAC,CAAC;AACX,CAAC,CAAC,CAAC"}
|