On NixOS (and other non-FHS systems) /bin/bash does not exist, so
scripts with an absolute shebang fail to run. Switch the remaining
holdouts to /usr/bin/env bash: eleven scripts/*.sh,
test-infrastructure/run.sh, and the three Claude Code hook scripts
emitted by src/cli/cli.c (gate, session reminder, subagent reminder).
Distilled from PR #674, with parser-test coverage preserved: the
infra_parse_shell* fixtures in tests/test_pipeline.c intentionally keep
#!/bin/bash so absolute-path shebang extraction stays covered, and
tests/repro fixtures are untouched.
Also replace the GitHub-PAT-shaped fixture string flagged in the #674
thread with an obviously fake placeholder (ghp_FAKE...) that still
matches the ghp_ + 36-alnum secret detector.
Co-authored-by: Sandro Jäckel <sandro.jaeckel@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Flat BM25 queries of the form:
SELECT ... FROM nodes_fts JOIN nodes WHERE MATCH ? AND project=? ORDER BY bm25() LIMIT N
block FTS5 WAND/MaxScore early-exit — the outer JOIN+WHERE is invisible to
the FTS5 planner, so it scores every matching document before any filter fires.
On a large codebase with 100K+ matches this causes 2–16 minute queries.
Fix: two-step subquery. The inner FTS5-only query:
SELECT rowid, bm25(nodes_fts) FROM nodes_fts WHERE MATCH ? ORDER BY bm25() LIMIT 2000
can early-terminate because no outer predicate blocks it. The outer query
then joins and filters at most BM25_INNER_LIMIT (2000) candidates.
The count query uses the identical inner-limit subquery, so it benefits too.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Make project a required CLI argument instead of a hardcoded name,
and remove internal query strings used during development testing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three compounding bugs caused 1.5–8.5s latency on name_pattern= searches against
large projects (216K nodes), now reduced to ~0ms query time (cold-start dominates):
Fix 1 — regex compiled once per statement, not once per row
sqlite_regexp / sqlite_iregexp now use sqlite3_get_auxdata / sqlite3_set_auxdata
to cache the compiled cbm_regex_t for the lifetime of the statement. Previously
cbm_regcomp + cbm_regfree ran for every row scanned.
Fix 2 — LIKE pre-filter cuts rows reaching the regex
Wire cbm_extract_like_hints (already implemented but dead) into search_where_basic
via a new where_add_like_hints helper. For .*Controller.* this prepends
n.name LIKE '%Controller%', letting the idx_nodes_name index satisfy the LIKE
clause first and passing only matching rows to iregexp(). Added search_like_pool_t
to manage the malloc'd LIKE strings across both statement executions.
ST_SEARCH_MAX_BINDS raised 16 → 32 to accommodate extra bind slots.
Fix 3 — count query no longer runs per-row edge subqueries
The count SQL previously wrapped the full SELECT (which includes two correlated
subqueries for in_deg / out_deg) in SELECT COUNT(*) FROM (...), executing those
edge counts for every matching row even though the count needs none of that.
Non-degree-filter path now uses SELECT COUNT(*) FROM nodes n WHERE <same WHERE>,
which has no per-row subqueries. Degree-filter path retains the wrapped form
since it needs those columns for the filter.
Benchmark on home-ubuntu-dev-sis (216K nodes, 509MB DB):
Query BEFORE AFTER speedup
name_pattern=.*Controller.* 3099ms 508ms 6×
name_pattern=.*Service.* 2006ms 506ms 4×
name_pattern=.*Repository.* 2006ms 508ms 4×
name_pattern=specificFuncName 1506ms 507ms 3×
label=Method + name_pattern=.*get.* 8509ms 509ms 17×
name_pattern=.*Approve.* 1506ms 507ms 3×
name_pattern=.*authorize.* 1506ms 509ms 3×
The ~500ms floor is cold-start I/O (opening a 509MB file from disk). In the
long-running MCP server process the warm-cache query time is sub-millisecond.
All store search tests pass including pagination, degree filter, and extract_like_hints.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>