
    Ki                    P   d Z ddlm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mZ ddlZddlmZmZmZ dd	lmZmZ dd
lmZmZ  ej6                  e      ZdZe	 G d d             Ze	 G d d             Z e	 G d d             Z! G d d      Z"dddZ#ddZ$ddZ%y)a  SQLite-backed knowledge graph storage and query engine.

Stores code structure as nodes (File, Class, Function, Type, Test) and
edges (CALLS, IMPORTS_FROM, INHERITS, IMPLEMENTS, CONTAINS, TESTED_BY, DEPENDS_ON, REFERENCES).
Supports impact-radius queries and subgraph extraction.
    )annotationsN)	dataclass)Path)AnyOptional   )
BFS_ENGINEMAX_IMPACT_DEPTHMAX_IMPACT_NODES)get_schema_versionrun_migrations)EdgeInfoNodeInfoar  
CREATE TABLE IF NOT EXISTS nodes (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    kind TEXT NOT NULL,          -- File, Class, Function, Type, Test
    name TEXT NOT NULL,
    qualified_name TEXT NOT NULL UNIQUE,
    file_path TEXT NOT NULL,
    line_start INTEGER,
    line_end INTEGER,
    language TEXT,
    parent_name TEXT,
    params TEXT,
    return_type TEXT,
    modifiers TEXT,
    is_test INTEGER DEFAULT 0,
    file_hash TEXT,
    extra TEXT DEFAULT '{}',
    updated_at REAL NOT NULL
);

CREATE TABLE IF NOT EXISTS edges (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    kind TEXT NOT NULL,           -- CALLS, IMPORTS_FROM, INHERITS, REFERENCES, etc.
    source_qualified TEXT NOT NULL,
    target_qualified TEXT NOT NULL,
    file_path TEXT NOT NULL,
    line INTEGER DEFAULT 0,
    extra TEXT DEFAULT '{}',
    confidence REAL DEFAULT 1.0,
    confidence_tier TEXT DEFAULT 'EXTRACTED',
    updated_at REAL NOT NULL
);

CREATE TABLE IF NOT EXISTS metadata (
    key TEXT PRIMARY KEY,
    value TEXT NOT NULL
);

CREATE INDEX IF NOT EXISTS idx_nodes_file ON nodes(file_path);
CREATE INDEX IF NOT EXISTS idx_nodes_kind ON nodes(kind);
CREATE INDEX IF NOT EXISTS idx_nodes_qualified ON nodes(qualified_name);
CREATE INDEX IF NOT EXISTS idx_edges_source ON edges(source_qualified);
CREATE INDEX IF NOT EXISTS idx_edges_target ON edges(target_qualified);
CREATE INDEX IF NOT EXISTS idx_edges_kind ON edges(kind);
CREATE INDEX IF NOT EXISTS idx_edges_target_kind ON edges(target_qualified, kind);
CREATE INDEX IF NOT EXISTS idx_edges_source_kind ON edges(source_qualified, kind);
CREATE INDEX IF NOT EXISTS idx_edges_file ON edges(file_path);
c                      e Zd ZU ded<   ded<   ded<   ded<   ded<   ded<   ded	<   ded
<   ded<   ded<   ded<   ded<   ded<   ded<   y)	GraphNodeintidstrkindnamequalified_name	file_path
line_startline_endlanguageOptional[str]parent_nameparamsreturn_typeboolis_test	file_hashdictextraN__name__
__module____qualname____annotations__     c/home/jay/workspace/scripts/.codegraph-venv/lib/python3.12/site-packages/code_review_graph/graph.pyr   r   Q   sM    G
I
INOMMMKr+   r   c                  p    e Zd ZU ded<   ded<   ded<   ded<   ded<   ded<   d	ed
<   dZded<   dZded<   y)	GraphEdger   r   r   r   source_qualifiedtarget_qualifiedr   liner#   r$         ?float
confidence	EXTRACTEDconfidence_tierN)r&   r'   r(   r)   r4   r6   r*   r+   r,   r.   r.   c   s;    G
IN
IKJ&OS&r+   r.   c                  T    e Zd ZU ded<   ded<   ded<   ded<   ded<   ded	<   d
ed<   y)
GraphStatsr   total_nodestotal_edgeszdict[str, int]nodes_by_kindedges_by_kind	list[str]	languagesfiles_countr   last_updatedNr%   r*   r+   r,   r8   r8   p   s,    !!!!r+   r8   c                     e Zd ZdZd=dZd>dZd?dZd?dZd?dZd?dZ	d@dAdZ
dBd	ZdCd
Z	 d@	 	 	 	 	 	 	 	 	 dDdZ	 	 	 	 dEdZdFdZdGdZd?dZd?dZdHdZdIdZdJdKdZdLdZdLdZdMdNdZ	 dO	 	 	 	 	 dPdZdQdZdRdZdSdTdZeef	 	 	 	 	 	 	 dUdZeef	 	 	 	 	 	 	 dUdZ eef	 	 	 	 	 	 	 dUdZ!dVdZ"dWdZ#	 	 	 	 	 dX	 	 	 	 	 	 	 	 	 	 	 dYd!Z$dZd"Z%	 d[	 	 	 	 	 d\d#Z&d]d$Z'd^d%Z(d_d&Z)	 	 	 	 d`d'Z*dad(Z+dbd)Z,	 	 	 	 	 	 dcd*Z-ddd+Z.	 	 	 	 ded,Z/	 	 	 	 dfd-Z0dgd.Z1dhd/Z2did0Z3	 	 dbd1Z4	 	 	 	 djd2Z5	 	 	 	 dkd3Z6	 	 	 	 dld4Z7	 	 	 	 dmd5Z8dnd6Z9dod7Z:dpd8Z;dqd9Z<drd:Z=dsd;Z>dtd<Z?y )u
GraphStorez#SQLite-backed code knowledge graph.c                   t        |      | _        | j                  j                  j                  dd       t	        j
                  t        | j                        ddd       | _        t        j                  | j                  _	        | j                  j                  d       | j                  j                  d       | j                          t        | j                        dk  r5| j                  j                  d	       | j                  j                          t        | j                         d | _        t!        j"                         | _        y )
NT)parentsexist_ok   F)timeoutcheck_same_threadisolation_levelzPRAGMA journal_mode=WALzPRAGMA busy_timeout=5000r   zJINSERT OR IGNORE INTO metadata (key, value) VALUES ('schema_version', '1'))r   db_pathparentmkdirsqlite3connectr   _connRowrow_factoryexecute_init_schemar   commitr   
_nxg_cache	threadingLock_cache_lock)selfrJ   s     r,   __init__zGraphStore.__init__   s    G}!!$!>__rU 

 ")



45

56djj)A-JJ1 JJtzz"-1$>>+r+   c                    | S Nr*   rY   s    r,   	__enter__zGraphStore.__enter__   s    r+   c                $    | j                          y r\   )close)rY   exc_typeexc_valexc_tbs       r,   __exit__zGraphStore.__exit__   s    

r+   c                v    | j                   j                  t               | j                   j                          y r\   )rO   executescript_SCHEMA_SQLrT   r]   s    r,   rS   zGraphStore._init_schema   s$    

  -

r+   c                T    | j                   5  d| _        ddd       y# 1 sw Y   yxY w)z<Invalidate the cached NetworkX graph after write operations.N)rX   rU   r]   s    r,   _invalidate_cachezGraphStore._invalidate_cache   s'     	#"DO	# 	# 	#s   'c                8    | j                   j                          y r\   )rO   r`   r]   s    r,   r`   zGraphStore.close   s    

r+   c                J   t        j                          }| j                  |      }|j                  rt        j                  |j                        nd}| j
                  j                  d|j                  |j                  ||j                  |j                  |j                  |j                  |j                  |j                  |j                  |j                   t#        |j$                        |||f       | j
                  j                  d|f      j'                         }|d   S )z-Insert or update a node. Returns the node ID.z{}ab  INSERT INTO nodes
               (kind, name, qualified_name, file_path, line_start, line_end,
                language, parent_name, params, return_type, modifiers, is_test,
                file_hash, extra, updated_at)
               VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
               ON CONFLICT(qualified_name) DO UPDATE SET
                 kind=excluded.kind, name=excluded.name,
                 file_path=excluded.file_path, line_start=excluded.line_start,
                 line_end=excluded.line_end, language=excluded.language,
                 parent_name=excluded.parent_name, params=excluded.params,
                 return_type=excluded.return_type, modifiers=excluded.modifiers,
                 is_test=excluded.is_test, file_hash=excluded.file_hash,
                 extra=excluded.extra, updated_at=excluded.updated_at
            z-SELECT id FROM nodes WHERE qualified_name = ?r   )time_make_qualifiedr$   jsondumpsrO   rR   r   r   r   r   r   r   r   r   r   	modifiersr   r!   fetchone)rY   noder"   now	qualifiedr$   rows          r,   upsert_nodezGraphStore.upsert_node   s    iik((.	*.**

4::&$

 		499i  $++t/?/?DLL 19s	
. jj  ;i\

(* 	 4yr+   c                (   t        j                          }|j                  r|j                  ni }t        |j                  dd            }t	        |j                  dd            }t        j                  |      }| j                  j                  d|j                  |j                  |j                  |j                  |j                  f      j                         }|r4| j                  j                  d|j                  |||||d   f       |d   S | j                  j                  d|j                  |j                  |j                  |j                  |j                  ||||f	       | j                  j                  d	      j                         d
   S )zInsert or update an edge.r4   r2   r6   r5   zSELECT id FROM edges
               WHERE kind=? AND source_qualified=? AND target_qualified=?
                     AND file_path=? AND line=?zZUPDATE edges SET line=?, extra=?, confidence=?, confidence_tier=?, updated_at=? WHERE id=?r   zINSERT INTO edges
               (kind, source_qualified, target_qualified, file_path, line, extra,
                confidence, confidence_tier, updated_at)
               VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)zSELECT last_insert_rowid()r   )rl   r$   r3   getr   rn   ro   rO   rR   r   sourcetargetr   r1   rq   )rY   edgers   
extra_dictr4   r6   r$   existings           r,   upsert_edgezGraphStore.upsert_edge   sN   iik#'::TZZ2
:>>,<=
jnn->LM

:& ::%%3 YYT[[$..$))L	

 (* 	 JJ+E:Xd^T
 D>!

5 YYT[[$..$))U#/	
 zz!!">?HHJ1MMr+   c                    | j                   j                  d|f       | j                   j                  d|f       | j                          y)z2Remove all nodes and edges associated with a file.z%DELETE FROM nodes WHERE file_path = ?z%DELETE FROM edges WHERE file_path = ?N)rO   rR   ri   )rY   r   s     r,   remove_file_datazGraphStore.remove_file_data   s<    

BYLQ

BYLQ r+   c                   | j                   j                  r/t        j                  d       | j                   j	                          | j                   j                  d       	 | j                  |       |D ]  }| j                  ||        |D ]  }| j                  |        | j                   j	                          | j                          y# t        $ r | j                   j                           w xY w)z'Atomically replace all data for a file.z;Flushing unexpected open transaction before BEGIN IMMEDIATEBEGIN IMMEDIATEr"   N)rO   in_transactionloggerwarningrT   rR   r   rv   r~   BaseExceptionrollbackri   )rY   r   nodesedgesfhashrr   r{   s          r,   store_file_nodes_edgesz!GraphStore.store_file_nodes_edges   s     ::$$NNXYJJ

,-		!!), 8   78 '  &'JJ 	   	JJ!	s   "AC %C5c                ~   | j                   j                  d       	 |D ]J  \  }}}}| j                  |       |D ]  }| j                  ||        |D ]  }| j	                  |        L | j                   j                          | j                          y# t        $ r | j                   j                           w xY w)z@Atomically replace data for a batch of files in one transaction.r   r   N)	rO   rR   r   rv   r~   rT   r   r   ri   )rY   batchr   r   r   r   rr   r{   s           r,   store_file_batchzGraphStore.store_file_batch  s     	

,-
	27 +.	5%%%i0! <D$$TU$;<! +D$$T*+	+ JJ 	   	JJ!	s   A)B %B<c                t    | j                   j                  d||f       | j                   j                          y )Nz:INSERT OR REPLACE INTO metadata (key, value) VALUES (?, ?))rO   rR   rT   )rY   keyvalues      r,   set_metadatazGraphStore.set_metadata%  s0    

H3PU,	
 	

r+   c                j    | j                   j                  d|f      j                         }|r|d   S d S )Nz&SELECT value FROM metadata WHERE key=?r   rO   rR   rq   )rY   r   ru   s      r,   get_metadatazGraphStore.get_metadata+  s5    jj  !IC6R[[]"s7|,,r+   c                8    | j                   j                          y r\   )rO   rT   r]   s    r,   rT   zGraphStore.commit/  s    

r+   c                8    | j                   j                          y r\   )rO   r   r]   s    r,   r   zGraphStore.rollback2  s    

r+   c                    | j                   j                  d|f      j                         }|r| j                  |      S d S )N,SELECT * FROM nodes WHERE qualified_name = ?rO   rR   rq   _row_to_node)rY   r   ru   s      r,   get_nodezGraphStore.get_node7  sB    jj  :^<M

(* 	 *-t  %6$6r+   c                    | j                   j                  d|f      j                         }|D cg c]  }| j                  |       c}S c c}w )Nz'SELECT * FROM nodes WHERE file_path = ?rO   rR   fetchallr   )rY   r   rowsrs       r,   get_nodes_by_filezGraphStore.get_nodes_by_file=  sH    zz!!5	|

(* 	 /33!!!$333   Ac                    |r*| j                   j                  d      j                         }n)| j                   j                  d      j                         }|D cg c]  }| j                  |       c}S c c}w )z2Return all nodes, optionally excluding File nodes.z(SELECT * FROM nodes WHERE kind != 'File'zSELECT * FROM nodesr   )rY   exclude_filesr   r   s       r,   get_all_nodeszGraphStore.get_all_nodesC  se    ::%%:hj  ::%%&;<EEGD.23!!!$333s   A5c                    | j                   j                  d|f      j                         }|D cg c]  }| j                  |       c}S c c}w )Nz.SELECT * FROM edges WHERE source_qualified = ?rO   rR   r   _row_to_edgerY   r   r   r   s       r,   get_edges_by_sourcezGraphStore.get_edges_by_sourceM  I    zz!!<~>O

(* 	 /33!!!$333r   c                    | j                   j                  d|f      j                         }|D cg c]  }| j                  |       c}S c c}w )Nz.SELECT * FROM edges WHERE target_qualified = ?r   r   s       r,   get_edges_by_targetzGraphStore.get_edges_by_targetS  r   r   c                    | j                   j                  d||f      j                         }|D cg c]  }| j                  |       c}S c c}w )a  Search for edges where target_qualified matches an unqualified name.

        CALLS edges often store unqualified target names (e.g. ``generateTestCode``)
        rather than fully qualified ones (``file.ts::generateTestCode``).  This
        method finds those edges by exact match on the plain function name so that
        reverse call tracing (callers_of) works even when qualified-name lookup
        returns nothing.
        z;SELECT * FROM edges WHERE target_qualified = ? AND kind = ?r   )rY   r   r   r   r   s        r,   search_edges_by_target_namez&GraphStore.search_edges_by_target_nameY  sO     zz!!I4L
 (* 	 /33!!!$333s   Ac                p   | j                   t               }g }|g}j                  d|f      j                         }|rB|d   dk(  r:j                  d|f      j	                         D ]  }|j                  |d           dfd}|D ]a  }	j                  d|	f      j	                         D ];  }|d   }
|
|vs|j                  |
        ||
d	
      }|s+|j                  |       = c d|v r|j                  dd      d   n|}j                  d|f      j	                         D ];  }|d   }
|
|vs|j                  |
        ||
d	
      }|s+|j                  |       = t        |      }t        |      D ]  }t               }|D ]<  }	j                  d|	f      j	                         D ]  }|j                  |d           > |D ]a  }j                  d|f      j	                         D ];  }|d   }
|
|vs|j                  |
        ||
d
      }|s+|j                  |       = c |} |S )aj  Find tests covering a node, including indirect (transitive) coverage.

        1. Direct: TESTED_BY edges targeting this node (+ bare-name fallback).
        2. Indirect: follow outgoing CALLS edges up to *max_depth* hops,
           then collect TESTED_BY edges on each callee.

        Returns a list of dicts with node fields plus ``indirect: bool``.
        z/SELECT kind FROM nodes WHERE qualified_name = ?r   ClasszSSELECT target_qualified FROM edges WHERE source_qualified = ? AND kind = 'CONTAINS'r0   c                t    j                  d| f      j                         }|sy |d   |d   |d   |d   |dS )Nr   r   r   r   r   )r   r   r   r   indirect)rR   rq   )qnr   ru   conns      r,   
_node_dictz3GraphStore.get_transitive_tests.<locals>._node_dict  sV    ,,>hj  F"%&6"7 -F$ r+   zTSELECT source_qualified FROM edges WHERE target_qualified = ? AND kind = 'TESTED_BY'r/   F)r   ::r   zPSELECT target_qualified FROM edges WHERE source_qualified = ? AND kind = 'CALLS'T)r   r   r   r    returnzdict | None)	rO   setrR   rq   r   appendaddrsplitrange)rY   r   	max_depthseenresults	input_qnsru   mrowr   r   srcdbarefrontier_next_frontiercalleer   s                    @r,   get_transitive_testszGraphStore.get_transitive_testsh  s    zz  $$	ll=
 (* 	 3v;')C! hj	;
   &8!9:;	  	*B||D hj	
*
 ,-d?HHSM"37Aq)
*	* 6:^5K~$$T1-b1Q_<<@G
 (*	
	&C
 ()C$sU3NN1%
	& y>y! 	%A&)eM ?<<DE (*	?C
 "%%c*<&=>?? ( .<<HI (*	
.C
 01C$&sT:#NN1-
.. %H+	%. r+   c                   | j                   }|j                  d      j                         }|syi }|j                  d      j                         D ])  }|j                  |d   g       j	                  |d          + i }|j                  d      j                         D ]N  }|d   }d|v r|j                  dd	      d   n|}|j                  |d
   t                     j                  |       P d}|D ]  }	|	d   }
|j                  |
g       }|st        |      d	k(  r|d   }nx|	d   }d|v r|j                  dd	      d   n|	d
   }|j                  |t                     }|D cg c]  }|j                  dd	      d   |v r| }}t        |      d	k(  r|d   }n|j                  d||	d   f       |d	z  } |r&|j                          t        j                  d|       |S c c}w )a:  Batch-resolve bare-name CALLS targets using the global node table.

        After parsing, some CALLS edges have bare targets (no ``::`` separator)
        because the parser couldn't resolve cross-file.  This method matches
        them against nodes and updates unambiguous matches in-place.

        Disambiguation strategy:
          1. Single node with that name -> resolve directly
          2. Multiple candidates -> prefer one whose file is imported by the
             source file (via IMPORTS_FROM edges)

        Returns the number of resolved edges.
        z}SELECT id, source_qualified, target_qualified, file_path FROM edges WHERE kind = 'CALLS' AND target_qualified NOT LIKE '%::%'r   zRSELECT name, qualified_name FROM nodes WHERE kind IN ('Function', 'Test', 'Class')r   r   zRSELECT DISTINCT file_path, target_qualified FROM edges WHERE kind = 'IMPORTS_FROM'r0   r   r   r   r/   z2UPDATE edges SET target_qualified = ? WHERE id = ?r   z#Resolved %d bare-name CALLS targets)rO   rR   r   
setdefaultr   splitr   r   rx   lenrT   r   info)rY   r   
bare_edgesnode_lookupru   import_targetsrz   target_fileresolvedr{   	bare_name
candidatesrt   src_qnsrc_fileimported_filescimporteds                     r,   resolve_bare_call_targetsz$GraphStore.resolve_bare_call_targets  s7    zz\\S
 (* 	  -/<<:
 (*	RC ""3v;3::3?O;PQ		R /1<<*
 (*	PC +,F6:fn&,,tQ/2&K%%c+&6>BB;O	P  	D/0I$B7J:!#&qM	 0104FLLq)!,k*  "0!3!3Hce!D)wwtQ'*n<   x=A% (ILLDDJ' MH;	> KKMKK=xH%s   $ G#c                    | j                   j                  d      j                         }|D cg c]  }|d   	 c}S c c}w )Nz8SELECT DISTINCT file_path FROM nodes WHERE kind = 'File'r   rO   rR   r   rY   r   r   s      r,   get_all_fileszGraphStore.get_all_files  s=    zz!!F

(* 	 )--1+---   =c                   |j                         }|sg S 	 t        |      dk(  rd|j                  dd      z   dz   }ndj                  d |D              }| j                  j                  d||f      j                         }|r|D cg c]  }| j                  |       c}S 	 g }g }|D ]>  }	|	j                         }
|j                  d       |j                  d|
 dd|
 dg       @ dj                  |      }d	| d
}|j                  |       | j                  j                  ||      j                         }|D cg c]  }| j                  |       c}S c c}w # t        $ r Y w xY wc c}w )zKeyword search across node names.

        Tries FTS5 first (fast, tokenized matching), then falls back to
        LIKE-based substring search when FTS5 returns no results.
        r   """ AND c              3  L   K   | ]  }d |j                  d d      z   d z     yw)r   r   N)replace).0ws     r,   	<genexpr>z*GraphStore.search_nodes.<locals>.<genexpr>-  s*      )9:C!))C..4)s   "$zZSELECT n.* FROM nodes_fts f JOIN nodes n ON f.rowid = n.id WHERE nodes_fts MATCH ? LIMIT ?z4(LOWER(name) LIKE ? OR LOWER(qualified_name) LIKE ?)%SELECT * FROM nodes WHERE z LIMIT ?)r   r   r   joinrO   rR   r   r   	Exceptionlowerr   extend)rY   querylimitwords	fts_queryr   r   
conditionsr   wordr   wheresqls                r,   search_nodeszGraphStore.search_nodes  s    I	5zQ%--T"::S@	#LL )>C) 	 ::%%2 E"	
 hj  6:;))!,;;  !#
"$ 	0D

AF MMQqc8q1X./	0 Z(*5':ezz!!#v.779.23!!!$33% < 		" 4s*   A1E  EE   E/E   	E,+E,c                d    t         dk(  r| j                  |||      S | j                  |||      S )a  BFS from changed files to find all impacted nodes within depth N.

        Delegates to ``get_impact_radius_sql()`` by default (faster for
        large graphs).  Set ``CRG_BFS_ENGINE=networkx`` to use the legacy
        Python-side BFS via NetworkX.

        Returns dict with:
          - changed_nodes: nodes in changed files
          - impacted_nodes: nodes reachable via edges
          - impacted_files: unique set of affected files
          - edges: connecting edges
        networkx)r   	max_nodes)r	   _get_impact_radius_networkxget_impact_radius_sql)rY   changed_filesr   r   s       r,   get_impact_radiuszGraphStore.get_impact_radiusM  sL    $ #33i 4   ))Y) * 
 	
r+   c           	         |s	g g g g dddS t               }|D ]5  }| j                  |      }|D ]  }|j                  |j                          7 |s	g g g g dddS | j                  j                  d       | j                  j                  d       d}t        |      }	t        dt        |	      |      D ]A  }
|	|
|
|z    }dj                  d |D              }| j                  j                  d	| |       C d
}| j                  j                  ||||t        |      z   f      j                         }t               }|D ]  }|d   }||vs|j                  |        | j                  |      }| j                  |      }t        |      }||kD  }|r|d| }t        |D ch c]  }|j                   c}      }g }||D ch c]  }|j                   c}z  }|r| j                  |      }||||||dS c c}w c c}w )zImpact radius via SQLite recursive CTE.

        Faster than NetworkX for large graphs because it avoids
        materialising the full graph in Python.
        Fr   changed_nodesimpacted_nodesimpacted_filesr   	truncatedtotal_impactedzCCREATE TEMP TABLE IF NOT EXISTS _impact_seeds (qn TEXT PRIMARY KEY)zDELETE FROM _impact_seeds  ,c              3      K   | ]  }d   yw)z(?)Nr*   r   r   s     r,   r   z3GraphStore.get_impact_radius_sql.<locals>.<genexpr>  s     #9aE#9   z0INSERT OR IGNORE INTO _impact_seeds (qn) VALUES a`  
        WITH RECURSIVE impacted(node_qn, depth) AS (
            SELECT qn, 0 FROM _impact_seeds
            UNION
            SELECT e.target_qualified, i.depth + 1
            FROM impacted i
            JOIN edges e ON e.source_qualified = i.node_qn
            WHERE i.depth < ?
            UNION
            SELECT e.source_qualified, i.depth + 1
            FROM impacted i
            JOIN edges e ON e.target_qualified = i.node_qn
            WHERE i.depth < ?
        )
        SELECT DISTINCT node_qn, MIN(depth) AS min_depth
        FROM impacted
        GROUP BY node_qn
        LIMIT ?
        N)r   r   r   r   rO   rR   listr   r   r   r   _batch_get_nodesr   get_edges_among)rY   r  r   r   seedsfr   n
batch_size	seed_listir   placeholderscte_sqlr   impacted_qnsr   r   r  r  r
  r	  r  relevant_edgesall_qnss                            r,   r  z GraphStore.get_impact_radius_sqli  sU    !#"$"$""#  % 	,A**1-E ,		!**+,	,
 !#"$"$""#  	

$	
 	

67
K	q#i.*5 	AaJ/E88#95#99LJJB<.Q	& zz!!iIE
,BC

(* 	
 "% 	%A1B  $	% --e4..|<^,"Y.	+JY7NNCqq{{CD*,^D1++DD!11':N +,,#",
 	
 D Es   !G6G;c                   | j                         }t               }|D ]5  }| j                  |      }|D ]  }|j                  |j                          7 t               }	|j                         }
d}t               }|
r||k  r|	j                  |
       t               }|
D ]  }||v r=|j                  |      D ])  }||	vs|j                  |       |j                  |       + ||v sI|j                  |      D ])  }||	vs|j                  |       |j                  |       +  ||	z  }t        |	      t        |      z   |kD  rn|}
|dz  }|
r||k  r| j                  |      }||z
  }| j                  |      }t        |      }||kD  }|r|d| }t        |D ch c]  }|j                   c}      }g }||D ch c]  }|j                   c}z  }|r| j                  |      }||||||dS c c}w c c}w )z=BFS via NetworkX (legacy). Used when CRG_BFS_ENGINE=networkx.r   r   Nr  )_build_networkx_graphr   r   r   r   copyupdate	neighborspredecessorsr   r  r  r   r  )rY   r  r   r   nxgr  r  r   r  visitedr   depthimpactedr   r   neighborpredr  r  r  r
  r	  r  r  r  s                            r,   r   z&GraphStore._get_impact_radius_networkx  s/    ((*% 	,A**1-E ,		!**+,	,
  E::< U59,NN8$&)eM 
/9$'MM"$5 3#72)--h7$LL23 9 # 0 0 4 /w.)--d3$LL./
/ W$M7|c-009<$HQJE% 59,( --e4%'..|<^,"Y.	+JY7NNCqq{{CD*,^D1++DD!11':N +,,#",
 	
 D Es   G2>G7c                    g }|D ]'  }| j                  |      }|s|j                  |       ) g }t        |      }|D ]8  }| j                  |      D ]"  }|j                  |v s|j                  |       $ : ||dS )zMExtract a subgraph containing the specified nodes and their connecting edges.)r   r   )r   r   r   r   r0   )rY   qualified_namesr   r   rr   r   qn_setes           r,   get_subgraphzGraphStore.get_subgraph  s    ! 	#B==$DT"	#
 _%! 	$B--b1 $%%/LLO$	$
 //r+   c           	     d   | j                   j                  d      j                         d   }| j                   j                  d      j                         d   }i }| j                   j                  d      D ]  }|d   ||d   <    i }| j                   j                  d      D ]  }|d   ||d   <    | j                   j                  d      D cg c]  }|d	   	 }}| j                   j                  d
      j                         d   }| j                  d      }	t	        |||||||	      S c c}w )z,Return aggregate statistics about the graph.zSELECT COUNT(*) FROM nodesr   zSELECT COUNT(*) FROM edgesz5SELECT kind, COUNT(*) as cnt FROM nodes GROUP BY kindcntr   z5SELECT kind, COUNT(*) as cnt FROM edges GROUP BY kindzQSELECT DISTINCT language FROM nodes WHERE language IS NOT NULL AND language != ''r   z.SELECT COUNT(*) FROM nodes WHERE kind = 'File'r@   )r9   r:   r;   r<   r>   r?   r@   )rO   rR   rq   r   r8   )
rY   r9   r:   r;   ru   r<   r   r>   r?   r@   s
             r,   	get_statszGraphStore.get_stats+  sJ   jj(()EFOOQRSTjj(()EFOOQRST(*::%%&]^ 	4C),UM#f+&	4 )+::%%&]^ 	4C),UM#f+&	4 $(::#5#5c$
AjM
	 
 jj((<

(*Q ((8##''#%
 	

s   D-Nc                   g d}|g}|"|j                  d       |j                  |       |r"|j                  d       |j                  |       |r&|j                  d       |j                  d| d       |j                  |       dj                  |      }| j                  j                  d| d|      j	                         }	|	D 
cg c]  }
| j                  |
       c}
S c c}
w )	a  Find nodes within a line-count range, ordered largest first.

        Args:
            min_lines: Minimum line count threshold (inclusive).
            max_lines: Maximum line count threshold (inclusive). None = no upper bound.
            kind: Filter by node kind (Function, Class, File, etc.).
            file_path_pattern: SQL LIKE pattern to filter by file path.
            limit: Maximum results to return.

        Returns:
            List of GraphNode objects, ordered by line count descending.
        )zline_start IS NOT NULLzline_end IS NOT NULLz (line_end - line_start + 1) >= ?z (line_end - line_start + 1) <= ?zkind = ?file_path LIKE ?r   r   r   z2 ORDER BY (line_end - line_start + 1) DESC LIMIT ?)r   r   rO   rR   r   r   )rY   	min_lines	max_linesr   file_path_patternr   r   r   r   r   r   s              r,   get_nodes_by_sizezGraphStore.get_nodes_by_sizeN  s    (


 "{ @AMM)$j)MM$01MMA/023eZ(zz!!( 0@ @
 (*	 	
 /33!!!$333s   C'c                    | j                   j                  d|f      j                         }|r| j                  |      S dS )z/Fetch a single node by its integer primary key.z SELECT * FROM nodes WHERE id = ?Nr   rY   node_idru   s      r,   get_node_by_idzGraphStore.get_node_by_id~  sA    jj  .


(* 	 *-t  %6$6r+   c                r   |sg S dj                  d |D              }d| dg}t        |      }|r&|j                  d       |j                  d| d       dj                  |      }| j                  j	                  d| |      j                         }|D cg c]  }| j                  |       c}S c c}w )	aM  Return nodes matching any of *kinds*, optionally filtered by file.

        Args:
            kinds: List of node kind strings (e.g. ``["Function", "Test"]``).
            file_pattern: If provided, only nodes whose ``file_path``
                contains *file_pattern* (SQL LIKE ``%pattern%``) are
                returned.
        r  c              3      K   | ]  }d   yw?Nr*   r  s     r,   r   z/GraphStore.get_nodes_by_kind.<locals>.<genexpr>  s     33r  z	kind IN ()r3  r   r   r   )r   r  r   rO   rR   r   r   )	rY   kindsfile_patternr  r   r   r   r   r   s	            r,   get_nodes_by_kindzGraphStore.get_nodes_by_kind  s     Ixx3U33!,q12
 K01MMAl^1-.Z(zz!!(0&

(* 	 /33!!!$333s   B4c                j    | j                   j                  d|f      j                         }|r|d   S dS )z2Return the number of flows a node participates in.z>SELECT COUNT(*) as cnt FROM flow_memberships WHERE node_id = ?r0  r   r   r9  s      r,   count_flow_membershipsz!GraphStore.count_flow_memberships  s?    jj   J
 (*	 	
 !s5z'a'r+   c                    | j                   j                  d|f      j                         }|D cg c]  }|d   	 c}S c c}w )z?Return criticality values for all flows a node participates in.zdSELECT f.criticality FROM flows f JOIN flow_memberships fm ON fm.flow_id = f.id WHERE fm.node_id = ?criticalityr   )rY   r:  r   r   s       r,   get_flow_criticalities_for_nodez*GraphStore.get_flow_criticalities_for_node  sI    zz!!# J	

 (* 	 +//Q- ///   ?c                r    | j                   j                  d|f      j                         }|r
|d   |d   S y)z4Return the ``community_id`` for a node, or ``None``.z+SELECT community_id FROM nodes WHERE id = ?community_idNr   r9  s      r,   get_node_community_idz GraphStore.get_node_community_id  sG    jj  9J
 (* 	 3~&2~&&r+   c                   i }d}t        dt        |      |      D ]b  }||||z    }dj                  d |D              }| j                  j	                  d| d|      j                         }|D ]  }|d   ||d   <    d |S )	zBatch-fetch ``community_id`` for a list of qualified names.

        Returns a mapping from qualified name to community_id (may be
        ``None`` if the node has no assigned community).
        r  r   r  c              3      K   | ]  }d   ywr>  r*   r  s     r,   r   zBGraphStore.get_community_ids_by_qualified_names.<locals>.<genexpr>       #7AC#7r  zHSELECT qualified_name, community_id FROM nodes WHERE qualified_name IN (r@  rK  r   )r   r   r   rO   rR   r   )	rY   qnsresultr  r  r   r  r   r   s	            r,   $get_community_ids_by_qualified_namesz/GraphStore.get_community_ids_by_qualified_names  s     )+
q#c(J/ 		@A!j.)E88#7#77L::%%,,8>< hj	 
  @./.?q)*+@		@ r+   c                    | j                   j                  dd| f      j                         }|D cg c]  }|d   	 c}S c c}w )z<Return distinct ``file_path`` values matching a LIKE suffix.z;SELECT DISTINCT file_path FROM nodes WHERE file_path LIKE ?r   r   r   )rY   patternr   r   s       r,   get_files_matchingzGraphStore.get_files_matching  sM    zz!!%	]
 (*	 	
 )--1+---s   Ac                T    | j                   j                  d      j                         S )z5Return raw rows for nodes that have no signature yet.zMSELECT id, name, kind, params, return_type FROM nodes WHERE signature IS NULLr   r]   s    r,   get_nodes_without_signaturez&GraphStore.get_nodes_without_signature  s'    zz!!1
 (*	r+   c                @    | j                   j                  d||f       y)z/Set the ``signature`` column for a single node.z+UPDATE nodes SET signature = ? WHERE id = ?N)rO   rR   )rY   r:  	signatures      r,   update_node_signaturez GraphStore.update_node_signature  s!     	

9 	
r+   c                    	 | j                   j                  d      j                         }|D ci c]  }|d   |d    c}S c c}w # t        j                  $ r"}t
        j                  d|       i cY d}~S d}~ww xY w)zReturn a mapping of *all* qualified names to their community_id.

        Used primarily by the visualization exporter.
        z.SELECT qualified_name, community_id FROM nodesr   rK  z7Community IDs unavailable (schema not yet migrated): %sNrO   rR   r   rM   OperationalErrorr   debug)rY   r   r   excs       r,   get_all_community_idsz GraphStore.get_all_community_ids  s    
	::%%@hj 
  "#Q~%66   '' 	LLRTWXI	s-   -A AA A A<A71A<7A<c                8   |s
t               S t               }d}t        dt        |      |      D ]h  }||||z    }dj                  d |D              }| j                  j                  d| d|      j                         }|j                  d |D               j |S )z2Return node IDs belonging to the given file paths.r  r   r  c              3      K   | ]  }d   ywr>  r*   r  s     r,   r   z3GraphStore.get_node_ids_by_files.<locals>.<genexpr>  rO  r  z)SELECT id FROM nodes WHERE file_path IN (r@  c              3  &   K   | ]	  }|d      yw)r   Nr*   r   r   s     r,   r   z3GraphStore.get_node_ids_by_files.<locals>.<genexpr>  s     0a!D'0   )r   r   r   r   rO   rR   r   r!  )rY   
file_pathsrQ  r  r  r   r  r   s           r,   get_node_ids_by_filesz GraphStore.get_node_ids_by_files  s     5L5
q#j/:6 	1AqZ0E88#7#77L::%%''3nA7 hj	 
 MM0400	1 r+   c                f   |sg S t        |      }g }d}t        dt        |      |      D ]h  }||||z    }dj                  d |D              }| j                  j                  d| d|      j                         }|j                  d |D               j t        t        j                  |            S )z8Return distinct flow IDs that contain any of *node_ids*.r  r   r  c              3      K   | ]  }d   ywr>  r*   r  s     r,   r   z6GraphStore.get_flow_ids_by_node_ids.<locals>.<genexpr>  rO  r  z@SELECT DISTINCT flow_id FROM flow_memberships WHERE node_id IN (r@  c              3  &   K   | ]	  }|d      yw)flow_idNr*   rd  s     r,   r   z6GraphStore.get_flow_ids_by_node_ids.<locals>.<genexpr>"  s     51!I,5re  )
r  r   r   r   rO   rR   r   r   r#   fromkeys)	rY   node_idsnidsrQ  r  r  r   r  r   s	            r,   get_flow_ids_by_node_idsz#GraphStore.get_flow_ids_by_node_ids  s     IH~
q#d)Z0 	6A1z>*E88#7#77L::%%%%1N!5 hj	 
 MM555	6 DMM&)**r+   c                    | j                   j                  d|f      j                         }|D ch c]  }|d   	 c}S c c}w )z6Return the set of qualified names for nodes in a flow.zgSELECT n.qualified_name FROM flow_memberships fm JOIN nodes n ON fm.node_id = n.id WHERE fm.flow_id = ?r   r   )rY   rk  r   r   s       r,   get_flow_qualified_namesz#GraphStore.get_flow_qualified_names&  sI    zz!!EJ
 (*	 	
 .22"#222rI  c                j    | j                   j                  d|f      j                         }|r|d   S dS )z8Return just the ``kind`` column for a node, or ``None``.z#SELECT kind FROM nodes WHERE id = ?r   Nr   r9  s      r,   get_node_kind_by_idzGraphStore.get_node_kind_by_id/  s:    jj  1G:

(* 	 "s6{+t+r+   c                    | j                   j                  d      j                         }|D ch c]  }|d   	 c}S c c}w )z8Return the set of all CALLS-edge target qualified names.z@SELECT DISTINCT target_qualified FROM edges WHERE kind = 'CALLS'r0   r   r   s      r,   get_all_call_targetszGraphStore.get_all_call_targets6  sC    zz!!#
 (* 	 044!$%444r   c                    	 | j                   j                  d      j                         S # t        j                  $ r"}t
        j                  d|       g cY d}~S d}~ww xY w)z/Return raw rows from the ``communities`` table.z SELECT id, name FROM communitiesz0Communities list unavailable (table missing): %sNr\  )rY   r_  s     r,   get_communities_listzGraphStore.get_communities_list>  sT    	::%%2hj '' 	LLKSQI	s   (+ A AA A c                    | j                   j                  d|f      j                         }|D cg c]  }|d   	 c}S c c}w )z/Return qualified names of nodes in a community.z7SELECT qualified_name FROM nodes WHERE community_id = ?r   r   rY   rK  r   r   s       r,   get_community_member_qnsz#GraphStore.get_community_member_qnsK  sJ     zz!!%O
 (*	 	
 .22"#222rI  c                    | j                   j                  d|f      j                         }|D cg c]  }| j                  |       c}S c c}w )z*Return all nodes belonging to a community.z*SELECT * FROM nodes WHERE community_id = ?r   ry  s       r,   get_nodes_by_community_idz$GraphStore.get_nodes_by_community_idV  sM     zz!!8O
 (* 	 /33!!!$333r   c                   g }d}t        dt        |      |      D ]h  }||||z    }dj                  d |D              }| j                  j	                  d| d|      j                         }|j                  d |D               j |S )z@Return ``target_qualified`` for edges sourced from *source_qns*.r  r   r  c              3      K   | ]  }d   ywr>  r*   r  s     r,   r   z2GraphStore.get_outgoing_targets.<locals>.<genexpr>h  rO  r  z>SELECT target_qualified FROM edges WHERE source_qualified IN (r@  c              3  &   K   | ]	  }|d      yw)r0   Nr*   rd  s     r,   r   z2GraphStore.get_outgoing_targets.<locals>.<genexpr>n       ?Q1/0?re  r   r   r   rO   rR   r   r   )rY   
source_qnsr   r  r  r   r  r   s           r,   get_outgoing_targetszGraphStore.get_outgoing_targets`        
q#j/:6 	@AqZ0E88#7#77L::%%..:^1> hj	 
 NN?$??	@ r+   c                   g }d}t        dt        |      |      D ]h  }||||z    }dj                  d |D              }| j                  j	                  d| d|      j                         }|j                  d |D               j |S )z=Return ``source_qualified`` for edges targeting *target_qns*.r  r   r  c              3      K   | ]  }d   ywr>  r*   r  s     r,   r   z2GraphStore.get_incoming_sources.<locals>.<genexpr>y  rO  r  z>SELECT source_qualified FROM edges WHERE target_qualified IN (r@  c              3  &   K   | ]	  }|d      yw)r/   Nr*   rd  s     r,   r   z2GraphStore.get_incoming_sources.<locals>.<genexpr>  r  re  r  )rY   
target_qnsr   r  r  r   r  r   s           r,   get_incoming_sourceszGraphStore.get_incoming_sourcesq  r  r+   c                    | j                   j                  d      j                         }|D cg c]  }| j                  |       c}S c c}w )zReturn all edges in the graph.SELECT * FROM edgesr   r   s      r,   get_all_edgeszGraphStore.get_all_edges  s?    zz!!"78AAC.23!!!$333s   A	c                n   |sg S t        |      }g }d}t        dt        |      |      D ]  }||||z    }dj                  d |D              }| j                  j                  d| d|      j                         }|D ]3  }	| j                  |	      }
|
j                  |v s#|j                  |
       5  |S )zReturn edges where both source and target are in the given set.

        Batches the source-side IN clause to stay under SQLite's default
        SQLITE_MAX_VARIABLE_NUMBER limit, then filters targets in Python.
        r  r   r  c              3      K   | ]  }d   ywr>  r*   r  s     r,   r   z-GraphStore.get_edges_among.<locals>.<genexpr>  rO  r  z/SELECT * FROM edges WHERE source_qualified IN (r@  )
r  r   r   r   rO   rR   r   r   r0   r   )rY   r+  rP  r   r  r  r   r  r   r   r{   s              r,   r  zGraphStore.get_edges_among  s     I?##%
q#c(J/ 
	)A!j.)E88#7#77L::%%A,qQ hj   )((+((O;NN4()
	) r+   c                4    |sg S t        |      }g }d}t        dt        |      |      D ]j  }||||z    }dj                  d |D              } j                  j                  d| d|      j                         }|j                   fd|D               l |S )zJBatch-fetch nodes by qualified name, staying under SQLite variable limits.r  r   r  c              3      K   | ]  }d   ywr>  r*   r  s     r,   r   z.GraphStore._batch_get_nodes.<locals>.<genexpr>  rO  r  z-SELECT * FROM nodes WHERE qualified_name IN (r@  c              3  @   K   | ]  }j                  |        y wr\   )r   )r   r   rY   s     r,   r   z.GraphStore._batch_get_nodes.<locals>.<genexpr>  s     >A4,,Q/>s   )r  r   r   r   rO   rR   r   r   )	rY   r+  rP  r   r  r  r   r  r   s	   `        r,   r  zGraphStore._batch_get_nodes  s    I?##%
q#c(J/ 	?A!j.)E88#7#77L::%%?~QO hj  NN>>>	? r+   c                \   | j                   5  | j                  | j                  cddd       S t        j                         }| j                  j                  d      j                         }|D ]  }|j                  |d   |d   |d          ! || _        |cddd       S # 1 sw Y   yxY w)zJBuild (or return cached) in-memory NetworkX directed graph from all edges.Nr  r/   r0   r   )r   )rX   rU   nxDiGraphrO   rR   r   add_edge)rY   gr   r   s       r,   r  z GraphStore._build_networkx_graph  s     	*	 	 JJLA::%%&;<EEGD Y

1/0!4F2GaPVi
XYDO	 	 	s   B"A)B""B+c                    |j                   dk(  r|j                  S |j                  r(|j                   d|j                   d|j                   S |j                   d|j                   S )NFiler   .)r   r   r   r   )rY   rr   s     r,   rm   zGraphStore._make_qualified  sb    99>>!nn%R(8(8'9499+FF..!DII;//r+   c                    t        |d   |d   |d   |d   |d   |d   |d   |d   xs d	|d
   |d   |d   t        |d         |d   |d   rt        j                  |d               S i       S )Nr   r   r   r   r   r   r   r    r   r   r   r!   r"   r$   )r   r   r   r   r   r   r   r   r   r   r   r!   r"   r$   )r   r    rn   loads)rY   ru   s     r,   r   zGraphStore._row_to_node  s    4yVV/0+&<(__*M*x=M*Y(+&.1'l$**S\*
 	
 AC
 	
r+   c                    |d   rt        j                  |d         ni }d|j                         v r|d   nd}d|j                         v r|d   nd}t        |d   |d   |d   |d	   |d
   |d   |||	      S )Nr$   r4   r2   r6   r5   r   r   r/   r0   r   r1   )	r   r   r/   r0   r   r1   r$   r4   r6   )rn   r  keysr.   )rY   ru   r$   r4   r6   s        r,   r   zGraphStore._row_to_edge  s    ,/L

3w<(b*6#((**DS&#
4E4S#/0Yd4yV !34 !34+&V!+

 
	
r+   )rJ   z
str | Pathr   None)r   z'GraphStore')r   r  )r  )rr   r   r"   r   r   r   )r{   r   r   r   )r   r   r   r  )
r   r   r   zlist[NodeInfo]r   zlist[EdgeInfo]r   r   r   r  )r   z5list[tuple[str, list[NodeInfo], list[EdgeInfo], str]]r   r  )r   r   r   r   r   r  )r   r   r   r   )r   r   r   Optional[GraphNode])r   r   r   list[GraphNode])T)r   r    r   r  )r   r   r   list[GraphEdge])CALLS)r   r   r   r   r   r  )r   )r   r   r   r   r   z
list[dict])r   r   )r   r=   )   )r   r   r   r   r   r  )r  r=   r   r   r   r   r   dict[str, Any])r+  r=   r   r  )r   r8   )2   NNNr  )r4  r   r5  
int | Noner   
str | Noner6  r  r   r   r   r  )r:  r   r   r  r\   )rA  r=   rB  r  r   r  )r:  r   r   r   )r:  r   r   zlist[float])r:  r   r   r  )rP  r=   r   dict[str, int | None])rT  r   r   r=   )r   zlist[sqlite3.Row])r:  r   rY  r   r   r  )r   r  )rf  r=   r   set[int])rm  r  r   z	list[int])rk  r   r   set[str])r:  r   r   r  )r   r  )rK  r   r   r=   )rK  r   r   r  )r  r=   r   r=   )r  r=   r   r=   )r   r  )r+  r  r   r  )r+  r  r   r  )r   z
nx.DiGraph)rr   r   r   r   )ru   sqlite3.Rowr   r   )ru   r  r   r.   )@r&   r'   r(   __doc__rZ   r^   rd   rS   ri   r`   rv   r~   r   r   r   r   r   rT   r   r   r   r   r   r   r   r   r   r   r   r
   r   r  r  r   r.  r1  r7  r;  rC  rE  rH  rL  rR  rU  rW  rZ  r`  rg  ro  rq  rs  ru  rw  rz  r|  r  r  r  r  r  r  rm   r   r   r*   r+   r,   rB   rB      s   -,.#

 D ND! Z\!!%3!<J!SV!	!6!J!	!$-
744444  56`!`.1`	`DL\.+4d *)	
 
 
 	

 

> *)	l
 l
 l
 	l

 
l
f *)	?
 ?
 ?
 	?

 
?
B0"!
J  $(,,4,4 ,4 	,4
 &,4 ,4 
,4`7 $(44 !4 
	46(0	,.

'*
	
$#	&+ +	+*3,5		3	3		344	4#	"#	&4
0&
0
$
r+   rB   c                <    dj                  d | D              }|d| S )a  Strip ASCII control characters and truncate to prevent prompt injection.

    Node names extracted from source code could contain adversarial strings
    (e.g. ``IGNORE_ALL_PREVIOUS_INSTRUCTIONS``).  This function removes control
    characters (0x00-0x1F except tab and newline) and enforces a length limit so
    that names flowing through MCP tool responses cannot easily influence AI
    agent behaviour.
    r  c              3  D   K   | ]  }|d v st        |      dk\  r|  yw))	
    N)ord)r   chs     r,   r   z!_sanitize_name.<locals>.<genexpr>  s*      RD 	s    N)r   )smax_lencleaneds      r,   _sanitize_namer    s.     gg  G 8Gr+   c                J   | j                   | j                  t        | j                        t        | j                        | j
                  | j                  | j                  | j                  | j                  rt        | j                        n| j                  | j                  d
S )N)
r   r   r   r   r   r   r   r   r   r!   )r   r   r  r   r   r   r   r   r   r   r!   )r  s    r,   node_to_dictr    sk    ddAFFN166,B()9)9:ll

JJ89~amm41==99 r+   c           	         | j                   | j                  t        | j                        t        | j                        | j
                  | j                  | j                  | j                  dS )N)r   r   ry   rz   r   r1   r4   r6   )	r   r   r  r/   r0   r   r1   r4   r6   )r-  s    r,   edge_to_dictr    sQ    ddAFF !3!34 !3!34[[!&&llq7H7H r+   )   )r  r   r  r   r   r   )r  r   r   r#   )r-  r.   r   r#   )&r  
__future__r   rn   loggingrM   rV   rl   dataclassesr   pathlibr   typingr   r   r   r  	constantsr	   r
   r   
migrationsr   r   parserr   r   	getLoggerr&   r   rg   r   r.   r8   rB   r  r  r  r*   r+   r,   <module>r     s    #      !     E E : &			8	$/d   " 	' 	' 	'      g
 g
T#"r+   