
    מi`.                         d 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m	Z	m
Z
mZ  G d d      Zdedefd	Zd
edefdZdedefdZdededefdZy)u  
scripts/knowledge/kg_to_obsidian.py — JSONL Knowledge Graph → Obsidian Vault converter.

Converts Genesis KG entities and axioms (stored as JSONL) into Obsidian-compatible
markdown files with YAML frontmatter and [[wiki-link]] cross-references.

Usage:
    from scripts.knowledge.kg_to_obsidian import KGToObsidian
    converter = KGToObsidian()
    stats = converter.convert_all()

# VERIFICATION_STAMP
# Story: 12.02 — KGToObsidian
# Verified By: parallel-builder
# Verified At: 2026-02-25
# Tests: 9/9
# Coverage: 100%
    N)Path)DictListOptionalTuplec            
           e Zd ZdZ	 	 ddededdfdZdeeef   fdZde	eef   fdZ
de	eef   fd	Zdd
Zdededede	eef   fdZdedededefdZy)KGToObsidianz
    Converts Genesis KNOWLEDGE_GRAPH JSONL files into an Obsidian-compatible
    markdown vault.  Each JSONL line becomes one .md file.
    kg_path
vault_pathreturnNc                 n    t        |      | _        t        |      | _        g | _        g | _        i | _        y )N)r   r
   r   _entity_ids
_axiom_ids_entity_types)selfr
   r   s      9/mnt/e/genesis-system/scripts/knowledge/kg_to_obsidian.py__init__zKGToObsidian.__init__"   s2    
 G}z*&(%'35    c                     | j                         \  }}| j                         \  }}| j                          ||||z   dS )z
        Run full conversion: entities + axioms.

        Returns:
            {"entities": N, "axioms": N, "errors": N}
        )entitiesaxiomserrors)convert_entitiesconvert_axiomsgenerate_index)r   entity_countentity_errorsaxiom_countaxiom_errorss        r   convert_allzKGToObsidian.convert_all1   sN     '+&;&;&=#m$($7$7$9!\$!#l2
 	
r   c                     | j                   dz  }| j                  dz  }|j                  dd       | j                  ||d      S )z
        Iterate all JSONL files in KNOWLEDGE_GRAPH/entities/ and write .md files
        to vault/entities/.

        Returns:
            (converted_count, error_count)
        r   Tparentsexist_okentitykindr
   r   mkdir_convert_directory)r   entities_inentities_outs      r   r   zKGToObsidian.convert_entitiesA   sK     llZ/34$7&&{Lx&PPr   c                     | j                   dz  }| j                  dz  }|j                  dd       | j                  ||d      S )z
        Iterate all JSONL files in KNOWLEDGE_GRAPH/axioms/ and write .md files
        to vault/axioms/.

        Returns:
            (converted_count, error_count)
        r   Tr"   axiomr&   r(   )r   	axioms_in
axioms_outs      r   r   zKGToObsidian.convert_axiomsN   sK     LL8+	__x/
5&&y*7&KKr   c                    | j                   j                  dd       g d}t        | j                  j	                               D ]  }|j                  d|j                                 |j                  d       t        | j                  |         D ]"  }t        |      }|j                  d| d       $ |j                  d        |j                  d       |j                  d       t        | j                        D ]"  }t        |      }|j                  d	| d       $ | j                   d
z  }|j                  dj                  |      d       y)za
        Create vault/INDEX.md with wikilinks to all converted entities grouped by type.
        Tr"   )u,   # Genesis Knowledge Graph — Obsidian Index z8Auto-generated by `scripts/knowledge/kg_to_obsidian.py`.r2   z## Entities by Typer2   z### r2   z- [[entities/]]z	## Axiomsz- [[axioms/zINDEX.md
utf-8encodingN)r   r)   sortedr   keysappend
capitalizesanitize_filenamer   
write_textjoin)r   linesetypeeidsafeaid
index_paths          r   r   zKGToObsidian.generate_index[   s9    	dT:
 D..3356 	ELL4 0 0 2345LLd0078 7(-}TF"567 LL	 	[!R$//* 	1C$S)DLL;tfB/0	1 __z1
dii.Ar   
source_dirdest_dirr'   c                    d}d}|j                         s||fS t        |j                               D ]  }|j                  dvr|j                  }	 |j                  dd      }t        |j                         d      D ]7  \  }	}
|
j                         }
|
s	 t        j                  |
      }t        |t              s|dz  }F| j                  |||      }t!        |j#                  d| d	|	             }t%        |      d
z   }||z  }	 |j'                  |d       |dz  }|dk(  rs| j(                  j+                  |       t!        |j#                  d|j#                  dd                  }| j,                  j/                  |g       j+                  |       | j0                  j+                  |       :  ||fS # t        $ r	 |dz  }Y w xY w# t        j                  $ r	 |dz  }Y rw xY w# t        $ r	 |dz  }Y w xY w)uu   
        Walk every .jsonl (and .json) file in source_dir.
        Each line is one record → one .md file.
        r   )z.jsonlz.jsonr5   replace)r7   r      )startidz-linez.mdr6   r%   typecategoryunknown)existsr8   iterdirsuffixname	read_textOSError	enumerate
splitlinesstripjsonloadsJSONDecodeError
isinstancedict_record_to_markdownstrgetr<   r=   r   r:   r   
setdefaultr   )r   rE   rF   r'   countr   
jsonl_filesource_namerawlinenolinerecord
md_content	record_idfilenameout_pathr@   s                    r   r*   zKGToObsidian._convert_directory~   s      "&=  !3!3!56 +	6J  (;;$//K **GI*N
 !*#..*:! D  6zz|!ZZ-F
 "&$/aKF!55fdKP


4K=fX1N OP	,Y7%?#h.''
W'EQJE 8#$$++I6

66::j)3T UVE&&11%<CCINOO**95A 6+	6Z f}M  ! ++ aKF   aKFs6   GGG6GGG32G36HHrg   source_filec                    g }|j                  d       |j                  dt        t        |j                  dd                   d       |dk(  rJ|j                  d|j                  dd	            }|j                  d
t        t        |             d       nd|j                  dd      }|j                  dt        t        |             d       |j                  dd      }|dk7  r|j                  d|        |j                  d|j                  d|j                  dd                  }|r'|j                  dt        t        |             d       |j                  d|j                  d|            }	|j                  dt        t        |	             d       |j                  d       |j                  d       |j                  d|j                  d|            }
|j                  d|
        |j                  d       t	        |      }|r.t        ||      }|j                  |       |j                  d       |j                  dg       }|rt        |t              r|j                  d       |j                  d       |D ]f  }t        |t              s|j                  dd      }|j                  dd      }|s;t        t        |            }|j                  d| d| d       h |j                  d       |j                  di       }|rt        |t              r|j                  d        |j                  d       |j                         D ]U  \  }}t        |t        t        f      rt        j                  |d!"      }nt        |      }|j                  d| d#|        W |j                  d       d$j                  |      S )%z
        Convert a single parsed JSON record to Obsidian markdown with YAML
        frontmatter and [[wiki-link]] cross-references.
        z---zid: "rK   r2   "r%   rL   rM   rN   ztype: "generalzcategory: "
confidencezconfidence: 
created_at	timestampdatezdate: "sourcerl   zsource_file: "rR   z# relationshipsz## Relationshipstarget
RELATED_TOz- **u   ** → [[entities/r3   
propertiesz## PropertiesF)ensure_ascii**: r4   )r:   _yaml_escaper^   r_   _extract_body_inject_wiki_linksr[   listr\   r<   itemsrX   dumpsr>   )r   rg   r'   rl   r?   r@   rM   rp   date_valrt   title	body_textru   relrv   rel_typesafe_targetpropskeyvalval_strs                        r   r]   z KGToObsidian._record_to_markdown   sF     	Uvl3vzz$/C+DEFbIJ8JJvvzz*i'HIELL8LU$<#=R@Azz*i8HLL<S](C'DBGHL"5JR|J<89::JJ{FJJvr$:;
 LL8LX$?#@CDHfjj&LM|CK'@&ADEUR 

66::dK#@Ar%\"R "&)	*9f=ILL#LL 

?B7Zt<LL+,LL$ c4( WWXr2F"wwv|<H&7F&D"8*,>{m2N LL 

<,Zt,LL)LL!KKM 8ScD$<0"jj5AG!#hGtC5WI678 LLyyr   )z%/mnt/e/genesis-system/KNOWLEDGE_GRAPHz$/mnt/e/genesis-system/obsidian-vault)r   N)__name__
__module____qualname____doc__r^   r   r   intr    r   r   r   r   r   r*   r\   r]    r   r   r	   r	      s     ?@	6	6 	6 
		6
T#s(^ 
 Q%S/ QLc3h LBF::*.:69:	sCx:xM M "%M 47M 	M r   r	   rR   r   c                     t        j                  dd|       } t        j                  dd|       } t        j                  dd|       } | j                  d      } | xs dS )z
    Make a string safe for use as a filename.
    Replaces spaces and any character that is not alphanumeric,
    hyphen, underscore, or dot with a hyphen.
    Strips leading/trailing hyphens.
    z\s+-z[^A-Za-z0-9._-]z-{2,}z-.unnamed)resubrW   )rR   s    r   r<   r<     sT     66&#t$D66$c40D66(C&D::dD9r   valuec                 &    | j                  dd      S )z8Escape double-quotes inside a YAML double-quoted string.rn   z\")rH   )r   s    r   r{   r{      s    ==e$$r   rg   c                 r   dD ]8  }| j                  |      }|st        |t              s(|j                         c S  g }h d}| j	                         D ]Q  \  }}||vst        |t              s|j                         s-|j                  d| d|j                                 S dj                  |      S )z}
    Pull the main prose body from a record.
    Tries: content, description, axiom, summary, text fields in that order.
    )contentdescriptionr.   summarytext>   rK   rs   rR   rL   rt   rM   rr   rp   rq   rx   rl   ru   genesis_actionz**rz   z

)r_   r[   r^   rW   r   r:   r>   )rg   fieldr   partsskipr   s         r   r|   r|   %  s    
 H jj:c3'99;
 ED
 LLN 6Sd?z#s3		LL2cU$syy{m456 ;;ur   r   c                     t        j                  d      }dt         j                  dt        fd}|j	                  ||       S )z
    Find ENT-xxx style IDs referenced in text and wrap them as [[wiki-links]].
    Only links IDs that look like Genesis entity IDs (ENT- prefix or uppercase
    hyphenated identifiers).
    z2\b(ENT-[A-Za-z0-9-]+|AGENT-\d+|AX-[A-Za-z0-9-]+)\bmr   c                 F    | j                  d      }t        |      }d| dS )Nr   z[[entities/r3   )groupr<   )r   
matched_idrB   s      r   replace_matchz)_inject_wiki_links.<locals>.replace_matchD  s(    WWQZ
 ,TF"%%r   )r   compileMatchr^   r   )r   rg   
id_patternr   s       r   r}   r}   ;  s=     QRJ& &c &
 >>-..r   )r   rX   osr   syspathlibr   typingr   r   r   r   r	   r^   r<   r{   r\   r|   r}   r   r   r   <module>r      s   &  	 	 
  . .k  k dC C $% % %
$ 3 ,/S /$ /3 /r   