diff --git a/CHANGELOG.md b/CHANGELOG.md index b66812bff..830855fba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Full release notes with details on each version: [GitHub Releases](https://githu ## Unreleased +- Fix: the `get_community` MCP tool now shows the community name in its header (`Community 12 — Auth & Sessions (8 nodes)`), matching `get_node` and the query-traversal output, which already read the `community_name` node attribute written by `to_json`. `get_community` was the only graph tool still returning a bare numeric id, so an agent that had located a community by name (via the report, `get_node`, or query output) couldn't confirm it here. The name is read from the community's member nodes — they all share it — and falls back to the bare id for graphs built before `community_name` was persisted; the value still passes through `sanitize_label` like every other LLM-derived field (#1448). - Fix: `validate_extraction` and `build_from_json` no longer crash on a non-hashable node `id` or edge `source`/`target` (e.g. a list emitted by a malformed LLM extraction) — previously a single bad node raised `TypeError: unhashable type` and aborted the entire build of an otherwise-complete corpus. The validator now reports the bad id/endpoint as an error string (its documented contract), and the build skips the malformed entry with a stderr warning while keeping every well-formed node/edge; non-dict nodes are still left to raise so shape diagnostics are unchanged (#1447, thanks @dschwartzi). - Fix: Python qualified class-method calls (`ClassName.method(...)`) now produce an EXTRACTED `calls` edge to the class-qualified method node (#1446). Previously these cross-class static/qualified calls were dropped: the shared cross-file pass skips all member calls (the #543/#1219 god-node guard against bare `obj.method()` collisions), and when the called method shared its name with an in-file node — e.g. a viewset action `approve()` delegating to a service `Service.approve()` — the bare-name lookup matched the caller's own node and silently dropped it. The Python extractor now captures a simple-identifier receiver, defers capitalized-receiver member calls to a new receiver-based resolver (`_resolve_python_member_calls`, mirroring the Swift pass), and emits the edge only when the receiver resolves to exactly one class that owns the method (single-definition god-node guard); instance/module calls (`self.x()`, `obj.x()`, lowercase receivers) are unaffected. - Feat: new first-class `agents` platform installs the skill to the generic cross-framework Agent-Skills locations. `graphify install --platform agents` (alias `--platform skills`) writes the spec's user-global `~/.agents/skills/graphify/SKILL.md` — the directory `npx skills` and spec-compliant frameworks read — and `--project` writes `./.agents/skills/graphify/SKILL.md`; `graphify uninstall` removes them. Previously that user-global location was only reachable as an accidental side effect of the gemini-on-Windows branch. The skill bundle re-homes amp's agents-md body (registered in `tools/skillgen/platforms.toml`, rendered through the skillgen drift/coverage guards); the body is identical to amp's, and only the on-demand hooks reference differs — it points at `graphify agents install`, which (as the amp-twin subcommand) wires the skill plus an AGENTS.md always-on section. Bare `graphify install` is unchanged — still single-platform (claude/windows) (#1432, closes #1405). diff --git a/graphify/serve.py b/graphify/serve.py index 06e8cf715..9bd451049 100644 --- a/graphify/serve.py +++ b/graphify/serve.py @@ -898,7 +898,13 @@ def _tool_get_community(arguments: dict) -> str: nodes = communities.get(cid, []) if not nodes: return f"Community {cid} not found." - lines = [f"Community {cid} ({len(nodes)} nodes):"] + # Surface the community name the same way get_node and query output do + # (the community_name node attribute written by to_json). All nodes in a + # community share it; fall back to the bare id for graphs built before + # community_name was persisted. + name = G.nodes[nodes[0]].get("community_name") + header = f"Community {cid} — {sanitize_label(str(name))}" if name else f"Community {cid}" + lines = [f"{header} ({len(nodes)} nodes):"] for n in nodes: d = G.nodes[n] # Sanitise label and source_file (F-010).