Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

Full release notes with details on each version: [GitHub Releases](https://github.com/safishamsi/graphify/releases)

## Unreleased

- Fix: `graphify query` now surfaces the work-memory lesson annotations. The renderer has carried `learning=<status>[:stale]` NODE-line support since the overlay shipped, and both `graphify explain` and the MCP server attach the `.graphify_learning.json` sidecar at load time — but the CLI `query` loader never did, so the same question answered over MCP showed the lessons while the CLI answer silently dropped them (the docs promise `query` surfaces a lesson hint). The CLI query path now attaches the overlay exactly like `serve._load_graph` (display-only, fail-safe to empty), so annotations — including the `:stale` "code changed since — re-verify" marker — render identically on both surfaces; without a sidecar the output is byte-identical to before.

## 0.9.50 (2026-08-25)

- Fix: Ruby methods whose names end in `!`, `?`, or `=` now keep distinct node ids, so `save` and `save!` (or `foo` and `foo=`) no longer collide into one node; the label keeps the raw spelling and member-call resolution still matches (#3077, thanks @hopstreax).
Expand Down
11 changes: 11 additions & 0 deletions graphify/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -1162,6 +1162,17 @@ def dispatch_command(cmd: str) -> None:
except Exception as exc:
print(f"error: could not load graph: {exc}", file=sys.stderr)
sys.exit(1)
# Work-memory overlay: attach the .graphify_learning.json sidecar the
# same way the MCP loader does (serve._load_graph), so the renderer's
# learning=<status>[:stale] NODE annotations appear in CLI `query`
# output too — `explain` and MCP already surface them, and the docs
# promise `query` does as well. Empty overlay on any error, leaving
# un-annotated output byte-identical.
try:
from graphify.reflect import load_learning_overlay as _llo
G.graph["_learning_overlay"] = _llo(gp)
except Exception:
G.graph["_learning_overlay"] = {}
import time as _time
_t0 = _time.perf_counter()
_mode = "dfs" if use_dfs else "bfs"
Expand Down
65 changes: 65 additions & 0 deletions tests/test_query_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,68 @@ def test_query_cli_rejects_oversized_graph(monkeypatch, tmp_path, capsys):
err = capsys.readouterr().err
assert "exceeds" in err
assert "byte cap" in err


# --- work-memory overlay: lesson annotations in CLI query output ---------------


def _write_sidecar(tmp_path, nodes: dict) -> None:
"""A .graphify_learning.json next to graph.json, in the writer's shape."""
(tmp_path / ".graphify_learning.json").write_text(
json.dumps({"version": 1, "generated_at": "2026-01-01T00:00:00+00:00",
"nodes": nodes}),
encoding="utf-8",
)


def test_query_cli_annotates_nodes_with_learning_status(monkeypatch, tmp_path, capsys):
"""`graphify query` must merge the learning sidecar into NODE lines with the
same learning=<status> form the MCP server renders — the docs promise
lessons surface in `query` output, not only in `explain`/MCP."""
graph_path = _write_graph(tmp_path)
_write_sidecar(tmp_path, {"n1": {"status": "preferred"}})
monkeypatch.setattr(mainmod, "_check_skill_version", lambda _: None)
monkeypatch.setattr(
mainmod.sys,
"argv",
["graphify", "query", "extract", "--graph", str(graph_path)],
)
mainmod.main()
out = capsys.readouterr().out
assert "learning=preferred]" in out
# Only the annotated node carries the suffix.
assert out.count("learning=") == 1


def test_query_cli_marks_stale_learning_entries(monkeypatch, tmp_path, capsys):
"""An entry whose cited code changed (or vanished) since the verdict must
carry the :stale marker, matching the MCP annotation form."""
graph_path = _write_graph(tmp_path)
_write_sidecar(
tmp_path,
{"n1": {"status": "tentative", "source_file": "gone.py",
"code_fingerprint": "0123abcd"}},
)
monkeypatch.setattr(mainmod, "_check_skill_version", lambda _: None)
monkeypatch.setattr(
mainmod.sys,
"argv",
["graphify", "query", "extract", "--graph", str(graph_path)],
)
mainmod.main()
out = capsys.readouterr().out
assert "learning=tentative:stale]" in out


def test_query_cli_without_sidecar_stays_unannotated(monkeypatch, tmp_path, capsys):
"""No sidecar -> no learning= anywhere; un-annotated output is unchanged."""
graph_path = _write_graph(tmp_path)
monkeypatch.setattr(mainmod, "_check_skill_version", lambda _: None)
monkeypatch.setattr(
mainmod.sys,
"argv",
["graphify", "query", "extract", "--graph", str(graph_path)],
)
mainmod.main()
out = capsys.readouterr().out
assert "learning=" not in out
Loading