Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
- name: Run ruff format check
run: uv run ruff format --check main.py src/ tests/ scripts/
- name: Biome check
run: npx @biomejs/biome check src/presentation/static/
run: npx -y @biomejs/biome@2.5.1 check src/presentation/static/

typecheck:
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion AskRag/test_rag_ask.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ def _build_test_app():
"""Build a minimal FastAPI app with only the search endpoint."""
from fastapi import FastAPI, HTTPException

app = FastAPI(title="SigmaHQ RAG β€” test")
app = FastAPI(title="SigmaForge β€” test")

@app.post("/search")
async def search(request: _SearchRequest) -> dict:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SigmaHQ RAG
# SigmaForge

A local RAG system for Sigma detection rules.

Expand Down
2 changes: 1 addition & 1 deletion main.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""SigmaHQ Rag - Application entry point."""
"""SigmaForge - Application entry point."""

import asyncio
import sys
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[project]
name = "sigmahqrag"
name = "sigmaforge"
version = "0.1.0"
description = "Local RAG system for Sigma rules"
requires-python = ">=3.12"
Expand Down
2 changes: 1 addition & 1 deletion src/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""SigmaHQ RAG - Local RAG system for Sigma rules."""
"""SigmaForge - Local RAG system for Sigma rules."""

__version__ = "0.1.0"
4 changes: 3 additions & 1 deletion src/api/routes/page_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@
async def config_page(request: Request):
"""Serve the unified config dashboard page."""
cfg = get_config().to_dict()
# Escape "</" so embedded JSON cannot break out of the <script> tag.
config_json = json.dumps(cfg).replace("</", "<\\/")
return templates.TemplateResponse(
request=request,
name="config/config.html.j2",
context={"config": cfg, "config_json": json.dumps(cfg)},
context={"config": cfg, "config_json": config_json},
)


Expand Down
5 changes: 4 additions & 1 deletion src/api/routes/page_logs.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import annotations

import json

from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
Expand All @@ -15,8 +17,9 @@
@router.get("/logs", response_class=HTMLResponse)
async def logs_explorer(request: Request):
cfg = get_config().to_dict()
config_json = json.dumps(cfg).replace("</", "<\\/")
return templates.TemplateResponse(
request=request,
name="logs/index.html.j2",
context={"config": cfg, "config_json": __import__("json").dumps(cfg)},
context={"config": cfg, "config_json": config_json},
)
2 changes: 1 addition & 1 deletion src/api/v1/system/logs.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def get_logs_dir() -> Path:


LOG_FILES = {
"system": "sigmahqrag.log",
"system": "sigmaforge.log",
"llamacpp": "llama.cpp.log",
"qdrant": "qdrant.log",
}
Expand Down
2 changes: 1 addition & 1 deletion src/application/system/duckdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class DuckDbStatus(NamedTuple):
needs_clean: bool


# Project root: goes up from src/application/system/ to sigmahqrag/
# Project root: goes up from src/application/system/ to SigmaForge/
_PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent.parent

# Tables that must exist per initdb.sql (CREATE TABLE IF NOT EXISTS order)
Expand Down
2 changes: 1 addition & 1 deletion src/config/constants.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Application constants - single source of truth for cross-cutting values."""

SCHEMA_VERSION = 20260619
SCHEMA_VERSION = 20260704

SIGMA_SPEC_REPO = "https://github.com/SigmaHQ/sigma-specification"
SIGMA_SPEC_REF = "main"
Expand Down
2 changes: 1 addition & 1 deletion src/infrastructure/database/initdb.sql
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
-- SigmaHQ RAG - DuckDB Schema + Seed Data
-- SigmaForge - DuckDB Schema + Seed Data
-- Single source of truth for database initialization.
-- All CREATE TABLE are IF NOT EXISTS and INSERT are OR IGNORE for idempotency.

Expand Down
4 changes: 2 additions & 2 deletions src/infrastructure/vectorstore/auto_start.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ async def start_qdrant(
_started_binary_service = binary_service

assert health_check is not None
for _ in range(10):
for _ in range(30):
try:
health = await health_check(timeout=2.0, port=config.qdrant_port)
if health.get("status") == "active":
Expand All @@ -102,7 +102,7 @@ async def start_qdrant(
pass
await asyncio.sleep(1)

raise ServiceStartError("Qdrant process started but health check timed out after 10s")
raise ServiceStartError("Qdrant process started but health check timed out after 30s")


async def stop_qdrant() -> None:
Expand Down
1 change: 1 addition & 0 deletions src/infrastructure/vectorstore/collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ def _create_payload_indexes_sync(client, collection_name: str) -> None:
collection_name=collection_name,
field_name=field_name,
field_schema=field_type,
wait=False,
)
except Exception:
logger.debug(
Expand Down
10 changes: 8 additions & 2 deletions src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ def _setup_logging(level: str = "INFO", max_size: str = "10M", max_files: int =
"""Setup logging to file with rotation."""
from src.config.settings import LOGS_DIR

log_file = LOGS_DIR / "sigmahqrag.log"
log_file = LOGS_DIR / "sigmaforge.log"
log_level = getattr(logging, level.upper(), logging.INFO)
LOGS_DIR.mkdir(parents=True, exist_ok=True)

Expand Down Expand Up @@ -207,6 +207,9 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
old_stderr = sys.stderr
sys.stderr = StringIO()

def _status(message: str) -> None:
print(f" {message}", file=old_stderr, flush=True)

try:
db = DatabaseService()
db.initialize()
Expand Down Expand Up @@ -242,13 +245,15 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
logger.info("Services validated.")

if not setup_mode:
_status("Starting llama.cpp...")
try:
await start_llamacpp()
logger.info("llama.cpp started.")
except Exception as e:
logger.warning("llama.cpp auto-start skipped: %s", e)

qdrant_started = False
_status("Starting Qdrant...")
try:
await start_qdrant()
qdrant_started = True
Expand All @@ -257,6 +262,7 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
logger.warning("Qdrant auto-start skipped: %s", e)

if qdrant_started:
_status("Initializing Qdrant collections...")
await _init_qdrant_collections()
logger.info("Qdrant collections initialized.")
else:
Expand Down Expand Up @@ -300,7 +306,7 @@ def _validate_services() -> None:
def create_app() -> FastAPI:
"""Create and configure the FastAPI application."""
app = FastAPI(
title="SigmaHQ RAG",
title="SigmaForge",
version="0.1.0",
description="Local RAG system for Sigma rules",
lifespan=lifespan,
Expand Down
103 changes: 103 additions & 0 deletions src/presentation/static/css/base/local.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/* Local page utilities */

.file-stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 16px;
margin-bottom: 24px;
}

.upload-section {
background: var(--bg-card);
border: 2px dashed var(--color-secondary);
border-radius: 8px;
padding: 40px 20px;
text-align: center;
cursor: pointer;
transition: all 0.2s;
margin-bottom: 24px;
}

.upload-section:hover,
.upload-section.dragover {
border-color: var(--color-primary);
background: var(--bg-status-row);
}

.upload-section h3 {
margin: 0 0 8px 0;
color: var(--text-card-title);
}

.upload-section p {
margin: 0;
color: var(--text-card-label);
font-size: 14px;
}

#file-input {
display: none;
}

.files-table {
background: var(--bg-card);
border-radius: 8px;
overflow: hidden;
box-shadow: 0 1px 3px var(--shadow-card);
}

.files-table table {
width: 100%;
border-collapse: collapse;
}

.files-table th {
background: var(--bg-sidebar);
color: var(--text-table-th);
padding: 12px 16px;
text-align: left;
font-weight: 600;
font-size: 14px;
}

.files-table td {
padding: 12px 16px;
border-bottom: 1px solid var(--border-table-inner);
font-size: 14px;
}

.files-table tr:hover {
background: var(--bg-status-row);
}

.empty-state {
text-align: center;
padding: 60px 20px;
color: var(--text-card-label);
}

.empty-state svg {
width: 64px;
height: 64px;
margin-bottom: 16px;
opacity: 0.5;
}

#upload-progress {
display: none;
background: var(--bg-card);
border-radius: 8px;
padding: 16px;
margin-bottom: 24px;
box-shadow: 0 1px 3px var(--shadow-card);
}

#status-message {
margin-top: 12px;
font-size: 14px;
}

.upload-section:focus-visible {
outline: 2px solid var(--color-focus);
outline-offset: 2px;
}
Loading
Loading