| 384 | |
| 385 | |
| 386 | def classify_file(path: Path) -> FileType | None: |
| 387 | # Package manifests (apm.yml, pyproject.toml, go.mod, pom.xml) are parsed |
| 388 | # deterministically, so route them to the AST path (CODE) rather than the LLM |
| 389 | # document path — otherwise apm.yml (a .yml "document") would be LLM-extracted |
| 390 | # and a package would split into duplicate file-anchored nodes (#1377). |
| 391 | from graphify.manifest_ingest import is_package_manifest_path |
| 392 | if is_package_manifest_path(path): |
| 393 | return FileType.CODE |
| 394 | # Compound extensions must be checked before simple suffix lookup |
| 395 | if path.name.lower().endswith(".blade.php"): |
| 396 | return FileType.CODE |
| 397 | ext = path.suffix.lower() |
| 398 | if not ext: |
| 399 | return _shebang_file_type(path) |
| 400 | if ext in CODE_EXTENSIONS: |
| 401 | return FileType.CODE |
| 402 | if ext in PAPER_EXTENSIONS: |
| 403 | # PDFs inside Xcode asset catalogs are vector icons, not papers |
| 404 | if any(part.endswith(tuple(_ASSET_DIR_MARKERS)) for part in path.parts): |
| 405 | return None |
| 406 | return FileType.PAPER |
| 407 | if ext in IMAGE_EXTENSIONS: |
| 408 | return FileType.IMAGE |
| 409 | if ext in DOC_EXTENSIONS: |
| 410 | # Check if it's a converted paper |
| 411 | if _looks_like_paper(path): |
| 412 | return FileType.PAPER |
| 413 | return FileType.DOCUMENT |
| 414 | if ext in OFFICE_EXTENSIONS: |
| 415 | return FileType.DOCUMENT |
| 416 | if ext in GOOGLE_WORKSPACE_EXTENSIONS: |
| 417 | return FileType.DOCUMENT |
| 418 | if ext in VIDEO_EXTENSIONS: |
| 419 | return FileType.VIDEO |
| 420 | return None |
| 421 | |
| 422 | |
| 423 | def extract_pdf_text(path: Path) -> str: |