Skip to content

fix: self-update check no longer defaults to installing upstream's package#7

Merged
Vit129 merged 1 commit into
mainfrom
audit-package-identity
Jul 5, 2026
Merged

fix: self-update check no longer defaults to installing upstream's package#7
Vit129 merged 1 commit into
mainfrom
audit-package-identity

Conversation

@Vit129

@Vit129 Vit129 commented Jul 5, 2026

Copy link
Copy Markdown
Owner

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's PACKAGE_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__.py to run on nearly every command, and with autoUpdate defaulting to True, would silently run uv tool upgrade graphifyy in 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 at 0.16.0, upstream's PyPI package is at 0.9.6, so the numeric comparison happens to not trigger today.

  • _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 — points at this fork's own GitHub releases, and only mentions the upstream-upgrade command inside the explicit opt-in explanation.
  • Two error-path messages in __main__.py (missing always-on block, skill-newer-than-package warning) suggested the same wrong reinstall command — repointed to uv tool install --reinstall git+https://github.com/Vit129/graphify.git.
  • The Antigravity MCP config snippet used uv run --with graphifyy, which would fetch upstream's package from PyPI at runtime instead of running this fork's query.py/serve.py (neither of which exist upstream) — repointed --with at 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)
  • Full suite: 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

…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
Vit129 merged commit ae4f2bc into main Jul 5, 2026
@Vit129
Vit129 deleted the audit-package-identity branch July 5, 2026 10:16
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
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>
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.

1 participant