Add cgraph CLI tool and Claude Code indexing skill#622
Add cgraph CLI tool and Claude Code indexing skill#622
Conversation
staging-->main
Introduce a typer-based CLI (`cgraph`) that wraps the existing sync Graph and Project classes, enabling code indexing and knowledge graph queries from the terminal. Add a Claude Code skill (`skills/code-graph/`) so Claude can autonomously manage the full lifecycle — database, indexing, and querying — during coding sessions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
📝 WalkthroughWalkthroughAdds a Typer-based CLI entry point ( Changes
Sequence Diagram(s)sequenceDiagram
participant User as User/CLI
participant CLI as cgraph.ensure-db
participant FalkorDB as FalkorDB
participant Docker as Docker
User->>CLI: run ensure-db
CLI->>FalkorDB: probe connection (PING)
alt FalkorDB reachable
FalkorDB-->>CLI: OK
CLI-->>User: {"status":"ok"} (stdout JSON / stderr status)
else FalkorDB unreachable
CLI->>Docker: inspect container falkordb-cgraph
alt container exists (stopped)
Docker-->>CLI: container stopped
CLI->>Docker: start container
else no container
CLI->>Docker: create & start container
end
Docker-->>CLI: container started
CLI->>FalkorDB: poll until ready
FalkorDB-->>CLI: ready
CLI-->>User: {"status":"ok"}
end
sequenceDiagram
participant User as User/CLI
participant CLI as cgraph.index
participant Git as Git (detect/clone)
participant Project as Project
participant Analyzer as Analyzer/Graph
participant FalkorDB as FalkorDB
User->>CLI: cgraph index --path ./repo
CLI->>Git: detect remote URL / clone if needed
Git-->>CLI: repo metadata (remote URL)
CLI->>Project: Project(path, metadata)
Project->>Analyzer: analyze_sources (build nodes & edges)
Analyzer->>FalkorDB: write nodes & edges
FalkorDB-->>Analyzer: ack
Analyzer-->>CLI: stats (node_count, edge_count)
CLI-->>User: {"status":"ok","repo":"name","node_count":N,"edge_count":M}
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
This PR adds a new cgraph command-line interface for indexing/querying the existing FalkorDB-backed code graph, plus accompanying “Claude Code skill” documentation and packaging/test updates so the CLI can be installed and exercised in CI/dev workflows.
Changes:
- Introduces a new Typer-based CLI (
api/cli.py) with commands for DB management, indexing, and graph queries (JSON on stdout). - Adds a new
skills/code-graph/skill with installation/management and querying reference docs. - Updates packaging and developer workflow: adds
typer, acgraphentry point, a build-system section, amake install-clitarget, and CLI tests.
Reviewed changes
Copilot reviewed 9 out of 10 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
api/cli.py |
New Typer CLI implementing cgraph commands (ensure-db/index/query). |
pyproject.toml |
Adds typer dependency, cgraph script entry point, and setuptools build-system config. |
uv.lock |
Locks new dependency (typer) and updates editable source metadata. |
Makefile |
Adds install-cli target to install the CLI entry point in editable mode. |
AGENTS.md |
Documents the new CLI and the new skill directory. |
tests/test_cli.py |
Adds CLI smoke/integration tests via typer.testing.CliRunner. |
skills/code-graph/SKILL.md |
Adds a Claude Code skill definition and usage guidance for cgraph. |
skills/code-graph/references/management.md |
Adds CLI installation + DB management reference documentation. |
skills/code-graph/references/querying.md |
Adds query JSON schemas + common query patterns for CLI usage. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
api/cli.py
Outdated
| folder = Path(path).resolve() | ||
| if not folder.exists(): | ||
| _json_out({"status": "error", "message": f"path does not exist: {folder}"}) | ||
| raise typer.Exit(code=1) |
There was a problem hiding this comment.
Fixed — added folder.is_dir() validation after the existence check, returning a JSON error if the path is a file.
| _stderr(f"Indexing {folder} as '{name}'…") | ||
| project = Project(name, folder, url) | ||
| graph = project.analyze_sources(ignore=list(ignore) if ignore else []) | ||
|
|
||
| stats = graph.stats() | ||
| _stderr(f"Done — {stats['node_count']} nodes, {stats['edge_count']} edges") | ||
| _json_out({"status": "ok", "repo": name, **stats}) |
There was a problem hiding this comment.
Fixed — all commands now wrap their core logic in try/except and emit structured {"status": "error", "message": ...} JSON with a non-zero exit code.
| _stderr(f"Cloning and indexing {url}…") | ||
| project = Project.from_git_repository(url) | ||
| graph = project.analyze_sources(ignore=list(ignore) if ignore else []) | ||
|
|
||
| stats = graph.stats() | ||
| _stderr(f"Done — {stats['node_count']} nodes, {stats['edge_count']} edges") | ||
| _json_out({"status": "ok", "repo": project.name, **stats}) |
There was a problem hiding this comment.
Good catch. The print() in _clone_source() is in the existing project.py code shared with the web server. Changing it there would affect the FastAPI flow too. Leaving as a known limitation for now — a follow-up could redirect stdout during clone or switch the print to logging.info.
|
|
||
| ### "Who calls function X?" | ||
|
|
||
| The graph stores directed edges `(caller)-[:CALLS]->(callee)`. To find callers, search for the function, then look for *incoming* CALLS edges. Since `neighbors` follows outgoing edges, use `find_paths` or search for likely callers and check their CALLS neighbors. |
There was a problem hiding this comment.
Fixed — updated the reference from find_paths to cgraph paths.
| host = os.getenv("FALKORDB_HOST", "localhost") | ||
| port = int(os.getenv("FALKORDB_PORT", "6379")) | ||
|
|
||
| if _check_connection(host, port): | ||
| _stderr(f"FalkorDB already running on {host}:{port}") | ||
| _json_out({"status": "ok", "host": host, "port": port}) | ||
| return | ||
|
|
||
| _stderr( | ||
| f"FalkorDB not reachable on {host}:{port}, starting Docker container…" | ||
| ) | ||
|
|
There was a problem hiding this comment.
Fixed — ensure-db now only attempts Docker auto-start when FALKORDB_HOST is localhost/127.0.0.1/::1. For remote hosts it returns a JSON error explaining auto-start is local-only.
api/cli.py
Outdated
| if inspect.returncode == 0: | ||
| if inspect.stdout.strip() == "false": | ||
| subprocess.run( | ||
| ["docker", "start", "falkordb-cgraph"], | ||
| check=True, | ||
| capture_output=True, | ||
| ) | ||
| _stderr("Started existing falkordb-cgraph container") | ||
| else: | ||
| subprocess.run( | ||
| [ | ||
| "docker", | ||
| "run", | ||
| "-d", | ||
| "--name", | ||
| "falkordb-cgraph", | ||
| "-p", | ||
| f"{port}:6379", | ||
| "falkordb/falkordb:latest", | ||
| ], | ||
| check=True, | ||
| capture_output=True, | ||
| ) | ||
| _stderr("Created and started falkordb-cgraph container") |
There was a problem hiding this comment.
Fixed — Docker subprocess calls are now wrapped in try/except catching FileNotFoundError (Docker not installed) and subprocess.CalledProcessError (port conflict, permissions, pull failure), both emitting structured JSON errors.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (3)
skills/code-graph/references/management.md (1)
19-23: Add language specifier to fenced code block.The code block is missing a language specifier for proper syntax highlighting.
📝 Proposed fix
### Via pipx (standalone) -``` +```bash pipx install code-graph-backend</details> <details> <summary>🤖 Prompt for AI Agents</summary>Verify each finding against the current code and only fix it if needed.
In
@skills/code-graph/references/management.mdaround lines 19 - 23, The fenced
code block under the "Via pipx (standalone)" section is missing a language
specifier; update the opening fence that currently encloses "pipx install
code-graph-backend" to include "bash" (i.e., changetobash) so the
command in the pipx install snippet is correctly highlighted—look for the block
containing the text "pipx install code-graph-backend" to make this change.</details> </blockquote></details> <details> <summary>tests/test_cli.py (1)</summary><blockquote> `68-76`: **Consider asserting search returns non-empty results.** The test verifies `results` is a list but doesn't assert it's non-empty. Since the fixture contains `src.py`, you could strengthen the test: <details> <summary>📝 Proposed enhancement</summary> ```diff def test_search(self): result = runner.invoke( app, ["search", "src", "--repo", self.REPO_NAME] ) self.assertEqual(result.exit_code, 0) data = json.loads(result.output) self.assertIn("results", data) # The fixture has a file named src.py, so we should find something self.assertIsInstance(data["results"], list) + self.assertGreater(len(data["results"]), 0, "Expected to find src.py in search results") ``` </details> <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against the current code and only fix it if needed. In `@tests/test_cli.py` around lines 68 - 76, In test_search, strengthen the assertion to ensure the search actually returns hits: after calling runner.invoke(app, ["search", "src", "--repo", self.REPO_NAME]) and loading data = json.loads(result.output), add an assertion that data["results"] is non-empty (e.g., assertTrue(len(data["results"]) > 0 or assertGreater(len(data["results"]), 0)) to guarantee the fixture src.py produced at least one result). ``` </details> </blockquote></details> <details> <summary>AGENTS.md (1)</summary><blockquote> `137-154`: **Make agent responsibilities/capabilities explicit in this section.** The CLI section documents commands well, but AGENTS.md should explicitly state agent responsibilities, capabilities, and integration points (e.g., what the agent should autonomously do vs. when to defer to API/manual flows). Consider adding a short dedicated subsection under this block. As per coding guidelines, "Document agent responsibilities, capabilities, and integration points in AGENTS.md". <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against the current code and only fix it if needed. In `@AGENTS.md` around lines 137 - 154, Add a short, dedicated subsection under the CLI (`cgraph`) block in AGENTS.md that explicitly lists agent responsibilities, capabilities, and integration points; mention which tasks the agent should perform autonomously (e.g., running indexing via cgraph index / index-repo, returning JSON results to stdout, and invoking skills/code-graph), which actions must defer to API/manual flows (e.g., database management via cgraph ensure-db or destructive operations), expected inputs/outputs (IDs, repo names, JSON on stdout, status on stderr), and integration touchpoints such as the CLI entrypoint api/cli.py and the Claude Code skill in skills/code-graph to guide implementers on when to call the agent vs. when to require human/API intervention. ``` </details> </blockquote></details> </blockquote></details> <details> <summary>🤖 Prompt for all review comments with AI agents</summary>Verify each finding against the current code and only fix it if needed.
Inline comments:
In@AGENTS.md:
- Around line 149-151: Update the CLI examples for cgraph commands to show that
--repo, --rel, and --label accept values rather than acting as booleans: change
the usages in the cgraph neighbors, cgraph paths, and cgraph info examples to
include explicit placeholders (e.g., --repo=, --rel=,
--label=) and update the short inline comments to mention the expected
value types (e.g., repository name or ID, relation type, label string) so users
know to supply values for --repo, --rel, and --label.In
@api/cli.py:
- Around line 88-101: The Docker run uses "{port}:6379" which maps the container
port to the host port but doesn't bind to loopback, so if FALKORDB_HOST is set
to a remote host the subsequent connection checks can fail; modify the container
start logic around the subprocess.run call to bind the host side explicitly to
localhost (e.g., use "127.0.0.1:{port}:6379" or set host="localhost" before
starting the container) so the newly-started container is reachable locally, and
add a short comment or doc note in the ensure-db/start container flow explaining
that auto-start only applies to local development.- Around line 145-150: The transformation of remote_url into url is currently
blindly chaining replaces and can mangle non-SSH URLs; update the logic around
GitRepo(...) remotes[0].url (remote_url) to detect SSH-style URLs (e.g.,
startswith "git@") and only apply the SSH conversion (replace "git@" ->
"https://", replace ":" -> "/", strip trailing ".git"), otherwise for HTTP/HTTPS
or other schemes simply strip a trailing ".git" if present and leave the rest
unchanged; implement this branching in the code that builds url so SSH and
non-SSH remotes are handled safely.
Nitpick comments:
In@AGENTS.md:
- Around line 137-154: Add a short, dedicated subsection under the CLI
(cgraph) block in AGENTS.md that explicitly lists agent responsibilities,
capabilities, and integration points; mention which tasks the agent should
perform autonomously (e.g., running indexing via cgraph index / index-repo,
returning JSON results to stdout, and invoking skills/code-graph), which actions
must defer to API/manual flows (e.g., database management via cgraph ensure-db
or destructive operations), expected inputs/outputs (IDs, repo names, JSON on
stdout, status on stderr), and integration touchpoints such as the CLI
entrypoint api/cli.py and the Claude Code skill in skills/code-graph to guide
implementers on when to call the agent vs. when to require human/API
intervention.In
@skills/code-graph/references/management.md:
- Around line 19-23: The fenced code block under the "Via pipx (standalone)"
section is missing a language specifier; update the opening fence that currently
encloses "pipx install code-graph-backend" to include "bash" (i.e., changetobash) so the command in the pipx install snippet is correctly
highlighted—look for the block containing the text "pipx install
code-graph-backend" to make this change.In
@tests/test_cli.py:
- Around line 68-76: In test_search, strengthen the assertion to ensure the
search actually returns hits: after calling runner.invoke(app, ["search", "src",
"--repo", self.REPO_NAME]) and loading data = json.loads(result.output), add an
assertion that data["results"] is non-empty (e.g.,
assertTrue(len(data["results"]) > 0 or assertGreater(len(data["results"]), 0))
to guarantee the fixture src.py produced at least one result).</details> --- <details> <summary>ℹ️ Review info</summary> <details> <summary>⚙️ Run configuration</summary> **Configuration used**: defaults **Review profile**: CHILL **Plan**: Pro **Run ID**: `3b0c10dd-6b27-4a6f-9a74-953ed909ea03` </details> <details> <summary>📥 Commits</summary> Reviewing files that changed from the base of the PR and between 887b82ffb35b204cb0e52f1986ecdb8705281113 and 3a3a11cd15f2941e3253265dcc73e56eee59b0c4. </details> <details> <summary>⛔ Files ignored due to path filters (1)</summary> * `uv.lock` is excluded by `!**/*.lock` </details> <details> <summary>📒 Files selected for processing (9)</summary> * `AGENTS.md` * `Makefile` * `README.md` * `api/cli.py` * `pyproject.toml` * `skills/code-graph/SKILL.md` * `skills/code-graph/references/management.md` * `skills/code-graph/references/querying.md` * `tests/test_cli.py` </details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
| remote_url = GitRepo(str(folder)).remotes[0].url | ||
| url = ( | ||
| remote_url.replace("git@", "https://") | ||
| .replace(":", "/") | ||
| .replace(".git", "") | ||
| ) |
There was a problem hiding this comment.
Git URL transformation may produce malformed URLs.
The chained .replace() calls can mangle URLs in certain cases:
git@github.com:org/repo.git→https://github.com/org/repo✓- But a URL containing ":" elsewhere (e.g., port numbers, or HTTPS URLs with
.gitin the path) could be incorrectly transformed.
Consider a more robust transformation or limiting this to SSH-style URLs.
📝 Proposed fix
- url = (
- remote_url.replace("git@", "https://")
- .replace(":", "/")
- .replace(".git", "")
- )
+ if remote_url.startswith("git@"):
+ # SSH format: git@host:org/repo.git
+ url = (
+ remote_url.replace("git@", "https://")
+ .replace(":", "/", 1) # Only replace first colon
+ .removesuffix(".git")
+ )
+ elif remote_url.startswith("https://"):
+ url = remote_url.removesuffix(".git")
+ else:
+ url = remote_url # Keep as-is for other formats📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| remote_url = GitRepo(str(folder)).remotes[0].url | |
| url = ( | |
| remote_url.replace("git@", "https://") | |
| .replace(":", "/") | |
| .replace(".git", "") | |
| ) | |
| remote_url = GitRepo(str(folder)).remotes[0].url | |
| if remote_url.startswith("git@"): | |
| # SSH format: git@host:org/repo.git | |
| url = ( | |
| remote_url.replace("git@", "https://") | |
| .replace(":", "/", 1) # Only replace first colon | |
| .removesuffix(".git") | |
| ) | |
| elif remote_url.startswith("https://"): | |
| url = remote_url.removesuffix(".git") | |
| else: | |
| url = remote_url # Keep as-is for other formats |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@api/cli.py` around lines 145 - 150, The transformation of remote_url into url
is currently blindly chaining replaces and can mangle non-SSH URLs; update the
logic around GitRepo(...) remotes[0].url (remote_url) to detect SSH-style URLs
(e.g., startswith "git@") and only apply the SSH conversion (replace "git@" ->
"https://", replace ":" -> "/", strip trailing ".git"), otherwise for HTTP/HTTPS
or other schemes simply strip a trailing ".git" if present and leave the rest
unchanged; implement this branching in the code that builds url so SSH and
non-SSH remotes are handled safely.
There was a problem hiding this comment.
This is the same URL transform pattern used in project.py:from_local_repository(). Making it more robust here while leaving the original would create inconsistency. A follow-up could improve both together — out of scope for this PR.
There was a problem hiding this comment.
@gkorland, that's a fair point — keeping the pattern consistent across both api/cli.py and project.py:from_local_repository() makes sense. I'll defer to a follow-up.
Would you like me to open a GitHub issue to track the URL transform robustness improvement for both locations so it doesn't get lost?
✏️ Learnings added
Learnt from: gkorland
Repo: FalkorDB/code-graph PR: 622
File: api/cli.py:165-170
Timestamp: 2026-03-19T09:09:07.297Z
Learning: In the FalkorDB/code-graph repository, the Git remote URL transformation pattern (replacing "git@", ":", and ".git") in `api/cli.py` is intentionally mirrored from `project.py:from_local_repository()`. Any improvement to URL handling should be applied to both locations together in a follow-up, not fixed in isolation, to maintain consistency.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
Add package.json to skills/code-graph/ so the skill can be published to npm as @falkordb/code-graph and installed with: npx skills add @falkordb/code-graph Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The skills CLI uses GitHub org/repo format, not npm scoped packages. Install command: npx skills add FalkorDB/code-graph Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Validate path is a directory, not just exists (Comment 2) - Wrap all commands in try/except for JSON error output (Comments 3, 7) - Only auto-start Docker for localhost, not remote hosts (Comments 6, 9) - Catch Docker subprocess errors (FileNotFoundError, CalledProcessError) - Add comment to bare except in git remote detection (Comment 1) - Fix querying.md reference: find_paths → cgraph paths (Comment 5) - Add value placeholders to AGENTS.md CLI examples (Comment 8) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
api/cli.py (1)
42-52: Consider using a context manager or finally block for connection cleanup.If
r.ping()raises an exception, the connection is never explicitly closed. While redis-py will garbage-collect it, explicit cleanup is better practice for resource management.♻️ Suggested improvement
def _check_connection(host: str, port: int) -> bool: """Check if FalkorDB/Redis is reachable via PING.""" try: import redis r = redis.Redis(host=host, port=port, socket_connect_timeout=2) - r.ping() - r.close() - return True + try: + r.ping() + return True + finally: + r.close() except Exception: return False🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@api/cli.py` around lines 42 - 52, The _check_connection function may leave the redis client open if r.ping() raises; ensure the Redis connection created by redis.Redis(host=host, port=port, socket_connect_timeout=2) is always closed by either using a context manager (e.g., contextlib.closing) or a try/finally: initialize r before the try, assign the Redis instance to r, call r.ping() inside the try, and in the finally block call r.close() if r is not None so the connection is always cleaned up.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@api/cli.py`:
- Around line 42-52: The _check_connection function may leave the redis client
open if r.ping() raises; ensure the Redis connection created by
redis.Redis(host=host, port=port, socket_connect_timeout=2) is always closed by
either using a context manager (e.g., contextlib.closing) or a try/finally:
initialize r before the try, assign the Redis instance to r, call r.ping()
inside the try, and in the finally block call r.close() if r is not None so the
connection is always cleaned up.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 0b9dc890-e20f-454b-b5c4-e20e5ac6560e
📒 Files selected for processing (3)
AGENTS.mdapi/cli.pyskills/code-graph/references/querying.md
✅ Files skipped from review due to trivial changes (2)
- skills/code-graph/references/querying.md
- AGENTS.md
Summary
api/cli.py— New typer-based CLI (cgraph) with 8 commands:ensure-db,index,index-repo,list,search,neighbors,paths,info. Wraps existing syncGraphandProjectclasses. JSON to stdout, status to stderr.skills/code-graph/— Claude Code skill that teaches Claude to autonomously manage the full cgraph lifecycle (database, indexing, querying) during coding sessions. Includes reference docs for installation/management and query patterns.pyproject.toml— Addedtyperdependency,[project.scripts]entry point (cgraph = "api.cli:app"), and[build-system]for setuptools packaging.Makefile— Addedinstall-clitarget (uv pip install -e .).AGENTS.md— Updated directory structure, commands, and added CLI section.tests/test_cli.py— CLI tests usingtyper.testing.CliRunnercovering help, list, ensure-db, and index+search+info on fixture data.Test plan
uv sync --all-extrasinstalls cleanly with entry pointcgraph --helpshows all 8 commandscgraph ensure-dbstarts or confirms FalkorDBcgraph index . --ignore node_modules --ignore .gitindexes the repocgraph listshows the indexed repocgraph search Graph --repo code-graphreturns matching entitiescgraph neighbors <id> --repo code-graphreturns connected nodescgraph info --repo code-graphshows node/edge countsmake test— existing tests still passuv run python -m pytest tests/test_cli.py -v— new CLI tests passmake lint-py— ruff passes🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
Tests
Chores