Watch for file changes and auto-update the graph. Uses a 300ms debounce to batch rapid-fire saves into a single update. Args: repo_root: Repository root to watch. store: Graph database to update. on_files_updated: Optional callback invoked after each debounced
(
repo_root: Path,
store: GraphStore,
on_files_updated: Optional[Callable] = None,
)
| 1065 | |
| 1066 | |
| 1067 | def watch( |
| 1068 | repo_root: Path, |
| 1069 | store: GraphStore, |
| 1070 | on_files_updated: Optional[Callable] = None, |
| 1071 | ) -> None: |
| 1072 | """Watch for file changes and auto-update the graph. |
| 1073 | |
| 1074 | Uses a 300ms debounce to batch rapid-fire saves into a single update. |
| 1075 | |
| 1076 | Args: |
| 1077 | repo_root: Repository root to watch. |
| 1078 | store: Graph database to update. |
| 1079 | on_files_updated: Optional callback invoked after each debounced |
| 1080 | batch of file updates completes. Receives the store as its |
| 1081 | only argument. Used by the CLI to run post-processing |
| 1082 | (FTS, flows, communities) after watch updates. |
| 1083 | """ |
| 1084 | import threading |
| 1085 | |
| 1086 | from watchdog.events import FileSystemEventHandler |
| 1087 | from watchdog.observers import Observer |
| 1088 | |
| 1089 | parser = CodeParser(repo_root) |
| 1090 | ignore_patterns = _load_ignore_patterns(repo_root) |
| 1091 | |
| 1092 | class GraphUpdateHandler(FileSystemEventHandler): |
| 1093 | def __init__(self): |
| 1094 | self._pending: set[str] = set() |
| 1095 | self._lock = threading.Lock() |
| 1096 | self._timer: threading.Timer | None = None |
| 1097 | |
| 1098 | def _should_handle(self, path: str) -> bool: |
| 1099 | if Path(path).is_symlink(): |
| 1100 | return False |
| 1101 | try: |
| 1102 | rel = str(Path(path).relative_to(repo_root)) |
| 1103 | except ValueError: |
| 1104 | return False |
| 1105 | if _should_ignore(rel, ignore_patterns): |
| 1106 | return False |
| 1107 | if parser.detect_language(Path(path)) is None: |
| 1108 | return False |
| 1109 | return True |
| 1110 | |
| 1111 | def on_modified(self, event): |
| 1112 | if event.is_directory: |
| 1113 | return |
| 1114 | if self._should_handle(event.src_path): |
| 1115 | self._schedule(event.src_path) |
| 1116 | |
| 1117 | def on_created(self, event): |
| 1118 | if event.is_directory: |
| 1119 | return |
| 1120 | if self._should_handle(event.src_path): |
| 1121 | self._schedule(event.src_path) |
| 1122 | |
| 1123 | def on_deleted(self, event): |
| 1124 | if event.is_directory: |