Resync with graphify-py @ 35665a7#43
Conversation
Port every applicable behavioural change from `graphify-py` v0.9.3 through v0.9.12 into the Rust workspace and bump the workspace version to `0.9.12` in lockstep with the submodule. Ports: - extract: case-sensitive cross-file resolution, cross-language family guard, JS/TS import-evidence gate, Ruby module/`Struct`/ `Class.new` containers, Pascal call-scoping + inherited-call resolver, JS/TS rationale + ADR/RFC doc-refs, ref-namespaced external imports, and the misc edge-emission batch. - indirect-call feature: `indirect_call` edges for assignment / return / getattr dispatch across Python and JS/TS, with `affected` member-seed tracking. - detect: `is_sensitive` source-file exemption, `snapshots` noise-dir gating, cached word-count, and the unclassified-file CLI notice. - reflect work-memory overlay (#1441): `.graphify_learning.json` sidecar plus read surfaces in report, `serve`, `explain`, and the interactive HTML viz. - CLI `--code-only` (#1734): local AST-only code graph that skips documents, papers, and images. - CLI out/cache placement (#1747): `--out` keeps all artefacts and caches under `<out>/graphify-out/`; `cluster-only` writes beside a `--graph` inside a `graphify-out/` dir. - watch reconciliation (#8d8d2b8): removed and renamed sources are reconciled through a root-aware identity resolver, with a separate hyperedge eviction set (#1755) and `follow_symlinks`. - hooks: `claude_uninstall` cleans `CLAUDE.local.md`; skill-version stamp writes plus a direction-aware startup mismatch warning (#1568). By the will of the Machine God.
Replace the `GRAPHIFY_TEST_FORCE_SHRINK_REFUSE` env-var fault injector with a typed, per-call test seam: `check_shrink` is threaded through the rebuild pipeline as an injectable `ShrinkChecker`, and `test_support::rebuild_code_forcing_shrink_refusal` supplies a rejecting checker scoped to a single call. No global state or environment variable can alter a production binary. Document two disputed findings inline (CodeRabbit does not read commit messages): - `rebase_relative_source_files` iterates `nodes`/`edges`/ `hyperedges` to match graphify-py; it runs on the fresh extraction whose edge bucket is always `edges`, so a `links` bucket would never match and would diverge from the reference. - `stabilize_rebuild_cwd`'s process-wide `chdir` is pre-existing `e5044c3` behaviour mirroring the reference; concurrent rebuilds are serialised by the per-repo lock. Glory to the Omnissiah.
Replace `stabilize_rebuild_cwd`'s process-global `set_current_dir` with `effective_watch_path`, which resolves the rebuild's watch path without mutating the working directory: a valid CWD keeps the caller-supplied (often relative) path so the committed `.graphify_root` marker stays portable (#777), and only a gone CWD falls back to rooting under `GRAPHIFY_REPO_ROOT`. A concurrent caller's working directory can no longer be disturbed. Add a regression test asserting `rebuild_code` leaves the process CWD unchanged. The shrink-guard's `had_explicit_deletions` short-circuit is left as-is: it mirrors graphify-py `_check_shrink`, which bypasses before the `rebuilt_sources` accounting, and the divergence suggested by review would make the Rust guard stricter than the reference. By the will of the Machine God.
Fix the still-valid findings from the extract-crate review and the case-insensitivity gaps they surfaced: - Reuse `HEADER_SUFFIXES` instead of a duplicate constant in `postprocess`. - Gate the JS/TS phantom-edge check case-insensitively (a `.JS`/`.TS` file is still JS/TS), via a new allocation-free `lang_configs::ends_with_suffix_ci` helper, and lowercase the cross-file resolver activation gate so an uppercase `.PAS`/`.CS` file's pass runs at all (#1671). Regression tests for uppercase `.TS` and `.PAS`. - Fix C++ receiver typing: visit locals in source order (a nested block shadow no longer wins over the outer binding) and key the table per function body so a local never types a same-named receiver in another function. - Fix Objective-C receiver typing the same way: per-method tables so a local never leaks across methods, source-ordered within a body. - Check the `_callable` leak assertions against each node's nested `metadata` map, where the marker actually rides. - Rewrite the `clippy::similar_names` justifications to describe the Rust field roles rather than referencing the Python source. Disputed via code comment (kept matching graphify-py): - `resolve_up_chain` keeps nearest-base-first BFS: Delphi lists the parent class before interfaces, so the first base owns the call; same-depth aggregation would drop real parent-method calls. - The C# receiver table stays a file-wide LIFO walk: it mirrors `_csharp_member_type_table`, and its reverse order lets a method local shadow an earlier class field, which is the C# rule. Glory to the Omnissiah.
Fix the still-valid findings and dispute the parity false positives surfaced by the fuller extract-crate review: - Deduplicate Julia import-symbol nodes (guard the push on the `seen_ids` insert that was already computed but ignored). - Register a Ruby `Struct.new`/`Class.new`/`Data.define` synthesized class in `callable_def_nids`, matching every other class def site and the reference. - Skip a C# generic-argument `references` self-edge (`class Foo : Base<Foo>`). - Include `.mjs`/`.cjs`/`.mts`/`.cts` in the TS/JS resolver activation suffixes. - Rewrite the last `clippy::similar_names` justification that referenced the Python source. - Scope `merge_decl_def_classes` self-loop / duplicate-edge cleanup to edges touching a merged id, so an unrelated file's edges are no longer corpus-dependent on whether a header/impl pair merged. Disputed via code comment (Rust already mirrors graphify-py): - `resolve_up_chain` callee is pre-lowercased at emission. - C# `method_index` last-wins (deterministic edge order) and C# `new_type` direct-child scan both port the reference and are covered by passing tests. - PowerShell simple-name-only bases, Rust enum struct-variant field typing, and Pascal exact-case qualified impl headers all match the reference. Glory to the Omnissiah.
Add `#[must_use]` to the pure `read_csharp_type_name`, per the project's pure-function-returns-owned convention. By the will of the Machine God.
The resolver-activation gate now normalises extension case, so a pass can fire for an uppercase `.CS`/`.JAVA`/`.RB`/`.TS` file. Route each language resolver's own `source_file` suffix guard through the shared `ends_with_suffix_ci` helper so the activated pass actually acts on those inputs instead of silently skipping them - the C#, Java, Ruby, and TypeScript member/reference/import guards were case-sensitive. Add an uppercase `.CS` member-resolution regression. Also skip a C# generic-argument reference whose name is an in-scope type parameter (`class Box<T> : Base<T>`), matching the base-name exclusion, so `T` produces no reference or phantom node. Ave Deus Mechanicus.
Add uppercase member-resolution regressions for Java (`.JAVA`), Ruby (`.RB`), and TypeScript (`.TS`), covering each resolver guard routed through `ends_with_suffix_ci` in the #1671 coherence sweep (C# `.CS` already covered). TypeScript's distinct four-suffix member set is exercised too. Glory to the Omnissiah.
Add a regression proving `merge_decl_def_classes`' edge cleanup is scoped to merged ids: merge-created self-loops / duplicate edges are normalised while unrelated ones survive. Add a `relativize_source_files` test exercising the `scope` gate (#8d8d2b8) - an absolute path outside the watched subtree is left untouched, one inside is relativised. By the will of the Machine God.
Rewrite the `.TS` member-call regression as a single-file case so it isolates the resolver's source-suffix guard instead of relying on case-insensitive filesystem folding for JS module-path resolution (which probes lowercase candidates and would miss `SVC.TS` on a case-sensitive volume). Glory to the Omnissiah.
Assert a module imported in two statements emits one imported-symbol node, defending the `seen_ids`-guarded node push against regression. Ave Deus Mechanicus.
Make `scope` narrower than `root` with a real sibling path inside `root` but outside the watched subtree: without the gate it would be relativised against `root`, so the test now actually protects the #8d8d2b8 scope behavior. Use `tempdir()` fixtures for isolation. By the will of the Machine God.
A README-only reader could not discover the no-LLM-key local-AST path. Add a Quick start example for `graphify extract . --code-only` (skips the semantic pass and doc/paper/image files). Glory to the Omnissiah.
- Fix `merge-graphs` tag deduplication so an index-suffixed tag (`front_src-2` from two `front/src` repos) can no longer collide with an input repo literally tagged `front_src-2`: reserve every returned tag and advance the suffix until unused. Regression test with two `front/src` and one `front/src-2` repo. - Clarify the stale-verdict note in `query` output. The token-wide skill-version argv scan is left as-is via a code comment: it mirrors graphify-py's `sys.argv` scan, and a stray arg value matching a silent-command name is a harmless warning false-negative not worth positional parsing. Glory to the Omnissiah.
Satisfy `clippy::doc_markdown` on the Julia dedup and uppercase-TS regression doc comments (`raw_calls`, `CodeRabbit`). By the will of the Machine God.
Revert the `explain` stale-verdict suffix to graphify-py's verbatim `[code changed since — re-verify]`: `test_explain_cli.py` pins that exact substring, so the earlier reword broke byte parity. Port the contested+stale and no-lesson explain cases so the string (and the no-sidecar contract) are defended in Rust. By the will of the Machine God.
Fix a provenance divergence: `record_node` recorded a provenance entry for every non-neutral outcome, including `dead_end`, but graphify-py `_record_node` records provenance only for `useful` / `corrected` events (a dead end updates the score, not the trail). Gate the push on the outcome and add a regression. This is the opposite of the review's "record even when sign == 0" suggestion, which would diverge - a neutral doc never reaches `record_node` (disputed via comment). The overlay's first-candidate-wins provenance is left as-is: it mirrors graphify-py's `_add` (`if cid in nodes_out: return`), so a losing alias contributes no provenance by design (disputed via comment). Glory to the Omnissiah.
Describe the semantic rule (useful/corrected-only trail; first-status -wins canonical collision) rather than narrating the review, per code -comment style. By the will of the Machine God.
The stale-skill check scanned all of argv for a silent-command name, so `graphify query install` (or any free-text arg equal to `install`/`uninstall`/`hook-check`/`hook-guard`) wrongly suppressed the warning. Validate command POSITIONS against clap's command tree: a top-level silent command, or a platform group's `install`/ `uninstall` subcommand. Extend the startup-warning test with `query install` (must warn) and `claude install` (silent). By the will of the Machine God.
Fix `claude_uninstall` so the absent-hook path adds no message: the empty string from `uninstall_claude_hook` was pushed into the joined output, leaving a trailing blank line. graphify-py's `_strip_graphify_hook` is silent when no hook is present, so skip the empty message (not a "nothing to do" line the reference never emits). Pin it with a no-trailing-newline assertion. The `graphifyy` package name in the stale-skill warning is left verbatim (disputed via comment): `graphifyy` is graphify-py's real PyPI distribution name (`pyproject.toml` `name = "graphifyy"`), so the upgrade command must name it - "graphify" would point users at the wrong package. Glory to the Omnissiah.
- Fail SAFE in the graph.json shrink guard when an existing file cannot be READ (permission / transient IO / a directory), instead of treating a read error as an empty file and permitting the overwrite. A missing file still permits growth. This is the fail-open data-loss path #479 exists to prevent; the parse-error branch already fails closed. Divergence from graphify-py, whose read-error branch sets `raw = ""`. - Drop canvas communities left with no members after the dangling filter, and synthesize the all-nodes community from the FILTERED result so an all-dangling input recovers real cards instead of emitting an empty group box. Divergence from graphify-py to_canvas, which emits an empty group box per fully-dangling community. Regressions for both: an unreadable existing graph.json is refused, and an all-dangling community yields one card per real node with a single synthesized group. Glory to the Omnissiah.
- Read a `Unit::Slice` from the containment-checked `safe_path` (via a new `read_slice_text_from`) instead of re-following the slice's stored path at read time, so slice reads honour the corpus-root symlink guard exactly like whole-file reads. - Record Bedrock token usage on every call the sink is configured for (zero when the response omits usage), matching the other backends instead of silently dropping the call from the tally. - Rename the internal `tokenizer::estimate_file_tokens(content, overhead)` helper to `estimate_content_tokens` and make it `pub(crate)`, so it no longer collides by name with the public path-based `estimate_file_tokens` wrapper (the source of a review false positive). - Tighten the #1685 special-token test to a token-only fixture and assert it encodes as multiple ordinary tokens (not a collapsed special id or a drop). Glory to the Omnissiah.
Pass a `UsageSink` to the Bedrock mock call and assert it records the fixture's 1/2 token usage, covering the always-record plumbing. By the will of the Machine God.
Describe caller-capped content plus the `<untrusted_source>` wrapper overhead, dropping the obsolete `char_cap` / `=== rel ===` wording. By the will of the Machine God.
Key the process stat index by resolved cache-file root instead of a single first-seen root. `detect_incremental_with_cache_root` is a public API promising a per-call cache root, but `ensure_stat_index` ignored every root after the first, so two sequential detections with different roots clobbered one shared index. `StatIndex` now holds a `Map<PathBuf, RootState>`; `ensure_stat_index` returns the resolved key, each hash/word-count lookup selects its keyed state, and `flush_stat_index` iterates dirty roots. Record per-entry read failures and stat failures in the sequential `walk_dir` (the memory sidecar) instead of dropping them via `rd.flatten()` and `let Ok(m) else continue`, so a file that cannot be enumerated or stat'd is surfaced in `walk_errors`. Extend the snapshot-prune test to assert pruned dirs are absent from `unclassified`, and add regressions: two `cache_root`s write to separate indexes (cache + detect layers) and a dangling sidecar symlink is recorded as a walk error. Glory to the Omnissiah
AGENTS.md requires all tests to live under `tests/` as integration tests, but `extensions.rs`, `sensitive.rs`, and `ignore.rs` each wired a `#[cfg(test)] #[path = "*_tests.rs"] mod` sibling. Remove those three declarations and files. `extensions_tests` and `sensitive_tests` were fully redundant: every case is already covered (identically or by a more granular case) in `tests/parity_classify.rs` and `tests/parity_sensitive.rs`. `ignore_tests` uniquely exercised the small parse/glob helpers, so port those into `tests/parity_ignore.rs`: the four `parse_gitignore_line` cases call the public function directly, and the `*` / `**` cases run through the public `is_ignored` (anchored `/*.py` keeps the "star does not cross a slash" negative; `/**/*.py` covers the recursive wildcard) so `glob_match` stays crate-private. Both new anchored tests canonicalise the temp root so the anchor `strip_prefix` survives the macOS `/var` symlink. Glory to the Omnissiah
The root-keyed map clobbered itself under an absolute `GRAPHIFY_OUT`: `out_base` ignores the root when the override is absolute, so two distinct cache roots resolve to the SAME `stat-index.json`, yet the map held two competing states that flushed to that one file nondeterministically. Key the map by the resolved index FILE path instead. Distinct files still get isolated states (the relative-`GRAPHIFY_OUT` case), while roots that collapse to one file now share and merge a single state. `flush_stat_index` writes each key path directly. Add a serial env-guarded regression asserting two roots under one absolute `GRAPHIFY_OUT` merge into a single index holding both files. Glory to the Omnissiah
Two hyperedge merges diverged from graphify-py `attach_hyperedges`,
which only appends when `h.get("id")` is truthy and records each
accepted id as it goes.
`graphify-build`'s `attach_carried_hyperedges` carried an id-less (or
empty-id) hyperedge forward instead of dropping it. Guard on a
non-empty id before insertion.
`graphify-export`'s `attach_hyperedges` built `seen_ids` once and never
updated it, so two entries sharing an id within a single batch both
appended. Record each id on insertion so an intra-batch duplicate
collapses, first occurrence winning.
Add regressions: a `build_merge` with an id-less carried hyperedge, and
an `attach_hyperedges` batch with a duplicate id.
Glory to the Omnissiah
The absolute-`GRAPHIFY_OUT` regression restored the env only after its `cached_word_count` / `flush_stat_index` calls, so a panic in any of those would leak the override into other serial tests. Introduce a `GraphifyOutGuard` that restores the previous value on drop, covering unwinding, and drop the manual restore block. Ave Deus Mechanicus
`ensure_atexit_flush_registered` stored a `FlushSentinel` in a `static OnceLock`, but Rust never drops `static` items at process exit, so the sentinel's `Drop` never ran and the stat index was never written to disk. The #1656 word-count / hash cache therefore never survived a run, silently recomputing every time. Replace it with a `StatIndexFlushGuard` whose `Drop` flushes; the CLI `run` binds one for the whole invocation, so the index persists on a normal return, an error return, or an unwind, mirroring graphify-py's `atexit` flush. Replace the idempotence test with a subprocess regression that mutates the index, exits normally with no explicit `flush_stat_index`, and asserts the file was written. By the will of the Machine God
`run` now owns a `StatIndexFlushGuard` rather than registering a static atexit hook. Update the doc to say so and note that clap's `--help`/`--version`/parse-error paths call `process::exit` and skip the guard, which is harmless since they never mutate the index. Ave Deus Mechanicus
Address CodeRabbit's review of the build port. Carried hyperedges were filtered only by `source_file`, so a hyperedge whose own file survived could still reference a member node from a file that was pruned or re-extracted — a dangling reference graphify-py keeps verbatim. Prune first, then carry forward only hyperedges whose every `nodes` member resolves to a live node in the final graph. The carry logic moves into a `carry_forward_hyperedges` helper (keeping `build_merge_with_graph_cap` under the line cap). `normalize_hyperedge_members` silently dropped a non-array member alias (e.g. `members: "a"`), emptying the hyperedge; warn on stderr instead, like the array-alias normalisation already does. Add regressions: a carried hyperedge with a pruned member is dropped while a local one survives, and a subprocess test asserting the malformed-alias warning reaches stderr. By the will of the Machine God
Address CodeRabbit's review of the serve port. `maybe_reload` now reports whether it replaced the graph, and `GraphCtx::reload` clears `idf_cache` on a successful reload. Without this a hot reload kept the previous graph's IDF weights, so queries after a graph-file change were ranked with stale term weights (graphify-py sidesteps this by storing `_idf_cache` on the graph). `call_tool` now validates `project_path`: absent / null / empty routes to the default graph, a non-empty string routes to that project, and any other JSON type is a tool error instead of silently falling back to the default (matches graphify-py, where `Path()` raises on a non-str). Tests: an IDF-staleness regression proving a persisted cache returns the wrong weight for a changed graph, a non-string project_path tool error, a required-schema assertion that `project_path` stays optional, and a bad-path test derived from a guaranteed-missing temp child. Declined with in-code rationale (parity with graphify-py, or perf-only in the off-by-default HTTP feature): structured `isError` results, canonicalising/evicting the project cache, and a lock-free load path. By the will of the Machine God
Follow-up to the serve review: rather than disputing on Python parity, fix the findings (the resync process requires owner approval to dispute, and these are genuine contract/robustness bugs). - MCP wire contract: `tools/call` failures now return a result with `isError: true`, and `resources/read` failures a JSON-RPC error, so clients can distinguish failures from benign output. - Project cache: key it by the canonicalised graph path (aliases share one load) and bound it at `MAX_PROJECT_CTXS` with FIFO eviction — `project_path` is remotely selectable, so an unbounded cache is a memory-exhaustion vector. - Hot reload: `maybe_reload` now takes and clears `idf_cache` on a successful reload, making the invalidation a testable part of the reload op; the regression drives `maybe_reload` across a file change and asserts the cache empties (an unchanged file keeps it). - HTTP: dispatch the blocking lock+`handle` via `spawn_blocking` so a slow graph load never stalls the async executor. Ave Deus Mechanicus
Address CodeRabbit review of the cluster port: the pure `degree_map` helper returns an owned map with no side effects, so flag a dropped result. Glory to the Omnissiah
Address CodeRabbit review of the semantic port: the fragment validator took `members`/`node_ids` only when `nodes` was absent, so a hyperedge with a present-but-non-array `nodes` and a valid alias list was wrongly rejected. Pick the first of `nodes`/`members`/`node_ids` that is an array, matching `normalize_hyperedge_members` (which folds the alias onto `nodes` when `nodes` is not a list). Regression added. Glory to the Omnissiah
The earlier `spawn_blocking` fix still held one global mutex across the graph load and the whole tool dispatch, so a single slow project blocked every other request and could exhaust the blocking pool. Give each resolved graph its own `Arc<Mutex<GraphCtx>>`: `select_ctx` takes only a brief lock to look up or insert the handle (loading OUTSIDE that lock, double-checking on the race), then the caller locks just that project's context for reload + dispatch. `McpServerState` now uses interior mutability (`Mutex<Option<_>>` for the lazily-loaded default, `Mutex<IndexMap<_, _>>` for the bounded project cache), so `handle` takes `&self` and the HTTP transport drops its outer mutex — concurrent requests to different projects run in parallel. A failed load is still not cached, so a later-appearing graph resolves. Add a multi-threaded HTTP test firing concurrent calls to two distinct projects and asserting isolated routing. By the will of the Machine God
Address CodeRabbit review of the dedup port: `warn_cross_chunk_collision` fires whenever the colliding nodes have different `source_file`s, but its text asserted they share the same base name in different directories — which it never checks. Describe the collision as between two different source files (noting a shared base name is the common cause) so the message cannot mislead when the paths differ entirely. Glory to the Omnissiah
Address CodeRabbit review of the affected port: the member-seed tests only exercised `method` edges and a non-traversable cross-class edge. Add a `contains`-bound variant asserting a caller of a contains-linked member is reachable from the class (member seeding covers both edge relations), and rework the exclusion test so `b` genuinely calls A's member — proving `b` is reached as a caller while its own member `b_m` is still not pulled into the walk. Glory to the Omnissiah
Phase 5 docs pass for this review round's intentional divergences: - `update`: carried hyperedges with a member from a deleted/re-extracted file are dropped (Python keeps them); malformed member aliases warn on stderr instead of silently emptying the hyperedge. - `serve`: project graphs are cached by canonical path and bounded; tool-load failures return `isError: true` and resource-load failures a JSON-RPC error (Python reports both as plain text). By the will of the Machine God
Serve re-review: a hung graph load (e.g. a stalled network filesystem) would tie up the HTTP connection indefinitely. Wrap the blocking dispatch join in a 2-minute `tokio::time::timeout`; on elapse return 503 Service Unavailable. The blocking task is not cancelled — it runs to completion and still populates the cache — we simply stop waiting. Ave Deus Mechanicus
The stat-index flush fix (scope guard) exposed a latent #1747 leak: the incremental second run of `extract <corpus> --out <dir>` wrote a stray `graphify-out/cache/stat-index.json` into the corpus. `detect_incremental_with_manifest` re-walked the corpus via `walk::detect` with no `cache_root`, so its word-count phase rooted the stat index at the scanned corpus instead of `--out`. (The prior broken atexit never flushed, so the leak was invisible.) Thread `cache_root` through `detect_incremental_with_manifest` to `walk::detect_with_cache_root`, and pass it from `detect_incremental_with_cache_root`. The clean-corpus regression now holds across both runs. Glory to the Omnissiah
|
Important Review skippedToo many files! This PR contains 243 files, which is 93 over the limit of 150. To get a review, narrow the scope: Upgrade to a paid plan to raise the limit. ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (243)
You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Cache re-review: when the cache dir does not exist yet (`canonicalize` fails, e.g. a first `--out` run), `ensure_stat_index` fell back to the raw `base` path, which could be relative or carry redundant `.`/`..` segments — so equivalent spellings of one dir produced different `stat-index.json` keys and competing in-memory indexes. Fall back to `std::path::absolute` (lexical, no existence needed) before the raw path, so the key is always a normalized absolute path. Regression added. Glory to the Omnissiah
Cache re-review: the multi-root `flush_stat_index` returned on the first root's I/O/serialisation error, stranding the remaining dirty roots' entries (lost on exit). Persist every dirty root via a `persist_root` helper, keep a failed root dirty for a later retry, and surface the first error only after the whole sweep. Regression: a healthy root is still flushed when an earlier root's cache dir is unwritable. By the will of the Machine God
The `flush_continues_past_a_failing_root` regression relied on the bad root being flushed first, but `StatIndex::roots` was a `HashMap` with randomised iteration — under the old early-return bug the good root could flush first and the test would pass anyway. Switch `roots` to an `IndexMap` (insertion order), so a root populated first is flushed first and the failing-first regression is deterministic. Glory to the Omnissiah
Export re-review: the #1324 synthesized all-nodes group was keyed under community id 0, so a stale `community_labels[0]` entry (labels present but every community fully dangled) would mislabel the fallback with a real community's name. Key it under a sentinel id outside the natural range; `emit_community_nodes` renders that sentinel as `Community 0` / `g0` (unchanged output when no label collides) and never consults `community_labels`. Regression covers a label for community 0. Glory to the Omnissiah
Semantic re-review: the alias-fold tests only checked `nodes.len() == 2`, which would pass even if sanitization produced the wrong ids. Assert the `nodes` array is exactly `["n1", "n2"]` (deduped, in order) in both the alias-fold and nodes-already-a-list cases, keeping the alias-key-absent checks. Glory to the Omnissiah
CodeRabbit review statusThe whole-branch review ( Every crate touched by this branch was reviewed and, after fixes, re-reviewed to
Each fix shipped as its own commit (no amends) so the iterative history is auditable. Full gate: |
Ports every applicable behavioural change from
graphify-pysince the lastpinned submodule commit up to
35665a7, bumps the submodule pointer, and runs afull per-crate CodeRabbit review + re-review loop until clean.
Ported changes
family guard, JS/TS import-evidence gate (#1659), ruby containers, pascal, ref-stub
externals, and the
indirect_callfeature (#1565/#1566) with the affected member-seed.pg_constraintintrospection, posix path (#1672), incrementalcache-root threading (#1747), and inline unit tests migrated to integration suites
per AGENTS.md.
hooks / cli-root: the remaining v8 changes, incl.
--code-only, skill-versionpositional matching (#1568), and the #1747 out/cache routing.
Fixes beyond parity (AGENTS.md: fix reference bugs, don't replicate)
static(Rust neverdrops statics at exit, so the #1656 cache never persisted); keyed by resolved index
file so an absolute
GRAPHIFY_OUTno longer clobbers across roots; incremental runsroute the cache under
--out, keeping the corpus clean (#1747).Arc<Mutex<GraphCtx>>locking so concurrent requests todifferent projects run in parallel and one slow load can't stall others; bounded,
canonicalised project cache (remote
project_pathis a memory-exhaustion vector);hot reload clears the stale IDF cache;
tools/callfailures returnisError: trueand
resources/readfailures a JSON-RPC error; a per-request timeout; non-stringproject_pathis a tool error.carried hyperedges with a member from a deleted/re-extracted file are dropped.
Intentional divergences from graphify-py
Documented in USAGE.md: carried-hyperedge dangling-member drop and the malformed-alias
stderr warning (
update), and the bounded project cache + structured MCP errors (serve).Testing
cargo clippy --all-targets --all-features --workspaceclean;cargo nextest run --workspace— 2703 passed, 2 skipped;hk checkclean. Every touched crate passed aCodeRabbit review (and a re-review after fixes) with zero remaining findings.