1
0
Fork 0
Code Issues Pull requests Projects Releases 2 Packages Wiki Activity Actions Pages

Add multi-language logic exploration

This commit is contained in:
Andraxion 2026-07-25 22:29:15 -04:00
parent 9b4258c852
commit 9161889492
18 changed files with 1639 additions and 76 deletions

View file

@ -34,7 +34,7 @@ from .errors import DocForgeError
from .index import APPLICATION_ID, INDEX_SCHEMA_VERSION, ProjectIndex, re_tokenize
from .project import project_root_fingerprint
VISUALIZATION_TEMPLATE = "graph-browser@16"
VISUALIZATION_TEMPLATE = "graph-browser@17"
DEFAULT_EDGE_LIMIT = 100
MAX_EDGE_LIMIT = 400
MAX_LINEAGE_EDGE_LIMIT = 1_000
@ -166,6 +166,8 @@ class VisualizationIndexSnapshot:
authorities=_facet_rows(connection, "nodes", "authority"),
statuses=_facet_rows(connection, "nodes", "status"),
relations=_facet_rows(connection, "edges", "relation"),
tags=_tag_facet_rows(connection),
capabilities=_capability_facet_rows(connection),
max_results=self.max_results,
max_depth=self.max_depth,
snapshot=True,
@ -176,9 +178,18 @@ class VisualizationIndexSnapshot:
*,
query: str,
family: str | None,
kind: str | None,
language: str | None,
capability: str | None,
limit: int,
) -> dict[str, object]:
bounded = self._bounded_limit(limit)
clauses, filter_values = _node_filter_clauses(
family=family,
kind=kind,
language=language,
capability=capability,
)
with self._connection() as connection:
if query:
if len(query) > self.max_query_chars:
@ -191,10 +202,8 @@ class VisualizationIndexSnapshot:
expression = " AND ".join(
f'"{term.replace(chr(34), chr(34) * 2)}"' for term in terms
)
family_clause = "AND nodes.family = ?" if family else ""
values: tuple[object, ...] = (
(expression, family, bounded) if family else (expression, bounded)
)
filter_clause = "".join(f" AND {clause}" for clause in clauses)
values = (expression, *filter_values, bounded)
rows = connection.execute(
"""
SELECT nodes.*, bm25(node_fts) AS rank,
@ -202,7 +211,7 @@ class VisualizationIndexSnapshot:
FROM node_fts JOIN nodes USING(node_id)
WHERE node_fts MATCH ?
"""
+ family_clause
+ filter_clause
+ " ORDER BY rank, nodes.node_id LIMIT ?",
values,
).fetchall()
@ -212,16 +221,19 @@ class VisualizationIndexSnapshot:
item.update({"rank": row["rank"], "snippet": row["snippet"]})
results.append(item)
else:
family_clause = "WHERE family = ?" if family else ""
values = (family, bounded) if family else (bounded,)
filter_clause = f"WHERE {' AND '.join(clauses)}" if clauses else ""
values = (*filter_values, bounded)
rows = connection.execute(
f"SELECT * FROM nodes {family_clause} ORDER BY node_id LIMIT ?",
f"SELECT nodes.* FROM nodes {filter_clause} ORDER BY node_id LIMIT ?",
values,
).fetchall()
results = [_node_dict(row, include_content=False) for row in rows]
return self._result(
query=query,
family=family,
kind=kind,
language=language,
capability=capability,
count=len(results),
results=results,
snapshot=True,
@ -865,7 +877,14 @@ class VisualizationRunner:
if node_id is not None:
reader.require_node(node_id)
elif query is not None:
reader.search(query=query, family=None, limit=1)
reader.search(
query=query,
family=None,
kind=None,
language=None,
capability=None,
limit=1,
)
with self._lock:
self._reader = reader
@ -1135,8 +1154,18 @@ class VisualizationRunner:
) -> dict[str, object]:
query = _one(params, "q").strip()
family = _one(params, "family").strip() or None
kind = _one(params, "kind").strip() or None
language = _one(params, "language").strip() or None
capability = _one(params, "capability").strip() or None
limit = _integer(_one(params, "limit") or "50")
return reader.search(query=query, family=family, limit=limit)
return reader.search(
query=query,
family=family,
kind=kind,
language=language,
capability=capability,
limit=limit,
)
def _node(
self,
@ -1523,7 +1552,14 @@ class PersistentVisualizationRunner:
if node_id is not None:
snapshot.require_node(node_id)
elif query is not None:
snapshot.search(query=query, family=None, limit=1)
snapshot.search(
query=query,
family=None,
kind=None,
language=None,
capability=None,
limit=1,
)
with self._locked_registry():
existing = self._read_registry()
@ -1740,6 +1776,64 @@ def _facet_rows(connection: sqlite3.Connection, table: str, column: str) -> list
return [{"value": row[0], "count": row[1]} for row in rows]
def _tag_facet_rows(connection: sqlite3.Connection) -> list[dict[str, object]]:
rows = connection.execute(
"""
SELECT value, COUNT(*) AS count
FROM nodes, json_each(nodes.tags_json)
GROUP BY value
ORDER BY count DESC, value
"""
).fetchall()
return [{"value": row["value"], "count": row["count"]} for row in rows]
def _capability_facet_rows(connection: sqlite3.Connection) -> list[dict[str, object]]:
logic_count = connection.execute("SELECT COUNT(*) FROM logic_owners").fetchone()[0]
source_count = connection.execute(
"SELECT COUNT(*) FROM nodes WHERE source_path <> ''"
).fetchone()[0]
return [
{"value": "logic", "count": logic_count},
{"value": "source", "count": source_count},
]
def _node_filter_clauses(
*,
family: str | None,
kind: str | None,
language: str | None,
capability: str | None,
) -> tuple[list[str], list[object]]:
clauses: list[str] = []
values: list[object] = []
for label, value in (("family", family), ("kind", kind), ("language", language)):
if value is not None and len(value) > 160:
raise DocForgeError("invalid_filter", f"Node {label} filter is invalid")
if family:
clauses.append("nodes.family = ?")
values.append(family)
if kind == "callable":
clauses.append(
"EXISTS (SELECT 1 FROM json_each(nodes.tags_json) "
"WHERE value IN ('function', 'method', 'nested-function'))"
)
elif kind:
clauses.append("EXISTS (SELECT 1 FROM json_each(nodes.tags_json) WHERE value = ?)")
values.append(kind)
if language:
clauses.append("EXISTS (SELECT 1 FROM json_each(nodes.tags_json) WHERE value = ?)")
values.append(language)
if capability == "logic":
clauses.append("EXISTS (SELECT 1 FROM logic_owners WHERE owner_node_id = nodes.node_id)")
elif capability == "source":
clauses.append("nodes.source_path <> ''")
elif capability is not None:
raise DocForgeError("invalid_filter", "Node capability filter is unsupported")
return clauses, values
def _read_browser_asset(name: str) -> str:
return resources.files("docforge.assets").joinpath(name).read_text(encoding="utf-8")