Skip to content

watch: keep hub fallback community labels out of the tracked file (#3334) - #3551

Open
ayushcodes10 wants to merge 6 commits into
Graphify-Labs:v8from
ayushcodes10:fix-3334-labels-pending-sidecar
Open

ayushcodes10 wants to merge 6 commits into
Graphify-Labs:v8from
ayushcodes10:fix-3334-labels-pending-sidecar

Conversation

@ayushcodes10

Copy link
Copy Markdown
Contributor

Summary

Fixes #3334 (option 1 from the issue's own preference-ordered list: keep unlabelled communities out of the tracked file). graphify update names any community without a saved label after its deterministic hub (its highest-degree member — usually a filename like README.md, vite.config.ts), so reports and graph.json always show something readable. That fallback name was written straight into .graphify_labels.json alongside genuinely curated (manual or LLM) names — the file the project's own "Migration and portability" guidance says to commit as the curated semantic layer that makes label reuse work. Every from-empty rebuild (a fresh clone, a new worktree, a CI job with no prior graphify-out/) therefore appended fresh placeholder entries to that tracked file.

Hub-fallback names now live in a new, separate sidecar (.graphify_labels.pending.json) next to the tracked file. The tracked file only ever contains names someone actually set. A pending entry is still reused across rebuilds (not regenerated every run, matching the existing reuse guarantee curated labels already had) as long as the community's membership hasn't changed, and a graphify label pass promoting a pending community to a real name removes it from the sidecar in the same move. Reports and graph.json are unaffected — they already read from the full, merged label set, so a fallback-named community still shows its name exactly as before; only the on-disk tracked file's content changes.

Scoped to the graphify update/watch rebuild path only (graphify/watch.py), which is the issue's own reproduction. cli.py's equivalent cluster-only/label code paths carry a materially more complex reuse/--missing-only/LLM-dispatch flow; extending the same split there is a reasonable follow-up but is deliberately left out of this PR to keep the change bounded and reviewable.

Test plan

  • Updated the existing test_rebuild_code_drops_labels_whose_community_changed test, which had assumed all first-rebuild labels land in the tracked file.
  • Added three new tests: hub-fallback names stay out of the tracked file (and still reach graph.json's community_name); a pending name is reused, not regenerated, across an unrelated rebuild; and a promoted (curated) name correctly leaves the pending sidecar.
  • Verified all 4 updated/new tests fail against the pre-fix code and pass with the fix.
  • Manually reproduced the issue's exact scenario end to end (fresh corpus, first rebuild, curated one label, grow the corpus, rebuild again) and confirmed the curated label survives while fallback names correctly split across files.
  • Full suite: python3 -m pytest -q — 5489 passed, only the pre-existing unrelated failures (test_ollama_retry_cap.py missing openai in this env, one flaky timing assertion in test_ts_import_type_arguments.py).
  • python3 -m tools.skillgen --check — OK.

🤖 Generated with Claude Code

https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh

ayushcodes10 and others added 6 commits September 14, 2026 14:31
A community without a curated name gets a deterministic hub fallback
so reports and graph.json always show something readable, but that
fallback has always been written straight into the same tracked file
as genuinely curated, manual or LLM, names. This is the read half of
splitting the two apart: a new sidecar next to the tracked labels
file is now also loaded and merged into the working labels dict, and
every cid that came from it, or gets freshly hub filled this run
rather than reused from the tracked file, is tracked in a separate
set so the write step can route each entry back to the file it
belongs in. Stale invalidation is extended to cover sidecar entries
the same way it already covers tracked ones. Toward Graphify-Labs#3334.

The write side, which is what actually stops fallback names from
landing in the tracked file, follows in the next commit; this commit
alone still writes the merged result into the tracked file as
before, so existing behavior is unchanged and no test needs updating
yet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
The write half of splitting the two label kinds apart. Labels are
split back into the tracked, curated set and the pending, hub
fallback set right before serialising, using the cid set the read
side built up. The tracked file gets only the curated names, exactly
as before this change set began; the pending set goes to the new
sidecar, or the sidecar is removed outright once nothing in it is
still unpromoted, so a stale, now empty pending file never lingers
once its last entry becomes a real label. Fixes Graphify-Labs#3334.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
This test simulates a naming pass by reading whatever cid list the
first rebuild produced and overwriting each with a distinctive name.
That list now comes from the pending sidecar rather than the tracked
file, matching what a real naming pass does: promote a pending, hub
fallback entry into a curated one, removing it from the sidecar in
the same move.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
A from empty rebuild on a two file corpus must leave the tracked
labels file empty while writing both communities' hub names to the
pending sidecar, and every node's community_name in graph.json must
still carry the right name regardless of which file it actually
lives in.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
A hand edited pending sidecar entry must survive an unrelated
rebuild untouched, matching the reuse guarantee already given to
curated labels, rather than getting silently regenerated from the
hub every single run.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
Simulates a real naming pass by promoting every pending entry into
the tracked file and deleting the sidecar, then forces a real
rebuild. The promoted names must survive, and the community must not
reappear in the sidecar even though it did not change membership.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh

@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.

Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).

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


Graphify review — findings

Splits community label persistence so hub-fallback names (used when a community has no curated name) now write to a separate .graphify_labels.pending.json sidecar instead of the tracked .graphify_labels.json, keeping the tracked file to genuinely curated names only. Fallback names are still merged into labels for report and graph.json generation, reused across rebuilds when membership is unchanged, and a curated name promotes its cid out of the pending file; the pending sidecar is deleted when nothing remains pending. Count-based staleness detection compares against the tracked file alone so a from-empty rebuild's fresh pending entries don't skew the "unchanged" signal.

No blocking issues surfaced. 6 lower-confidence candidates did not survive cross-model review.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 593 functions depend on the 407 functions this change touches.

Health — this change adds coupling hotspots:

  • new: _rebuild_code() — 119 callers, 51 callees
  • new: dispatch_command() — 2 callers, 124 callees
  • new: watch() — 5 callers, 7 callees
  • new: _reconcile_graph_html() — 6 callers, 5 callees
  • new: _reconcile_existing_graph() — 1 callers, 8 callees
  • new: _reconcile_markdown_links() — 1 callers, 6 callees
  • new: test_poisoned_manifest_is_healed() — 0 callers, 6 callees

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

Test selection

Test selection

3 of 275 test file(s) selected (1%) via static blast radius.

  • tests/test_labeling.py — impact
  • tests/test_watch.py — impact, changed-test
  • tests/test_watch_manifest_location.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 \_rebuild\_code.

The verifier did not have enough to check \_rebuild\_code, 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 `watch_path` is annotated `Path` — outside the synthesizable primitive/collection set

· 7 more finding(s) on lines outside this diff (see the check run).

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.

update writes filename placeholder names into the tracked .graphify_labels.json for unlabelled communities

1 participant