query #

Run read-only SQL over a repo's code-understanding index and get back just the answer — a project-wide question (e.g. "what names does this project use for error indicators?") answered as a small table instead of a dump of rows.

Synopsis #

lvkit query <path> "<SELECT …>"
lvkit query <path> --schema
lvkit query <path> "<SELECT …>" --format json

<path> is any file or directory inside the repo — a directory, .lvproj, .lvlib, .lvclass, or .vi. Its enclosing project is queried, so the index always covers the whole repo. Build the index first with lvkit index (or let ordinary describe/render/generate runs warm it as you work); querying an unindexed project fails with a clear message.

Options #

OptionDescription
--schemaList the queryable views and their columns, then exit (ignores the SQL argument).
--format {table,json}Output format. table (default) prints an aligned text table; json prints {columns, rows, row_count, truncated}.

The SQL argument is optional only when --schema is given; otherwise it is required.

Building & refreshing the index #

query reads a persisted index. You rarely build it by hand — query (and the callers/callees/blast-radius commands) build it on first use and incrementally refresh it before each read, and ordinary describe/render/ generate/docs runs warm it as you work. To build or refresh it explicitly:

lvkit index <path>              # build (or, with --refresh, incrementally update)
lvkit index <path> --refresh    # rebuild only content-changed/added VIs; drop deleted

<path> resolves to its enclosing project, and the whole repo is indexed (path-keyed, so same-named VIs like setUp.vi ×17 never collide). A refresh is keyed by each VI's content hash, so it only re-parses what actually changed. Pass --no-refresh to query/callers/… to skip the pre-read refresh (faster, but results may be stale if a VI changed since the last build).

The views #

Query these curated views (run lvkit query <path> --schema for the exact columns of each):

ViewOne row perKey columns
viindexed VIpath, name, qualified_name, library, is_stub, impact_score, callers_count
terminalconnector-pane terminalvi_path, name, direction, is_indicator, type_descriptor, type_kind, field_names
constantblock-diagram constantvi_path, value, label, type_descriptor, type_kind, wired_to
nodeblock-diagram nodevi_path, kind, name, prim_id, qualified_name, callee_path, parent_uid, frame
type_usetype referencevi_path, type_key
class_factclass-member VIvi_path, owning_class, parent, scope, is_accessor, accessor_field
lvproj.lvproj memberlvproj_name, member_name, member_type, resolved_path, is_in_repo

Reachability questions ("what calls this?", "what breaks if I change it?") are answerable two ways: as SQL over node's callee_path column (direct callers are SELECT DISTINCT vi_path FROM node WHERE callee_path='<path>', direct callees are SELECT callee_path FROM node WHERE vi_path='<X>' AND kind='vi', transitive blast radius a WITH RECURSIVE over callee_path), or as the typed lvkit callers / lvkit callees / lvkit blast-radius CLI commands — graph walks, not SQL, with no MCP tool twin of their own. For a quick count without the full list, impact_score (transitive) and callers_count (direct, in-degree) on the vi view are precomputed.

Example #

The driving question — the names a project uses for error indicators, as a histogram:

lvkit query MyRepo \
  "SELECT name, COUNT(*) AS n FROM terminal
   WHERE type_descriptor='Error' AND direction='output'
   GROUP BY name ORDER BY n DESC"
name                    n
----------------------  ---
error out               382
control_100             3
control_101             3
Error out               2
Test Method Error       2

The GROUP BY returns the answer — a handful of rows — rather than every matching terminal. --format json gives the same data for piping into another tool.

Read-only by construction #

query opens the index database read-only and rejects anything that isn't a single SELECT/WITH:

  • writes (INSERT/UPDATE/DELETE/DROP/CREATE), PRAGMA, and ATTACH are refused;
  • a second, stacked statement (SELECT …; DROP …) is refused;
  • a long-running query is cut off by a time limit, and results are row-capped (truncated reports when the cap was hit).

A rejected or failing query prints query error: … to stderr and exits 2.

Notes #

  • The index is stored per project root under ~/.lvkit/cache/index/projects/<slug>/index.db (SQLite/WAL), rebuilt cheaply from the content-hash-keyed extraction cache.
  • The views are the interface; you never query the tables. They are a curated layer that decouples callers from the physical schema, so the tables can change underneath without breaking your SQL. Pre-1.0, the views themselves may still evolve — but they are the intentional, documented seam, and --schema always reports the current shape. The SQL dialect is SQLite's.
  • The MCP server exposes the same surface as its query / query_schema tools — see mcp.

See also #

  • CLI reference — the map of every lvkit command (lvkit index builds the index query reads).
  • mcp — the same query surface for an AI agent.
  • describe — deep, single-VI inspection when SQL isn't the right grain.