Skip to content

skill.md --update inline merge bypasses fixed build_merge() — direction-inversion bug from #760 still hits Claude Code skill workflow #801

Description

@twipixel

Package: graphifyy v0.7.13
Skill version installed: 0.7.8 (warning printed by CLI)
Environment: macOS 24.6.0, Python 3.11 (uv tool install of graphifyy)
Related (closed): #760

Summary

Issue #760 fixed the build_merge() direction-inversion bug in build.py. The fix is correctly used by the CLI graphify update <path> command (__main__.py:2608 calls _build_merge).

However, the bundled skill.md template still ships a hand-rolled merge block in its --update section that never calls build_merge(). It uses the exact node_link_graph() round-trip pattern that #760 identified as the root cause. As a result, any AI coding assistant that follows the skill instructions (Claude Code, Codex, Cursor, etc.) re-introduces the very bug #760 closed.

Evidence

graphify-0.7.13/skill.md line 788 (the --update flow):

existing_data = json.loads(Path('graphify-out/graph.json').read_text())
G_existing = json_graph.node_link_graph(existing_data, edges='links')   # round-trip — direction lost
...
G_existing.update(G_new)
to_json(G_existing, communities, 'graphify-out/graph.json')             # write back inverted

Compare with the fix in build.py:235:

def build_merge(...):
    ...
    # Read JSON directly instead of going through node_link_graph().

The skill never imports build_merge.

Reproduction

  1. Build a TypeScript project graph with a small helper imported by many call sites — e.g. an axios wrapper called requestGet imported in 100+ API files.
  2. Run /graphify --update (Claude Code skill path), which executes the inline merge code from skill.md.
  3. Inspect graph.json for edges where source = requestGet. They will all be of the form requestGet --calls--> getXxx() despite requestGet being a callee, not a caller, in every actual source file.

In one real codebase:

  • requestGet (5-line axios wrapper) — graph reports 105 outgoing INFERRED 0.8 calls edges.
  • grep ground truth: 171 files import requestGet, 178 actual call sites.
  • All 105 edges point the wrong way; another 73 actual call sites are missing entirely.
  • Same pattern on every other god helper: requestPost (32), getEncryptUrl (18), usePageMove (17), getApiEncryptKey (17), getDeviceInfo (12), buildUrl (10).

(Bug 2 from #760INFERRED 0.8 for deterministic cross-file calls — is also visible because these edges were inserted by an older version, then preserved across every --update because the skill's inline path never re-extracts edges that aren't in the changed-file set.)

Why this matters

  • The Claude Code skill is the default invocation path for most users (/graphify and /graphify --update). Fixing build_merge() while leaving the skill template broken means almost no end-user gets the fix.
  • The skill template duplicates merge logic instead of calling build_merge(), so future build.py improvements will silently fail to reach skill users — exactly what happened here.
  • Skill is overwritten on every graphify install. There is no user-side workaround that survives upgrades.

Suggested fix

Replace the inline merge block in skill.md's --update section with a call to the fixed function:

from graphify.build import build_merge

merged = build_merge(
    existing_path='graphify-out/graph.json',
    new_extraction=new_extract,
    deleted_files=incremental.get('deleted_files', []),
    changed_files=incremental['new_files'].get('code', []),
)
Path('graphify-out/.graphify_extract.json').write_text(json.dumps(merged))

(Adjust signature to whatever build_merge already exposes.)

This eliminates ~40 lines of duplicated logic, keeps skill flow in sync with CLI flow, and prevents future build.py fixes from being orphaned.

Optional follow-ups

  • Same drift risk exists for the --cluster-only, query, path, explain, and add subcommand instructions — any block that re-implements logic inline rather than calling a graphify Python entry point is a future drift hazard.
  • Consider gating the skill on a minimum graphify version (skill 0.7.8 + package 0.7.13 was the warning seen here). If the inline blocks were replaced with library calls, version mismatch would surface as an ImportError instead of a silent semantic regression.

Severity

Medium-high. The bug is invisible (graph.json still loads, all stats render), but the most prominent output of the skill — the "God Nodes" section of GRAPH_REPORT.md — gives users a categorically wrong reading of their codebase's call structure.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions