"""AtoCore Wiki — navigable HTML pages from structured data. A lightweight wiki served directly from the AtoCore API. Every page is generated on-demand from the database so it's always current. Source of truth is the database — the wiki is a derived view. Routes: /wiki Homepage with project list + search /wiki/projects/{name} Full project overview /wiki/entities/{id} Entity detail with relationships /wiki/search?q=... Search entities, memories, state """ from __future__ import annotations import markdown as md from atocore.context.project_state import get_state from atocore.engineering.service import ( get_entities, get_entity, get_entity_with_context, get_relationships, ) from atocore.memory.service import get_memories from atocore.projects.registry import load_project_registry def render_html(title: str, body_html: str, breadcrumbs: list[tuple[str, str]] | None = None) -> str: nav = "" if breadcrumbs: parts = [] for label, href in breadcrumbs: if href: parts.append(f'{label}') else: parts.append(f"{label}") nav = f'' return _TEMPLATE.replace("{{title}}", title).replace("{{nav}}", nav).replace("{{body}}", body_html) def render_homepage() -> str: projects = [] try: registered = load_project_registry() for p in registered: entity_count = len(get_entities(project=p.project_id, limit=200)) memory_count = len(get_memories(project=p.project_id, active_only=True, limit=200)) state_entries = get_state(p.project_id) # Pull stage/type/client from state entries stage = "" proj_type = "" client = "" for e in state_entries: if e.category == "status": if e.key == "stage": stage = e.value elif e.key == "type": proj_type = e.value elif e.key == "client": client = e.value projects.append({ "id": p.project_id, "description": p.description, "entities": entity_count, "memories": memory_count, "state": len(state_entries), "stage": stage, "type": proj_type, "client": client, }) except Exception: pass # Group by high-level bucket buckets: dict[str, list] = { "Active Contracts": [], "Leads & Prospects": [], "Internal Tools & Infra": [], "Other": [], } for p in projects: t = p["type"].lower() s = p["stage"].lower() if "lead" in t or "lead" in s or "prospect" in s: buckets["Leads & Prospects"].append(p) elif "contract" in t or ("active" in s and "contract" in s): buckets["Active Contracts"].append(p) elif "infra" in t or "tool" in t or "internal" in t: buckets["Internal Tools & Infra"].append(p) else: buckets["Other"].append(p) lines = ['
{p["description"][:140]}
') lines.append(f'{len(all_entities)} entities · {len(all_memories)} active memories · {len(projects)} projects
') lines.append(f'API Dashboard (JSON) · Health Check
') return render_html("AtoCore Wiki", "\n".join(lines)) def render_project(project: str) -> str: from atocore.engineering.mirror import generate_project_overview markdown_content = generate_project_overview(project) # Convert entity names to links entities = get_entities(project=project, limit=200) html_body = md.markdown(markdown_content, extensions=["tables", "fenced_code"]) for ent in sorted(entities, key=lambda e: len(e.name), reverse=True): linked = f'{ent.name}' html_body = html_body.replace(f"{ent.name}", f"{linked}", 1) return render_html( f"{project}", html_body, breadcrumbs=[("Wiki", "/wiki"), (project, "")], ) def render_entity(entity_id: str) -> str | None: ctx = get_entity_with_context(entity_id) if ctx is None: return None ent = ctx["entity"] lines = [f'Project: {ent.project}
') if ent.description: lines.append(f'{ent.description}
') if ent.properties: lines.append('No results found.
') lines.append('') return render_html( f"Search: {query}", "\n".join(lines), breadcrumbs=[("Wiki", "/wiki"), ("Search", "")], ) _TEMPLATE = """