Skip to content

perf(global-graph): batch repository updates - #3444

Open
ROHIT8759 wants to merge 37 commits into
Graphify-Labs:v8from
ROHIT8759:perf/global-add-many
Open

perf(global-graph): batch repository updates#3444
ROHIT8759 wants to merge 37 commits into
Graphify-Labs:v8from
ROHIT8759:perf/global-add-many

Conversation

@ROHIT8759

@ROHIT8759 ROHIT8759 commented Sep 9, 2026

Copy link
Copy Markdown

Fixes #3438.

This PR batches global graph repository updates so the graph is loaded once, all changed repositories are composed, cross-repository member calls are resolved once, and the graph plus manifest are saved once per batch. global_add() remains backward-compatible and delegates to global_add_many().

Key changes:

  • Add global_add_many() with ordered repository replacement, duplicate-tag/source validation, unchanged-source skipping, and batch-level cross-repository resolution.
  • Preserve each repository's external stubs so batched and sequential composition remain equivalent.
  • Keep CLI support for adding multiple graph paths, with shared non-empty tag inference and legacy empty-tag removal compatibility.
  • Keep subprocess CLI tests isolated from the developer's home directory.
  • Remove obsolete external-label remapping/indexing and temporary reference files.
  • Preserve the documented contract that callers must serialize concurrent updates to the same global store.

Verification:

  • tests/test_global_graph.py: 30 relevant tests passed; three unrelated dedup tests require missing rapidfuzz.
  • tests/test_cli_global_add_many.py: 4 passed.
  • tests/test_global_add_tag_inference.py: 6 passed.
  • Python unresolved-call regression test is present but skipped locally because tree_sitter_python is unavailable; it should execute in CI/an environment with the parser dependency installed.
  • git diff --check passed and python -m graphify update . completed.

Coupling-health findings are advisory metrics and are intentionally not addressed by unrelated architectural restructuring.

ROHIT8759 and others added 14 commits September 9, 2026 21:34
`from . import brain, ledger` in a directory with no __init__.py — a
namespace package, which `python -m pkg.mod` runs without complaint —
emitted nothing: _resolve_python_module_path returns None when there is
no module file to probe, and the Graphify-Labs#1146 submodule branch sits behind that
None. So every `brain.think()` / `ledger.write()` call in such a repo was
invisible to the Graphify-Labs#1883 module arm, and the most-called functions carried
in-degree 0 in the graph.

_resolve_python_namespace_dir mirrors the module-path walk (relative
base, scan root, then sys.path-root ancestors per Graphify-Labs#2072) and returns only
a directory that exists inside the root and has no __init__.py. The fact
collector uses it when the module path resolves to no file; the existing
submodule probe then emits the same imports_from edges a regular package
gets. A name that is not a module file on disk still emits nothing, and
a namespace package binds no symbols of its own.

Measured on a 275-node Python repo laid out this way, same graphify
version with and without the change: calls 201 -> 307, nodes unchanged;
think() 0 -> 15 callers, remember() 0 -> 10, ledger write() 0 -> 20.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KTNEBgonu6ASph6w8R2Wcp
(cherry picked from commit e2ec796)
An aliased use clause (use GuzzleHttp\Client as HttpClient) always
targeted the bare imported name, ignoring the alias entirely. Two
files importing the same external class under different local
aliases, a real pattern for disambiguating two same named classes
from different namespaces, produced two different stub targets for
one class, splitting its identity. The cross file resolver repoints
a stub through the file's own alias to FQN map, keyed by the alias
when the import has one, so a bare name derived stub could never be
found under that key and stayed stuck, unresolved. The alias, when
present, is also what the rest of the file actually references, so
preferring it here keeps this edge's target consistent with those
reference edges too.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 73f9f16)
…-Labs#3347)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JJbLfztSxm2tH5cJwBbe9q
(cherry picked from commit eca5af3)
…the first 20

Review follow-up: a wide TS outDir keeping compiled files only under a
late-sorted module dir slipped past the 20-subdirectory cap. The level-two
probe now covers every subdirectory, bounded by total entries scanned.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JJbLfztSxm2tH5cJwBbe9q
(cherry picked from commit ff0a8f6)
`script_invocation` edges (Graphify-Labs#1756) only ever resolved a path that is a
bare literal -- `./helpers.sh`, or `bash ./helpers.sh`. Both paths into
that branch go through `literal()`, which rejects by design any token
containing `$`, so the single most common way a shell script calls a
sibling is invisible:

    script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
    "$script_dir/helpers.sh" --flag

`cmd` is None for that command node, the `elif cmd and ...` guard short-
circuits, and no edge is emitted. On a 206-file shell tree that uses the
`script_dir` idiom throughout, `script_invocation` produced *zero* edges
-- the feature was effectively dead for that whole style of codebase.

The `source` branch already solved the same problem for
`source "$DIR/lib/x.sh"` (Graphify-Labs#2079): strip the leading `${VAR}/` segments
with `_bash_source_suffix` and resolve the literal remainder against the
sourcing file's own directory, because the canonical `script_dir` idiom
makes that variable equal to exactly that. This applies the identical
treatment to the exec position -- read the command-name node's raw text
rather than its `literal()`, strip the same way, and fall through to the
existing `resolved.is_file()` gate.

Nothing new is fabricated. `_bash_source_suffix` still returns None when
the remainder holds another expansion (`"$dir/$name.sh"`) or a `..`
segment, and the on-disk check still means a wrong script-dir guess
emits nothing at all. The three existing negative tests -- missing,
shadowed, and dynamic invocations -- pass unchanged.

Measured on the same 206-file tree: 0 -> 16 edges across 10 distinct
file pairs, every one hand-checked to a real invocation of a real file,
no false positives. Full suite: no change in outcomes (the 25 failures
here are pre-existing and unrelated -- terraform, ollama, skillgen).

Fixes Graphify-Labs#3416.

(cherry picked from commit 2e112c3)
Copilot AI lite review requested due to automatic review settings September 9, 2026 19:03

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 5 advisory finding(s) below merit a look before merge.

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

Replaces global_add with global_add_many, which processes a batch of (path, tag) sources in one manifest load — deduplicating externals across the whole batch, computing a batch-total cross_repo_calls, and rejecting duplicate tags or missing source files up front. Adds an on_error policy ("abort" vs "skip") so a failing source can either halt the batch or be recorded as failed while the rest proceed, with per-repo changes staged and rolled back on error rather than partially applied. Extends graphify global add to accept multiple graph paths plus a --keep-going flag, still inferring each repo tag from the path (or requiring --as for a single graph), and prints per-source added/skipped/failed lines.

Worth a look

  • Load/size-check errors occur after prune, so error rollback path (except) does not restore pruned nodes on skipgraphify/global_graph.py · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • on_error='skip' failure leaves repo's stale nodes deleted but manifest unchanged, corrupting graph vs manifest consistencygraphify/global_graph.py · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Rollback removes nodes by data['repo'] but prefixed nodes may not carry repo attrgraphify/global_graph.py:217 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • on_error='skip' persists partial mutations for failed repo without full rollbackgraphify/global_graph.py:240 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • --as can consume --keep-going as the repo taggraphify/cli.py:3122 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 333 functions depend on the 124 functions this change touches.

Health — this change adds coupling hotspots:

  • new: dispatch_command() — 2 callers, 125 callees
  • new: global_add_many() — 18 callers, 8 callees
  • new: _stale_graph_sources() — 7 callers, 6 callees
  • new: _run_hook_guard() — 4 callers, 8 callees
  • new: global_remove() — 5 callers, 5 callees
  • new: test_poisoned_manifest_is_healed() — 0 callers, 6 callees
  • new: test_global_add_many_replacement_failure_preserves_original() — 0 callers, 6 callees

Verification — 333 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: 279 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

25 of 263 test file(s) selected (10%) via static blast radius.

  • tests/test_affected_cli.py — impact
  • tests/test_agents_platform.py — impact
  • tests/test_cli_global_add_many.py — changed-test
  • tests/test_codebuddy.py — impact
  • tests/test_devin.py — impact
  • tests/test_explain_cli.py — impact
  • tests/test_extract_cli.py — impact
  • tests/test_global_graph.py — impact, changed-test
  • tests/test_god_nodes_cli.py — impact
  • tests/test_hollow_chunks_arm_shrink_guard.py — impact
  • tests/test_hook_guard_token_match.py — impact
  • tests/test_hook_out_of_project_paths.py — impact
  • tests/test_hook_strict.py — impact
  • tests/test_incomplete_build_guard.py — impact
  • tests/test_install.py — impact
  • tests/test_install_references.py — impact
  • tests/test_merge_chunks_validation.py — impact
  • tests/test_multigraph_diagnostics.py — impact
  • tests/test_no_dedup_flag.py — impact
  • tests/test_partial_cache.py — impact
  • tests/test_path_cli.py — impact
  • tests/test_query_cli.py — impact
  • tests/test_query_induced_edges.py — impact
  • tests/test_stale_prune.py — impact
  • tests/test_unverified_semantic_shrink.py — impact

Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.

Formal verification

Could not verify: Could not verify dispatch\_command.

The verifier did not have enough to check dispatch\_command, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: not verifiable: all 23 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly SystemExit — names the real obstacle, not a sampling gap)

Could not verify: Could not verify global\_add.

The verifier did not have enough to check global\_add, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `source_path` is annotated `Path` — outside the synthesizable primitive/collection set

· 3 grounded finding(s) anchored inline below; 4 more finding(s) on lines outside this diff (see the check run).

Comment thread graphify/global_graph.py Outdated

def global_add(source_path: Path, repo_tag: str) -> dict:
"""Add or update a project graph in the global graph.
def global_add_many(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressionglobal_add_many()

fans out to 8 callees (efferent coupling); 18 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Comment thread graphify/global_graph.py
return global_add_many([(source_path, repo_tag)])[0]


def global_remove(repo_tag: str) -> int:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressionglobal_remove()

high coupling complexity (Ca·Ce = 25).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Comment thread tests/test_global_graph.py Outdated
# It does not scale with the number of repositories.
assert scan_count == 2

def test_global_add_many_replacement_failure_preserves_original(tmp_path):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressiontest_global_add_many_replacement_failure_preserves_original()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The batch-merge rollback and persistence logic has edge cases that can leave partial mutations or write to disk even when nothing was applied, and the new CLI subprocess tests currently aren’t isolated from the user’s real ~/.graphify state.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR introduces a batched global-graph update pathway so multiple repository graphs can be merged into the global graph with a single load → resolve → save cycle, addressing the repeated O(K²)-style work described in #3438.

Changes:

  • Added global_add_many() as the primary batch API and refactored global_add() to delegate to it.
  • Reworked the merge flow to initialize external-label and repo-node indexes once per batch, and to support rollback on apply-time failures.
  • Updated the CLI to accept multiple graph paths and --keep-going, and added regression tests for batch equivalence and CLI behavior.
File summaries
File Description
graphify/global_graph.py Adds global_add_many() batching + rollback logic and delegates global_add() to it.
graphify/cli.py Extends graphify global add to accept multiple graphs and --keep-going mapped to on_error="skip".
tests/test_global_graph.py Adds extensive regression coverage for batching, skip/abort, replacement, rollback, and cross-repo call resolution.
tests/test_cli_global_add_many.py Adds subprocess-based CLI tests for single/multi add, --as validation, and inferred-tag errors.
Review details

Suppressed comments (5)

tests/test_cli_global_add_many.py:22

  • This subprocess-based CLI test currently writes to the real user HOME (~/.graphify) because graphify.global_graph uses Path.home(); set HOME (and PYTHONPATH) in the subprocess env to isolate test state under tmp_path.
    res = subprocess.run([sys.executable, "-m", "graphify", "global", "add", str(g), "--as", "myrepo"], capture_output=True, text=True)

tests/test_cli_global_add_many.py:37

  • This subprocess-based CLI test currently writes to the real user HOME (~/.graphify) because graphify.global_graph uses Path.home(); set HOME (and PYTHONPATH) in the subprocess env to isolate test state under tmp_path.
    res = subprocess.run([sys.executable, "-m", "graphify", "global", "add", str(g1), str(g2)], capture_output=True, text=True)

tests/test_cli_global_add_many.py:53

  • This subprocess-based CLI test currently writes to the real user HOME (~/.graphify) because graphify.global_graph uses Path.home(); set HOME (and PYTHONPATH) in the subprocess env to isolate test state under tmp_path.
    res = subprocess.run([sys.executable, "-m", "graphify", "global", "add", str(g1), str(g2), "--keep-going"], capture_output=True, text=True)

tests/test_cli_global_add_many.py:64

  • This subprocess-based CLI test should set HOME (and PYTHONPATH) in the subprocess env to avoid depending on or mutating ~/.graphify on the machine running the test suite.
    res = subprocess.run([sys.executable, "-m", "graphify", "global", "add", str(g1), str(g2), "--as", "myrepo"], capture_output=True, text=True)

tests/test_cli_global_add_many.py:77

  • This subprocess invocation sets PYTHONPATH but not HOME; setting HOME to tmp_path isolates graphify's global graph location (Path.home()/.graphify) so the test doesn't depend on or mutate the user's real ~/.graphify.
    env = os.environ.copy()
    env["PYTHONPATH"] = str(Path.cwd())
    res = subprocess.run([sys.executable, "-m", "graphify", "global", "add", "graph.json"], cwd=tmp_path, env=env, capture_output=True, text=True)
  • Files reviewed: 4/4 changed files
  • Comments generated: 5
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread graphify/global_graph.py Outdated
Comment on lines +189 to +212
nodes_to_add = [(n, d) for n, d in prefixed.nodes(data=True) if n not in remap]
edges_to_add = []
for u, v, data in prefixed.edges(data=True):
u = remap.get(u, u)
v = remap.get(v, v)
if u != v:
edges_to_add.append((u, v, data))

# --- APPLY ---
for node, data in nodes_to_add:
G.add_node(node, **data)
if not data.get("source_file") and data.get("label"):
external_labels[data["label"]] = node

for u, v, data in edges_to_add:
G.add_edge(u, v, **data)

except Exception as apply_error:
G.remove_nodes_from([n for n, d in G.nodes(data=True) if d.get("repo") == repo_tag])
G.add_nodes_from(to_remove_nodes)
G.add_edges_from(to_remove_edges)
external_labels.clear()
external_labels.update(old_external_labels)
raise apply_error
Comment thread graphify/global_graph.py
Comment on lines 237 to 240
cross_repo_calls = link_cross_repo_member_calls(G)
_save_global_graph(G)

manifest["repos"][repo_tag] = {
"added_at": datetime.now(timezone.utc).isoformat(),
"source_path": str(source_path.resolve()),
"node_count": added,
"edge_count": prefixed.number_of_edges(),
"source_hash": src_hash,
}
_save_global_graph(G)
_save_manifest(manifest)
Comment thread tests/test_cli_global_add_many.py Outdated
Comment on lines +12 to +13

res = subprocess.run([sys.executable, "-m", "graphify", "global", "add", str(g)], capture_output=True, text=True)
Comment thread tests/test_global_graph.py Outdated
Comment on lines +658 to +660
# The node for X in repoB should have repo label repoB, because the old one was from repoA and was deleted.
# Wait, external nodes in prefixed graph don't necessarily get repo tag prefix. Actually they do get repo in prefix_graph_for_global.
# The node from repoA should be deleted.
Comment thread tests/test_global_graph.py Outdated
Comment on lines +795 to +797
# Should exactly be 2 scans (1 for our upfront index, 1 for cross_repo_calls pass)!
# It does not scale with the number of repositories.
assert scan_count == 2
xiongjianxu and others added 12 commits September 10, 2026 14:03
build.py annotates chunks and ast_sources with Iterable but never imports it, so
ruff reports four F821 on v8 and typing.get_type_hints() raises on
_tier_replacement_sources, merge_raw_extraction and build_merge. Nothing breaks at
import because `from __future__ import annotations` keeps the hints as strings.

The added test walks build.py's own functions rather than the package: nx under
if TYPE_CHECKING in cross_repo_calls.py, cross_repo_types.py and prs.py is
unresolvable on purpose, while build.py imports every annotated name at module scope.

(cherry picked from commit c315a15)
`tag or source.parent.parent.name` is empty whenever the graph is not two levels
below a named directory: `graphify global add /tmp/graph.json`, or any relative
path. Every node is then prefixed `::`, the prune key is `""`, and the manifest
entry cannot be removed because the `remove` subcommand reads an empty tag as a
missing argument.

Infer through `distinct_repo_tags`, the helper `merge-graphs` already uses, so the
two entry points cannot diverge again and the degrade is `"repo"`; absolutize
first so a relative path names the repo dir rather than the cwd placeholder. An
omitted `remove` tag stays a usage error, while an explicitly empty one is now
addressable so stores written by earlier revisions can be cleaned up.

(cherry picked from commit 6a317e6)
A workspace-barrel import (`import { evaluate } from '@scope/domain'`)
resolves through `export *` by probing each star target for a symbol
node with the imported name. The symbol map normalizes labels by
stripping the leading dot, so an interface/class METHOD `.evaluate()`
in the first star target satisfied the probe before the exported
function in the second one was reached. The import edge and the
cross-file call then bound to the method node, and the real function
lost its inbound `calls` edge (read as dead code).

Track member nodes separately and skip them in the star-export walk: a
module can only re-export top-level bindings. A member never shadows a
same-named top-level symbol in the map anymore either.

Fixes Graphify-Labs#3436

(cherry picked from commit 52e4a2b)
Adds an AST branch for create_index in the SQL extractor, emitting an index
node with an 'indexes' edge to its table (cross-file stub when the table is
defined elsewhere), mirroring the existing trigger handling. (Graphify-Labs#3470)
The Rust extractor gated every non-function item on struct_item,
enum_item and trait_item, so tree-sitter-rust's static_item and
const_item never produced a node: a constant existed in the graph only
through the files that referenced it, never from the Rust that defines
it (0 of 783 names in a jdx/mise snapshot).

Add a branch for both node types: a module-level declaration gets a
node with a file-level contains edge, an associated const inside an
impl is attributed to the impl like a method, and the declared type is
referenced the way a struct field type is.

Fixes Graphify-Labs#3471

(cherry picked from commit 70ff464)
… unwritable

`install()` copies the skill files first and writes the always-on
registration afterwards, so an unguarded write to `~/.claude/CLAUDE.md`
left a half-completed install plus a traceback whenever that file could
not be written. That is the normal shape for a declaratively managed
dotfile: nix/home-manager symlinks it into a read-only /nix/store, and
chezmoi or stow with read-only sources do the same.

Route the two registrations inside `install()` (CLAUDE.md and
CODEBUDDY.md, which had the identical unguarded pattern) through one
helper that reports the skip on stderr and lets the install finish, so
the skill files that were already laid down stay usable.

Refs Graphify-Labs#3474

Co-Authored-By: Claude Code <noreply@anthropic.com>
(cherry picked from commit 47041ee)
…expires

Two independent failures, both silent, found while repairing 16 repositories whose
graphs had quietly stopped tracking their code.

1. `graphify label` downgraded graphs while reporting success

`detect_backend()` is key-based, and `claude-cli` is the one backend with no API key
to find — `_get_backend_api_key` can never return one for it, and the fallback loop
excludes it by name. Nothing else looked for it. So on a machine with the Claude Code
CLI installed and no API key, labelling announced "no LLM backend configured",
replaced every real community name with a `Community N` placeholder, and exited 0.
Run over an existing graph, that overwrites good names with worse ones and looks like
success; taking the exit code at face value commits the damage.

The fallback is placed in the labelling path, NOT in `detect_backend()`. Widening
detection itself was the first attempt and it broke
`test_mixed_repo_without_key_errors_and_points_at_code_only`: extraction deliberately
refuses to run without a configured backend and points at `--code-only`, and on any
machine with the CLI present that contract would have silently changed to "shell out
to the CLI instead". That contract is unchanged here, and a test now pins it.

2. The post-commit hook pinned a path with an expiry date

`_pinned_python()` wrote `sys.executable` verbatim. graphify installed from inside a
snap-confined editor lives under `~/snap/<app>/<revision>/`, and snap swaps that
revision on update and prunes the old tree. Every hook pinned to it then dies —
observed across 15 repositories at once when an editor snap moved past revision 259.
Each commit printed "could not locate a Python with graphify installed" and the graphs
stopped following the code, with nothing failing loudly enough to notice.

Two changes: `_pinned_python()` declines to pin a path under a rotating revision
directory, which its docstring already describes as safe degradation; and the
uv-tools probe — which already scans tool environments — additionally scans
snap-confined HOMEs by glob, since an install made inside a snap lands in that snap's
private HOME and the plain `$HOME` roots never see it once the hook runs from an
ordinary shell.

Verified: same 25 pre-existing failures before and after (unrelated: terraform
fixtures, a TS scaling test); passing goes 5110 -> 5119, the nine tests added here.
Each change was mutation-tested — removing it fails a test that the others still pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit c6065e2)
…cli fallback

Graphify-Labs#3475 added a PATH probe for a claude-cli labelling fallback; the no-backend
test must neutralize it so it stays deterministic on machines with claude
installed (follow-up to Graphify-Labs#3475).
Autouse conftest fixture clears every backend env var detect_backend() reads
(API keys, Azure/AWS/Ollama), with a self-guard test that the cleared list
covers every variable the detector reads. (Graphify-Labs#3484)
L4XB and others added 9 commits September 10, 2026 23:05
The postgres extra's psycopg[binary] was the sole dep missing from all; a new
union test guards the all-extra against future drift. (Graphify-Labs#3483)
…h normalization, SQL indexes, rust statics, install/labelling robustness, all-extra psycopg
…ph cost once

`global_add` reads the global graph, runs the cross-repo member call pass over it
and writes it back on every call, so registering K units did each of those K
times on a graph that grows with every one — quadratic in K.

`global_add_many` composes an ordered batch against one in-memory graph: one
read, one cross-repo pass over the finished graph, one write. `global_add`
delegates to it with a batch of one, so there is a single composition
implementation and the two paths cannot drift.

Composition order is unchanged. Each unit is still pruned and composed in turn,
and the external-label dedup is still read off the graph as it stands when that
unit arrives, so a later unit merges onto a stub an earlier one composed. The
cross-repo pass recomputes from the parked entries, so one run over the composed
batch lands where a run per unit does.

An unchanged batch returns before the graph is read. Missing sources, oversized
sources and one tag naming two sources in a batch are all rejected before the
first unit is composed, since the batch writes only at the end and a late
failure discards everything composed before it.
The index has to be built after the prune, not before it. A stub the previous
revision owned is deleted by the prune; an index built ahead of it still names that
id, the unit's own stub is then treated as a duplicate and never composed, and
rewiring an edge onto the missing id has add_edge invent an attribute-less node in
its place.

The assertion is on the stored graph rather than on the dedup, so it keeps its
meaning if the per-repo-stub change removes the dedup entirely. Hoisting the index
above the prune turns it red.
…hes merge-graphs

`global_add` merged nodes with no `source_file` by label, so an external
stub's `repo` tag became a function of merge order. `merge-graphs` composes
the prefixed units as-is, so the two paths produced different graphs from
identical inputs.

Ownership by merge order also fabricated cross-repo edges (one endpoint with
no `source_file`) and let `prune_repo_from_graph` delete other repos' real
edges along with the stub they pointed at on re-scan. `added` subtracted the
remap size, under-reporting each repo's `node_count` in the manifest.

Compose each unit's stubs directly. The self-loop guard went with the remap
that produced the self-loops.
… list operations

- Added `global_add`, `global_add_many`, `global_remove`, and `global_list` functions to manage project graphs in a global context.
- Introduced manifest handling to track repositories and their associated graphs.
- Implemented graph loading and saving using NetworkX and JSON serialization.
- Added error handling for corrupted manifest files and oversized graph files.
- Developed unit tests to ensure functionality and correctness of global graph operations, including handling of duplicate nodes and edge rewiring.
- Created helper functions for graph creation and JSON conversion for testing purposes.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 5 advisory finding(s) below merit a look before merge.

Formal verification. 1 change(s) tested, no difference found (not proven).


Graphify review — findings

Resolves Python calls to a nested inner function by lexical scope while keeping direct recursion as a self-loop, and fixes namespace-package, flat-script-sibling, and PHP use ... as alias import resolution. Normalizes source_file/definition_file to the build root before dedup so semantic absolute paths no longer collide with their AST twins and drop rationale/summary, skips an out/ directory only with build-output evidence, resolves variable-path shell script invocations, and adds the missing Iterable import in build.py. Extends graphify global add to accept multiple graphs via global_add_many with per-path tag inference (degrading to a non-empty tag instead of an empty one), makes install skip an unwritable always-on registration with a warning rather than aborting, and resolves the claude CLI at run time so community labelling survives updates.

Worth a look

  • Unresolved calls no longer recorded for cross-file resolutiongraphify/extractors/engine.py:5872 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Duplicate global_add_many definition; second overwrites first and has incompatible return typegraphify/global_graph.py · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Duplicate global_add_many definitions; second shadows firstgraphify/global_graph.py · Escalate · high · 2 independent checks
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Duplicate definition of global_add_many with incompatible signature and return typegraphify/global_graph.py:81 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Concurrent global_add_many calls can lose graph updatesgraphify/global_graph.py:269 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 5734 functions depend on the 3079 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 585 callers, 44 callees
  • new: _rebuild_code() — 115 callers, 51 callees
  • new: build_from_json() — 204 callers, 19 callees
  • new: deduplicate_entities() — 79 callers, 22 callees
  • new: detect() — 110 callers, 15 callees
  • new: build_merge() — 68 callers, 14 callees
  • new: to_obsidian() — 38 callers, 14 callees
  • new: dispatch_command() — 4 callers, 123 callees
  • …and 141 more — each is listed as a finding

Verification — 5734 functions in the blast radius were not formally verified this run (proofs are advisory here).

Health delta baseline: last indexed commit 23f2ffa (diverged from this PR's base — delta is approximate).

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: 5650 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

270 of 270 test file(s) selected (100%) via static blast radius.

Escalated to a full run for safety — the selection is not trustworthy on its own (see below). CI should run the whole suite.

  • tests/test_affected_cli.py — impact, full-run-safety
  • tests/test_affected_member_seed.py — full-run-safety
  • tests/test_agents_platform.py — impact, full-run-safety
  • tests/test_analyze.py — impact, full-run-safety
  • tests/test_anthropic_custom_endpoint.py — full-run-safety
  • tests/test_antigravity_install.py — full-run-safety
  • tests/test_apm_fallback_version.py — full-run-safety
  • tests/test_architecture_doc.py — full-run-safety
  • tests/test_astro_extraction.py — impact, full-run-safety
  • tests/test_astro_import_ids.py — impact, full-run-safety
  • tests/test_atomic_canvas_export.py — impact, full-run-safety
  • tests/test_atomic_version_stamp.py — impact, full-run-safety
  • tests/test_atomic_writes.py — impact, full-run-safety
  • tests/test_backend_env_isolation.py — impact, changed-test, full-run-safety
  • tests/test_backend_extras.py — impact, changed-test, full-run-safety
  • tests/test_benchmark.py — impact, full-run-safety
  • tests/test_benchmark_raw_graph.py — impact, full-run-safety
  • tests/test_build.py — impact, changed-test, full-run-safety
  • tests/test_build_merge_hyperedges_and_prune.py — impact, full-run-safety
  • tests/test_build_merge_shrink_guard.py — impact, full-run-safety
  • tests/test_builtin_global_type_refs.py — impact, full-run-safety
  • tests/test_cache.py — full-run-safety
  • tests/test_callflow_html.py — full-run-safety
  • tests/test_cargo_introspect.py — full-run-safety
  • tests/test_carried_hyperedge_remap.py — impact, full-run-safety
  • tests/test_case_sensitive_resolution.py — impact, full-run-safety
  • tests/test_charmap_encoding.py — impact, full-run-safety
  • tests/test_chunking.py — impact, full-run-safety
  • tests/test_cjs_module_extension.py — impact, full-run-safety
  • tests/test_claude_cli_backend.py — impact, full-run-safety
  • tests/test_claude_md.py — impact, full-run-safety
  • tests/test_cli_broken_pipe.py — full-run-safety
  • tests/test_cli_export.py — impact, full-run-safety
  • tests/test_cli_global_add_many.py — changed-test, full-run-safety
  • tests/test_cli_help.py — full-run-safety
  • tests/test_cluster.py — impact, full-run-safety
  • tests/test_codebuddy.py — impact, full-run-safety
  • tests/test_community_hub_labels.py — full-run-safety
  • tests/test_community_labels_skill.py — full-run-safety
  • tests/test_confidence.py — impact, full-run-safety
  • tests/test_corrupt_graph_json.py — impact, full-run-safety
  • tests/test_cpp_nested_and_cli.py — impact, full-run-safety
  • tests/test_cpp_objc_cross_file_calls.py — impact, full-run-safety
  • tests/test_cpp_preprocess.py — full-run-safety
  • tests/test_cross_extension_reexport_self_cycle.py — impact, full-run-safety
  • tests/test_cross_language_call_resolution.py — impact, full-run-safety
  • tests/test_cross_repo_member_calls.py — impact, full-run-safety
  • tests/test_cross_repo_shared_types.py — full-run-safety
  • tests/test_csharp_call_site_generic_args.py — impact, full-run-safety
  • tests/test_csharp_enum_members.py — impact, full-run-safety
  • … and 220 more

non-code file(s) changed (CHANGELOG.md, README.md, docs/graphify-card.png, pyproject.toml, uv.lock) → running the full suite for safety (a code graph can't see config/fixture/data deps)

changed code file(s) with no mapped test (CHANGELOG.md, README.md, pyproject.toml, tests/conftest.py, tests/fixtures/sample.rs) — a coverage gap or a missing link — running the full suite rather than only the selected tests

Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.

Formal verification

Could not verify: Could not verify build.

The verifier did not have enough to check build, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: not verifiable: all 9 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly AttributeError — names the real obstacle, not a sampling gap)

Could not verify: Could not verify build\_merge.

The verifier did not have enough to check build\_merge, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `graph_path` is annotated `str | Path | None` — outside the synthesizable primitive/collection set

Could not verify: Could not verify dispatch\_command.

The verifier did not have enough to check dispatch\_command, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: not verifiable: all 23 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly SystemExit — names the real obstacle, not a sampling gap)

Could not verify: Could not verify \_is\_noise\_dir.

The verifier did not have enough to check \_is\_noise\_dir, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `parent` is annotated `'Path | None'` — outside the synthesizable primitive/collection set

Could not verify: Could not verify extract.

The verifier did not have enough to check extract, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `cache_root` is annotated `Path | None` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_import\_php.

The verifier did not have enough to check \_import\_php, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: not verifiable: all 200 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly AttributeError — names the real obstacle, not a sampling gap)

Could not verify: Could not verify extract\_bash.

The verifier did not have enough to check extract\_bash, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_extract\_generic.

The verifier did not have enough to check \_extract\_generic, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_apply\_symbol\_resolution\_facts.

The verifier did not have enough to check \_apply\_symbol\_resolution\_facts, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `root` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_collect\_python\_symbol\_resolution\_facts.

The verifier did not have enough to check \_collect\_python\_symbol\_resolution\_facts, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `root` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify extract\_rust.

The verifier did not have enough to check extract\_rust, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify extract\_sql.

The verifier did not have enough to check extract\_sql, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify global\_add.

The verifier did not have enough to check global\_add, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `source_path` is annotated `Path` — outside the synthesizable primitive/collection set

No difference found (not proven): No behavior difference found in \_pinned\_python (not a proof).

The verifier ran both versions of \_pinned\_python on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.

Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.

Note: An input the sampler did not try could still differ.

Could not verify: Could not verify install.

The verifier did not have enough to check install, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: not verifiable: all 23 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly SystemExit — names the real obstacle, not a sampling gap)

Could not verify: Could not verify generate\_community\_labels.

The verifier did not have enough to check generate\_community\_labels, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: non-vacuity: domain too small (only 1 distinct inputs exercised, need 3) — 'no divergence' would be near-vacuous

· 8 grounded finding(s) anchored inline below; 141 more finding(s) on lines outside this diff (see the check run).

Comment thread graphify/extract.py
e["target"] = alias_map[tgt]


def _repoint_python_sibling_imports(paths, all_nodes, all_edges, root) -> None:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regression_repoint_python_sibling_imports()

high coupling complexity (Ca·Ce = 12).

Grounded coupling-delta finding (deterministic), not an LLM guess.

@@ -59,7 +59,7 @@ def _rust_collect_type_refs(node, source: bytes, generic: bool, out: list[tuple[
})

def extract_rust(path: Path) -> dict:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressionextract_rust()

fans out to 6 callees (efferent coupling); 19 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Comment thread graphify/global_graph.py Outdated
return h.hexdigest()[:16]


def global_add_many(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressionglobal_add_many()

fans out to 9 callees (efferent coupling); 25 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Comment thread graphify/global_graph.py
return {"results": results, "cross_repo_calls": cross_repo_calls}


def global_remove(repo_tag: str) -> int:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressionglobal_remove()

8 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Comment thread graphify/global_graph_pr3434.py Outdated
return result


def global_add_many(sources: Sequence[tuple[Path, str]]) -> dict:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressionglobal_add_many()

fans out to 9 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Comment thread graphify/llm.py
return tuple(seen)


def detect_backend() -> str | None:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressiondetect_backend()

19 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Comment thread tests/test_global_graph_pr3434.py Outdated
assert G.has_edge("repoC::worker", "repoA::logging")


def test_global_add_many_re_adding_a_repo_keeps_its_own_stub_in_the_graph(tmp_path):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressiontest_global_add_many_re_adding_a_repo_keeps_its_own_stub_in_the_graph()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.



@pytest.mark.parametrize("suffix", ["ts", "js"])
def test_js_namespace_reexport_import_targets_real_binding(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressiontest_js_namespace_reexport_import_targets_real_binding()

fans out to 7 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 5 advisory finding(s) below merit a look before merge.

Formal verification. 1 change(s) tested, no difference found (not proven).


Graphify review — findings

Adds SQL index nodes for CREATE [UNIQUE] INDEX linked to their tables, and infers non-empty repo tags for graphify global add when given a bare path — now also accepting multiple graph paths in one invocation via global_add_many (rejecting --as with more than one). Normalizes source_file/definition_file to the build root before dedup so a semantic node's absolute path no longer collides falsely with its relative AST twin and drops the node's rationale/summary. Skips a directory named out as build output only when there's actual build-output evidence, resolves variable-path exec-position shell invocations ("$SCRIPT_DIR/foo.sh") to their constant target, and resolves community-labelling's claude CLI at run time instead of a pinned path that expires under snap/nvm. Makes graphify install skip an unwritable always-on registration target with a warning and still install the skill instead of aborting, adds the missing Iterable import in build.py, and swaps the README logo for graphify-card.png.

Worth a look

  • build_merge references undefined _eff_rootgraphify/build.py:2031 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • global_add_many no longer dedups external-library nodes across reposgraphify/global_graph.py · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Concurrent global_add_many calls can lose graph updatesgraphify/global_graph.py:170 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • '.d.ts' suffix check also matches legitimate TypeScript source filesgraphify/detect.py:903 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Any out/production directory is treated as generated outputgraphify/detect.py:918 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 5667 functions depend on the 3012 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 586 callers, 44 callees
  • new: _rebuild_code() — 115 callers, 51 callees
  • new: build_from_json() — 204 callers, 19 callees
  • new: deduplicate_entities() — 75 callers, 22 callees
  • new: detect() — 110 callers, 15 callees
  • new: build_merge() — 68 callers, 14 callees
  • new: to_obsidian() — 38 callers, 14 callees
  • new: dispatch_command() — 4 callers, 125 callees
  • …and 139 more — each is listed as a finding

Verification — 5667 functions in the blast radius were not formally verified this run (proofs are advisory here).

Health delta baseline: last indexed commit 522ea96 (diverged from this PR's base — delta is approximate).

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: 5583 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

269 of 269 test file(s) selected (100%) via static blast radius.

Escalated to a full run for safety — the selection is not trustworthy on its own (see below). CI should run the whole suite.

  • tests/test_affected_cli.py — impact, full-run-safety
  • tests/test_affected_member_seed.py — full-run-safety
  • tests/test_agents_platform.py — impact, full-run-safety
  • tests/test_analyze.py — impact, full-run-safety
  • tests/test_anthropic_custom_endpoint.py — full-run-safety
  • tests/test_antigravity_install.py — full-run-safety
  • tests/test_apm_fallback_version.py — full-run-safety
  • tests/test_architecture_doc.py — full-run-safety
  • tests/test_astro_extraction.py — impact, full-run-safety
  • tests/test_astro_import_ids.py — impact, full-run-safety
  • tests/test_atomic_canvas_export.py — impact, full-run-safety
  • tests/test_atomic_version_stamp.py — impact, full-run-safety
  • tests/test_atomic_writes.py — impact, full-run-safety
  • tests/test_backend_env_isolation.py — impact, changed-test, full-run-safety
  • tests/test_backend_extras.py — impact, changed-test, full-run-safety
  • tests/test_benchmark.py — impact, full-run-safety
  • tests/test_benchmark_raw_graph.py — impact, full-run-safety
  • tests/test_build.py — impact, changed-test, full-run-safety
  • tests/test_build_merge_hyperedges_and_prune.py — impact, full-run-safety
  • tests/test_build_merge_shrink_guard.py — impact, full-run-safety
  • tests/test_builtin_global_type_refs.py — impact, full-run-safety
  • tests/test_cache.py — full-run-safety
  • tests/test_callflow_html.py — full-run-safety
  • tests/test_cargo_introspect.py — full-run-safety
  • tests/test_carried_hyperedge_remap.py — impact, full-run-safety
  • tests/test_case_sensitive_resolution.py — impact, full-run-safety
  • tests/test_charmap_encoding.py — impact, full-run-safety
  • tests/test_chunking.py — impact, full-run-safety
  • tests/test_cjs_module_extension.py — impact, full-run-safety
  • tests/test_claude_cli_backend.py — impact, full-run-safety
  • tests/test_claude_md.py — impact, full-run-safety
  • tests/test_cli_broken_pipe.py — full-run-safety
  • tests/test_cli_export.py — impact, full-run-safety
  • tests/test_cli_global_add_many.py — impact, changed-test, full-run-safety
  • tests/test_cli_help.py — full-run-safety
  • tests/test_cluster.py — impact, full-run-safety
  • tests/test_codebuddy.py — impact, full-run-safety
  • tests/test_community_hub_labels.py — full-run-safety
  • tests/test_community_labels_skill.py — full-run-safety
  • tests/test_confidence.py — impact, full-run-safety
  • tests/test_corrupt_graph_json.py — impact, full-run-safety
  • tests/test_cpp_nested_and_cli.py — impact, full-run-safety
  • tests/test_cpp_objc_cross_file_calls.py — impact, full-run-safety
  • tests/test_cpp_preprocess.py — full-run-safety
  • tests/test_cross_extension_reexport_self_cycle.py — impact, full-run-safety
  • tests/test_cross_language_call_resolution.py — impact, full-run-safety
  • tests/test_cross_repo_member_calls.py — impact, full-run-safety
  • tests/test_cross_repo_shared_types.py — full-run-safety
  • tests/test_csharp_call_site_generic_args.py — impact, full-run-safety
  • tests/test_csharp_enum_members.py — impact, full-run-safety
  • … and 219 more

non-code file(s) changed (CHANGELOG.md, README.md, docs/graphify-card.png, pyproject.toml, uv.lock) → running the full suite for safety (a code graph can't see config/fixture/data deps)

changed code file(s) with no mapped test (CHANGELOG.md, README.md, pyproject.toml, tests/conftest.py, tests/fixtures/sample.rs) — a coverage gap or a missing link — running the full suite rather than only the selected tests

Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.

Formal verification

Could not verify: Could not verify build.

The verifier did not have enough to check build, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: not verifiable: all 9 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly AttributeError — names the real obstacle, not a sampling gap)

Could not verify: Could not verify build\_merge.

The verifier did not have enough to check build\_merge, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `graph_path` is annotated `str | Path | None` — outside the synthesizable primitive/collection set

Could not verify: Could not verify dispatch\_command.

The verifier did not have enough to check dispatch\_command, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: not verifiable: all 23 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly SystemExit — names the real obstacle, not a sampling gap)

Could not verify: Could not verify \_is\_noise\_dir.

The verifier did not have enough to check \_is\_noise\_dir, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `parent` is annotated `'Path | None'` — outside the synthesizable primitive/collection set

Could not verify: Could not verify extract.

The verifier did not have enough to check extract, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `cache_root` is annotated `Path | None` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_import\_php.

The verifier did not have enough to check \_import\_php, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: not verifiable: all 200 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly AttributeError — names the real obstacle, not a sampling gap)

Could not verify: Could not verify extract\_bash.

The verifier did not have enough to check extract\_bash, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_extract\_generic.

The verifier did not have enough to check \_extract\_generic, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_apply\_symbol\_resolution\_facts.

The verifier did not have enough to check \_apply\_symbol\_resolution\_facts, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `root` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_collect\_python\_symbol\_resolution\_facts.

The verifier did not have enough to check \_collect\_python\_symbol\_resolution\_facts, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `root` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify extract\_rust.

The verifier did not have enough to check extract\_rust, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify extract\_sql.

The verifier did not have enough to check extract\_sql, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify global\_add.

The verifier did not have enough to check global\_add, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `source_path` is annotated `Path` — outside the synthesizable primitive/collection set

No difference found (not proven): No behavior difference found in \_pinned\_python (not a proof).

The verifier ran both versions of \_pinned\_python on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.

Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.

Note: An input the sampler did not try could still differ.

Could not verify: Could not verify install.

The verifier did not have enough to check install, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: not verifiable: all 23 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly SystemExit — names the real obstacle, not a sampling gap)

Could not verify: Could not verify generate\_community\_labels.

The verifier did not have enough to check generate\_community\_labels, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: non-vacuity: domain too small (only 1 distinct inputs exercised, need 3) — 'no divergence' would be near-vacuous

· 6 grounded finding(s) anchored inline below; 141 more finding(s) on lines outside this diff (see the check run).

Comment thread graphify/extract.py
e["target"] = alias_map[tgt]


def _repoint_python_sibling_imports(paths, all_nodes, all_edges, root) -> None:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regression_repoint_python_sibling_imports()

high coupling complexity (Ca·Ce = 12).

Grounded coupling-delta finding (deterministic), not an LLM guess.

@@ -59,7 +59,7 @@ def _rust_collect_type_refs(node, source: bytes, generic: bool, out: list[tuple[
})

def extract_rust(path: Path) -> dict:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressionextract_rust()

fans out to 6 callees (efferent coupling); 19 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Comment thread graphify/global_graph.py
"cross_repo_calls": 0}

# Load source graph
def global_add_many(sources: Sequence[tuple[Path, str]]) -> dict:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressionglobal_add_many()

fans out to 9 callees (efferent coupling); 14 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Comment thread graphify/global_graph.py
return {"results": results, "cross_repo_calls": cross_repo_calls}


def global_remove(repo_tag: str) -> int:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressionglobal_remove()

high coupling complexity (Ca·Ce = 25).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Comment thread graphify/llm.py
return tuple(seen)


def detect_backend() -> str | None:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressiondetect_backend()

19 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.



@pytest.mark.parametrize("suffix", ["ts", "js"])
def test_js_namespace_reexport_import_targets_real_binding(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressiontest_js_namespace_reexport_import_targets_real_binding()

fans out to 7 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

perf(global-graph): avoid repeated whole-graph reload/resolve/save in global add