Lines 109-149javascript
111if (event === 'end' || event === 'precompact') {
112 // Option C: harvest learnings + mirror to the vector index, DETACHED (fire-and-forget) so the
113 // hook never waits on transcript parsing or the embedding model. Fires on BOTH SessionEnd and
114 // PreCompact (the reliable trigger for long sessions) — THROTTLED via .dz/.last-consolidate so
115 // rapid back-to-back compactions don't repeatedly pay the model-load cost.
117 try { last = parseInt(String(readFileSync(CONSOLIDATE_MARKER, 'utf-8')).trim(), 10) || 0; } catch { last = 0; }
118 if (Date.now() - last < THROTTLE_MS) {
119 note({ consolidate: 'skipped-throttled' });
121 try { writeFileSync(CONSOLIDATE_MARKER, String(Date.now())); } catch { /* best-effort marker */ }
122 // Uses the globally-installed \`dz\` from PATH. NO shell on posix: spawn's args-array +
123 // shell:true silently word-splits a ROOT containing spaces AND is Node's documented
124 // command-injection hazard (QE P1+P2); a plain spawn searches PATH itself and quotes nothing.
125 // Windows needs a shell for the .cmd shim, so there we pass ONE pre-quoted string (") — quotes
126 // are illegal in Windows paths, so wrapping is sufficient. Failure is detected via BOTH the
127 // error event (posix ENOENT) and a non-zero exit code (shell-mediated "not found").
129 const child = process.platform === 'win32'
130 ? spawn('dz consolidate --project "' + ROOT + '"', { detached: true, stdio: 'ignore', shell: true, cwd: ROOT })
131 : spawn('dz', ['consolidate', '--project', ROOT], { detached: true, stdio: 'ignore', cwd: ROOT });
132 child.on('error', (err) => note({ consolidate: 'skipped', error: String((err && err.message) || err) }));
133 child.on('exit', (code) => { if (code !== null && code !== 0) note({ consolidate: 'skipped', error: 'dz exited ' + code + ' (not on PATH?)' }); });
136 note({ consolidate: 'skipped', error: String((err && err.message) || err) });
142/** Parse the `dz-writer-version` stamp from a deployed writer file ('' or absent → 0). */
143export function writerVersionOf(content) {
144 const m = /^\/\/ dz-writer-version:\s*(\d+)/m.exec(content);
145 return m ? parseInt(m[1] ?? '0', 10) : 0;
147/** Generate Claude Code hooks configuration for self-learning. */
149 * Commands of a Claude Code hook entry in EITHER shape: the valid matcher-group form