@mertyalti/agentia-graph-cli
v0.1.3
Published
oclif plugin that exposes agentia graph commands. Depends on @mertyalti/agentia-graph. No library CLI.
Readme
@mertyalti/agentia-graph-cli
oclif plugin that adds graph commands to the agentia host. It extracts a Salesforce metadata relationship graph from a working tree, writes reports, and inspects nodes and paths. It depends on @mertyalti/agentia-graph and does not put a CLI inside that library.
Independent personal project by mert yaltı. Not developed, endorsed, supported or warranted by Copado. Copado and Agentia are trademarks of Copado.
Usage
$ npm install -g @mertyalti/agentia-graph-cli
$ agentia COMMAND
running command...
$ agentia (--version)
@mertyalti/agentia-graph-cli/0.1.2 darwin-arm64 node-v25.9.0
$ agentia --help [COMMAND]
USAGE
$ agentia COMMAND
...Requires Node.js 18 or newer and Agentia CLI (@copado/agentia-cli / Agentia Headless) >= 1.0.0. This package has no npm bin; the host command is agentia. Proprietary software: see LICENSE.md.
Install
Install the plugin into a host that already provides agentia:
$ agentia plugins install @mertyalti/agentia-graph-cli
$ agentia graph --helpOr install the package from the public registry, then install the plugin:
$ npm install @mertyalti/[email protected]
$ agentia plugins install @mertyalti/agentia-graph-cliThis plugin depends on @mertyalti/[email protected] from npm. The @mertyalti scope resolves to the public npm registry by default.
Sibling checkout (local development)
In a checkout next to agentia-graph:
$ npm ci
$ npm run build
$ agentia plugins link .
$ agentia graph --helpnpm run build compiles this plugin and copies the HTML page into dist/lib; run npm run build:graph first if ../agentia-graph is not built yet. Packing never builds: npm run build compiles this plugin only, and prepack is a guard that fails fast if dist/ or oclif.manifest.json is missing or stale. Build the sibling ../agentia-graph explicitly with npm run build:graph when you need it (the typecheck step and check still do). To pack, run npm run pack:ci: npm run build, then npm run manifest (oclif manifest through scripts/oclif.mjs, which sets OCLIF_SKIP_NEW_VERSION_CHECK=true for every oclif call, including docs:readme, and sends oclif output to stderr), then npm pack --ignore-scripts. Add -- --json for clean JSON on stdout, or -- --dry-run to list files. Run it from a clean clone or a gate pair, never from a primary or a shared worktree. Never run oclif (or these scripts) with DEBUG=* or DEBUG=update-check in CI: the oclif update check's debug output prints the npm auth header. npm run build (so npm test, check and lint too) first runs scripts/ensure-graph.mjs as prebuild, which runs build:graph only when ../agentia-graph/dist is missing; pack:ci builds with --ignore-scripts, so packing never builds the sibling (on a clone whose graph is not built, pack:ci fails instead). Use npm run --silent pack:ci -- --json for JSON on stdout: without --silent, npm prints its > script banners there. pack:ci leaves oclif.manifest.json in the working tree on purpose (it is gitignored, and a later plain npm pack reuses it); the prepack guard rejects it if it is missing, malformed, for another version, or its command IDs differ from dist/commands. For day-to-day linking you can temporarily use "@mertyalti/agentia-graph": "file:../agentia-graph" instead of the published version.
Ship bar
| Script | What it runs |
|---|---|
| npm run check | build → format:check → lint → typecheck → test:unit → test:contract → test:e2e → verify:core. Builds ../agentia-graph and the plugin once, up front. scripts/check-suites.mjs runs unit/contract/e2e without their pre-build hooks, with AGENTIA_GOLDEN_REPO removed (the fixture UCs UC-03/04/05 and UC-11 run, golden-repo rows stay pending), and with HOME, TMPDIR and XDG_* inside a temp work dir it removes afterwards; it fails if a *.test.* file is outside test/unit, test/contract and test/e2e. golden-path and mcp-smoke run once (in test:e2e / test:contract; test:usecases is their subset). For a gate, run from a nested pair (<gate>/agentia-graph + <gate>/agentia-graph-cli) so a shared ../agentia-graph dist is never rebuilt under another run. |
| npm test | every suite under test/ (test:unit ∪ test:contract ∪ test:e2e) |
| npm run test:unit / test:contract / test:e2e | mocha over test/unit, test/contract, test/e2e (contract, e2e, and usecases build first via their pre hooks) |
| npm run test:usecases | golden-path use cases on the sf-repo fixture: UC-03/04/05 (test/e2e/golden-path.test.ts) and UC-11 (test/contract/mcp-smoke.test.ts). Golden-repo rows run only with AGENTIA_GOLDEN_REPO=<path to a private golden repository checkout> (45 min golden timeouts). |
| npm run verify:core | links the built plugin into a pristine Agentia Headless export (git archive of ../cli at origin/main into a temp dir; override with --core/--ref or AGENTIA_HEADLESS_REPO) and runs the graph workflow through the real agentia binary, with HOME/TMPDIR/XDG_* in the temp dir. Never checks out, installs or builds in the checkout; never installs from the registry unless --allow-install. The last line is Core compatibility: OK (zero core modifications required; node_modules drift: none), or the missing/mismatched counts (node_modules drift: 71 missing, 22 version mismatches; NOTE above) when the checkout's tree differs from the lockfile at the ref. |
Without linking, the local oclif entry (not an npm bin) is:
$ node bin/run.js graph --helpWorking-tree graph vs documentation graph
agentia graph generate writes <project root>/.agentia/agentia-graph/graphs/graph.json (the project root is the git top level of --dir; --out overrides) and does not commit. The first write under .agentia/ creates .agentia/.gitignore ignoring only /agentia-graph/ and /.gitignore, so the outputs stay out of git and the tree stays clean; an existing .agentia/.gitignore and your own .gitignore are never edited. built_at_commit is git rev-parse HEAD. A folder with no HEAD fails; the command does not invent a hash.
agentia docs graph, from @mertyalti/agentia-docs, is a different command: it rebuilds a graph from the base revision and can commit that file onto doc/<source revision> inside the repository you are documenting.
Concrete commands:
$ agentia graph generate
$ agentia graph report --format both
$ agentia graph openThat path never commits. For a graph committed on the documentation branch instead:
$ agentia docs graph
$ agentia docs draftBoth plugins call @mertyalti/agentia-graph. Choose graph generate for local inspection; choose docs graph when the documentation workflow should own the file on doc/<source revision>.
Quickstart
From a Salesforce metadata working tree with at least one commit:
$ agentia graph generate
Graph generated (not committed)
Nodes N
Edges N
Unresolved N
Time N.NN s
Wrote
/path/to/repo/.agentia/agentia-graph/graphs/graph.json
built_at_commit
<40 hex characters>
$ agentia graph report --format both
$ agentia graph open--json is accepted on every command except open.
Typical inspection:
$ agentia graph query Account
$ agentia graph path Account BillingService
$ agentia graph report --jsonReport contents
agentia graph report writes Markdown (for agents; default --format markup), HTML (for architects and developers; --format html), or both (--format both). --out is the Markdown path; the HTML file sits beside it (GRAPH_REPORT.md → GRAPH_REPORT.html).
The report includes headline counts, hubs, unresolved tokens grouped by rule, and (when present) communities. When nodes have no community ids, the same deterministic undirected clustering used by graph open assigns them for the report; existing ids are kept. This is not Leiden.
Additional insight sections (Markdown and HTML):
- Blast radius — hottest inbound reference targets, with a test-vs-production caller name heuristic (
definesedges are not counted as inbound references) - Cross-type coupling — objects linked across metadata types / namespace boundaries
- Permission concentration — share of reference edges that start from a Profile or PermissionSet (access inventory, not design coupling)
- Orphans and single points — orphans plus hubs ranked by distinct neighbor metadata types
- Unresolved review list — platform-looking tokens vs missing metadata
- Freshness — built commit, graph source, and known extraction skips
Other HTML sections include the pipeline object model, reference edges by source/target type, and components by metadata type.
Files written
| File | Command | Committed by this plugin |
| --- | --- | --- |
| .agentia/agentia-graph/graphs/graph.json (or --out) | graph generate | No (ignored by .agentia/.gitignore) |
| .agentia/agentia-graph/reports/GRAPH_REPORT.md (or --out) | graph report | No (ignored by .agentia/.gitignore) |
| GRAPH_REPORT.html beside the markdown | graph report --format html or --format both | No |
| graph.html | graph open | No |
| reference facts file (only with --facts-out FILE) | graph generate --facts --facts-out FILE | No |
| field dictionary file (only with --out FILE) | graph reference dictionary --out FILE | No |
| .git/hooks/post-commit, post-merge, post-checkout (post-rewrite with --hook) | graph setup hooks (opt-in) | No (git dir) |
| .git/agentia-graph/hook.log, refresh-*.lock, refresh-*.pending (per worktree) | graph watch --once from hooks / graph watch | No (git dir) |
| .{agents,cursor,claude}/skills/agentia-graph/**, .cursor/rules/agentia-graph.mdc, managed block in AGENTS.md / CLAUDE.md | graph setup skills (see Local MCP and skills) | No |
When refreshing an existing graph.json, agentia graph generate --update wires G7-LIB path deltas against the prior graph at --out. v1 always full-rebuilds over the final path set so the digest matches a cold extract over the same paths and bodies — it is not a per-file splice and is not faster-than-full until lib mode can be incremental. Real warm speedup remains the shared-cache hit on clean-tree generate. Runtime budgets: docs/perf-budgets.md.
The walk for generate skips .git, .idea, .sf, .sfdx, coverage, dist, node_modules, and tmp, and it skips symbolic links. When --out is inside that tree, that file is not read as source.
Some standard platform names stay unresolved. They are names with no matching component in the repository, not a failed parse. The graph file shape is the version-2 document from @mertyalti/agentia-graph. Extraction limitations are documented on that package.
Reference facts and the field dictionary
Reference facts are the declared properties of objects, fields, validation rules, record types and global value sets (types, labels, required/unique/external id, lengths, reference targets, picklist values and their record type restrictions). They are opt-in and never change graph.json:
agentia graph generate --factscollects them in the same extract pass. On a strictly clean tree (nothing ingit status, untracked files included) the graph and its facts are cached together in the shared cache, keyed onHEAD^{tree}. On a dirty tree they stay in memory and nothing is cached. The command's own outputs inside the tree (--out,--facts-out,reference dictionary --out, and their.<pid>.tmpfiles) do not count as dirty when reading the cache, so a rerun that writes into the tree still gets a cache hit; a cache write still needs a strictly clean tree. Without--facts, generate behaves exactly as before.--facts-out FILE(requires--facts) also writes the sealed facts toFILE. It is the only facts file the plugin writes; it is skipped by the extract walk and is never read as source.agentia graph watch --facts [--facts-out FILE]andagentia graph setup hooks --facts [--facts-out FILE]pass the same options to every refresh (the hooks bake them in like--dir/--out; a hooks--facts-outmust be inside the repository and is resolved per worktree). The sidecar is an own output: watch never reacts to it and it does not block the cache read. Without--facts, hooks and watch behave exactly as before (a hook written without--factsis byte-identical to an A7 hook).agentia graph reference dictionary [--object Name ...]renders a field dictionary per object (Markdown, or--json;--objectnames ignore case, soaccountmatchesAccount; an exact match wins, and a name matching several objects that differ only in case exits 1 with the candidates): field properties, readers and writers from graph edges with their evidence, validation rules and the fields their formulas use, record types and their picklist restrictions, and inbound lookups and master-detail relationships. Without--graph/--factsit reads the cached pair forHEAD; when the facts are not cached, or the tree is dirty, it extracts now (cached only on a clean tree) and prints oneNote:line on stderr. With--graph FILE --facts FILEit reads exactly those files, which must pair (samebuilt_at_commitandgraph_digest).graph generate --out/--facts-out,graph reference dictionary --out, andgraph setup hooks/graph watch--out/--facts-outrefuse a file git tracks (CONFIGURATION_INVALID) before anything is read or written. The path is resolved to its on-disk spelling first (fs.realpathSync.nativeof the deepest existing ancestor), so a case variant of a tracked file is refused on a case-insensitive filesystem. generate and reference dictionary exit 1, like their other configuration errors; setup hooks and watch exit 2, like their other refusals (nothing changed), with the same exit under--json. The shared-cache content key carries an#r1d3suffix, so entries written before this guard (when a tracked--outwas skipped by the walk) are never read; the first run per tree re-extracts.- Exit codes (the same with
--json, where the error envelope carries the code): an object name that is not in the facts exits 2; files that do not pair, unreadable files, an--outthat is a directory, or an--outthat is a file tracked by git exit 1 (CONFIGURATION_INVALID); a facts file that holds a secret exits 1 (SECRET_DETECTED) and prints nothing. Error lines show control (ESC, newline), bidirectional and zero-width characters as escapes (\u{1B},\n,\u{202E}). The dictionary JSON shape is documented in the@mertyalti/agentia-graphREADME;--jsonprints the lib renderer's bytes, with DEL and C1 controls, bidirectional, zero-width and separator characters as\uXXXXescapes. - Known gap:
quick-action-fieldbinds are not currently produced by the extractor, so fields used only in object-specific quick actions are missing from reader and writer counts (the counts may under-report). Every dictionary lists this first in its limitations (JSONlimitationsand the Markdown footer).
There is no MCP tool for reference facts yet.
Keeping graph.json fresh: opt-in hooks and watch
Nothing here is on by default. Pick one when you want graph.json to follow the tree without running generate by hand.
Watch while you edit (explicit start; Ctrl-C stops it):
$ agentia graph watch
$ agentia graph watch --debounce 3000 --out graph.jsonOr install git hooks that refresh after commit, merge/pull, and checkout:
$ agentia graph setup hooks
$ agentia graph setup hooks --status
$ agentia graph setup hooks --uninstall- Both call
agentia graph watch --once, which runs the G7generate --updatepath. When the tree is clean apart from the graph outputs themselves (graph.json,GRAPH_REPORT.*,graph.html), it reads the shared cache first. The digest is the same as a manualgenerate --updateor a coldgenerateon the same tree. - Cost: v1
--updateis still a full extract over the final path set, and watch inherits that. It is not a per-file splice. The real warm speedup is a shared-cache hit on a clean tree. Addinggraph.jsonto.gitignoreis still recommended. - Hooks are shared by linked worktrees. Each hook refreshes the graph of the worktree that fired it (
--dir/--outare stored relative to the top level, so they must be inside the repo). post-checkoutrefreshes on branch checkouts only. Hooks skip while a rebase,git am, or cherry-pick sequence is running; add--hook post-rewriteto refresh once at the end of a rebase.- At most one refresh runs and one waits per worktree; extra triggers exit at once.
- Hooks run in the background by default, detached from git's stdout/stderr: git exits right away, and callers reading its output (
$(git commit), IDEs, agents) do not wait for the refresh.--syncwaits, with no time limit. They log to.git/agentia-graph/hook.log(truncated past 1 MB). SetAGENTIA_GRAPH_HOOKS=0to skip a refresh for one git command.--statusshows each hook's command and out, and warns when the command is not onPATH. Install warns when--outis not gitignored. --commandis trusted: it is written verbatim into the hook files.- Install is idempotent. It refuses to touch an existing hook it did not write, unless you pass
--chain: the original moves to<hook>.agentia-graph-origand runs first.--uninstallremoves only managed hooks and puts chained originals back. Acore.hooksPathoutside the repository git dir (husky, a global hooks folder) is refused. - Hooks and watch never commit or push.
Details and design choices: docs/hooks-and-watch.md.
Local MCP and skills
This plugin runs its own stdio MCP server (plugins cannot inject tools into the host Agentia MCP). Copado AI runs inside Cline, so setup mcp registers the server in Cline's MCP settings:
$ agentia graph setup mcp
$ agentia graph setup mcp --dry-run --jsonThat adds (or updates) one agentia-graph stdio entry in Cline's global settings file:
{
"type": "stdio",
"command": "agentia",
"args": ["graph", "mcp", "start"],
"env": { "AGENTIA_PROJECT_ROOT": "/absolute/path/to/your/repo" }
}- Where. Cline (4.0 and later, in VS Code, JetBrains, and the CLI) reads one file. Setup uses the same precedence as Cline:
CLINE_MCP_SETTINGS_PATH(the file itself), thenCLINE_DATA_DIR/settings/cline_mcp_settings.json, thenCLINE_DIR/data/settings/cline_mcp_settings.json, then~/.cline/data/settings/cline_mcp_settings.json. An empty or whitespace-only variable counts as unset. A value is never trimmed: one that is relative, contains a control character, or starts or ends with whitespace (Cline would trim it and read a different file) is refused withCONFIGURATION_INVALID(exit 2). These variables must be set in the shell that runs setup and in Cline's environment. Pre-4.0 Cline paths are never written. - Global, last setup wins. Cline has no per-project MCP file and does not expand
${workspaceFolder}, so the entry carries an absoluteAGENTIA_PROJECT_ROOT: the exact git top-level of the current directory (the current directory outside git). Every Cline window uses the repository you ran setup in last. Text output says so, and--jsonreportsglobal: true,lastSetupWins: true,previousRoot(the root our entry had before, ornull), androotChanged. Run setup again in the repository you switch to. - Only our entry changes. Other servers and keys are kept byte for byte: setup splices only the
agentia-graphvalue into the existing text (same indentation, colon spacing, and line endings) and checks that the result parses to exactly the expected settings. Only when the file has duplicatemcpServers/agentia-graphkeys, or that check fails, would the whole file have to be re-serialized with 2-space indentation (Cline's own format). That is refused by default (SETUP_MCP_REWRITE_REFUSED, exit 2, nothing written) because it can change other servers' entries; pass--allow-rewriteto accept it. In a rewrite duplicate keys collapse to the last one, number spelling can change (1.0→1,1e3→1000, very large integers are rounded), and\uescapes become characters.--jsonreportslayout(new-file,splice,reserialized, ornone). On our own entry, yourdisabled,autoApprove,timeout,cwd, extraenvkeys, and other fields are kept;type,command,args, andenv.AGENTIA_PROJECT_ROOTare set. Setup never prints other entries' contents (they can hold tokens), not even in errors or--json. - Refusals (exit 2, nothing written, always with a
code).SETUP_MCP_NAME_CONFLICT:agentia-graphholds a server that does not runagentia graph mcp start.SETUP_MCP_INVALID_JSON: the file is not strict JSON in UTF-8 (comments, trailing commas, a BOM, invalid UTF-8) ormcpServersis not an object; the message names the file only.SETUP_MCP_CONCURRENT_EDIT: the file changed (size, mtime, or identity) between the read and the rename, or Cline's lock directory (<file>.lock, younger than 10 s) stays held for 10 s while setup waits to take it; rerun. Setup takes the same lock as Cline for the write, so the two never write at once. A held lock at preflight is not a refusal: setup waits,--dry-runreportslockHeld: truewithout waiting, and a run with nothing to change isunchangedwithout taking the lock.SETUP_PATH_ESCAPES_ROOT: a symlinked.cline, settings directory, or settings file leaves the base directory (your home, or the override), or the directory was swapped during the run.SETUP_PATH_IN_GIT_DIR,SETUP_DANGLING_SYMLINK,SETUP_NOT_A_FILE,SETUP_NOT_A_DIRECTORY,SETUP_NOT_WRITABLE,SETUP_ROOT_INSIDE_GIT_DIR,CONFIGURATION_INVALID, andSETUP_USAGE_INVALID(an invalid flag value). Setup writes one file with one rename, so it never ends in a partial write: exit 1 (SETUP_WRITE_FAILED) is not produced. - Backup. Before an existing file is replaced, setup copies it to
<file>.baknext to it (the real file's directory for a symlinked file), mode 0600 whatever the umask, with a temp file and a rename. The backup is only one level deep: each run overwrites the previous.bak, so it always holds the file as it was right before the latest update. The path is printed (Backup: …) and returned asbackupPathin--json(nullfor a new file or an unchanged entry;--dry-runnames the path it would use and writes nothing). If the backup cannot be written, setup refuses withSETUP_MCP_BACKUP_FAILED(exit 2) and the settings file is not touched. - Safe write. Missing directories are created (mode 0700) only below the base directory;
CLINE_DIR,CLINE_DATA_DIR, and the directory ofCLINE_MCP_SETTINGS_PATHmust already exist. A new file gets mode 0600; an existing file keeps its mode. A settings file that is a symlink inside the base directory is written through and stays a link. The new text goes to a temp file in the same directory and replaces the file with a rename after the re-checks; on any failure the temp file and the directories setup created are removed.--dry-runwrites nothing and gives the same refusals. - Cursor.
--target cursor(and the old--scope local|global) is deprecated: it writes nothing, exits 0, and prints manual instructions (--json:deprecated: true,written: []). Existing.cursor/mcp.jsonentries are left untouched and keep working. - After setup, restart the
agentia-graphserver in Cline (or reload the window). - Breaking (0.x). The Cline target writes the
agentia-graphentry into Cline's global MCP settings file:CLINE_MCP_SETTINGS_PATH, elseCLINE_DATA_DIR, elseCLINE_DIR, else~/.cline/data/settings/cline_mcp_settings.json.
Start the server: agentia graph mcp start (stdout is reserved for MCP framing; enableJsonFlag is false).
AGENTIA_PROJECT_ROOT is used exactly as given (never trimmed); empty or whitespace-only counts as unset and the current directory is used. A value that is not an absolute path to an existing directory, or that contains a newline or another control character, stops the server with CONFIGURATION_INVALID on stderr (exit 2). setup skills and setup hooks refuse a project root (git top-level, or the current directory outside git for setup skills) whose path contains a control character with the same code (exit 2, nothing written); the message shows the root with control characters escaped. Installed hooks resolve the top level exactly and skip the refresh (exit 1, message on stderr) when it contains a control character or belongs to a different repository. setup hooks --uninstall still works in such a repository, so old managed hooks can be removed.
Install the agent skill and the always-on GRAPH_REPORT rule (agents / cursor / claude targets):
$ agentia graph setup skills
$ agentia graph setup skills --target agents --target cursor --target claude
$ agentia graph setup skills --target cursor --dry-run
$ agentia graph setup skills --no-rulesFiles written per target (paths are relative to the project root):
| Target | Skill (always) | Rule (default; skipped with --no-rules) |
| --- | --- | --- |
| agents (default) | .agents/skills/agentia-graph/** | managed block in AGENTS.md |
| cursor | .cursor/skills/agentia-graph/** | .cursor/rules/agentia-graph.mdc (alwaysApply: true, whole file owned by the plugin) |
| claude | .claude/skills/agentia-graph/** | managed block in CLAUDE.md |
The rule tells agents to read .agentia/agentia-graph/reports/GRAPH_REPORT.md in the repository root (git rev-parse --show-toplevel), or the agentia://graph/report MCP resource when the agentia-graph server is connected, before metadata impact, relationship, or dependency work. If the report is missing, its Freshness commit differs from HEAD, or the tree is dirty, they run agentia graph generate --update (or a full agentia graph generate) and then agentia graph report. They run both from the repository root, because graph.json and GRAPH_REPORT.md default to the current directory. From anywhere else, they pass --dir <root> --out <root>/graph.json to generate and --graph <root>/graph.json --out <root>/GRAPH_REPORT.md to report; report has no --dir flag. They do not commit GRAPH_REPORT.*.
- Project root. Skills and rules are written at the git top-level (
git rev-parse --show-toplevel, run from the current directory), so running from a subdirectory installs at the repository root. Outside a git work tree the current directory is used. Earlier versions always used the current directory. The resolved root is printed, and--jsonincludesrootandrootSource(gitorcwd). - Managed blocks. In
AGENTS.md/CLAUDE.mdthe rule lives between<!-- agentia-graph:managed:start -->and<!-- agentia-graph:managed:end -->(a marker counts only at the start of a line and outside fenced code blocks,```or~~~; indented, mid-line, or fenced marker text is ordinary content and is kept byte for byte). The block is appended after a blank line when absent and replaced in place when present. Bytes outside the markers and the file's line endings (LF or CRLF) are kept. The Agentia Headless<!-- agentia:managed:start -->…<!-- agentia:managed:end -->block is never edited. Setup stops if there is more than one live block (SETUP_MARKER_AMBIGUOUS), or if the markers are unbalanced, out of order, nested in that host block, or the file ends inside an unclosed code fence (SETUP_MALFORMED_MARKERS). - Safety. Every target is checked before anything is written. An unmanaged
SKILL.mdor.cursor/rules/agentia-graph.mdc(no Agentia Graph marker) is refused unless you pass--force; a malformed managed block is always refused. If any check fails, nothing is written. Each file is written through a temp file and a rename in the same directory. Byte-identical files are left alone and reported asunchanged. If a later write fails, the error lists the files already written; the run is not a transaction. Writes that would resolve outside the repository (through a symlinkedAGENTS.md,CLAUDE.md, rule file, skill file, or any parent directory such as.cursoror.agents) are always refused, even with--force. So are writes whose real path lands in the repository's git dir or git common dir (for exampleAGENTS.md -> .git/hooks/pre-commit, or a linked worktree's main.git). So are dangling symlinks, read-only targets that would change, and running from inside.git. Exit 2 means setup refused and wrote nothing. That covers every refusal, including a broken package (SETUP_PACKAGE_INVALID). Exit 1 means only a partial write: a write failed partway through the run (SETUP_WRITE_FAILED). With--json, the error envelope carries a stablecode(for exampleSETUP_PATH_ESCAPES_ROOTorSETUP_PATH_IN_GIT_DIR), aproblemslist, andwritten(the files already written, forSETUP_WRITE_FAILED). --dry-runreports each planned file and status and writes nothing.- Output. Text lists every file with its status (
Created,Updated,Unchanged;Would create/Would updateon a dry run).--jsonreturnsroot,rootSource,dryRun,rules,files({path, status, kind: skill|rule, target}), andwritten(the paths actually written by this run).
MCP tools (call lib helpers; they do not spawn agentia graph …): graph_generate, graph_report, graph_query, graph_path, graph_explain, graph_communities, graph_wiki, graph_surprises, graph_open. MCP resource: agentia://graph/report (the Markdown report). Prefer MCP when connected; otherwise agentia graph … --json. graph_open defaults openBrowser: false on MCP.
This is the working-tree graph plugin. Do not confuse it with agentia docs graph (documentation branch).
License
Copyright (c) mert yaltı. All rights reserved. See LICENSE.md. Permission is granted to install and use this software with Agentia Headless; redistribution, sublicensing, and modification require prior written permission.
Commands
agentia graph communities [ID]agentia graph explain NAMEagentia graph generateagentia graph mcp startagentia graph openagentia graph path FROM TOagentia graph query NAMEagentia graph reference dictionaryagentia graph reference erdagentia graph reportagentia graph setup hooksagentia graph setup mcpagentia graph setup skillsagentia graph watchagentia graph wiki
agentia graph communities [ID]
List communities or show one community’s members
USAGE
$ agentia graph communities [ID] [--json] [--graph <value>] [--algorithm louvain|louvain-refine] [--limit <value>]
ARGUMENTS
[ID] Community id to show members for
FLAGS
--algorithm=<option> [default: louvain] Clustering when community ids are missing: louvain (default) or
louvain-refine (Louvain + connectivity refine; not multilevel Leiden / not Graphify)
<options: louvain|louvain-refine>
--graph=<value> Graph file to read (default: <project root>/.agentia/agentia-graph/graphs/graph.json)
--limit=<value> Max communities to list (default all)
GLOBAL FLAGS
--json Format output as json.
DESCRIPTION
List communities or show one community’s members
First-class communities surface. When nodes lack community ids, runs deterministic clustering.
--algorithm louvain (default) is the existing Louvain-style partition.
--algorithm louvain-refine runs that partition plus a connectivity refinement step (not multilevel Leiden; not
Graphify).
EXAMPLES
$ agentia graph communities
$ agentia graph communities 0
$ agentia graph communities --algorithm louvain-refine --jsonagentia graph explain NAME
Explain one node and why its edges exist
USAGE
$ agentia graph explain NAME [--json] [--graph <value>]
ARGUMENTS
NAME Metadata name
FLAGS
--graph=<value> Graph file to read (default: <project root>/.agentia/agentia-graph/graphs/graph.json)
GLOBAL FLAGS
--json Format output as json.
DESCRIPTION
Explain one node and why its edges exist
Resolves a metadata name and lists its incident edges with relation, rule, evidence, line, and confidence when present
on the graph (text: grouped summary; --limit, --rule, --direction, --all). This is the graph explain surface (not change impact).
EXAMPLES
$ agentia graph explain Accountagentia graph generate
Extract the working tree and write graph.json
USAGE
$ agentia graph generate [--json] [--dir <value>] [--facts-out <value> --facts] [--out <value>] [--update]
FLAGS
--dir=<value> [default: .] Working tree to extract
--facts Also collect reference facts (object, field, validation rule, record type and global value set
properties) in the same pass. Opt-in; graph.json is unchanged. Cached with the graph only on a
strictly clean tree.
--facts-out=<value> Also write the reference facts to this file (the only sidecar; must differ from --out). Requires
--facts. Skipped by the extract walk.
--out=<value> Graph file to write (default: <project root>/.agentia/agentia-graph/graphs/graph.json;
.agentia/.gitignore is created with * on first write). Not committed. With --update, also the
prior graph to read when present.
--update Refresh via G7-LIB path deltas vs prior graph at --out. v1: digest-identical full extract over
final paths (not a per-file splice; not faster-than-full until mode=incremental).
GLOBAL FLAGS
--json Format output as json.
DESCRIPTION
Extract the working tree and write graph.json
Walks the working tree, calls @mertyalti/agentia-graph extractSalesforceMetadata with a file reader, and writes
graph.json. Does not commit. built_at_commit is git HEAD. A folder with no HEAD fails instead of inventing a hash.
With --update, refreshes via extractSalesforceMetadataIncremental using path deltas against a prior graph at --out (if
present). v1 always performs a digest-identical full extract over the final path set — it does not claim per-file
splice or faster-than-full until lib mode can be incremental. Shared-cache warm hits remain the non-update clean-tree
path. With --facts, the same pass also collects reference facts (opt-in; graph.json is unchanged): cached beside the
graph on a strictly clean tree, kept in memory on a dirty tree, and written to a file only with --facts-out.
EXAMPLES
$ agentia graph generate
$ agentia graph generate --json
$ agentia graph generate --update
$ agentia graph generate --update --out graph.json
$ agentia graph generate --facts
$ agentia graph generate --facts --facts-out reference-facts.jsonagentia graph mcp start
Start the local Agentia Graph MCP server over stdio
USAGE
$ agentia graph mcp start
DESCRIPTION
Start the local Agentia Graph MCP server over stdio
EXAMPLES
$ agentia graph mcp start
$ agentia graph mcp start # long-running stdio MCP server for Cursor and other clientsagentia graph open
Write graph.html and open it
USAGE
$ agentia graph open [--graph <value>] [--out <value>]
FLAGS
--graph=<value> Graph file to read (default: <project root>/.agentia/agentia-graph/graphs/graph.json)
--out=<value> [default: graph.html] HTML file to write and open
DESCRIPTION
Write graph.html and open it
Writes a vis-network 9.1.6 page (full height, search, node sidebar, legend that shows or hides a community) and opens
it. Nodes without community ids are clustered on the undirected graph first. Graphs over 5000 nodes are drawn as one
node per community, not the full edge list. Does not accept --json.
EXAMPLES
$ agentia graph openagentia graph path FROM TO
Shortest path between two metadata names
USAGE
$ agentia graph path FROM TO [--json] [--graph <value>]
ARGUMENTS
FROM Metadata name
TO Metadata name
FLAGS
--graph=<value> Graph file to read (default: <project root>/.agentia/agentia-graph/graphs/graph.json)
GLOBAL FLAGS
--json Format output as json.
DESCRIPTION
Shortest path between two metadata names
Undirected shortest path. Two components connect only through the files that define or reference them.
EXAMPLES
$ agentia graph path Account BillingServiceagentia graph query NAME
Show one node and its neighbors
USAGE
$ agentia graph query NAME [--json] [--graph <value>] [--depth <value>] [--dfs] [--relation <value>]
ARGUMENTS
NAME Metadata name
FLAGS
--depth=<value> Max edge hops from the seed (default 1)
--dfs Traverse depth-first instead of breadth-first
--graph=<value> Graph file to read (default: <project root>/.agentia/agentia-graph/graphs/graph.json)
--relation=<value> Keep only edges whose relation matches (case-insensitive)
GLOBAL FLAGS
--json Format output as json.
DESCRIPTION
Show one node and its neighbors
Resolves a metadata name (exact id, then component API name, then label, then file path). Default is a 1-hop BFS
neighborhood. Use --depth for multi-hop traversal and --dfs for depth-first order. Optional --relation filters edges
by relation name.
EXAMPLES
$ agentia graph query Account
$ agentia graph query Account --depth 2
$ agentia graph query Account --dfs --depth 3 --relation referencesagentia graph reference dictionary
Field dictionary per object from the graph and its reference facts
USAGE
$ agentia graph reference dictionary [--json] [--dir <value>] [--facts <value> --graph <value>] [--object <value>...] [--out
<value>]
FLAGS
--dir=<value> [default: .] Working tree to read (cache or extract) when --graph and --facts are not given
--facts=<value> Reference facts file to read (from graph generate --facts --facts-out). Requires --graph.
--graph=<value> graph.json to read. Requires --facts; the two must pair.
--object=<value>... Object API name to include (case-insensitive: account matches Account; an exact match wins).
Repeat for more. Default: every object.
--out=<value> Write the dictionary to this file instead of stdout (Markdown, or JSON with --json).
GLOBAL FLAGS
--json Format output as json.
DESCRIPTION
Field dictionary per object from the graph and its reference facts
Builds a field dictionary for each Salesforce object: field properties from the reference facts (type, label,
required, unique, external id, length, precision, reference targets, picklist values, formula), who reads and writes
each field (graph edges, with their evidence and line), validation rules and the fields their formulas use, record
types and the picklist values they restrict, and inbound lookups and master-detail relationships.
Without --graph and --facts, reads the graph and reference facts cached for HEAD on a strictly clean tree. When they
are not cached, or the tree has uncommitted changes, it extracts them now (cached only on a strictly clean tree) and
prints one note on stderr.
With --graph and --facts, reads exactly those files (graph generate --facts --facts-out writes the facts file). They
must pair: same built_at_commit and graph_digest; otherwise exits 1.
Markdown by default; --json prints the dictionary JSON (shape in the @mertyalti/agentia-graph README). --object
ignores case (account matches Account; an exact match wins); a name that matches several objects differing only in
case exits 1 (CONFIGURATION_INVALID, the candidates listed). An object name that is not in the reference facts exits
2. Does not commit.
EXAMPLES
$ agentia graph reference dictionary --object Account
$ agentia graph reference dictionary --object Account --object Contact --json
$ agentia graph reference dictionary --out FIELD_DICTIONARY.md
$ agentia graph reference dictionary --graph graph.json --facts reference-facts.json --object Accountagentia graph reference erd
Entity-relationship diagram of objects from the graph and its reference facts
USAGE
$ agentia graph reference erd [--json] [--depth <value>] [--dir <value>] [--facts <value> --graph <value>] [--format
md|mermaid|json|html] [--object <value>...] [--out <value>]
FLAGS
--depth=<value> Hops from the --object set (integer >= 0, default 2). Master-detail edges are followed past it.
--dir=<value> [default: .] Working tree to read (cache or extract) when --graph and --facts are not given
--facts=<value> Reference facts file to read (from graph generate --facts --facts-out). Requires --graph.
--format=<option> Output: md (Markdown with diagram, default), mermaid (erDiagram text), json (same as --json) or
html (self-contained offline page).
<options: md|mermaid|json|html>
--graph=<value> graph.json to read. Requires --facts; the two must pair.
--object=<value>... Object API name to start from (case-insensitive: order__c matches Order__c; an exact match wins).
Repeat for more. Default: whole-repo index.
--out=<value> Write the output to this file instead of stdout (must not be a git-tracked file).
GLOBAL FLAGS
--json Format output as json.
DESCRIPTION
Entity-relationship diagram of objects from the graph and its reference facts
Builds the entity-relationship model of Salesforce objects from relationship fields in the reference facts:
master-detail (identifying) and lookup-style edges, with delete constraint, reparentable and evidence (file and line).
Standard objects referenced but not in the repository are stub nodes. Polymorphic and unresolved targets are listed as
limitations, never guessed.
Without --object: a whole-repository index (one row per object: in-edges, out-edges, stub), no diagram. With --object
(case-insensitive, exact match first; repeatable): the objects within --depth hops (default 2), following
master-detail edges past it, capped at 50 objects, with a Mermaid erDiagram.
Without --graph and --facts, reads the graph and reference facts cached for HEAD on a strictly clean tree. When they
are not cached, or the tree has uncommitted changes, it extracts them now (cached only on a strictly clean tree) and
prints one note on stderr.
With --graph and --facts, reads exactly those files (graph generate --facts --facts-out writes the facts file). They
must pair: same built_at_commit and graph_digest; otherwise exits 1.
--format md (default) is Markdown with the diagram and tables; mermaid is the erDiagram text only; json (same bytes as
--json) is the ERD JSON with a stable digest (shape in the @mertyalti/agentia-graph README); html is one
self-contained offline HTML file that draws the diagram with an inlined, vendored Mermaid (MIT notice kept), a strict
Content-Security-Policy with a per-file script nonce, and the object and relationship tables (the whole-repo index has
tables only). A name that matches several objects differing only in case exits 1 (CONFIGURATION_INVALID, the
candidates listed); an object name that is not in the graph exits 2. The JSON query echoes the declared names. Does
not commit.
EXAMPLES
$ agentia graph reference erd --object Order__c
$ agentia graph reference erd --object Order__c --depth 1 --json
$ agentia graph reference erd --object Order__c --format mermaid
$ agentia graph reference erd --format json --out erd.json
$ agentia graph reference erd --object Order__c --format html --out erd.htmlagentia graph report
Write a graph report as markup, HTML, or both
USAGE
$ agentia graph report [--json] [--graph <value>] [--out <value>] [--title <value>] [--facts <value>]
[--relationships] [--format html|markup|both]
FLAGS
--facts=<value> Reference facts file for --relationships (from graph generate --facts --facts-out); must pair with
--graph. Without --relationships: CONFIGURATION_INVALID, exit 1.
--format=<option> [default: markup] markup writes only the markdown (default). html writes only the HTML report. both
writes the markdown and the HTML file beside it.
<options: html|markup|both>
--graph=<value> Graph file to read (default: <project root>/.agentia/agentia-graph/graphs/graph.json)
--out=<value> Markdown report path (default: <project root>/.agentia/agentia-graph/reports/GRAPH_REPORT.md). HTML
is written beside it, with .html instead of .md.
--relationships Add the Relationships section (entity-relationship index and diagrams) to the HTML report.
--title=<value> Report title. Defaults to the working directory name.
GLOBAL FLAGS
--json Format output as json.
DESCRIPTION
Write a graph report as markup, HTML, or both
Counts, hubs, unresolved buckets, and communities. When nodes have no community ids, the same deterministic undirected
clustering as graph open assigns them. Existing ids are kept. This is not Leiden clustering.
--format markup (the default) writes only the markdown report, for agents. --format html writes only the HTML report,
for architects and developers. --format both writes both. --out is the markdown path. The HTML file sits beside it:
GRAPH_REPORT.md becomes GRAPH_REPORT.html. Markup is not written unless the format is markup or both.
--relationships adds a Relationships section to the HTML (after Cross-type coupling): the whole-repo
entity-relationship index, the Mermaid erDiagram source of the top 5 objects by relationship edges (static text, no
script), and the ERD limitations, from the reference facts in --facts (graph generate --facts --facts-out writes them;
they must pair with --graph). Without --facts the section says that facts are not available. Without --relationships
the HTML is unchanged.
EXAMPLES
$ agentia graph report
$ agentia graph report --format markup
$ agentia graph report --format html
$ agentia graph report --format both
$ agentia graph report --jsonagentia graph setup hooks
Opt in to git hooks that refresh graph.json after commit, merge, and checkout
USAGE
$ agentia graph setup hooks [--json] [--chain | --uninstall | --status] [--command <value>] [--dir <value>]
[--facts-out <value> --facts] [--hook post-commit|post-merge|post-checkout|post-rewrite...] [--out <value>] [--sync]
FLAGS
--chain Keep existing non-Agentia hooks: move each to <hook>.agentia-graph-orig and run it first
--command=<value> [default: agentia] Shell command prefix the hooks use to reach the host CLI (for example `npx
agentia`). Trusted: written verbatim into the hook files
--dir=<value> [default: .] Extract root for the hooks. Must be inside the repository; stored relative to the
top level and resolved per worktree
--facts Refresh with `graph watch --once --facts`: reference facts are collected and cached beside the
graph on a strictly clean tree. Off by default. Install only (ignored by --status and
--uninstall)
--facts-out=<value> Also write the reference facts to this file on each refresh. Must be inside the repository;
resolved per worktree like --out
--hook=<option>... Hooks to manage. Install default: post-commit, post-merge, post-checkout. Uninstall/status
default: all supported
<options: post-commit|post-merge|post-checkout|post-rewrite>
--out=<value> Graph file the hooks refresh (default: .agentia/agentia-graph/graphs/graph.json). Must be inside
the repository; stored relative to the top level and resolved per worktree
--status Show hook state without changing anything
--sync Run the refresh in the foreground: git waits for it, with no time limit. Default runs it in the
background, detached from git stdio
--uninstall Remove managed hooks and restore chained originals
GLOBAL FLAGS
--json Format output as json.
DESCRIPTION
Opt in to git hooks that refresh graph.json after commit, merge, and checkout
Writes managed post-commit, post-merge, and post-checkout hooks (post-rewrite with --hook) into this repository's
hooks dir. Each hook runs `agentia graph watch --once` for the worktree that fired it (hooks are shared by linked
worktrees; --dir/--out resolve against each worktree's top level). post-checkout refreshes on branch checkouts only,
and hooks skip while a rebase, am, or cherry-pick sequence is running (post-rewrite covers the end of a rebase). The
refresh uses the G7 `generate --update` path plus the shared cache on a clean tree, in the background by default.
Nothing is installed unless you run this command. Rerunning is idempotent. An existing hook that Agentia Graph did not
write is never overwritten: the install is refused unless you pass --chain, which moves it to
<hook>.agentia-graph-orig and runs it first. --uninstall removes only the managed hooks and moves chained originals
back. Refuses to install or uninstall in a core.hooksPath outside the repository git dir (--status still reports it).
Background refreshes are fully detached from git's stdout/stderr, so callers reading git's output do not wait for
them; --sync has no time limit. Never commits or pushes.
EXAMPLES
$ agentia graph setup hooks
$ agentia graph setup hooks --chain
$ agentia graph setup hooks --hook post-commit --hook post-rewrite --sync
$ agentia graph setup hooks --status --json
$ agentia graph setup hooks --uninstallagentia graph setup mcp
Register the Agentia Graph MCP server in Cline's global MCP settings (~/.cline/data/settings/cline_mcp_settings.json) with this project as AGENTIA_PROJECT_ROOT. Cline's config is global: the last setup wins. Only the agentia-graph entry is changed; other servers are kept byte for byte where possible. Before an existing file is replaced, it is copied to .bak (0600); the backup is only one level deep: each run overwrites the previous .bak. --target cursor is deprecated and writes nothing.
USAGE
$ agentia graph setup mcp [--json] [--allow-rewrite] [--dry-run] [--scope local|global] [--target cline|cursor]
FLAGS
--allow-rewrite Allow a full rewrite of the settings file when our entry cannot be updated in place (duplicate
keys, or a layout setup cannot splice). A rewrite can change other servers' entries: integers above
2^53 lose precision, number spellings such as 1.50 become 1.5, and duplicate keys keep only the
last value. Without this flag that case is refused with SETUP_MCP_REWRITE_REFUSED and nothing is
written
--dry-run Report what would change, with the same refusals, without writing anything
--scope=<option> Deprecated (Cursor only): implies --target cursor, which writes nothing
<options: local|global>
--target=<option> cline (default): Cline global MCP settings. cursor: deprecated, writes nothing and prints manual
instructions
<options: cline|cursor>
GLOBAL FLAGS
--json Format output as json.
DESCRIPTION
Register the Agentia Graph MCP server in Cline's global MCP settings (~/.cline/data/settings/cline_mcp_settings.json)
with this project as AGENTIA_PROJECT_ROOT. Cline's config is global: the last setup wins. Only the agentia-graph entry
is changed; other servers are kept byte for byte where possible. Before an existing file is replaced, it is copied to
<file>.bak (0600); the backup is only one level deep: each run overwrites the previous .bak. --target cursor is
deprecated and writes nothing.
EXAMPLES
$ agentia graph setup mcp
$ agentia graph setup mcp --dry-run --json
CLINE_DIR=/opt/cline agentia graph setup mcpagentia graph setup skills
Install the Agentia Graph agent skill and always-on GRAPH_REPORT rule into this project
USAGE
$ agentia graph setup skills [--json] [--dry-run] [--force] [--rules] [--target agents|cursor|claude...]
FLAGS
--dry-run Report the planned files and statuses without writing anything
--force Replace skill files and the Cursor rule file when they are not managed by Agentia Graph
--[no-]rules Also write the always-on GRAPH_REPORT rule: .cursor/rules/agentia-graph.mdc (cursor), a managed
block in AGENTS.md (agents) or CLAUDE.md (claude)
--target=<option>... [default: agents] Skill root to install into
<options: agents|cursor|claude>
GLOBAL FLAGS
--json Format output as json.
DESCRIPTION
Install the Agentia Graph agent skill and always-on GRAPH_REPORT rule into this project
EXAMPLES
$ agentia graph setup skills
$ agentia graph setup skills --target cursor --target claude
$ agentia graph setup skills --target agents --target cursor --target claude --dry-run
$ agentia graph setup skills --no-rulesagentia graph watch
Keep graph.json fresh while you edit (explicit start; opt-in)
USAGE
$ agentia graph watch [--json] [--debounce <value>] [--dir <value>] [--facts-out <value> --facts] [--initial]
[--once] [--out <value>]
FLAGS
--debounce=<value> [default: 1500] Quiet period in ms after the last change before refreshing
--dir=<value> [default: .] Working tree to watch and extract
--facts Also collect reference facts on each refresh (as `generate --facts`); cached beside the graph on
a strictly clean tree. Off by default
--facts-out=<value> Also write the reference facts to this file on each refresh. Never watched.
--[no-]initial Refresh once at start so graph.json matches the tree being watched
--once Run one refresh and exit (used by git hooks)
--out=<value> Graph file to refresh (default: <project root of
--dir>/.agentia/agentia-graph/graphs/graph.json). Not committed. Never watched.
GLOBAL FLAGS
--json Format output as json.
DESCRIPTION
Keep graph.json fresh while you edit (explicit start; opt-in)
Watches the working tree and, after a debounce, refreshes graph.json through the G7 `generate --update` path. On a
clean tree the shared cache is read first (a hit is the stored cold extract for HEAD^{tree}); otherwise v1 `--update`
runs a full extract over the final path set, so each refresh costs about as much as a cold extract. The digest always
matches a manual `generate --update` / cold generate on the same tree. Ignores the output graph, .git*, and the
generate skip dirs. Never commits or pushes. Stop with Ctrl-C. --once runs a single refresh and exits (this is what
`graph setup hooks` hooks call).
EXAMPLES
$ agentia graph watch
$ agentia graph watch --debounce 3000 --out graph.json
$ agentia graph watch --once --jsonagentia graph wiki
Export a markdown wiki of communities and hubs
USAGE
$ agentia graph wiki [--json] [--graph <value>] [--out <value>] [--algorithm louvain|louvain-refine] [--title
<value>]
FLAGS
--algorithm=<option> [default: louvain] Clustering when community ids are missing. louvain is the default
deterministic partition; louvain-refine adds connectivity refinement. Not multilevel Leiden; not
Graphify.
<options: louvain|louvain-refine>
--graph=<value> Graph file to read (default: <project root>/.agentia/agentia-graph/graphs/graph.json)
--out=<value> [default: wiki] Directory to write wiki markdown into
--title=<value> Wiki index title. Defaults to the working directory name.
GLOBAL FLAGS
--json Format output as json.
DESCRIPTION
Export a markdown wiki of communities and hubs
Writes index.md, one article per community, and god-node articles under --out.
Layout caps keep large graphs readable (max communities 200, members 25, cross-links 12, god nodes 30). Graphify is a
shape reference only; this does not vendor Graphify.
EXAMPLES
$ agentia graph wiki
$ agentia graph wiki --out wiki --algorithm louvain