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:
- explicit path in
.graphify/config.json: {"push": "./scripts/push.py", "pull": "..."}
.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.
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:graphify-outper module — dozens of heavy folders scattered through the tree.GRAPHIFY_OUT(env) can redirect the CLI's output, but the agent skill references the literalgraphify-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 remoteThe store: links, not path redirection
.graphify/config.json—{ "store": "~/graphify-store" }— makes every graphify run keep./graphify-outas a link (POSIX symlink; Windows directory junction, no admin rights) into:graphify-out/...path — CLI defaults, the agent skill's code blocks, plaincat— 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.graphify-out/(even one tracked in git) is migrated into the store once; committing then records the graph files leaving git.remote deletereplaces each link with a real folder holding a copy of its store data (store untouched — other branches/teammates unaffected).graphify-outentry is maintained in.gitignoreautomatically. Bare on purpose: the commongraphify-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/pullrun 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:
.graphify/config.json:{"push": "./scripts/push.py", "pull": "..."}.graphify/<push|pull>.{py,js,mjs,cjs,sh,ps1,cmd,bat}committed in the repoAn 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):GRAPHIFY_ACTIONpushorpullGRAPHIFY_STORE_DIR<store>/<repo>/<branch>— the tree to mirrorGRAPHIFY_STOREGRAPHIFY_CONFIG.graphify/config.json(teams read their own extra keys)GRAPHIFY_REPO_ROOTGRAPHIFY_REPO/GRAPHIFY_BRANCHGRAPHIFY_PREFIX<repo>/<branch>— a natural S3 object-key / path prefixremote initscaffolds working S3/MinIO starter hooks (boto3 viauvshebang,cache/excluded as a local-only accelerator, bucket/endpoint viaGRAPHIFY_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/.cmdof the same name or point the config at any script.Minimal shell hook, for flavour:
Team flow
remote pullfans 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 likemerge-graphs ./services/api/graphify-out/graph.jsonwork 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 inskill.mdalone). 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 tov8.