Clean up lint/LSP across gateway, Android, and desktop (alpha -> stable)
Gateway (gateway-plugin/): - Fix interactive_setup broken imports: print helpers were imported from the wrong hermes module (hermes_cli.config instead of hermes_cli.cli_output) plus a non-existent print_code; the try/except swallowed the ImportError so `hermes gateway setup` for android always bailed out early. - Fix release_scoped_lock type error (str | None passed where str required). - Rewrite empty `except: pass` blocks as contextlib.suppress with rationale. - Restructure two ambiguous ws_server try blocks (hello-auth, frame loop). - Ruff cleanup: type annotations, import sorting, line wrapping, magic values -> named constants, `raise ... from e`, complexity. Add gateway-plugin/ruff.toml. - Add pyrightconfig.json so the Python LSP resolves hermes-runtime imports. - Suppress verified false positives inline (parameterized SQL, column-name "secrets", hermes-generated media path). Android (app/androidApp + app/shared): - Consolidate launcher icons into a single mipmap-anydpi (minSdk 29 >= 26) with the monochrome layer; clears ObsoleteSdkInt + MonochromeLauncherIcon. - Bump core-splashscreen 1.0.1 -> 1.2.0; pin targetSdk 34 (deliberate). - Suppress verified findings inline (LAN ws:// default, correct GCM IV usage). Desktop (app/desktopApp): - Move the desktop to a Java 21 runtime (org.gradle.java.home) and set the desktop jvmTarget to 21 (Android stays JVM 17 / minSdk 29). Fixes the startup UnsupportedClassVersionError and restores Markdown renderer 0.44.0. Tooling/config: - .pi-lens.json: disable verified-noisy heuristics (documented in docs). - .gitleaks.toml: allowlist git-ignored false-positive paths. - docs/18-code-review.md: full findings + verification. Verified: ruff clean, pyright 0 errors, 64/64 gateway tests, all Kotlin tests, Android lint 0 issues, Android installed+launched on device, desktop launches on JDK 21.
This commit is contained in:
1 parent
9f3f9842c8
commit
678c0344c8
27 files changed
+928
-454
No files matched your search
+33
-25
@@ -26,11 +26,12 @@ machine.
|
||||
Milestone M3.
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
import logging
|
||||
import re
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
from typing import Any
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -40,7 +41,7 @@ MAX_LIMIT = 100
|
||||
|
||||
# FTS5 special chars (mirror of hermes_state_search._FTS5_SPECIAL_CHARS) for the
|
||||
# fallback sanitizer when the real one can't be imported.
|
||||
_FTS5_SPECIAL_CHARS = '+{}():"^@/#&|~[]<>,;!?$=\\\''
|
||||
_FTS5_SPECIAL_CHARS = "+{}():\"^@/#&|~[]<>,;!?$=\\'"
|
||||
_FTS5_SPECIAL_RE = re.compile(f"[{re.escape(_FTS5_SPECIAL_CHARS)}]")
|
||||
|
||||
|
||||
@@ -77,8 +78,7 @@ def _sanitize_fallback(query: str) -> str:
|
||||
def _fts_available(conn: sqlite3.Connection) -> bool:
|
||||
try:
|
||||
row = conn.execute(
|
||||
"SELECT 1 FROM sqlite_master WHERE type = 'table' "
|
||||
"AND name = 'messages_fts' LIMIT 1"
|
||||
"SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'messages_fts' LIMIT 1"
|
||||
).fetchone()
|
||||
return row is not None
|
||||
except sqlite3.Error:
|
||||
@@ -86,11 +86,11 @@ def _fts_available(conn: sqlite3.Connection) -> bool:
|
||||
|
||||
|
||||
def _scope_clauses(
|
||||
scope: str, chat_id: Optional[str], thread_id: Optional[str]
|
||||
) -> Tuple[List[str], List[Any]]:
|
||||
scope: str, chat_id: str | None, thread_id: str | None
|
||||
) -> tuple[list[str], list[Any]]:
|
||||
"""Build the scope WHERE clauses + params (empty for scope='all')."""
|
||||
clauses: List[str] = []
|
||||
params: List[Any] = []
|
||||
clauses: list[str] = []
|
||||
params: list[Any] = []
|
||||
if scope == "chat" and chat_id:
|
||||
clauses.append("s.chat_id = ?")
|
||||
params.append(chat_id)
|
||||
@@ -100,7 +100,7 @@ def _scope_clauses(
|
||||
return clauses, params
|
||||
|
||||
|
||||
def _row_to_hit(row: sqlite3.Row) -> Dict[str, Any]:
|
||||
def _row_to_hit(row: sqlite3.Row) -> dict[str, Any]:
|
||||
ts = row["timestamp"]
|
||||
try:
|
||||
ts_ms = int(float(ts) * 1000)
|
||||
@@ -120,16 +120,18 @@ def _fts_query(
|
||||
conn: sqlite3.Connection,
|
||||
query: str,
|
||||
scope: str,
|
||||
chat_id: Optional[str],
|
||||
thread_id: Optional[str],
|
||||
chat_id: str | None,
|
||||
thread_id: str | None,
|
||||
limit: int,
|
||||
) -> List[Dict[str, Any]]:
|
||||
) -> list[dict[str, Any]]:
|
||||
where = ["messages_fts MATCH ?", "(m.active = 1 OR m.compacted = 1)"]
|
||||
params: List[Any] = [query]
|
||||
params: list[Any] = [query]
|
||||
scope_clauses, scope_params = _scope_clauses(scope, chat_id, thread_id)
|
||||
where.extend(scope_clauses)
|
||||
params.extend(scope_params)
|
||||
params.extend([limit])
|
||||
# The f-string only splices a fixed set of static WHERE fragments; every
|
||||
# user value is bound via ``?`` placeholders (see execute below).
|
||||
sql = f"""
|
||||
SELECT
|
||||
m.id,
|
||||
@@ -141,10 +143,12 @@ def _fts_query(
|
||||
FROM messages_fts
|
||||
JOIN messages m ON m.id = messages_fts.rowid
|
||||
JOIN sessions s ON s.id = m.session_id
|
||||
WHERE {' AND '.join(where)}
|
||||
WHERE {" AND ".join(where)}
|
||||
ORDER BY rank
|
||||
LIMIT ?
|
||||
"""
|
||||
# Safe: every value is bound via ``?`` placeholders (no user data in SQL).
|
||||
# pi-lens-ignore: python-sql-injection
|
||||
rows = conn.execute(sql, params).fetchall()
|
||||
return [_row_to_hit(r) for r in rows]
|
||||
|
||||
@@ -153,10 +157,10 @@ def _like_query(
|
||||
conn: sqlite3.Connection,
|
||||
query: str,
|
||||
scope: str,
|
||||
chat_id: Optional[str],
|
||||
thread_id: Optional[str],
|
||||
chat_id: str | None,
|
||||
thread_id: str | None,
|
||||
limit: int,
|
||||
) -> List[Dict[str, Any]]:
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Substring fallback when FTS5 is unavailable."""
|
||||
# First plain word of the query is the LIKE needle (best-effort).
|
||||
needle = re.split(r"\s+", query.strip(), maxsplit=1)[0].strip('"')
|
||||
@@ -164,11 +168,13 @@ def _like_query(
|
||||
return []
|
||||
like = f"%{needle}%"
|
||||
where = ["(m.active = 1 OR m.compacted = 1)", "m.content LIKE ?"]
|
||||
params: List[Any] = [like]
|
||||
params: list[Any] = [like]
|
||||
scope_clauses, scope_params = _scope_clauses(scope, chat_id, thread_id)
|
||||
where.extend(scope_clauses)
|
||||
params.extend(scope_params)
|
||||
params.extend([limit])
|
||||
# The f-string only splices a fixed set of static WHERE fragments; every
|
||||
# user value is bound via ``?`` placeholders (see execute below).
|
||||
sql = f"""
|
||||
SELECT
|
||||
m.id,
|
||||
@@ -179,12 +185,14 @@ def _like_query(
|
||||
s.thread_id
|
||||
FROM messages m
|
||||
JOIN sessions s ON s.id = m.session_id
|
||||
WHERE {' AND '.join(where)}
|
||||
WHERE {" AND ".join(where)}
|
||||
ORDER BY m.timestamp DESC
|
||||
LIMIT ?
|
||||
"""
|
||||
# The needle appears twice (LIKE + instr); params order: like, scope..., needle, limit
|
||||
full_params = [like, *scope_params, needle, limit]
|
||||
# Safe: every value is bound via ``?`` placeholders (no user data in SQL).
|
||||
# pi-lens-ignore: python-sql-injection
|
||||
rows = conn.execute(sql, full_params).fetchall()
|
||||
return [_row_to_hit(r) for r in rows]
|
||||
|
||||
@@ -193,10 +201,10 @@ def search(
|
||||
db_path: Path,
|
||||
query: str,
|
||||
scope: str = "all",
|
||||
chat_id: Optional[str] = None,
|
||||
thread_id: Optional[str] = None,
|
||||
chat_id: str | None = None,
|
||||
thread_id: str | None = None,
|
||||
limit: int = DEFAULT_LIMIT,
|
||||
) -> List[Dict[str, Any]]:
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Run a scoped search over the session store. Returns a list of hits.
|
||||
|
||||
Never raises: any DB/FTS error yields an empty result (the caller sends an
|
||||
@@ -230,7 +238,7 @@ def search(
|
||||
logger.warning("android search: query failed: %s", e)
|
||||
return []
|
||||
finally:
|
||||
try:
|
||||
# Best-effort: a close failure on a read-only connection is not
|
||||
# actionable (nothing to roll back).
|
||||
with contextlib.suppress(Exception):
|
||||
conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
Reference in new issue
Block a user