feat(qmd): warm the index and daemon from a SessionStart hook
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
08f015de92
commit
f3c88a089e
12 files changed
+202
-14
No files matched your search
@@ -0,0 +1,11 @@
|
||||
# SessionStart: warm the QMD index and daemon in the background; never blocks or fails the session.
|
||||
$ErrorActionPreference = 'SilentlyContinue'
|
||||
$script = Join-Path $HOME '.my-ai-configuration/qmd/qmd-warm.mjs'
|
||||
$node = Get-Command node -ErrorAction SilentlyContinue
|
||||
if ((Test-Path -LiteralPath $script) -and $node) {
|
||||
$null = git rev-parse --show-toplevel 2>$null
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Start-Process -FilePath $node.Source -ArgumentList @("`"$script`"") -WindowStyle Hidden -WorkingDirectory (Get-Location).Path
|
||||
}
|
||||
}
|
||||
exit 0
|
||||
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
# SessionStart: warm the QMD index and daemon in the background; never blocks or fails the session.
|
||||
script="$HOME/.my-ai-configuration/qmd/qmd-warm.mjs"
|
||||
if [ -f "$script" ] && command -v node >/dev/null 2>&1 && git rev-parse --show-toplevel >/dev/null 2>&1; then
|
||||
nohup node "$script" >/dev/null 2>&1 &
|
||||
fi
|
||||
exit 0
|
||||
@@ -0,0 +1,57 @@
|
||||
// Background warm-up started by the SessionStart hook; never fails the session.
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { appendFileSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { join, resolve } from 'node:path';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
import { parseArgs } from 'node:util';
|
||||
import { collectionName, readJson, stateDir } from './qmd-lib.mjs';
|
||||
import { startDaemon } from './qmd-search.mjs';
|
||||
|
||||
const STALE_MS = 30 * 60_000;
|
||||
|
||||
export async function withLock(lockPath, fn, now = Date.now) {
|
||||
try {
|
||||
writeFileSync(lockPath, String(now()), { flag: 'wx' });
|
||||
} catch {
|
||||
const age = now() - Number(readFileSync(lockPath, 'utf8'));
|
||||
if (!(age > STALE_MS)) return false;
|
||||
writeFileSync(lockPath, String(now()));
|
||||
}
|
||||
try {
|
||||
await fn();
|
||||
return true;
|
||||
} finally {
|
||||
rmSync(lockPath, { force: true });
|
||||
}
|
||||
}
|
||||
|
||||
async function refresh(root) {
|
||||
const info = readJson(join(stateDir(), 'daemon.json'), null);
|
||||
if (!info?.port) throw new Error('daemon not running');
|
||||
const response = await fetch(`http://127.0.0.1:${info.port}/refresh`, { method: 'POST',
|
||||
headers: { 'x-qmd-token': info.token, 'content-type': 'application/json' }, body: JSON.stringify({ repo: root }),
|
||||
signal: AbortSignal.timeout(35 * 60_000) });
|
||||
if (!response.ok) throw new Error(`refresh answered ${response.status}`);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const { values } = parseArgs({ options: { root: { type: 'string' } } });
|
||||
const root = resolve(values.root || execFileSync('git', ['rev-parse', '--show-toplevel'], { encoding: 'utf8' }).trim());
|
||||
const dir = stateDir();
|
||||
mkdirSync(dir, { recursive: true });
|
||||
// A refresh through the daemon indexes the repository incrementally; start the daemon first if needed.
|
||||
await withLock(join(dir, `${collectionName(root)}.lock`), async () => {
|
||||
try {
|
||||
await refresh(root);
|
||||
} catch {
|
||||
try { await startDaemon(); } catch { /* another daemon may have won the start race */ }
|
||||
await refresh(root);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (import.meta.url === pathToFileURL(process.argv[1] || '').href) {
|
||||
main().catch((error) => {
|
||||
try { appendFileSync(join(stateDir(), 'warm.log'), `${new Date().toISOString()} ${error.message}\n`); } catch { /* ignore */ }
|
||||
}).finally(() => process.exit(0));
|
||||
}
|
||||
Reference in new issue
Block a user