smriti/backend/app/main.py
Himanshu Dongre ef8faf6e4d Add Project Current State endpoint and UI panel
Add GET /api/v5/current/spaces/{id} — a packaged, computed-on-demand
snapshot of where a space is right now: current direction, counts,
attention signals (open questions, divergence, active work), active
work claims, open tasks grouped by intent, recent milestones, and
recent activity. Shared payload contract with the smriti current CLI
surface built in parallel.

Render it as the ProjectCurrentState panel at the top of LineagePage,
replacing the hand-rolled current-state summary. Extract a shared
_get_active_claims helper so the state and current endpoints report
active work identically. No schema changes.
2026-05-16 20:01:16 +05:30

154 lines
5.6 KiB
Python

import logging
import pathlib
import re
import subprocess
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.config import settings
def _resolve_git_sha() -> str:
"""Return the short git SHA of the backend directory, or 'unknown'."""
try:
result = subprocess.run(
["git", "rev-parse", "--short", "HEAD"],
cwd=pathlib.Path(__file__).resolve().parent.parent,
capture_output=True,
text=True,
timeout=5,
)
if result.returncode == 0:
return result.stdout.strip()
except Exception:
pass
return "unknown"
class SecretGuardFilter(logging.Filter):
def filter(self, record):
if isinstance(record.msg, str) and re.search(r'sk-[a-zA-Z0-9_\-]+', record.msg):
record.msg = re.sub(r'sk-[a-zA-Z0-9_\-]+', '[REDACTED_SECRET]', record.msg)
return True
def create_app() -> FastAPI:
"""Create and configure the FastAPI application."""
app = FastAPI(
title=settings.app_name,
description="Cross-agent memory handoff system",
version="0.1.0",
)
# CORS
app.add_middleware(
CORSMiddleware,
allow_origins=settings.cors_origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Secret Guard
for handler in logging.root.handlers:
handler.addFilter(SecretGuardFilter())
from app.api.routes import (
chat,
checkpoint,
claims,
commits,
context_git,
current,
lineage,
metrics,
repos,
worktrees,
)
# V2 Routes (Git for memory)
app.include_router(repos.router, prefix="/api/v2", tags=["repos"])
app.include_router(commits.router, prefix="/api/v2", tags=["commits"])
app.include_router(context_git.router, prefix="/api/v2", tags=["commits"])
# V4 Routes (Chat workspace)
app.include_router(chat.router, prefix="/api/v4", tags=["chat-v4"])
# V5 Routes
app.include_router(checkpoint.router, prefix="/api/v5/checkpoint", tags=["checkpoint-v5"])
app.include_router(lineage.router, prefix="/api/v5", tags=["lineage-v5"])
app.include_router(claims.router, prefix="/api/v5", tags=["claims-v5"])
app.include_router(metrics.router, prefix="/api/v5", tags=["metrics-v5"])
app.include_router(current.router, prefix="/api/v5", tags=["current-v5"])
app.include_router(worktrees.router, prefix="/api/v5", tags=["worktrees-v5"])
# ── Capabilities manifest ────────────────────────────────────────
# Computed once at startup so /health is zero-cost at request time.
_git_sha = _resolve_git_sha()
# Hardcoded feature flags matching the route modules included above.
# When a new feature ships (new route module, new query param, new
# schema shape), add it here so agents can detect stale backends.
_capabilities = [
"claims", # /api/v5/claims
"structured_tasks", # task objects with intent_hint/blocked_by/status
"task_ids", # structured task ids + claim.task_id support
"checkpoint_notes", # /api/v5/checkpoint/{id}/notes
"branch_disposition", # PATCH /api/v5/lineage/branches/disposition
"freshness", # since_commit_id on state endpoint
"compact_state", # --compact mode on state brief
"worktrees", # /api/v5/worktrees
"worktree_binding", # claims can bind to worktrees + state drift summary
]
@app.get("/health")
async def health_check():
return {
"status": "ok",
"git_sha": _git_sha,
"capabilities": _capabilities,
}
@app.on_event("startup")
async def startup_event():
from app.config_loader import providers_status
import logging
logger = logging.getLogger("smriti.startup")
status = providers_status()
logger.info("Provider runtime validation:")
for name, info in status.items():
if info.get("missing_package", False):
logger.warning(f" - {name}: disabled (package not installed)")
elif not info.get("has_key", False):
logger.info(f" - {name}: disabled (no API key configured)")
elif not info.get("enabled", False):
logger.info(f" - {name}: disabled (explicitly disabled in config)")
else:
logger.info(f" - {name}: configured and ready")
# Specific check for the background provider — this is what
# powers checkpoint extraction, drafting, and review. If it's
# not configured, every LLM-backed endpoint will silently
# return mock content, which is the single most confusing
# failure mode in local development.
bg = status.get("background_intelligence", {})
if not bg.get("configured"):
bg_provider = bg.get("provider", "openai")
logger.warning(
"⚠ Background intelligence provider '%s' is NOT configured. "
"Checkpoint extraction, drafting, and review will return mock "
"content instead of real LLM output. To fix: set %s in .env "
"or add the key to config/providers.yaml.",
bg_provider,
{
"openai": "OPENAI_API_KEY",
"anthropic": "ANTHROPIC_API_KEY",
"openrouter": "OPENROUTER_API_KEY",
}.get(bg_provider, f"{bg_provider.upper()}_API_KEY"),
)
return app
app = create_app()