"""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 = ['

AtoCore Wiki

'] lines.append('') for bucket_name, items in buckets.items(): if not items: continue lines.append(f'

{bucket_name}

') lines.append('
') for p in items: client_line = f'
{p["client"]}
' if p["client"] else '' stage_tag = f'{p["stage"].split(" — ")[0]}' if p["stage"] else '' lines.append(f'') lines.append(f'

{p["id"]} {stage_tag}

') lines.append(client_line) lines.append(f'

{p["description"][:140]}

') lines.append(f'
{p["entities"]} entities · {p["memories"]} memories · {p["state"]} state
') lines.append('
') lines.append('
') # Quick stats all_entities = get_entities(limit=500) all_memories = get_memories(active_only=True, limit=500) lines.append('

System

') 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'

[{ent.entity_type}] {ent.name}

'] if ent.project: lines.append(f'

Project: {ent.project}

') if ent.description: lines.append(f'

{ent.description}

') if ent.properties: lines.append('

Properties

') lines.append(f'

confidence: {ent.confidence} · status: {ent.status} · created: {ent.created_at}

') if ctx["relationships"]: lines.append('

Relationships

') breadcrumbs = [("Wiki", "/wiki")] if ent.project: breadcrumbs.append((ent.project, f"/wiki/projects/{ent.project}")) breadcrumbs.append((ent.name, "")) return render_html(ent.name, "\n".join(lines), breadcrumbs=breadcrumbs) def render_search(query: str) -> str: lines = [f'

Search: "{query}"

'] # Search entities by name entities = get_entities(name_contains=query, limit=20) if entities: lines.append(f'

Entities ({len(entities)})

') # Search memories all_memories = get_memories(active_only=True, limit=200) query_lower = query.lower() matching_mems = [m for m in all_memories if query_lower in m.content.lower()][:10] if matching_mems: lines.append(f'

Memories ({len(matching_mems)})

') if not entities and not matching_mems: lines.append('

No results found.

') lines.append('') return render_html( f"Search: {query}", "\n".join(lines), breadcrumbs=[("Wiki", "/wiki"), ("Search", "")], ) _TEMPLATE = """ {{title}} — AtoCore {{nav}} {{body}} """