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:
Julian lechnerandClaude Opus 5.5 committed 2026-10-08 11:37:47 +02:00
1 parent 08f015de92
commit f3c88a089e
12 files changed
+202 -14

No files matched your search

+4 -1
View File
@@ -16,7 +16,10 @@
},
"hooks": {
"PreCompact": [{"hooks": [{"type": "command", "command": "__HOOK_COMMAND__ \"__AI_CONFIG_ROOT__/hooks/scripts/__COMPACT_SCRIPT__\"", "timeout": 4}]}],
"SessionStart": [{"hooks": [{"type": "command", "command": "__HOOK_COMMAND__ \"__AI_CONFIG_ROOT__/hooks/scripts/__SESSION_POINTER_SCRIPT__\"", "timeout": 4}]}],
"SessionStart": [
{"hooks": [{"type": "command", "command": "__HOOK_COMMAND__ \"__AI_CONFIG_ROOT__/hooks/scripts/__SESSION_POINTER_SCRIPT__\"", "timeout": 4}]},
{"hooks": [{"type": "command", "command": "__HOOK_COMMAND__ \"__AI_CONFIG_ROOT__/hooks/scripts/__QMD_WARM_SCRIPT__\"", "timeout": 4}]}
],
"Stop": [
{
"hooks": [
+8 -1
View File
@@ -4,7 +4,7 @@ sandbox_mode = "workspace-write"
[sandbox_workspace_write]
network_access = true
writable_roots = ['__AI_CONFIG_ROOT__/../.my-ai-configuration/semantic-retrieval/data']
writable_roots = ['__AI_CONFIG_ROOT__/../.my-ai-configuration/qmd', '__AI_CONFIG_ROOT__/../.cache/qmd']
[windows]
sandbox = "unelevated"
@@ -26,3 +26,10 @@ type = "command"
command = '__HOOK_COMMAND__ "__AI_CONFIG_ROOT__/hooks/__HOOK_SCRIPT__"'
command_windows = '__POWERSHELL_HOOK_COMMAND__ "__AI_CONFIG_ROOT__/hooks/__POWERSHELL_HOOK_SCRIPT__"'
timeout = 8
[[hooks.SessionStart]]
[[hooks.SessionStart.hooks]]
type = "command"
command = '__HOOK_COMMAND__ "__AI_CONFIG_ROOT__/hooks/scripts/__QMD_WARM_SCRIPT__"'
command_windows = '__POWERSHELL_HOOK_COMMAND__ "__AI_CONFIG_ROOT__/hooks/scripts/__QMD_WARM_SCRIPT__"'
timeout = 4
+2 -1
View File
@@ -209,7 +209,8 @@ foreach ($shell in @('powershell','bash')) {
$allowRulesJson = ConvertTo-Json -InputObject @('Agent', "$shellTool(mcporter call context7.*)") -Compress
$compactScript = if ($shell -eq 'powershell') { 'Record-Compact.ps1' } else { 'record-compact.sh' }
$pointerScript = if ($shell -eq 'powershell') { 'Show-SessionStatePointer.ps1' } else { 'show-session-state-pointer.sh' }
$content = $content.Replace('__HOOK_COMMAND__', $command).Replace('__POWERSHELL_HOOK_COMMAND__', $command).Replace('__HOOK_SCRIPT__', $script).Replace('__POWERSHELL_HOOK_SCRIPT__', $script).Replace('__COMPACT_SCRIPT__', $compactScript).Replace('__SESSION_POINTER_SCRIPT__', $pointerScript).Replace('__STATUSLINE_COMMAND__', $command).Replace('__STATUSLINE_SCRIPT__', $statusLineScript).Replace('__CLAUDE_SANDBOX__', $claudeSandbox).Replace('__CLAUDE_DENY_RULES__', $denyRulesJson).Replace('__CLAUDE_ALLOW_RULES__', $allowRulesJson)
$qmdWarmScript = if ($shell -eq 'powershell') { 'Start-QmdWarm.ps1' } else { 'qmd-warm.sh' }
$content = $content.Replace('__HOOK_COMMAND__', $command).Replace('__POWERSHELL_HOOK_COMMAND__', $command).Replace('__HOOK_SCRIPT__', $script).Replace('__POWERSHELL_HOOK_SCRIPT__', $script).Replace('__COMPACT_SCRIPT__', $compactScript).Replace('__SESSION_POINTER_SCRIPT__', $pointerScript).Replace('__QMD_WARM_SCRIPT__', $qmdWarmScript).Replace('__STATUSLINE_COMMAND__', $command).Replace('__STATUSLINE_SCRIPT__', $statusLineScript).Replace('__CLAUDE_SANDBOX__', $claudeSandbox).Replace('__CLAUDE_DENY_RULES__', $denyRulesJson).Replace('__CLAUDE_ALLOW_RULES__', $allowRulesJson)
Set-Content -LiteralPath $path -Value $content -Encoding UTF8
}
+3
View File
@@ -195,12 +195,14 @@ for client in codex claude; do
statusline_script=statusline.sh
compact_script=record-compact.sh
pointer_script=show-session-state-pointer.sh
qmd_warm_script=qmd-warm.sh
if [ "$shell" = powershell ]; then
command='__POWERSHELL_COMMAND__'
script=flashbang.ps1
statusline_script=statusline.ps1
compact_script=Record-Compact.ps1
pointer_script=Show-SessionStatePointer.ps1
qmd_warm_script=Start-QmdWarm.ps1
fi
path="$output/$client-$shell/$file"
content=$(<"$path")
@@ -217,6 +219,7 @@ for client in codex claude; do
content=${content//__POWERSHELL_HOOK_SCRIPT__/$script}
content=${content//__COMPACT_SCRIPT__/$compact_script}
content=${content//__SESSION_POINTER_SCRIPT__/$pointer_script}
content=${content//__QMD_WARM_SCRIPT__/$qmd_warm_script}
content=${content//__STATUSLINE_COMMAND__/$command}
content=${content//__STATUSLINE_SCRIPT__/$statusline_script}
content=${content//__CLAUDE_SANDBOX__/$sandbox}
+46 -6
View File
@@ -11,7 +11,27 @@ def is_flashbang(hook):
for key in ('command', 'command_windows'))
def filter_options(path, flashbang_enabled=True, statusline_enabled=True):
def is_qmd_warm(hook):
return any(re.search(r'(?:qmd-warm\.sh|Start-QmdWarm\.ps1)', str(hook.get(key, '')), re.I)
for key in ('command', 'command_windows'))
def without_hooks(groups, predicate):
remaining = []
for group in groups:
hooks = [hook for hook in group.get('hooks', []) if not predicate(hook)]
if hooks:
remaining.append(dict(group, hooks=hooks))
return remaining
def filter_instructions(text, semantic_retrieval_enabled):
if semantic_retrieval_enabled:
return text
return ''.join(line for line in text.splitlines(keepends=True) if '`semantic-search`' not in line)
def filter_options(path, flashbang_enabled=True, statusline_enabled=True, semantic_retrieval_enabled=True):
content = path.read_text(encoding='utf-8-sig')
if path.name == 'settings.json':
settings = json.loads(content)
@@ -26,6 +46,12 @@ def filter_options(path, flashbang_enabled=True, statusline_enabled=True):
settings['hooks']['Stop'] = remaining
elif 'Stop' in settings.get('hooks', {}):
del settings['hooks']['Stop']
if not semantic_retrieval_enabled:
remaining = without_hooks(settings.get('hooks', {}).get('SessionStart', []), is_qmd_warm)
if remaining:
settings['hooks']['SessionStart'] = remaining
else:
settings.get('hooks', {}).pop('SessionStart', None)
if not statusline_enabled:
settings.pop('statusLine', None)
return json.dumps(settings, indent=2, ensure_ascii=False) + '\n'
@@ -44,6 +70,17 @@ def filter_options(path, flashbang_enabled=True, statusline_enabled=True):
filtered.append(section)
result = ''.join(filtered)
result = re.sub(r'(?m)^\[\[hooks\.Stop\]\][ \t]*\r?\n\s*(?=\[|\Z)(?!\[\[hooks\.Stop\.hooks\]\])', '', result)
if not semantic_retrieval_enabled:
sections = re.split(r'(?m)(?=^\s*\[\[?[^\r\n]+\]\]?\s*$)', result)
kept = []
for section in sections:
if re.match(r'\s*\[\[hooks\.SessionStart\.hooks\]\]', section):
hook = tomllib.loads(section)['hooks']['SessionStart']['hooks'][0]
if is_qmd_warm(hook):
continue
kept.append(section)
result = ''.join(kept)
result = re.sub(r'(?m)^\[\[hooks\.SessionStart\]\][ \t]*\r?\n\s*(?=\[|\Z)(?!\[\[hooks\.SessionStart\.hooks\]\])', '', result)
if not statusline_enabled:
sections = re.split(r'(?m)(?=^\s*\[\[?[^\r\n]+\]\]?\s*$)', result)
for index, section in enumerate(sections):
@@ -69,7 +106,7 @@ def merge_json(current, managed):
for key, value in managed.items():
if key == 'hooks' and isinstance(value, dict) and isinstance(result.get(key), dict):
hooks = dict(result[key])
for event in set(value) | {'Stop'}:
for event in set(value) | {'Stop', 'SessionStart'}:
groups = value.get(event, [])
old_groups = hooks.get(event, [])
if isinstance(groups, list) and isinstance(old_groups, list):
@@ -79,7 +116,7 @@ def merge_json(current, managed):
retained.append(group)
continue
foreign = [hook for hook in group['hooks'] if not (isinstance(hook, dict) and re.search(
r'(?:flashbang|statusline|record-compact|session-state-pointer)\.(?:ps1|sh)',
r'(?:flashbang|statusline|record-compact|session-state-pointer|qmd-warm|Start-QmdWarm)\.(?:ps1|sh)',
str(hook.get('command', '')) + str(hook.get('command_windows', '')), re.I))]
if foreign:
retained.append(dict(group, hooks=foreign))
@@ -131,7 +168,7 @@ def merge_toml(current_text, managed_text, statusline_enabled=True):
for header, block in current_sections:
plugin_table = re.match(r'^\[(?:plugins|marketplaces)(?:\.|\])', header)
array_table = header.startswith('[[')
owned_hook = any(re.search(r'(?:flashbang|statusline|record-compact|session-state-pointer)\.(?:ps1|sh)', line, re.I) for line in block)
owned_hook = any(re.search(r'(?:flashbang|statusline|record-compact|session-state-pointer|qmd-warm|Start-QmdWarm)\.(?:ps1|sh)', line, re.I) for line in block)
if plugin_table and header in managed_headers:
raise ValueError('Cannot merge conflicting managed plugin tables.')
if (header not in managed_headers and not array_table) or (array_table and not owned_hook):
@@ -173,17 +210,20 @@ def merge_toml(current_text, managed_text, statusline_enabled=True):
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('action', choices=('filter', 'merge-json', 'merge-toml'))
parser.add_argument('action', choices=('filter', 'filter-instructions', 'merge-json', 'merge-toml'))
parser.add_argument('--path', type=Path)
parser.add_argument('--current', type=Path)
parser.add_argument('--flashbang', choices=('true', 'false'))
parser.add_argument('--statusline', choices=('true', 'false'), default='true')
parser.add_argument('--semantic-retrieval', choices=('true', 'false'), default='true')
parser.add_argument('--managed', type=Path)
parser.add_argument('--output', type=Path)
args = parser.parse_args()
try:
if args.action == 'filter':
result = filter_options(args.path, args.flashbang == 'true', args.statusline == 'true')
result = filter_options(args.path, args.flashbang == 'true', args.statusline == 'true', args.semantic_retrieval == 'true')
elif args.action == 'filter-instructions':
result = filter_instructions(args.path.read_text(encoding='utf-8-sig'), args.semantic_retrieval == 'true')
else:
managed = args.managed.read_text(encoding='utf-8-sig') if args.managed else sys.stdin.buffer.read().decode('utf-8-sig')
current = args.current.read_text(encoding='utf-8-sig')
+7 -3
View File
@@ -227,13 +227,17 @@ function Invoke-ManagedSync {
if (-not $SemanticRetrievalEnabled -and $relative -match '(^|/)semantic-search/') { return }
$target = Resolve-ManagedPath -Destination $Destination -Relative $relative
$rawContent = Get-Content -LiteralPath $_.FullName -Raw -Encoding UTF8
$filterOptions = ((-not $FlashbangEnabled) -or (-not $StatusLineEnabled)) -and $relative -in @('settings.json', 'config.toml')
$filterOptions = ((-not $FlashbangEnabled) -or (-not $StatusLineEnabled) -or (-not $SemanticRetrievalEnabled)) -and $relative -in @('settings.json', 'config.toml')
if ($filterOptions) {
$flashbangOption = if ($FlashbangEnabled) { 'true' } else { 'false' }
$statusLineOption = if ($StatusLineEnabled) { 'true' } else { 'false' }
$rawContent = Invoke-InstallOptions -Arguments @('filter', '--path', $_.FullName, '--flashbang', $flashbangOption, '--statusline', $statusLineOption) -ErrorMessage 'Could not configure install options.'
$rawContent = Invoke-InstallOptions -Arguments @('filter', '--path', $_.FullName, '--flashbang', $flashbangOption, '--statusline', $statusLineOption, '--semantic-retrieval', $(if ($SemanticRetrievalEnabled) { 'true' } else { 'false' })) -ErrorMessage 'Could not configure install options.'
}
$needsSubstitution = $filterOptions -or $rawContent.Contains('__AI_CONFIG_ROOT__') -or $rawContent.Contains('__HOOK_COMMAND__') -or $rawContent.Contains('__POWERSHELL_HOOK_COMMAND__') -or $rawContent.Contains('__POWERSHELL_COMMAND__') -or $rawContent.Contains('__CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY__')
$filterInstructions = (-not $SemanticRetrievalEnabled) -and $relative -in @('CLAUDE.md', 'AGENTS.md', 'rules/general.md')
if ($filterInstructions) {
$rawContent = Invoke-InstallOptions -Arguments @('filter-instructions', '--path', $_.FullName, '--semantic-retrieval', 'false') -ErrorMessage 'Could not configure install options.'
}
$needsSubstitution = $filterOptions -or $filterInstructions -or $rawContent.Contains('__AI_CONFIG_ROOT__') -or $rawContent.Contains('__HOOK_COMMAND__') -or $rawContent.Contains('__POWERSHELL_HOOK_COMMAND__') -or $rawContent.Contains('__POWERSHELL_COMMAND__') -or $rawContent.Contains('__CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY__')
if ($needsSubstitution) {
$finalContent = $rawContent.Replace('__AI_CONFIG_ROOT__', $AiConfigRoot).Replace('__HOOK_COMMAND__', $ShellCommand).Replace('__POWERSHELL_HOOK_COMMAND__', $PowerShellCommand).Replace('__POWERSHELL_COMMAND__', $PowerShellCommand).Replace('__CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY__', $ClaudeConcurrency).Replace('__HOOK_SCRIPT__', 'flashbang.ps1').Replace('__POWERSHELL_HOOK_SCRIPT__', 'flashbang.ps1')
$newBytes = [System.Text.Encoding]::UTF8.GetBytes($finalContent)
+7 -2
View File
@@ -210,8 +210,13 @@ sys.stdout.buffer.write(("\n".join(lines) + "\n").encode("utf-8"))
content=$(cat -- "$source_file" && printf '\034') || return
content=${content%$'\034'}
needs_sub=false
if { [ "$flashbang_enabled" = false ] || [ "$statusline_enabled" = false ]; } && [[ "$relative" = settings.json || "$relative" = config.toml ]]; then
content=$(python3 "$(dirname -- "${BASH_SOURCE[0]}")/install-options.py" filter --path "$source_file" --flashbang "$flashbang_enabled" --statusline "$statusline_enabled" && printf '\034') || return
if { [ "$flashbang_enabled" = false ] || [ "$statusline_enabled" = false ] || [ "$semantic_retrieval_enabled" = false ]; } && [[ "$relative" = settings.json || "$relative" = config.toml ]]; then
content=$(python3 "$(dirname -- "${BASH_SOURCE[0]}")/install-options.py" filter --path "$source_file" --flashbang "$flashbang_enabled" --statusline "$statusline_enabled" --semantic-retrieval "$semantic_retrieval_enabled" && printf '\034') || return
content=${content%$'\034'}
needs_sub=true
fi
if [ "$semantic_retrieval_enabled" = false ] && [[ "$relative" = CLAUDE.md || "$relative" = AGENTS.md || "$relative" = rules/general.md ]]; then
content=$(python3 "$(dirname -- "${BASH_SOURCE[0]}")/install-options.py" filter-instructions --path "$source_file" --semantic-retrieval false && printf '\034') || return
content=${content%$'\034'}
needs_sub=true
fi
+25
View File
@@ -73,6 +73,31 @@ class InstallOptionsTests(unittest.TestCase):
subprocess.run([sys.executable, script, 'merge-toml', '--current', str(current), '--managed', str(managed_path), '--output', str(output)], check=True)
self.assertIn('# Größe', output.read_text(encoding='utf-8'))
def test_semantic_retrieval_disabled_removes_qmd_session_hook(self):
with tempfile.TemporaryDirectory() as directory:
settings = Path(directory) / 'settings.json'
settings.write_text(json.dumps({'hooks': {'SessionStart': [
{'hooks': [{'type': 'command', 'command': 'bash "/c/hooks/scripts/show-session-state-pointer.sh"'}]},
{'hooks': [{'type': 'command', 'command': 'bash "/c/hooks/scripts/qmd-warm.sh"'}]}]}}), encoding='utf-8')
result = json.loads(OPTIONS.filter_options(settings, semantic_retrieval_enabled=False))
commands = [hook['command'] for group in result['hooks']['SessionStart'] for hook in group['hooks']]
self.assertEqual(commands, ['bash "/c/hooks/scripts/show-session-state-pointer.sh"'])
def test_semantic_retrieval_disabled_removes_codex_session_hook(self):
with tempfile.TemporaryDirectory() as directory:
config = Path(directory) / 'config.toml'
config.write_text('[features]\nhooks = true\n\n[[hooks.SessionStart]]\n[[hooks.SessionStart.hooks]]\ntype = "command"\n'
'command = \'bash "/c/hooks/scripts/qmd-warm.sh"\'\ncommand_windows = \'pwsh "/c/hooks/scripts/Start-QmdWarm.ps1"\'\ntimeout = 4\n',
encoding='utf-8')
result = OPTIONS.filter_options(config, semantic_retrieval_enabled=False)
self.assertNotIn('qmd-warm', result)
self.assertNotIn('hooks.SessionStart', result)
def test_instruction_line_only_with_semantic_search(self):
text = 'Line one.\nWhen `semantic-search` is installed, run it first.\nLine three.\n'
self.assertEqual(OPTIONS.filter_instructions(text, False), 'Line one.\nLine three.\n')
self.assertEqual(OPTIONS.filter_instructions(text, True), text)
def test_filter_preserves_other_claude_hooks_and_status(self):
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / 'settings.json'
+25
View File
@@ -0,0 +1,25 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync, writeFileSync, existsSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { withLock } from '../../shared/qmd/qmd-warm.mjs';
test('a fresh lock skips the second warm-up and is released afterwards', async () => {
const lock = join(mkdtempSync(join(tmpdir(), 'qmd-warm-')), 'repo.lock');
let runs = 0;
await withLock(lock, async () => {
runs += 1;
await withLock(lock, async () => { runs += 1; });
});
assert.equal(runs, 1);
assert.equal(existsSync(lock), false);
});
test('a stale lock older than 30 minutes is replaced', async () => {
const lock = join(mkdtempSync(join(tmpdir(), 'qmd-warm-')), 'repo.lock');
writeFileSync(lock, String(Date.now() - 31 * 60_000));
let runs = 0;
await withLock(lock, async () => { runs += 1; });
assert.equal(runs, 1);
});
+11
View File
@@ -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
+7
View File
@@ -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
+57
View File
@@ -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));
}