Skip to content

Central graph store: keep graphify-out out of git, share via repo-local push/pull hooks (graphify remote) #1751

Description

@muthuishere

Problem

graphify always writes graphify-out/ in the current directory, and the documented team workflow is to commit it to git. That breaks down quickly:

  • Graphs are large — 300MB–1GB per module on big codebases; git history balloons.
  • Monorepos (Linux-kernel-style, many submodules) produce one graphify-out per module — dozens of heavy folders scattered through the tree.
  • Every branch overwrites the same committed folder, so branch graphs collide.
  • There is no built-in way for a teammate to fetch a graph someone else already built — everyone rebuilds (API cost, time) or graphs go stale.

GRAPHIFY_OUT (env) can redirect the CLI's output, but the agent skill references the literal graphify-out/... path throughout, so an env-var redirect splits the world: CLI honours it, the skill's file reads don't.

Proposal: central graph store + repo-local sync hooks (implemented in #1752)

Opt-in via a committed .graphify/ folder. No behaviour change without it, and zero changes to the agent skill.

Commands — one group, graphify remote

graphify remote init      # scaffold .graphify/ (config.json + starter push/pull hooks) — commit it
graphify remote push      # run the push hook — upload the store tree
graphify remote pull      # run the pull hook — download the store tree + recreate all links
graphify remote delete    # leave the store — links become real local folders again

The store: links, not path redirection

.graphify/config.json{ "store": "~/graphify-store" } — makes every graphify run keep ./graphify-out as a link (POSIX symlink; Windows directory junction, no admin rights) into:

<store>/<repo>/<branch>/<module-relative-path>/graphify-out
  • Writes physically land outside the repo; every literal graphify-out/... path — CLI defaults, the agent skill's code blocks, plain cat — keeps working through the link. This is why the skill stays byte-identical.
  • <repo> comes from the origin remote's basename, so every clone shares the key regardless of local folder name ({"repo": "name"} overrides). <branch> keeps branches from colliding; switching branches retargets the link on the next run.
  • Monorepos: one link per directory you build in, keyed by repo-relative path.
  • Adoption is safe: a pre-existing real graphify-out/ (even one tracked in git) is migrated into the store once; committing then records the graph files leaving git.
  • Exit is safe: remote delete replaces each link with a real folder holding a copy of its store data (store untouched — other branches/teammates unaffected).
  • A bare graphify-out entry is maintained in .gitignore automatically. Bare on purpose: the common graphify-out/ pattern matches only directories — git treats a symlink as a file (it would slip through), and on Windows git descends into unignored junctions. Even unignored, a symlink commits as a ~100-byte target path, never data.

The hooks: pluggable, committed in the repo

remote push/pull run a hook — any executable: Python, Node, shell, PowerShell, .cmd/.bat, or a binary. graphify core stays backend-free (no boto3/SDK dependency); teams version their sync logic next to the config. If the store folder is already shared (NFS, Dropbox, a mounted drive), no hooks are needed at all.

Resolution order:

  1. explicit path in .graphify/config.json: {"push": "./scripts/push.py", "pull": "..."}
  2. .graphify/<push|pull>.{py,js,mjs,cjs,sh,ps1,cmd,bat} committed in the repo

An executable hook runs directly (its shebang wins — e.g. #!/usr/bin/env -S uv run --with boto3 python3); otherwise graphify picks the interpreter by extension, so it also works on Windows where shebangs are ignored.

Hook environment (the whole contract — mirror GRAPHIFY_STORE_DIR, exit non-zero on failure):

Variable Meaning
GRAPHIFY_ACTION push or pull
GRAPHIFY_STORE_DIR <store>/<repo>/<branch> — the tree to mirror
GRAPHIFY_STORE the configured store base folder (expanded)
GRAPHIFY_CONFIG path to .graphify/config.json (teams read their own extra keys)
GRAPHIFY_REPO_ROOT repo top-level dir (context only — data is not here)
GRAPHIFY_REPO / GRAPHIFY_BRANCH store key parts
GRAPHIFY_PREFIX <repo>/<branch> — a natural S3 object-key / path prefix

remote init scaffolds working S3/MinIO starter hooks (boto3 via uv shebang, cache/ excluded as a local-only accelerator, bucket/endpoint via GRAPHIFY_BUCKET/GRAPHIFY_S3_ENDPOINT, credentials from the standard env chain — secrets never enter the repo). Header comments tell teams how to swap the hook for .sh/.js/.ps1/.cmd of the same name or point the config at any script.

Minimal shell hook, for flavour:

#!/bin/sh
# .graphify/push.sh
exec rsync -a --exclude 'cache/' "$GRAPHIFY_STORE_DIR/" "backup-host:/graphs/$GRAPHIFY_PREFIX/"

Team flow

# publisher (once)
graphify remote init && $EDITOR .graphify/config.json && git add .graphify && git commit
graphify .              # writes through the link into the store
graphify remote push

# teammate
git clone … && cd …
graphify remote pull    # downloads the store tree AND recreates every module's link
graphify query "how does auth work?"

remote pull fans a link out for every module found in the store, so one pull on a fresh clone recreates the whole working set — root-level reads like merge-graphs ./services/api/graphify-out/graph.json work immediately.

Why hooks instead of built-in adapters

Backends differ per team (S3, MinIO, GCS, rsync, LFS, network shares) and each would drag a heavy optional dependency into core. A one-line contract keeps graphify lean and the sync logic reviewable in the repo.

Why links instead of path redirection

The skill and users' habits use the literal graphify-out/... path everywhere (75 references in skill.md alone). A link redirects the bytes while every existing path keeps working — the filesystem does the redirection, so the CLI and the skill can never disagree. Skill, skillgen fragments, and generated artifacts remain byte-identical to v8.

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