"""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 ( GLOBAL_PROJECT, INBOX_PROJECT, load_project_registry, ) _TOP_NAV_LINKS = [ ("π Home", "/wiki"), ("π‘ Activity", "/wiki/activity"), ("π Triage", "/admin/triage"), ("π Dashboard", "/admin/dashboard"), ] def _render_topnav(active_path: str = "") -> str: items = [] for label, href in _TOP_NAV_LINKS: cls = "topnav-item active" if href == active_path else "topnav-item" items.append(f'{label}') return f'' def render_html(title: str, body_html: str, breadcrumbs: list[tuple[str, str]] | None = None, active_path: str = "") -> str: topnav = _render_topnav(active_path) crumbs = "" if breadcrumbs: parts = [] for label, href in breadcrumbs: if href: parts.append(f'{label}') else: parts.append(f"{label}") crumbs = f'' nav = topnav + crumbs 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 = ['
Autonomous actors: ' +
" Β· ".join(f'{k} ({v})' for k, v in auto_actors.items()) +
'
Entities that don\'t belong to a specific ' 'project yet. Inbox holds pre-project leads and quotes. ' 'Global holds cross-project facts (material properties, ' 'vendor capabilities) that apply everywhere.
' ) lines.append('Pre-project leads, quotes, early conversations.
' f'Cross-project facts: materials, vendors, shared knowledge.
' f'{p["description"][:140]}
') lines.append(f'Projects that appear in memories but aren\'t yet registered. ' 'One click to promote them to first-class projects.
') lines.append('{len(all_entities)} entities Β· {len(all_memories)} active memories Β· {len(projects)} projects
') # Triage queue prompt β surfaced prominently if non-empty if pending: tone = "triage-warning" if len(pending) > 50 else "triage-notice" lines.append( f'ποΈ {len(pending)} candidates awaiting triage β ' f'review now β
' ) lines.append(f'Triage Queue Β· API Dashboard (JSON) Β· Health Check
') return render_html("AtoCore Wiki", "\n".join(lines), active_path="/wiki") 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", "")], ) # --------------------------------------------------------------------- # /wiki/capture β DEPRECATED emergency paste-in form. # Kept as an endpoint because POST /interactions is public anyway, but # REMOVED from the topnav so it's not promoted as the capture path. # The sanctioned surfaces are Claude Code (Stop + UserPromptSubmit # hooks) and OpenClaw (capture plugin with 7I context injection). # This form is explicitly a last-resort for when someone has to feed # in an external log and can't get the normal hooks to reach it. # --------------------------------------------------------------------- def render_capture() -> str: lines = ['If you\'re reaching for this page because you had a chat somewhere AtoCore didn\'t see, ' 'fix the capture surface instead β don\'t paste. The deliberate scope is Claude Code + OpenClaw.
' ) lines.append('') lines.append(""" """) lines.append( 'The extractor is aggressive about capturing signal β don\'t hand-filter. ' 'If the conversation had nothing durable, triage will auto-reject.
' ) return render_html( "Capture β AtoCore", "\n".join(lines), breadcrumbs=[("Wiki", "/wiki"), ("Capture", "")], active_path="/wiki/capture", ) # --------------------------------------------------------------------- # Phase 7E β /wiki/memories/{id}: memory detail page # --------------------------------------------------------------------- def render_memory_detail(memory_id: str) -> str | None: """Full view of a single memory: content, audit trail, source refs, neighbors, graduation status. Fills the drill-down gap the list views can't.""" from atocore.memory.service import get_memory_audit from atocore.models.database import get_connection with get_connection() as conn: row = conn.execute("SELECT * FROM memories WHERE id = ?", (memory_id,)).fetchone() if row is None: return None import json as _json mem = dict(row) try: tags = _json.loads(mem.get("domain_tags") or "[]") or [] except Exception: tags = [] lines = [f'') # Metadata row meta_items = [ f'{mem["status"]}', f'{mem["memory_type"]}', ] if mem.get("project"): meta_items.append(f'{mem["project"]}') meta_items.append(f'confidence: {float(mem.get("confidence") or 0):.2f}') meta_items.append(f'refs: {int(mem.get("reference_count") or 0)}') if mem.get("valid_until"): meta_items.append(f'valid until {str(mem["valid_until"])[:10]}') lines.append(f'{mem["content"]}
{" Β· ".join(meta_items)}
') if tags: tag_links = " ".join(f'{t}' for t in tags) lines.append(f'') lines.append(f'') # Graduation if mem.get("graduated_to_entity_id"): eid = mem["graduated_to_entity_id"] lines.append( f'
This memory was promoted to a typed entity: ' f'{eid[:8]}
' ) # Source chunk if mem.get("source_chunk_id"): lines.append(f'{mem["source_chunk_id"]}
{a["timestamp"]} '
f'{a["action"]} '
f'{a["actor"]}{note}No tag specified.
", breadcrumbs=[("Wiki", "/wiki"), ("Domains", "")]) all_mems = get_memories(active_only=True, limit=500) matching = [m for m in all_mems if any((t or "").lower() == tag for t in (m.domain_tags or []))] # Group by project by_project: dict[str, list] = {} for m in matching: by_project.setdefault(m.project or "(global)", []).append(m) lines = [f'{tag}No memories currently carry the tag {tag}.
Domain tags are assigned by the extractor when it identifies ' 'the topical scope of a memory. They update over time.
' ) return render_html( f"Domain: {tag}", "\n".join(lines), breadcrumbs=[("Wiki", "/wiki"), ("Domains", ""), (tag, "")], ) # Sort projects by count descending, (global) last def sort_key(item: tuple[str, list]) -> tuple[int, int]: proj, mems = item return (1 if proj == "(global)" else 0, -len(mems)) for proj, mems in sorted(by_project.items(), key=sort_key): proj_link = proj if proj == "(global)" else f'{proj}' lines.append(f'By action: ' + " Β· ".join(f'{k}: {v}' for k, v in sorted(by_action.items(), key=lambda x: -x[1])) + '
') lines.append('By actor: ' +
" Β· ".join(f'{k}: {v}' for k, v in sorted(by_actor.items(), key=lambda x: -x[1])) +
'
{ts_short} '
f'{a["action"]} '
f'{a["actor"]} '
f'{mid_short}'
f'{note}'
+ (f'