fix: self-update check no longer defaults to installing upstream's package#7
Merged
Conversation
…package
graphify/update_check.py's PACKAGE_NAME ("graphifyy") is upstream's own PyPI
package — a different, unrelated codebase from this fork (this fork isn't
published to PyPI at all, per README's "Source-only fork" note). This check
runs on nearly every command (wired in __main__.py, skipped only for
version/help/silent commands) and, with autoUpdate defaulting to True,
would silently run `uv tool upgrade graphifyy` / `pipx upgrade graphifyy`
in the background the moment upstream's PyPI version number ever exceeds
this fork's local version — replacing this fork's install with upstream's
package. Exact same bug class as harness-terminal's Sparkle appcast issue,
just auto-triggered instead of one click away.
- _resolve_auto_update now defaults to False; opt-in only via
{"autoUpdate": true} in ~/.config/graphify/config.json.
- The default nudge message no longer suggests a command that installs the
wrong package — it points at this fork's own GitHub releases instead, and
only mentions the upstream-upgrade command inside the explicit opt-in
explanation.
- Two other places printed the same wrong advice on error paths
(__main__.py's "always-on block missing" and "skill newer than package"
warnings) — repointed to `uv tool install --reinstall
git+https://github.com/Vit129/graphify.git`.
- The Antigravity MCP config snippet's `uv run --with graphifyy` would have
fetched upstream's package from PyPI at runtime instead of running this
fork's query.py/serve.py (which don't exist upstream) — repointed --with
at this fork's git URL.
Verified: full test suite passes (2980 passed, 1 pre-existing unrelated
failure confirmed to also reproduce on a clean origin/main checkout when
run from a path containing a hidden directory component — an artifact of
this sandbox's .claude/worktrees/ layout, not a regression).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016B7gQRU7Egffu3neFYJAjE
Vit129
added a commit
that referenced
this pull request
Jul 19, 2026
Enabled pagerank_ranking on kouen-terminal (real repo), rebuilt (100% of 15393 nodes got a pagerank attribute), re-ran the exact original benchmark query. The correct answer did not surface in the top results - root-caused why rather than stopping at the negative observation: SessionEditor.zoomPane() scores #7 of 1765 real BM25 candidates, ~27% behind a genuine keyword-collision false positive (.addNewTab() matching "add"/"new"/"tab" from the query) - a real relevance gap that exceeds the boost's 15% ceiling by design, not a near-tie the feature was ever built to close. Implementation verdict: correct and shippable as designed (Task 4's test suite already proves this in isolation this feature just wasn't sufficient to fix the specific case the roadmap named as motivation. Documented in both this task-progress file and a follow-up note in p17-post-competitor-audit-roadmap.md, so the roadmap accurately reflects what shipped and what the next real hypothesis should be (P9's synonym/vocabulary territory, not more ranking tweaks) rather than silently declaring victory. Cleaned up kouen-terminal fully after testing - graphify.toml reverted, the 4 git-tracked graphify-out/ files restored via git checkout, none of the repo's own unrelated in-progress work touched. All 7 tasks done. PR #15 ready for review. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvDvoAyysaoQiWPmmLmLqR
5 tasks
Vit129
added a commit
that referenced
this pull request
Jul 19, 2026
) * docs(plans): add P17 post-competitor-audit roadmap Records the prioritized punch-list from this session's search-quality benchmark (6 real projects) + upstream diff + 3-agent competitor research pass (PKM tools, code-graph MCP tools, AI-agent context approaches), so it survives context compaction: file-watcher auto-sync first (biggest gap vs CodeGraph/GitNexus), then PageRank-style ranking, a 0.18.0 cut for PR #12's already-merged content, the affected --relation prefix-match gap, the Home-Assistant config decision, and content-as-data indexing (still held pending explicit sign-off). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvDvoAyysaoQiWPmmLmLqR * docs(plans): mark P14 done - doc was stale, code already shipped Verified live: --no-open/webbrowser.open/osascript reload in __main__.py, settings-2d/settings-3d slider panels in export.py. The doc's remaining 3 goals landed in e4e4f9c and d79b6fb but the plan doc itself was never updated to reflect it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvDvoAyysaoQiWPmmLmLqR * docs(plans): design file-watcher auto-sync (P17 item 1) Orientation first, per the roadmap's own instruction: graphify watch (watch.py) already exists and works - a real watchdog-based daemon, not a stub. The actual gap is that nothing auto-starts it for an AI agent session; also found _install_claude_hook's printed message already (falsely) claims a post-edit rebuild happens. Decision: add a PostToolUse hook that triggers a debounced background `graphify update`, not an auto-started `watch` daemon - closes the exact gap this session hit (agent edits, forgets to rebuild) with no new dependency (watchdog is optional/not installed by default) and no new process-lifecycle surface (PID files, orphaned daemons across session restarts). `graphify watch` stays as-is for the human-editing- in-an-IDE case, which is a different, unconfirmed need. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvDvoAyysaoQiWPmmLmLqR * docs(plans): correct file-watcher-auto-sync design - missed a 3rd existing mechanism graphify/hooks.py installs real git post-commit/post-checkout hooks with a detached background rebuild (hooks.py:230/483) - already installed in this repo (the recurring graphify-out/.last-refresh.log line seen on every commit this whole session was this hook firing). Missed in the design's first orientation pass despite that being its own explicit stated principle. Caught before task-design proceeded on a wrong premise. Reframes the gap correctly: not "no auto-rebuild exists" (two mechanisms already do, watch's filesystem daemon and hooks.py's commit hook) but "neither covers the uncommitted-edit window an agent session actually lives in." The PostToolUse-hook decision (option B) still stands - it's the one missing trigger point, not a replacement for either existing mechanism. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvDvoAyysaoQiWPmmLmLqR * docs(plans): task-design for file-watcher auto-sync (P17 item 1) 6 tasks, Server Logic (trigger_background_update in watch.py, a --trigger CLI entry, the _POST_EDIT_HOOK constant) then Integration (wire into _install_claude_hook/_uninstall_claude_hook, full test suite, live validation against a real repo). Confirmed two concrete pre-existing gaps along the way: tests/test_watch.py exists (extend, don't create new), but _install_claude_hook/_uninstall_claude_hook have zero test coverage at all - closed as a side effect of task 4 rather than left as separate cleanup. Not implemented yet - task list only, awaiting go-ahead per this session's established pattern (design/plan first, confirm, then code). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvDvoAyysaoQiWPmmLmLqR * feat(watch): trigger_background_update() - task 1 of file-watcher auto-sync Spawns a detached, non-blocking background rebuild for a project root. Meant to be called once per agent write via a future PostToolUse hook (task 3) - covers the window neither existing auto-rebuild mechanism does: graphify watch's foreground daemon (must be started by hand) and hooks.py's git post-commit hook (fires only on commit) both miss uncommitted, mid-session edits. Deviated from the original task-design plan on implementation: _rebuild_code already has its own non-blocking lock + queue/drain coalescing (watch.py:459-480), so no new debounce-marker file or reimplemented PID check was needed - a burst of rapid triggers already collapses into the fewest rebuilds necessary via that existing mechanism. Detach technique mirrors hooks.py's _LAUNCHER_TEMPLATE flags exactly (proven cross-platform there already) as plain Python, since this call site has no shell layer to work around. Verified: 5 new unit tests (spawn shape, argv-passed paths, log dir creation, spawn-failure survival, _TRIGGER_BODY standalone-parses) + a real non-mocked end-to-end smoke test (detached child produced a correct graph.json from a real 2-function scratch corpus). Full suite: 3003 passed, 0 failed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvDvoAyysaoQiWPmmLmLqR * feat: implement post-edit hook for auto-syncing the knowledge graph * docs(plans): design PageRank-style ranking (P17 item 2) Orientation first, per this session's established discipline: _score_nodes/_pick_seeds read at their current line numbers, god_nodes(by="pagerank") confirmed as real existing precedent (not reimplementing PageRank), scipy confirmed optional and NOT in the `all` extras bundle (pyproject.toml:64) - this cannot become a hard dependency of query/explain/path. Benchmarked nx.pagerank on the largest known real graph (kouen-terminal, 15,251 nodes): 0.297s - cheap once at build time, too slow to redo per query, settling the precompute-vs-query-time question with a real number instead of a guess. Found the exact existing hook point to store it (to_json's node["community"] = ... loop, export.py:1001-1050) and the exact existing config-threading convention to mirror (value_coupling's plain proj_config.get() read, no schema file involved - verified config.py is an unschemad generic TOML loader). Decision: opt-in via graphify.toml's pagerank_ranking key (mirrors value_coupling/P15's exact rollout shape), precomputed once at build time and stored as a node attribute, composed as a small bounded multiplicative boost (max +15%) inside _pick_seeds' existing _seed_penalty step - not a separate re-scoring pass, not additive to raw BM25 scores. Reasoning for each choice fully documented in the design doc's Strategic/Tactical sections. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvDvoAyysaoQiWPmmLmLqR * docs(plans): task-design for PageRank ranking (P17 item 2) 7 tasks: shared scipy-missing message constant, _rebuild_code computes+passes PageRank, to_json writes the attribute, _seed_penalty composes a bounded boost (candidate-list max, not full-graph scan - avoids undoing the build-time-precompute performance win), config/CLI threading, full suite, live validation against the named motivating case (kouen-terminal's zoom/fullscreen query). Verified exact test file locations before writing the plan rather than leaving "grep first" placeholders: tests/test_analyze.py + test_watch.py (god_nodes pagerank), tests/test_serve.py:938+ (_pick_seeds, has the exact concept-penalty precedent pattern to mirror), test_watch.py likely for --value-coupling CLI threading (test_value_coupling.py turned out to be the YAML extractor's edge logic, not CLI flag tests). Not implemented yet - task list only, awaiting go-ahead per this session's established pattern. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvDvoAyysaoQiWPmmLmLqR * refactor(analyze): shared scipy-missing message constant - task 1 of pagerank ranking Extracted the install-instructions half of god_nodes(by="pagerank")'s ImportError message into _PAGERANK_SCIPY_MISSING_MSG, so the upcoming _rebuild_code pagerank-ranking path (task 2) can reuse the exact same install instructions with its own natural context prefix, instead of drifting into a second wording over time. Message text verified byte-identical to the original. Zero behavior change - pure refactor. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvDvoAyysaoQiWPmmLmLqR * feat(watch,export): compute+persist PageRank on graph.json nodes - tasks 2+3 _rebuild_code gets a new pagerank_ranking: bool = False parameter, mirroring value_coupling's exact shape (P15). When enabled, computes nx.pagerank(G) right before the existing to_json call, guarded by try/except ImportError (scipy is an optional dependency, not bundled in the `all` extras) - printing the shared install-instructions message on failure and building normally either way. Forwarded through both recursive acquire_lock=False self-call sites - caught myself missing the second one via grep verification before moving on, the same forwarded-kwargs bug class value_coupling shipped once already (#value_coupling P15 changelog note). to_json gets a new pagerank_scores parameter, writing a "pagerank" node attribute in the same loop that already attaches "community" - omitted entirely (not even null) when not provided, so every existing call site's output is byte-identical to before. Implemented together (task 3's to_json parameter is a hard prereq for task 2's call site to be complete/testable) rather than leaving task 2 in a syntactically-incomplete state. Verified: 5 new tests (3 watch.py + 2 export.py) covering the enabled/ disabled/scipy-missing paths and the default-omits-key regression guard, plus a real non-mocked end-to-end smoke test confirming the computed PageRank values are structurally sensible (hub/shared-leaf nodes ranked above individual branches), not just present. Full suite: 3011 passed, 0 failed, 0 regressions. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvDvoAyysaoQiWPmmLmLqR * feat(query): compose bounded PageRank boost into _seed_penalty - task 4 The riskiest task in this feature: touches _pick_seeds, the scoring path every query/explain/path call goes through. Added _PAGERANK_BOOST_MAX = 0.15 and extended _seed_penalty to multiply its existing concept/prose penalty by 1.0 + BOOST_MAX * (node_pagerank / max_pagerank_in_candidates) - max computed once per call, over the already-BM25-narrowed candidate list only, not a full graph scan (would undo task 2's build-time-precompute performance win). Zero regression guarantee for the default case: when no candidate carries a pagerank attribute (every graph built without pagerank_ranking, i.e. today's entire installed base), the boost multiplication is skipped entirely - literally the same code path as before this feature existed, not just numerically a no-op. Verified: 4 new tests (near-tie tiebreak, boundedness - hand-verified a max 15% boost can't close a 10x raw BM25 gap, zero-pagerank regression guard, missing-attribute-on-some-nodes doesn't crash) - all 15 pre-existing _pick_seeds tests (concept/prose penalty behavior) pass unchanged. Full suite: 3015 passed, 0 failed, 0 regressions. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvDvoAyysaoQiWPmmLmLqR * feat(__main__): wire pagerank_ranking through config/CLI - tasks 5+6 Config read (proj_config.get("pagerank_ranking", False)) and --pagerank-ranking CLI flag, mirroring value_coupling's exact shape. Scope expanded past the original plan, documented rather than silently absorbed: grepping every to_json/_to_json call site (not just the ones named in the task doc) found two more independent graph-build paths beyond _rebuild_code - the standalone `extract` command (builds from scratch, never calls _rebuild_code) and `cluster-only` (re-clusters an existing graph.json). Both needed a real decision: - `extract` command: genuinely needed wiring - added the same compute-pagerank-before-to_json pattern, config read, and CLI flag. Verified live: `graphify extract . --pagerank-ranking` on a real scratch corpus produced pagerank values matching _rebuild_code's own computation on an equivalent corpus exactly - two independent code paths agreeing is a real correctness signal. - `cluster-only`: verified empirically that NO change was needed - build_from_json generically preserves arbitrary node attributes (including pagerank) from the loaded graph.json, and to_json's conditional-write design never deletes an attribute it wasn't asked to set. Confirmed with a real round-trip (pagerank values byte-identical before/after cluster-only). Also theoretically correct: pagerank depends only on graph structure, which cluster-only never changes. No CLI-level test precedent exists for --value-coupling either (checked before deciding not to add a new mock-heavy CLI test file for --pagerank-ranking specifically) - real live end-to-end checks used instead, matching this session's validate-against-the-real-thing discipline. Full suite: 3015 passed, 0 failed, 0 regressions across all three touched command paths (update, update --all, extract). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvDvoAyysaoQiWPmmLmLqR * docs(plans): task 7 live validation - honest result, feature done Enabled pagerank_ranking on kouen-terminal (real repo), rebuilt (100% of 15393 nodes got a pagerank attribute), re-ran the exact original benchmark query. The correct answer did not surface in the top results - root-caused why rather than stopping at the negative observation: SessionEditor.zoomPane() scores #7 of 1765 real BM25 candidates, ~27% behind a genuine keyword-collision false positive (.addNewTab() matching "add"/"new"/"tab" from the query) - a real relevance gap that exceeds the boost's 15% ceiling by design, not a near-tie the feature was ever built to close. Implementation verdict: correct and shippable as designed (Task 4's test suite already proves this in isolation this feature just wasn't sufficient to fix the specific case the roadmap named as motivation. Documented in both this task-progress file and a follow-up note in p17-post-competitor-audit-roadmap.md, so the roadmap accurately reflects what shipped and what the next real hypothesis should be (P9's synonym/vocabulary territory, not more ranking tweaks) rather than silently declaring victory. Cleaned up kouen-terminal fully after testing - graphify.toml reverted, the 4 git-tracked graphify-out/ files restored via git checkout, none of the repo's own unrelated in-progress work touched. All 7 tasks done. PR #15 ready for review. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvDvoAyysaoQiWPmmLmLqR --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Same class of bug as the harness-terminal Sparkle appcast fix — found while doing the equivalent domain/identity audit for graphify.
graphify/update_check.py'sPACKAGE_NAME = "graphifyy"is upstream's own PyPI package (a different, unrelated codebase — this fork isn't published to PyPI at all). This check is wired into__main__.pyto run on nearly every command, and withautoUpdatedefaulting toTrue, would silently runuv tool upgrade graphifyyin the background the moment upstream's PyPI version number exceeds this fork's local version — replacing this fork's install with upstream's package. Currently dormant only by coincidence: this fork is at0.16.0, upstream's PyPI package is at0.9.6, so the numeric comparison happens to not trigger today._resolve_auto_updatenow defaults toFalse— opt-in only via{"autoUpdate": true}in~/.config/graphify/config.json.__main__.py(missing always-on block, skill-newer-than-package warning) suggested the same wrong reinstall command — repointed touv tool install --reinstall git+https://github.com/Vit129/graphify.git.uv run --with graphifyy, which would fetch upstream's package from PyPI at runtime instead of running this fork'squery.py/serve.py(neither of which exist upstream) — repointed--withat this fork's git URL.Test plan
pytest tests/test_update_check.py— 12/12 pass (updated 2 tests for the new default + message text)pytest tests/— 2980 passed, 1 pre-existing failure (test_collect_files_skips_hidden) confirmed unrelated — reproduced identically when run from any path containing a hidden directory component (an artifact of this sandbox's.claude/worktrees/layout, not a regression from this change)https://claude.ai/code/session_016B7gQRU7Egffu3neFYJAjE