fix(windows): preserve deep local and UNC files across the corpus pipeline - #2317
fix(windows): preserve deep local and UNC files across the corpus pipeline#2317nimide wants to merge 5 commits into
Conversation
There was a problem hiding this comment.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify reviewed this change.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify review — findings
This PR introduces a cross-platform filesystem path adapter (in graphify.paths) and routes I/O operations throughout the codebase—build.py, cache.py, and various extractors, detection, watch, LLM, and security modules—through helper functions like resolve_path, read_text, path_exists, and io_path. The stated intent is to let Windows scans handle deeply nested local/UNC paths that exceed the legacy MAX_PATH limit by using extended-length paths at the I/O boundary while keeping ordinary paths in graph IDs, manifests, and cache keys. It also adds a new platform-paths CI job that runs a set of path-related tests across Ubuntu, macOS, and Windows, and adds an "Unreleased" entry to the CHANGELOG describing the change. The surface area is broad but mostly mechanical: swapping direct Path/os calls for the shared adapter wrappers across many files.
No blocking issues surfaced. 4 lower-confidence candidates did not survive cross-model review.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 4345 functions depend on the 1683 functions this change touches.
Health — this change adds coupling hotspots:
- worse:
extract()— 353 callers, 30 callees - worse:
_rebuild_code()— 55 callers, 57 callees - worse:
build_from_json()— 138 callers, 14 callees - worse:
detect()— 82 callers, 16 callees - worse:
load_cached()— 39 callers, 8 callees - worse:
extract_bash()— 34 callers, 9 callees - worse:
_extract_generic()— 18 callers, 16 callees - worse:
file_hash()— 39 callers, 7 callees - …and 112 more
Verification — 4345 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 4284 function(s) in the blast radius were not formally verified this run
· 20 grounded finding(s) anchored inline below; 86 more finding(s) on lines outside this diff (see the check run); 14 additional anchorable finding(s) not shown (cap).
| @@ -931,38 +952,38 @@ def save_cached(path: Path, result: dict, root: Path = Path("."), kind: str = "a | |||
|
|
|||
| def cached_files(root: Path = Path(".")) -> set[str]: | |||
There was a problem hiding this comment.
cached_files()
high coupling complexity (Ca·Ce = 12).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return hashes | ||
|
|
||
|
|
||
| def clear_cache(root: Path = Path(".")) -> None: |
There was a problem hiding this comment.
clear_cache()
7 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| _unlink_path(f) | ||
|
|
||
|
|
||
| def prune_semantic_cache(root: Path, live_hashes: set[str]) -> int: |
There was a problem hiding this comment.
prune_semantic_cache()
9 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return paths | ||
|
|
||
|
|
||
| def introspect_cargo(root: str | Path) -> dict[str, Any]: |
There was a problem hiding this comment.
introspect_cargo()
11 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return True | ||
|
|
||
|
|
||
| def detect(root: Path, *, follow_symlinks: bool | None = None, google_workspace: bool | None = None, extra_excludes: list[str] | None = None, cache_root: Path | None = None, gitignore: bool = True) -> dict: |
There was a problem hiding this comment.
detect()
fans out to 16 callees (efferent coupling); 82 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| from graphify.paths import read_bytes as _read_file_bytes | ||
|
|
||
|
|
||
| def extract_julia(path: Path) -> dict: |
There was a problem hiding this comment.
extract_julia()
16 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| from graphify.extractors.base import _file_stem, _make_id | ||
|
|
||
|
|
||
| def extract_razor(path: Path) -> dict: |
There was a problem hiding this comment.
extract_razor()
14 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| from graphify.extractors.base import _make_id | ||
|
|
||
|
|
||
| def extract_sln(path: Path) -> dict: |
There was a problem hiding this comment.
extract_sln()
10 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| @@ -51,9 +52,9 @@ def _pkg_id(name: str) -> str: | |||
| def extract_package_manifest(path: Path) -> dict[str, Any]: | |||
There was a problem hiding this comment.
extract_package_manifest()
7 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return value | ||
|
|
||
|
|
||
| def resolve_path(path: PathLike) -> Path: |
There was a problem hiding this comment.
resolve_path()
86 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
…h 0932] Removed GRAPHIFY_OUT_NAME constant and updated related logic to prevent treating the default output directory as source input. Added coverage artifact detection functionality.
…ith 0932] Refactor _git_head function to accept cwd parameter for better repo context. Update comments and logic to improve clarity on graph extraction and node preservation.
There was a problem hiding this comment.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify reviewed this change.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify review — findings
This PR introduces a cross-platform filesystem path adapter (in graphify.paths) and threads its helpers (resolve_path, read_text, path_exists, io_path, logical_path, etc.) through build.py, cache.py, and the various language extractors, Google Workspace, and cargo-introspection modules, replacing direct Path/os filesystem calls. The stated intent is to let Windows scans handle deeply nested local and UNC paths that exceed the legacy MAX_PATH limit by using extended-length paths at I/O boundaries while keeping ordinary paths in graph IDs, manifests, and diagnostics. It also adds a new CI job matrix (Ubuntu/macOS/Windows) exercising long-path and related tests, plus a CHANGELOG entry. Surface area spans many extractor modules and rationale comment blocks in addition to the core build.py/cache.py changes and workflow/config files.
No blocking issues surfaced. 4 lower-confidence candidates did not survive cross-model review.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 4347 functions depend on the 1685 functions this change touches.
Health — this change adds coupling hotspots:
- worse:
extract()— 353 callers, 30 callees - worse:
_rebuild_code()— 55 callers, 57 callees - worse:
build_from_json()— 138 callers, 14 callees - worse:
detect()— 82 callers, 16 callees - worse:
load_cached()— 39 callers, 8 callees - worse:
extract_bash()— 34 callers, 9 callees - worse:
_extract_generic()— 18 callers, 16 callees - worse:
file_hash()— 39 callers, 7 callees - …and 112 more
Verification — 4347 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 4286 function(s) in the blast radius were not formally verified this run
· 20 grounded finding(s) anchored inline below; 86 more finding(s) on lines outside this diff (see the check run); 14 additional anchorable finding(s) not shown (cap).
| @@ -931,38 +952,38 @@ def save_cached(path: Path, result: dict, root: Path = Path("."), kind: str = "a | |||
|
|
|||
| def cached_files(root: Path = Path(".")) -> set[str]: | |||
There was a problem hiding this comment.
cached_files()
high coupling complexity (Ca·Ce = 12).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return hashes | ||
|
|
||
|
|
||
| def clear_cache(root: Path = Path(".")) -> None: |
There was a problem hiding this comment.
clear_cache()
7 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| _unlink_path(f) | ||
|
|
||
|
|
||
| def prune_semantic_cache(root: Path, live_hashes: set[str]) -> int: |
There was a problem hiding this comment.
prune_semantic_cache()
9 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return paths | ||
|
|
||
|
|
||
| def introspect_cargo(root: str | Path) -> dict[str, Any]: |
There was a problem hiding this comment.
introspect_cargo()
11 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return True | ||
|
|
||
|
|
||
| def detect(root: Path, *, follow_symlinks: bool | None = None, google_workspace: bool | None = None, extra_excludes: list[str] | None = None, cache_root: Path | None = None, gitignore: bool = True) -> dict: |
There was a problem hiding this comment.
detect()
fans out to 16 callees (efferent coupling); 82 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| from graphify.paths import read_bytes as _read_file_bytes | ||
|
|
||
|
|
||
| def extract_julia(path: Path) -> dict: |
There was a problem hiding this comment.
extract_julia()
16 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| from graphify.extractors.base import _file_stem, _make_id | ||
|
|
||
|
|
||
| def extract_razor(path: Path) -> dict: |
There was a problem hiding this comment.
extract_razor()
14 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| from graphify.extractors.base import _make_id | ||
|
|
||
|
|
||
| def extract_sln(path: Path) -> dict: |
There was a problem hiding this comment.
extract_sln()
10 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| @@ -51,9 +52,9 @@ def _pkg_id(name: str) -> str: | |||
| def extract_package_manifest(path: Path) -> dict[str, Any]: | |||
There was a problem hiding this comment.
extract_package_manifest()
7 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return value | ||
|
|
||
|
|
||
| def resolve_path(path: PathLike) -> Path: |
There was a problem hiding this comment.
resolve_path()
86 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
There was a problem hiding this comment.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify reviewed this change.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify review — findings
This PR introduces a cross-platform filesystem path adapter (in graphify.paths) to handle Windows extended-length paths, wiring helper functions like resolve_path, path_exists, read_text, io_path, make_dirs, and others through call sites in build.py, cache.py, and across many extractors, watch, CLI, and LLM modules. The intent is to use extended-length paths only at the I/O boundary while keeping ordinary paths in graph IDs, manifests, cache keys, and diagnostics. It also adds a new platform-paths CI matrix job running path-related tests on Ubuntu/macOS/Windows and a corresponding changelog entry. The surface area is broad—touching path handling in build/merge logic, cache stat-indexing and cleanup, and numerous extractor and resolution modules—but the changes appear to be mechanical substitutions swapping direct Path/os filesystem calls for the shared adapter helpers.
No blocking issues surfaced. 3 lower-confidence candidates did not survive cross-model review.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 4347 functions depend on the 1685 functions this change touches.
Health — this change adds coupling hotspots:
- worse:
extract()— 353 callers, 30 callees - worse:
_rebuild_code()— 55 callers, 57 callees - worse:
build_from_json()— 138 callers, 14 callees - worse:
detect()— 82 callers, 16 callees - worse:
load_cached()— 39 callers, 8 callees - worse:
extract_bash()— 34 callers, 9 callees - worse:
_extract_generic()— 18 callers, 16 callees - worse:
file_hash()— 39 callers, 7 callees - …and 112 more
Verification — 4347 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 4286 function(s) in the blast radius were not formally verified this run
· 20 grounded finding(s) anchored inline below; 88 more finding(s) on lines outside this diff (see the check run); 12 additional anchorable finding(s) not shown (cap).
| @@ -931,38 +952,38 @@ def save_cached(path: Path, result: dict, root: Path = Path("."), kind: str = "a | |||
|
|
|||
| def cached_files(root: Path = Path(".")) -> set[str]: | |||
There was a problem hiding this comment.
cached_files()
high coupling complexity (Ca·Ce = 12).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return hashes | ||
|
|
||
|
|
||
| def clear_cache(root: Path = Path(".")) -> None: |
There was a problem hiding this comment.
clear_cache()
7 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| _unlink_path(f) | ||
|
|
||
|
|
||
| def prune_semantic_cache(root: Path, live_hashes: set[str]) -> int: |
There was a problem hiding this comment.
prune_semantic_cache()
9 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return paths | ||
|
|
||
|
|
||
| def introspect_cargo(root: str | Path) -> dict[str, Any]: |
There was a problem hiding this comment.
introspect_cargo()
11 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return True | ||
|
|
||
|
|
||
| def detect(root: Path, *, follow_symlinks: bool | None = None, google_workspace: bool | None = None, extra_excludes: list[str] | None = None, cache_root: Path | None = None, gitignore: bool = True) -> dict: |
There was a problem hiding this comment.
detect()
fans out to 16 callees (efferent coupling); 82 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| from graphify.extractors.base import _make_id | ||
|
|
||
|
|
||
| def extract_sln(path: Path) -> dict: |
There was a problem hiding this comment.
extract_sln()
10 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| @@ -51,9 +52,9 @@ def _pkg_id(name: str) -> str: | |||
| def extract_package_manifest(path: Path) -> dict[str, Any]: | |||
There was a problem hiding this comment.
extract_package_manifest()
7 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return value | ||
|
|
||
|
|
||
| def resolve_path(path: PathLike) -> Path: |
There was a problem hiding this comment.
resolve_path()
86 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return Path(logical_path(os.path.realpath(io_path(path)))) | ||
|
|
||
|
|
||
| def path_exists(path: PathLike) -> bool: |
There was a problem hiding this comment.
path_exists()
47 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return os.path.exists(io_path(path)) | ||
|
|
||
|
|
||
| def path_is_file(path: PathLike) -> bool: |
There was a problem hiding this comment.
path_is_file()
43 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
There was a problem hiding this comment.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify reviewed this change.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify review — findings
This PR introduces a cross-platform filesystem path adapter (via graphify.paths helpers like _resolve_path, _read_text, _path_exists, _io_path, etc.) and routes build.py, cache.py, and other modules through it so that OS-level I/O uses extended-length paths on Windows while logical paths (graph IDs, manifests, cache keys) stay ordinary. It also adds a new platform-paths CI job running path-related tests across Ubuntu, macOS, and Windows, along with new/updated tests and a changelog entry describing the Windows long-path (MAX_PATH/UNC) fix. The surface area spans path handling across build, caching, extractors, detection, LLM helpers, and security modules, plus CI workflow and documentation changes.
No blocking issues surfaced. 3 lower-confidence candidates did not survive cross-model review.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 4421 functions depend on the 1696 functions this change touches.
Health — this change adds coupling hotspots:
- worse:
extract()— 361 callers, 31 callees - worse:
_rebuild_code()— 68 callers, 58 callees - worse:
build_from_json()— 142 callers, 15 callees - worse:
detect()— 84 callers, 16 callees - worse:
load_cached()— 39 callers, 8 callees - worse:
extract_bash()— 34 callers, 9 callees - worse:
build_merge()— 29 callers, 10 callees - worse:
_extract_generic()— 18 callers, 16 callees - …and 113 more
Verification — 4421 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 4360 function(s) in the blast radius were not formally verified this run
· 20 grounded finding(s) anchored inline below; 87 more finding(s) on lines outside this diff (see the check run); 14 additional anchorable finding(s) not shown (cap).
| @@ -931,38 +952,38 @@ def save_cached(path: Path, result: dict, root: Path = Path("."), kind: str = "a | |||
|
|
|||
| def cached_files(root: Path = Path(".")) -> set[str]: | |||
There was a problem hiding this comment.
cached_files()
high coupling complexity (Ca·Ce = 12).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return hashes | ||
|
|
||
|
|
||
| def clear_cache(root: Path = Path(".")) -> None: |
There was a problem hiding this comment.
clear_cache()
7 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| _unlink_path(f) | ||
|
|
||
|
|
||
| def prune_semantic_cache(root: Path, live_hashes: set[str]) -> int: |
There was a problem hiding this comment.
prune_semantic_cache()
9 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return paths | ||
|
|
||
|
|
||
| def introspect_cargo(root: str | Path) -> dict[str, Any]: |
There was a problem hiding this comment.
introspect_cargo()
11 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return True | ||
|
|
||
|
|
||
| def detect(root: Path, *, follow_symlinks: bool | None = None, google_workspace: bool | None = None, extra_excludes: list[str] | None = None, cache_root: Path | None = None, gitignore: bool = True) -> dict: |
There was a problem hiding this comment.
detect()
fans out to 16 callees (efferent coupling); 84 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| from graphify.paths import read_bytes as _read_file_bytes | ||
|
|
||
|
|
||
| def extract_julia(path: Path) -> dict: |
There was a problem hiding this comment.
extract_julia()
16 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| from graphify.extractors.base import _file_stem, _make_id | ||
|
|
||
|
|
||
| def extract_razor(path: Path) -> dict: |
There was a problem hiding this comment.
extract_razor()
14 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| from graphify.extractors.base import _make_id | ||
|
|
||
|
|
||
| def extract_sln(path: Path) -> dict: |
There was a problem hiding this comment.
extract_sln()
10 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| @@ -51,9 +52,9 @@ def _pkg_id(name: str) -> str: | |||
| def extract_package_manifest(path: Path) -> dict[str, Any]: | |||
There was a problem hiding this comment.
extract_package_manifest()
7 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return value | ||
|
|
||
|
|
||
| def resolve_path(path: PathLike) -> Path: |
There was a problem hiding this comment.
resolve_path()
86 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
Summary
Graphify 0.9.30 enters recursive corpus enumeration with ordinary Windows drive or UNC paths. A real descendant that crosses the legacy
MAX_PATHboundary can therefore fail before Graphify discovers it, producing a misleading[WinError 3] The system cannot find the path specifiedwarning and an incomplete graph.This change introduces a centralized, cross-platform filesystem I/O boundary in
graphify.pathsand applies it throughout the source-corpus pipeline. Windows filesystem calls use extended-length path spelling internally; Linux and macOS remain pass-through. Graph IDs, manifests, cache keys, ignore matching, diagnostics, and user-visible output continue to use ordinary logical paths.Representative UNC translation:
Problem and root cause
Selected late-stage reads already used a private Windows path helper, but recursive discovery began with an ordinary path:
On Windows,
os.walk()usesos.scandir()for recursive enumeration. When the ordinary path reaches the legacy boundary, enumeration can fail even though the directory exists. A directory just below 260 characters can also fail because native enumeration adds a search suffix.Changing only the first
os.walk()call would not close the defect. A deep file could be discovered and then fail during classification, hashing, cache access, structural extraction, semantic extraction, project resolution, incremental reconciliation, or document/media processing. This migration therefore covers the corpus from discovery through graph production.Design
Separate logical identity from filesystem transport
The implementation maintains two path representations:
Logical/public path
Filesystem I/O path
\\?\C:\...for local Windows paths.\\?\UNC\server\share\...for UNC paths.This prevents the same file from acquiring two Graphify identities and keeps generated artifacts portable.
Centralize the boundary
graphify.pathsnow owns the path translation and filesystem helpers used by the corpus pipeline, including:io_path()andlogical_path();The adapter normalizes and makes a Windows path absolute before adding the extended prefix. Already-extended paths and device paths remain unchanged. Off Windows, the helpers pass paths through unchanged.
Traversal reconstructs returned paths from the caller's logical root, so relative inputs remain relative even though Windows uses an absolute extended path internally.
Keep extended spelling away from external programs
The
\\?\spelling remains an internal filesystem transport detail. External tools receive ordinary absolute paths. Tests cover this rule for Google Workspace export and the C preprocessor.Preserve real errors
The change does not suppress traversal failures. Permission errors, disconnected shares, deletion races, broken links, and genuinely missing paths still flow to existing diagnostics. Only the transport-only prefix is removed before an error reaches user-facing code.
Implementation scope
The migration covers source-corpus filesystem boundaries used by:
collect_files()extraction;The extended prefix is removed immediately when traversal yields a path. It is not persisted in graph output, manifests, cache metadata, or diagnostics.
Graphify 0.9.30 baseline
This proposal is based directly on Graphify 0.9.30 commit:
The port preserves behavior introduced in 0.9.30, including TSX-specific parsing, portable AST cache anchors, fail-closed post-commit graph loading, and protected atomic graph replacement.
Tests and CI
tests/test_windows_long_paths.pyprovides host-neutral contract tests and native Windows integration coverage for:os.walk()receiving extended UNC spelling;walk_errors;\\?\never appears in returned or persisted logical artifacts.The focused GitHub Actions matrix runs the path suite on:
The Windows job creates and extracts a real 300-plus-character local path. UNC behavior is unit-tested at the conversion and traversal boundary because standard hosted runners do not provide a live SMB share.
Native Windows validation
Validation was performed against the 0.9.30 baseline above using Python 3.12 on Windows.
The focused suite validates:
\\?\prefixes;The one skip is the symlink-specific atomic-write test. The test host did not grant Windows symlink creation permission (
WinError 1314); it requires Developer Mode, administrator rights, orSeCreateSymbolicLinkPrivilege.Full-suite baseline comparison
An exploratory full Windows run before the final C-preprocessor follow-up produced:
The exact 42 failed node IDs were then rerun against untouched Graphify 0.9.30:
One shared C-preprocessor failure exposed an extended-path prefix crossing into an external executable. That issue was corrected and the test was added to the focused cross-platform gate.
After that correction, rerunning the cached failed set on the patched tree produced:
The remaining 40 failures reproduce on the untouched 0.9.30 baseline. They are existing Windows portability or test-environment issues, including unavailable symlink privileges, POSIX-only separator assertions, Windows current-directory deletion semantics, default text encoding, output filename limits, and installer/hook behavior outside this corpus-I/O change.
The one earlier non-baseline CodeBuddy directory-rename failure did not reproduce:
This PR therefore does not claim that the complete existing Windows suite is clean. It establishes a passing native gate for the source-corpus long-path pipeline and avoids broadening this change into unrelated Windows portability work.
Why not rely only on Windows policy?
Windows can opt applications into longer ordinary Win32 paths through
LongPathsEnabledand alongPathAwareexecutable manifest. That remains useful defense in depth, but it is not a sufficient Graphify contract:References:
Compatibility and risk
io_path()is a no-op and existing path behavior is retained.The broad call-site migration is intentional. Adapting only initial enumeration would allow a file to be discovered and then fail later during hashing or extraction, recreating the same incomplete-graph defect at another stage.
Scope and non-goals
This PR makes Graphify's source-corpus pipeline long-path-safe and closes a concrete Windows platform-parity gap. It does not claim that every external executable or third-party library accepts every possible path. It cannot prevent failures caused by permissions, share outages, authentication, invalid names, per-component filename limits, or network disconnections.
Unrelated installer, hook, exporter, and user-preference portability issues remain separate work. The watchdog subscription continues to receive the ordinary watch root so event paths retain Graphify's logical identity; a watch root that is itself already beyond the legacy boundary should receive separate native validation.
Reviewer guide
This submission is one commit created from the author-neutral 0.9.30 diff plus native-Windows test follow-ups.
Suggested review order:
graphify/paths.pyfor the logical-path versus filesystem-I/O-path contract.tests/test_windows_long_paths.py, the atomic-write/external-process tests, and the cross-platform CI matrix.Checklist