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 modules) 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/<folder>" } — makes every graphify run keep ./graphify-out as a link (POSIX symlink; Windows directory junction, no admin rights) into:

<store>/<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.
  • The store path is the whole key — no <repo> or <branch> segment. The repo points at the same store location on every branch (switch branches → no retarget, no rebuild; graphify update refreshes the graph). Each repo names its own store path in its config; remote init defaults it to ~/graphify-store/<folder>, change it to anything.
  • 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 — teammates who share it are 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 the configured <store> path — the tree to mirror
GRAPHIFY_STORE the same store path, 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_PREFIX the store folder's basename — a natural object-key / path prefix

Starter templates — remote init --backend <name>

remote init scaffolds working hooks for the backend you pick (default s3); it's non-destructive (an existing config or a hook in any extension is kept). The hook is just a script — edit it freely, graphify runs whatever is there.

backend write read
s3 (default) creds (boto3) creds (boto3)
s3-public creds (boto3) URL only — no creds, no SDK (public bucket + _manifest.json, stdlib urllib)
git-lfs GRAPHIFY_GIT_REMOTE same
rsync GRAPHIFY_RSYNC_DEST (SSH / mounted share) same

Secrets always come from the environment (env vars, ~/.aws, ~/.ssh) — never the repo. Non-secret keys you add to config.json (e.g. the public store_url) are yours to read via GRAPHIFY_CONFIG.

Asymmetric access (s3-public) is the "sometimes you don't need secure" case: make the bucket public-read, commit its public store_url in config.json, and readers pull over plain HTTP with a manifest — only pushers ever touch credentials. Verified live end-to-end on a real public bucket: a fresh clone with credentials stripped from the environment pulled every module by URL alone.

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