
    6jWB                     d   d Z ddlZddlZddlZddlZddlZddlmZ ddlm	Z	 ddl
mZ  eej                  j                  dd            j                         Z ed      Z ed	      Zej$                  j'                  dd
       	 ddlmZmZ dZ	 ddlmZ dZ ed      Zdede	e   fdZej?                         d&dede defd       Z!ej?                         d&dede defd       Z"ej?                         dedefd       Z#ej?                         defd       Z$ej?                         defd       Z%ej?                         d'dedede	e&   defd       Z'ej?                         d(dede defd       Z( ed       Z)	 ddl*m+Z,  e,jZ                  d!e)      Z. e,j^                  e.      Z0e.jb                  je                  e0       dZ3ej?                         d)dede d"e6d#e def
d$       Z7e8d%k(  rejs                          yy# e$ r dZY zw xY w# e$ r dZY w xY w# e4$ rZ5dZ3dZ0Y dZ5[5cdZ5[5ww xY w)*u  
Aria-Brain MCP-Server (V5 Sprint 6, 2026-05-09)

Exposes Aria-Brain as MCP tools:
  - brain_search(query, top_n)       — Hybrid BM25-Search via aria-brain-search.py
  - memory_search(query)             — grep on /root/.claude/projects/-/memory/
  - read_brain_file(rel_path)        — direct read with path validation
  - list_brain_folders()             — show folder structure of 02-Wissen
  - brain_status()                   — quick stats (files, total bytes, last commit)

Uses FastMCP from official MCP Python SDK.
Run via: claude --mcp-server aria-brain (configured in /root/.claude/mcp.json).

Path security: nur Pfade unter /root/aria/brain/ akzeptiert.
    N)Path)Optional)FastMCP
ARIA_BRAINz/root/aria/brainz/root/.claude/projects/-/memoryz'/root/aria/scripts/aria-brain-search.pyz/root/aria/scripts)atomic_write_text	file_lockTF)scan_contentz
aria-brainrel_pathreturnc                     | syt         | z  j                         }	 |j                  t                |S # t        $ r Y yw xY w)zKStelle sicher, dass rel_path unter BRAIN_ROOT bleibt (kein Path-Traversal).N)
BRAIN_ROOTresolverelative_to
ValueError)r
   	candidates     $/root/aria/mcps/aria-brain/server.py_validate_pathr   2   sJ    h&//1Ij)   s   3 	??querytop_nc                 N   t         j                         s	dt          S | j                         syt        dt	        |d            }	 t        j                  dt        t               | dt        |      gdddi t        j                  d	t        t              i
      }|j                  j                         }|j                  dk7  r-d|j                   d|j                  j                         dd  S |xs dS # t
        j                  $ r Y yt        $ r}d| cY d}~S d}~ww xY w)u   
    Hybrid-Search im Aria-Brain (BM25 + Recency + Frontmatter + Path-Match).
    Liefert Top-N Treffer mit Pfad, Score, kurzem Kontext.

    Nutze für: konzeptuelle Fragen, Pattern-Lookup, „wo steht etwas zu X".
    z"ERROR: Search script not found at ERROR: Empty query      python3--topT
   r   capture_outputtexttimeoutenvr   zSearch exited : N  zKeine Treffer.zERROR: Search timeout (>10s)ERROR: )SEARCH_SCRIPTexistsstripmaxmin
subprocessrunstrosenvironr   stdout
returncodestderrTimeoutExpired	Exception)r   r   resultoutexcs        r   brain_searchr7   >   s    !3M?CC;;=#3ub>"EM*E7CJG=2::=|S_=
 mm!!#!#F$5$5#6b9L9L9NtPS9T8UVV&&&$$ .- s+   B+C8 2C8 8D$D$DD$D$max_hitsc                    | j                         syt        j                         s	dt         S t        j                  d| j                               D cg c]  }t        |      dkD  s| }}|sydj                  d |dd	 D              }g }t        t        j                  d
            D ]  }	 |j                  d      }t        j                  ||t        j                        s<|j                  }t        j                  d|t        j                        }	|	r"|	j!                  d      j                         dd nd}
|j#                         d	d D cg c]  }|j                         s| c}dd }dj                  |      dd }|j%                  ||
|f       t        |      |k\  s n |sd| S dj                  d |D              S c c}w # t        $ r Y 2w xY wc c}w )u   
    Grep-Search auf Memory-Index (`/root/.claude/projects/-/memory/`).
    Liefert Filename + Description + Erste Zeilen pro Treffer.

    Nutze für: konkrete Memory-Lookup (LRN-Records, Feedback, Project-Notes,
    User-Profile-Fakten).
    r   zERROR: Memory dir not found at z\W+   z?ERROR: Query contains only short words; need >3 chars per token|c              3   F   K   | ]  }t        j                  |        y wN)reescape).0ks     r   	<genexpr>z memory_search.<locals>.<genexpr>o   s     :ryy|:s   !N   *.mdignoreerrorsz^description:\s*(.+)$r          
i  u   Keine Treffer für Pattern: 

c              3   :   K   | ]  \  }}}d | d| d|   yw)z**z**
  z
  N )r@   ndss       r   rB   z memory_search.<locals>.<genexpr>   s.      %,Q1"QCvaSQC s   )r'   
MEMORY_DIRis_dirr>   splitlowerlenjoinsortedglob	read_textOSErrorsearch
IGNORECASEstem	MULTILINEgroup
splitlinesappend)r   r8   wkeywordspatternhitsfcontentname
desc_matchdescl
body_linessnippets                 r   memory_searchro   ^   s    ;;=#0==88FEKKM:Iac!fqjIHIPhh:Xbq\::GDJOOF+, 	kkk2G 99Wgr}}566D#;WbllSJ8B:##A&,,.t4D%,%7%7%9!B%?M1779!MbqQJii
+DS1GKKtW-.4yH$ -gY77;; 04  / J  		 Ns*   G)G1GG#+G#	G G c                 6   t        |       }|sd|  dt         dS |j                         sd| S |j                         sd| S 	 |j	                  d      }t        |      dkD  r|d
d dt        |       dz   S |S # t
        $ r}d| d	| cY d
}~S d
}~ww xY w)z
    Liest eine Datei aus dem Brain (`/root/aria/brain/`).
    Pfad muss relativ zum Brain-Root sein (z.B. `SOUL.md`, `02-Wissen/foo.md`).
    Path-Traversal wird verhindert.

    Liefert max 50 KB, danach truncated mit Marker.
    ERROR: Invalid path ''. Must be relative under .zERROR: File not found: zERROR: Not a regular file: replacerF   zERROR reading r"   NiP  z(

[TRUNCATED at 50 KB. Total file size: z bytes])r   r   r&   is_filerZ   r[   rV   )r
   targetrh   r6   s       r   read_brain_filerw      s     H%F&xj0J:,VWXX==?(11>>,VH550"")"4 7|fw$NsSZ|n\c"dddN  0xr#//0s   A= =	BBBBc            	      *   t         j                         s	dt          S dt          dg} t        t         j                  d            }| j	                  dt        |       d       |D ]>  }|j                         j                  }| j	                  d|j                   d| d	       @ t        t         j                         D cg c]1  }|j                         s|j                  j                  d
      r0|3 c}      }| j	                  dt        |       d       |D ]  }t        d |j                  d      D              }| j	                  d|j                   d| d       |j                  dk(  sVt        |j                         D cg c]  }|j                         s| c}      }|D ]E  }t        d |j                  d      D              }	| j	                  d|j                   d|	 d       G  dj                  |       S c c}w c c}w )zq
    Zeigt Folder-Struktur von /root/aria/brain/ und 02-Wissen/-Sub-Folders.
    Plus File-Count pro Folder.
    ERROR: Brain root not found at zBrain-Root: rK   rD   zRoot-Files ():   (zB)rs   z

Folders (c              3       K   | ]  }d   ywr   NrN   r@   _s     r   rB   z%list_brain_folders.<locals>.<genexpr>   s     5!5   u   / — z markdown filesz	02-Wissenc              3       K   | ]  }d   ywr~   rN   r   s     r   rB   z%list_brain_folders.<locals>.<genexpr>   s     9Qq9r   z    z files)r   rS   rX   rY   rb   rV   statst_sizeri   iterdir
startswithsumrglobrW   )
outputmd_filesrg   sizerP   foldersfiles_countrQ   sub_folderssf_counts
             r   list_brain_foldersr      s    0==ZL+,F joof-.H
MMLXr23 /vvx166("TF"-./
 !3!3!5cAAFFL]L]^aLbacdG
MMKG~R01 	E5QWWV_55166(&_EF 66[  QYY[!GAHHJ!!GHK  E9!&&.99QVVHF8*FCDE	E 99V d "Hs   HH5HH
#H
c            
         t         j                         s	dt          S d} d}t         j                  d      D ]4  }d|j                  v r	 | dz  } ||j	                         j
                  z  }6 d}	 t        j                  g dt         ddd	
      }|j                  j                         xs d}dt          d|  d| d|dz  dd| 
S # t        $ r Y w xY w# t        $ r}d| d}Y d}~<d}~ww xY w)zJ
    Schnelle Statistik: total files, total size, letzter Git-Commit.
    ry   r   rD   z.gitr   rI   )gitlogz-1z--format=%h %ai %an %sTr:   )cwdr   r   r    z(no git log)z(git log failed: )NzBrain-Status:
  Root: z
  Total .md files: z
  Total size: z bytes (i   z.1fz KB)
  Last commit: )r   rS   r   partsr   r   r[   r*   r+   r/   r'   r3   )total_filestotal_bytesrg   git_logr4   r6   s         r   brain_statusr      s+   
 0==KKf% QWW	1K1668+++K G
-:
 --%%'9>
, )] +$XkD.@-E F!		$!  		  -%cU!,-s*   
"C1>C 	CC	C0 C++C0rh   frontmatterc                 ~   t        |       }|sd|  dt         dS |j                  t              j                         g d}dg}t	        fd|D              st	        fd|D              rd S t
        sy	|j                  j                  d
d
       |}|r|j                         j                  d      sdg}|j                         D ]O  \  }}	t        |	t        t        f      rt        j                  t        |	            }	|j!                  | d|	        Q |j!                  d       dj#                  |      dz   |z   }|j%                  |j&                  dz         }
	 t)        |
      5  t+        ||       ddd       d| dt/        |       dS # 1 sw Y   xY w# t,        $ r}d| cY d}~S d}~ww xY w)u'  
    Schreibt eine Markdown-Datei ins Brain. Atomar (mkstemp + fsync + os.replace)
    + File-Lock (Hermes-Pattern aus KAR-125).

    Pfad-Validierung wie bei read_brain_file. Wenn `frontmatter` übergeben:
    YAML-Block wird auto-vorne eingefügt (nur wenn content keinen `---`-Header hat).

    Use-Cases:
    - Daily-Log-Append
    - Brain-Note neu anlegen
    - Memory-File aktualisieren

    KEINE Schreibrechte für: 04-Feedback/ (Kais' Eigentum), Schutzzonen
    (08-Finanzen, 09-Buch-Aria, 11-Legal — falls existent), .git/, .archive/.
    rq   rr   rs   )z04-Feedback/z08-Finanzen/z09-Buch-Aria/z	11-Legal/z.git/z	.archive/c              3   @   K   | ]  }j                  |        y wr=   r   r@   prels     r   rB   zbrain_write.<locals>.<genexpr>  s     
93>>!
9s   c              3   &   K   | ]  }|v  
 y wr=   rN   r   s     r   rB   zbrain_write.<locals>.<genexpr>  s     @fac@fs   z(ERROR: write to protected zone refused: z@ERROR: aria_atomic_write nicht importierbar (KAR-125 dependency)T)parentsexist_ok---r"   rK   rL   z.lockNzERROR atomic write: z	WRITTEN: r|   z chars))r   r   r   as_posixany_HAS_ATOMICparentmkdirlstripr   items
isinstancelisttuplejsondumpsrb   rW   with_suffixsuffixr   r   r3   rV   )r
   rh   r   rv   forbidden_prefixesforbidden_containsbodyfm_linesrA   v	lock_pathr6   r   s               @r   brain_writer      s   " H%F&xj0J:,VWXX 

Z
(
1
1
3C`%

9&8
99S@fSe@f=f9#??Q
MMt4D7>>+66u=7%%' 	)DAq!dE]+JJtAw'OOqcA3K(	) 	yy"V+g5""6==7#:;I,y! 	,fd+	, vhbT733		, 	, ,%cU++,s6   &F$ 1F>F$ F!F$ $	F<-F71F<7F<filter_yamlc                    i }| j                         j                         D ]k  }|j                         }|rd|vr|j                  d      \  }}}|j                         j                         ||j                         j                         <   m |syg }t        j                  d      D ]  }t        d |j                  D              r!	 |j                  dd      }	|	j                         j                  d      sU|	j                  d	d
      }
|
dk  rm|	d
|
 j                         }i }|j                         D ]=  }d|v s|j                  d      \  }}}|j                         ||j                         <   ? d}i }|j                         D ]1  \  }}|j                  |d      }|dk(  r||vrd} n||vrd} n|||<   3 |s|j                  t              j!                         }d}t#        j$                  d|	d|
 t"        j&                  t"        j(                  z        }|r=|j+                  d      j                         j                  d      j                  d      }|j-                  |||f       t/        |      |k\  s n |sd| S dt/        |       dg}|D ]G  \  }}}|j-                  d|        |r|j-                  d|dd         |j-                  d|        I dj1                  |      S # t        $ r Y bw xY w)u  
    Sucht Brain-Files anhand Frontmatter-Filter. `filter_yaml` ist key:value-Pairs,
    je Zeile, z.B.:

        type: research
        status: aktiv
        tags: aria-radar

    Match-Logik:
    - String-Werte: case-insensitive substring oder exact match
    - `tags:` matched wenn Filter-Tag in der Tag-Liste der Note ist
    - Multiple Filter sind AND-verknüpft

    Liefert: Pfade + Title (aus Frontmatter) + die gematchten Felder.
    :zCERROR: no filters provided. Format: 'type: research\nstatus: aktiv'rD   c              3   J   K   | ]  }|j                  d       xs |dv   yw)rs   )__pycache__Nr   )r@   parts     r   rB   z*brain_frontmatter_query.<locals>.<genexpr>?  s'     UDts#?t/?'??Us   !#zutf-8rt   )encodingrG   r   z
---   r   TrI   tagsFz^title:\s*(.+)$Nr   "'u   Keine Treffer für Filter: zFound z	 matches:r{   z    title: P   z    matched: rK   )r'   ra   	partitionrU   r   r   r   r   rZ   r[   r   r   findr   getr   r   r>   r\   r_   r]   r`   rb   rV   rW   )r   r8   filterslinerA   r   r   rf   mdr   fm_endfm_block	fm_fieldsflinefkfvmatches_allmatchedpresentr   titlemliness                          r   brain_frontmatter_queryr   !  s   " G!!#..0 7zz|s$..%1a%&WWY__%6	!"7 UDv& 'UBHHUU	<<<CD {{}''.7A&A:&>'')	((* 	3Ee|!OOC0	Ar(*
	"((*%	3 mmo 
	"FBmmB+GV|W$"'KW$"'K!GBK
	" ..,557CE		,d7FmR\\BMM=YZA
((*005;;C@KKeW-.4yH$O'R ,WI66c$i[	*+E# 0UGr#Z LL;uSbzl34}WI./	0
 99UY  		s   K??	LLz&/root/aria/scripts/aria-brain-think.pyaria_brain_thinkno_llm
stale_daysc                    t         j                         s	dt          S | j                         syt        dt	        |d            }t
        r}t        rv	 t        j                  | || |      }g }|d   }|j                  d      r|d|d    d	|d
    dz  }|j                  d| d|  d       |j                  d       |j                  |d          |j                  d       |j                  dg       }|r[|j                  dt        |       d       |D ]'  }|j                  d|d    d|d    d|d   dd       ) |j                  d       |j                  di       }	|	j                  dg       }
|
rY|j                  dt        |
       d       |
D ]%  }|j                  d|d    d |d!    d"|d#           ' |j                  d       |	j                  d$g       }|r#|j                  d%d&j                  |              |	j                  d'g       }|rb|j                  d(t        |       d       |D ].  }|j                  dd)j                  |d*          d+|d,           0 |j                  d       |	j                  d-g       }|r#|j                  d.d&j                  |              dj                  |      S 	 d1t        t               | d2t        |      d3t        |      g}|r|j                  d4       t        j                  |d5d5d6i t         j"                  d7t        t$              i8      }|j&                  j                         }|j(                  d9k7  r-d:|j(                   d+|j*                  j                         d0d;  S |xs d<S # t        $ r}d/| cY d0}~S d0}~ww xY w# t        j,                  $ r Y y=t        $ r}d>| cY d0}~S d0}~ww xY w)?uw  
    Synthesis + Gap-Analysis über das Aria Brain (KAR-622).

    Im Gegensatz zu brain_search (gibt Treffer-Liste) liefert brain_think:
    - Eine synthetisierte Antwort mit zitierten Quellen ([slug])
    - GAP ANALYSIS: STALE (veraltete Treffer), UNCITED (unbelegte Aspekte),
      CONTRADICTION (widersprüchliche Dokumente), MISSING (unbekannte Aspekte)
    - LLM-Synthese wenn ein Key verfügbar ist (Gemini-Flash → DeepSeek → GPT-4o-mini),
      sonst deterministisch extraktiv.

    Nutze für: "Was weiß das Brain über X?", "Gibt es Widersprüche zum Thema Y?",
    "Wo hat das Brain Lücken zu Z?".

    Args:
        query: Frage oder Thema für die Synthese
        top_n: Anzahl der Treffer (1-20, Default 7)
        no_llm: True = deterministischer Extraktions-Modus ohne LLM-Aufruf
        stale_days: Alter in Tagen ab dem eine Note als STALE gilt (Default 90)
    z!ERROR: Think script not found at r   r   r   )r   topuse_llmr   modellm_providerr|   /	llm_modelr   z[think/z	] query: rK   ANSWERanswerrI   	citationszCITATIONS (rz   z  [slugz] brain/pathz (score=scorez.3fgapsstalezGAP STALE (r{   u    — age_daysu   d — reasonuncitedzGAP UNCITED: z, contradictionzGAP CONTRADICTION (z vs pathsr"   signalmissingz!GAP MISSING (Brain kennt nicht): zERROR in think (in-process): Nr   r   z--stale-daysz--no-llmT-   r   r   r   zThink exited r#   zKeine Antwort generiert.zERROR: Think timeout (>45s)r$   )THINK_SCRIPTr&   r'   r(   r)   
_HAS_THINK
_think_modthinkr   rb   rV   rW   r3   r,   r*   r+   r-   r.   r   r/   r0   r1   r2   )r   r   r   r   r4   r   mode_strr   cr   r   rQ   r   r   r   r6   cmdr5   s                     r   brain_thinkr     s   *  2<.AA;;=#3ub>"Ej/	9%%"
%	 & F Ef~Hzz.)b!7 8&:M9NaPPLL78*IeWB?@LL"LL)*LL

;3I{3y>*:"=>" `ALL3qyk!F)HQwZX[L\\]!^_`R ::fb)DHHWb)E{3u:,b9: ZALL2ai[a
m_F1X;-!XYZR hhy"-G}TYYw-?,@AB HH_b9M23}3E2FbIJ& PALL2fkk!G*&=%>b8!NOPR hhy"-G@7AS@TUV99U##
#l+UGSZs:0JJz"=2::=|S_=
 mm!!#!"6#4#4"5R8K8K8Mds8S7TUU000'  	923%88	9( $$ -, sJ   I4N 
CN7 N7 	N4%N/)N4/N47O#O#OO#O#__main__)rC   r=   )r   )   FZ   ):__doc__r-   sysr*   r   r>   pathlibr   typingr   mcp.server.fastmcpr   r.   r   r   r   rR   r%   r   insertaria_atomic_writer   r   r   ImportErroraria_prompt_injection_scannerr	   _HAS_SCANNERmcpr,   r   toolintr7   ro   rw   r   r   dictr   r   r   importlib.utilutil_iluspec_from_file_location_think_specmodule_from_specr   loaderexec_moduler   r3   _think_import_errboolr   __name__r+   rN       r   <module>r     s    
 
   	   &"**../ABCKKM
34
>? ' (>K
:L l	S 	Xd^ 	  C   > & & &C & &R c c  2 C  D %c % %P /4# /4 /4(4. /4TW /4 /4d M M MS M M` <=!.$../A<PK&&&{3J"":.J bs b3 bD bc b[^ b bJ zGGI C  K  L^
  JJs=   
H  H ?AH  H
HHHH/!H**H/