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/README.md +20 -0
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/bmad-adapter.py +776 -0
- package/autonomy/loki +78 -0
- package/autonomy/prd-analyzer.py +26 -4
- package/autonomy/run.sh +149 -4
- package/dashboard/__init__.py +1 -1
- package/docs/INSTALLATION.md +1 -1
- package/docs/architecture/bmad-integration-epic.md +271 -0
- package/docs/architecture/bmad-integration-review.md +86 -0
- package/docs/architecture/bmad-integration-validation.md +249 -0
- package/docs/architecture/bmad-loki-voice-agent-council-analysis.md +61 -0
- package/mcp/__init__.py +1 -1
- package/mcp/requirements.txt +1 -0
- package/mcp/server.py +152 -0
- package/package.json +1 -1
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
|
# ============================================================
|