loki-mode 6.0.0 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/mcp/server.py CHANGED
@@ -1214,6 +1214,158 @@ async def loki_quality_report() -> str:
1214
1214
  return json.dumps({"error": str(e)})
1215
1215
 
1216
1216
 
1217
+ # ============================================================
1218
+ # CODE SEARCH - ChromaDB-backed semantic code search
1219
+ # ============================================================
1220
+
1221
+ # ChromaDB connection (lazy-initialized)
1222
+ _chroma_client = None
1223
+ _chroma_collection = None
1224
+
1225
+ CHROMA_HOST = os.environ.get("LOKI_CHROMA_HOST", "localhost")
1226
+ CHROMA_PORT = int(os.environ.get("LOKI_CHROMA_PORT", "8100"))
1227
+ CHROMA_COLLECTION = os.environ.get("LOKI_CHROMA_COLLECTION", "loki-codebase")
1228
+
1229
+
1230
+ def _get_chroma_collection():
1231
+ """Get or create ChromaDB collection (lazy connection)."""
1232
+ global _chroma_client, _chroma_collection
1233
+ if _chroma_collection is not None:
1234
+ return _chroma_collection
1235
+ try:
1236
+ import chromadb
1237
+ _chroma_client = chromadb.HttpClient(host=CHROMA_HOST, port=int(CHROMA_PORT))
1238
+ _chroma_collection = _chroma_client.get_collection(name=CHROMA_COLLECTION)
1239
+ return _chroma_collection
1240
+ except Exception as e:
1241
+ logger.warning(f"ChromaDB not available: {e}")
1242
+ return None
1243
+
1244
+
1245
+ @mcp.tool()
1246
+ async def loki_code_search(
1247
+ query: str,
1248
+ n_results: int = 10,
1249
+ language: Optional[str] = None,
1250
+ file_filter: Optional[str] = None,
1251
+ type_filter: Optional[str] = None,
1252
+ ) -> str:
1253
+ """Search the loki-mode codebase semantically.
1254
+
1255
+ Finds functions, classes, and code sections by meaning, not just keywords.
1256
+ Returns file paths, line numbers, and code snippets ranked by relevance.
1257
+
1258
+ Args:
1259
+ query: Natural language search query (e.g., "rate limit detection",
1260
+ "model selection for RARV tier", "how does the council vote")
1261
+ n_results: Number of results to return (default 10, max 30)
1262
+ language: Filter by language: "shell", "python", "markdown" (optional)
1263
+ file_filter: Filter by file path substring (e.g., "autonomy/", "dashboard/") (optional)
1264
+ type_filter: Filter by chunk type: "function", "class", "header", "section", "file" (optional)
1265
+ """
1266
+ _emit_tool_event_async('loki_code_search', 'start', query=query)
1267
+
1268
+ collection = _get_chroma_collection()
1269
+ if collection is None:
1270
+ return json.dumps({
1271
+ "error": "ChromaDB not available. Start it with: docker start loki-chroma",
1272
+ "hint": "Re-index with: python3.12 tools/index-codebase.py --reset"
1273
+ })
1274
+
1275
+ n_results = min(max(1, n_results), 30)
1276
+
1277
+ # Build where filter
1278
+ where_clauses = []
1279
+ if language:
1280
+ where_clauses.append({"language": language})
1281
+ if type_filter:
1282
+ where_clauses.append({"type": type_filter})
1283
+
1284
+ where = None
1285
+ if len(where_clauses) == 1:
1286
+ where = where_clauses[0]
1287
+ elif len(where_clauses) > 1:
1288
+ where = {"$and": where_clauses}
1289
+
1290
+ try:
1291
+ results = collection.query(
1292
+ query_texts=[query],
1293
+ n_results=n_results,
1294
+ where=where,
1295
+ include=["documents", "metadatas", "distances"],
1296
+ )
1297
+
1298
+ # Format results
1299
+ output = []
1300
+ for i in range(len(results["ids"][0])):
1301
+ meta = results["metadatas"][0][i]
1302
+ doc = results["documents"][0][i]
1303
+ dist = results["distances"][0][i]
1304
+
1305
+ # Apply file_filter post-query (ChromaDB where doesn't support substring match)
1306
+ if file_filter and file_filter not in meta.get("file", ""):
1307
+ continue
1308
+
1309
+ # Truncate document for response
1310
+ preview = doc[:500] + "..." if len(doc) > 500 else doc
1311
+
1312
+ output.append({
1313
+ "file": meta.get("file", ""),
1314
+ "line": meta.get("line", 0),
1315
+ "name": meta.get("name", ""),
1316
+ "type": meta.get("type", ""),
1317
+ "language": meta.get("language", ""),
1318
+ "relevance": round(1 - dist, 4), # Convert distance to similarity
1319
+ "preview": preview,
1320
+ })
1321
+
1322
+ _emit_tool_event_async('loki_code_search', 'complete',
1323
+ result_status='success', result_count=len(output))
1324
+ return json.dumps({"query": query, "results": output, "total": len(output)})
1325
+
1326
+ except Exception as e:
1327
+ logger.error(f"Code search failed: {e}")
1328
+ _emit_tool_event_async('loki_code_search', 'complete',
1329
+ result_status='error', error=str(e))
1330
+ return json.dumps({"error": str(e)})
1331
+
1332
+
1333
+ @mcp.tool()
1334
+ async def loki_code_search_stats() -> str:
1335
+ """Get statistics about the code search index.
1336
+
1337
+ Shows total chunks, files indexed, breakdown by language and type.
1338
+ Useful for verifying the index is up to date.
1339
+ """
1340
+ collection = _get_chroma_collection()
1341
+ if collection is None:
1342
+ return json.dumps({"error": "ChromaDB not available"})
1343
+
1344
+ try:
1345
+ count = collection.count()
1346
+ results = collection.get(limit=count, include=["metadatas"])
1347
+
1348
+ langs = {}
1349
+ types = {}
1350
+ files = set()
1351
+ for meta in results["metadatas"]:
1352
+ lang = meta.get("language", "unknown")
1353
+ typ = meta.get("type", "unknown")
1354
+ langs[lang] = langs.get(lang, 0) + 1
1355
+ types[typ] = types.get(typ, 0) + 1
1356
+ files.add(meta.get("file", ""))
1357
+
1358
+ return json.dumps({
1359
+ "total_chunks": count,
1360
+ "unique_files": len(files),
1361
+ "by_language": langs,
1362
+ "by_type": types,
1363
+ "reindex_command": "python3.12 tools/index-codebase.py --reset",
1364
+ })
1365
+ except Exception as e:
1366
+ return json.dumps({"error": str(e)})
1367
+
1368
+
1217
1369
  # ============================================================
1218
1370
  # PROMPTS - Pre-built prompt templates
1219
1371
  # ============================================================
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loki-mode",
3
- "version": "6.0.0",
3
+ "version": "6.1.0",
4
4
  "description": "Loki Mode by Autonomi - Multi-agent autonomous startup system for Claude Code, Codex CLI, and Gemini CLI",
5
5
  "keywords": [
6
6
  "agent",