diff --git a/CHANGELOG.md b/CHANGELOG.md index 884eb91e..847f4548 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ Full release notes with details on each version: [GitHub Releases](https://github.com/safishamsi/graphify/releases) +## 0.3.18 (2026-04-09) + +- Fix: `--watch` mode now respects `.graphifyignore` — `_rebuild_code` was calling `collect_files()` directly instead of `detect()`, bypassing ignore patterns (#120) +- Fix: Codex PreToolUse hook now uses `systemMessage` instead of `additionalContext` — Codex does not support `additionalContext` and was returning an error (#121) +- Fix: Trae link corrected from `trae.com` to `trae.ai` in README, README.zh-CN.md, README.ja-JP.md, README.ko-KR.md (#122) +- Docs: Korean README added (README.ko-KR.md) (#112) +- Refactor: `save_query_result` inline Python blocks in all 6 skill files replaced with `graphify save-result` CLI command — shorter, maintainable, less tokens for LLM (#114) +- Add: `graphify save-result` CLI subcommand — saves Q&A results to memory dir without inline Python + ## 0.3.17 (2026-04-08) - Add: Julia (.jl) support — modules, structs, abstract types, functions, short functions, using/import, call edges, inherits edges via tree-sitter-julia (#98) diff --git a/graphify/__main__.py b/graphify/__main__.py index 3dc8f7ac..093539d3 100644 --- a/graphify/__main__.py +++ b/graphify/__main__.py @@ -170,7 +170,7 @@ _CODEX_HOOK = { "type": "command", "command": ( "[ -f graphify-out/graph.json ] && " - r"""echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","additionalContext":"graphify: Knowledge graph exists. Read graphify-out/GRAPH_REPORT.md for god nodes and community structure before searching raw files."}}' """ + r"""echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","systemMessage":"graphify: Knowledge graph exists. Read graphify-out/GRAPH_REPORT.md for god nodes and community structure before searching raw files."}}' """ "|| true" ), } @@ -388,6 +388,12 @@ def main() -> None: print(" --dfs use depth-first instead of breadth-first") print(" --budget N cap output at N tokens (default 2000)") print(" --graph path to graph.json (default graphify-out/graph.json)") + print(" save-result save a Q&A result to graphify-out/memory/ for graph feedback loop") + print(" --question Q the question asked") + print(" --answer A the answer to save") + print(" --type T query type: query|path_query|explain (default: query)") + print(" --nodes N1 N2 ... source node labels cited in the answer") + print(" --memory-dir DIR memory directory (default: graphify-out/memory)") print(" benchmark [graph.json] measure token reduction vs naive full-corpus approach") print(" hook install install post-commit/post-checkout git hooks (all platforms)") print(" hook uninstall remove git hooks") @@ -518,6 +524,25 @@ def main() -> None: start = [nid for _, nid in scored[:5]] nodes, edges = (_dfs if use_dfs else _bfs)(G, start, depth=2) print(_subgraph_to_text(G, nodes, edges, token_budget=budget)) + elif cmd == "save-result": + # graphify save-result --question Q --answer A --type T [--nodes N1 N2 ...] + import argparse as _ap + p = _ap.ArgumentParser(prog="graphify save-result") + p.add_argument("--question", required=True) + p.add_argument("--answer", required=True) + p.add_argument("--type", dest="query_type", default="query") + p.add_argument("--nodes", nargs="*", default=[]) + p.add_argument("--memory-dir", default="graphify-out/memory") + opts = p.parse_args(sys.argv[2:]) + from graphify.ingest import save_query_result as _sqr + out = _sqr( + question=opts.question, + answer=opts.answer, + memory_dir=Path(opts.memory_dir), + query_type=opts.query_type, + source_nodes=opts.nodes or None, + ) + print(f"Saved to {out}") elif cmd == "benchmark": from graphify.benchmark import run_benchmark, print_benchmark graph_path = sys.argv[2] if len(sys.argv) > 2 else "graphify-out/graph.json" diff --git a/graphify/skill-claw.md b/graphify/skill-claw.md index c267a7df..ca353efd 100644 --- a/graphify/skill-claw.md +++ b/graphify/skill-claw.md @@ -894,18 +894,7 @@ Replace `QUESTION` with the user's actual question, `MODE` with `bfs` or `dfs`, After writing the answer, save it back into the graph so it improves future queries: ```bash -$(cat .graphify_python) -c " -from graphify.ingest import save_query_result -from pathlib import Path -save_query_result( - question='QUESTION', - answer='ANSWER', - memory_dir=Path('graphify-out/memory'), - query_type='query', - source_nodes=SOURCE_NODES, # list of node labels cited, or [] -) -print('Query result saved to graphify-out/memory/') -" +$(cat .graphify_python) -m graphify save-result --question "QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 ``` Replace `QUESTION` with the question, `ANSWER` with your full answer text, `SOURCE_NODES` with the list of node labels you cited. This closes the feedback loop: the next `--update` will extract this Q&A as a node in the graph. @@ -980,18 +969,7 @@ Replace `NODE_A` and `NODE_B` with the actual concept names from the user. Then After writing the explanation, save it back: ```bash -$(cat .graphify_python) -c " -from graphify.ingest import save_query_result -from pathlib import Path -save_query_result( - question='Path from NODE_A to NODE_B', - answer='ANSWER', - memory_dir=Path('graphify-out/memory'), - query_type='path_query', - source_nodes=PATH_NODES, # list of node labels on the path -) -print('Path result saved to graphify-out/memory/') -" +$(cat .graphify_python) -m graphify save-result --question "Path from NODE_A to NODE_B" --answer "ANSWER" --type path_query --nodes NODE_A NODE_B ``` --- @@ -1057,18 +1035,7 @@ Replace `NODE_NAME` with the concept the user asked about. Then write a 3-5 sent After writing the explanation, save it back: ```bash -$(cat .graphify_python) -c " -from graphify.ingest import save_query_result -from pathlib import Path -save_query_result( - question='Explain NODE_NAME', - answer='ANSWER', - memory_dir=Path('graphify-out/memory'), - query_type='explain', - source_nodes=['NODE_NAME'], -) -print('Explanation saved to graphify-out/memory/') -" +$(cat .graphify_python) -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME ``` --- diff --git a/graphify/skill-codex.md b/graphify/skill-codex.md index 33d82dfb..e94f2da4 100644 --- a/graphify/skill-codex.md +++ b/graphify/skill-codex.md @@ -950,18 +950,7 @@ Replace `QUESTION` with the user's actual question, `MODE` with `bfs` or `dfs`, After writing the answer, save it back into the graph so it improves future queries: ```bash -$(cat .graphify_python) -c " -from graphify.ingest import save_query_result -from pathlib import Path -save_query_result( - question='QUESTION', - answer='ANSWER', - memory_dir=Path('graphify-out/memory'), - query_type='query', - source_nodes=SOURCE_NODES, # list of node labels cited, or [] -) -print('Query result saved to graphify-out/memory/') -" +$(cat .graphify_python) -m graphify save-result --question "QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 ``` Replace `QUESTION` with the question, `ANSWER` with your full answer text, `SOURCE_NODES` with the list of node labels you cited. This closes the feedback loop: the next `--update` will extract this Q&A as a node in the graph. @@ -1036,18 +1025,7 @@ Replace `NODE_A` and `NODE_B` with the actual concept names from the user. Then After writing the explanation, save it back: ```bash -$(cat .graphify_python) -c " -from graphify.ingest import save_query_result -from pathlib import Path -save_query_result( - question='Path from NODE_A to NODE_B', - answer='ANSWER', - memory_dir=Path('graphify-out/memory'), - query_type='path_query', - source_nodes=PATH_NODES, # list of node labels on the path -) -print('Path result saved to graphify-out/memory/') -" +$(cat .graphify_python) -m graphify save-result --question "Path from NODE_A to NODE_B" --answer "ANSWER" --type path_query --nodes NODE_A NODE_B ``` --- @@ -1113,18 +1091,7 @@ Replace `NODE_NAME` with the concept the user asked about. Then write a 3-5 sent After writing the explanation, save it back: ```bash -$(cat .graphify_python) -c " -from graphify.ingest import save_query_result -from pathlib import Path -save_query_result( - question='Explain NODE_NAME', - answer='ANSWER', - memory_dir=Path('graphify-out/memory'), - query_type='explain', - source_nodes=['NODE_NAME'], -) -print('Explanation saved to graphify-out/memory/') -" +$(cat .graphify_python) -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME ``` --- diff --git a/graphify/skill-droid.md b/graphify/skill-droid.md index 7dfb7381..915dc784 100644 --- a/graphify/skill-droid.md +++ b/graphify/skill-droid.md @@ -947,18 +947,7 @@ Replace `QUESTION` with the user's actual question, `MODE` with `bfs` or `dfs`, After writing the answer, save it back into the graph so it improves future queries: ```bash -$(cat .graphify_python) -c " -from graphify.ingest import save_query_result -from pathlib import Path -save_query_result( - question='QUESTION', - answer='ANSWER', - memory_dir=Path('graphify-out/memory'), - query_type='query', - source_nodes=SOURCE_NODES, # list of node labels cited, or [] -) -print('Query result saved to graphify-out/memory/') -" +$(cat .graphify_python) -m graphify save-result --question "QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 ``` Replace `QUESTION` with the question, `ANSWER` with your full answer text, `SOURCE_NODES` with the list of node labels you cited. This closes the feedback loop: the next `--update` will extract this Q&A as a node in the graph. @@ -1033,18 +1022,7 @@ Replace `NODE_A` and `NODE_B` with the actual concept names from the user. Then After writing the explanation, save it back: ```bash -$(cat .graphify_python) -c " -from graphify.ingest import save_query_result -from pathlib import Path -save_query_result( - question='Path from NODE_A to NODE_B', - answer='ANSWER', - memory_dir=Path('graphify-out/memory'), - query_type='path_query', - source_nodes=PATH_NODES, # list of node labels on the path -) -print('Path result saved to graphify-out/memory/') -" +$(cat .graphify_python) -m graphify save-result --question "Path from NODE_A to NODE_B" --answer "ANSWER" --type path_query --nodes NODE_A NODE_B ``` --- @@ -1110,18 +1088,7 @@ Replace `NODE_NAME` with the concept the user asked about. Then write a 3-5 sent After writing the explanation, save it back: ```bash -$(cat .graphify_python) -c " -from graphify.ingest import save_query_result -from pathlib import Path -save_query_result( - question='Explain NODE_NAME', - answer='ANSWER', - memory_dir=Path('graphify-out/memory'), - query_type='explain', - source_nodes=['NODE_NAME'], -) -print('Explanation saved to graphify-out/memory/') -" +$(cat .graphify_python) -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME ``` --- diff --git a/graphify/skill-opencode.md b/graphify/skill-opencode.md index e03db2b7..9ced2a61 100644 --- a/graphify/skill-opencode.md +++ b/graphify/skill-opencode.md @@ -946,18 +946,7 @@ Replace `QUESTION` with the user's actual question, `MODE` with `bfs` or `dfs`, After writing the answer, save it back into the graph so it improves future queries: ```bash -$(cat .graphify_python) -c " -from graphify.ingest import save_query_result -from pathlib import Path -save_query_result( - question='QUESTION', - answer='ANSWER', - memory_dir=Path('graphify-out/memory'), - query_type='query', - source_nodes=SOURCE_NODES, # list of node labels cited, or [] -) -print('Query result saved to graphify-out/memory/') -" +$(cat .graphify_python) -m graphify save-result --question "QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 ``` Replace `QUESTION` with the question, `ANSWER` with your full answer text, `SOURCE_NODES` with the list of node labels you cited. This closes the feedback loop: the next `--update` will extract this Q&A as a node in the graph. @@ -1032,18 +1021,7 @@ Replace `NODE_A` and `NODE_B` with the actual concept names from the user. Then After writing the explanation, save it back: ```bash -$(cat .graphify_python) -c " -from graphify.ingest import save_query_result -from pathlib import Path -save_query_result( - question='Path from NODE_A to NODE_B', - answer='ANSWER', - memory_dir=Path('graphify-out/memory'), - query_type='path_query', - source_nodes=PATH_NODES, # list of node labels on the path -) -print('Path result saved to graphify-out/memory/') -" +$(cat .graphify_python) -m graphify save-result --question "Path from NODE_A to NODE_B" --answer "ANSWER" --type path_query --nodes NODE_A NODE_B ``` --- @@ -1109,18 +1087,7 @@ Replace `NODE_NAME` with the concept the user asked about. Then write a 3-5 sent After writing the explanation, save it back: ```bash -$(cat .graphify_python) -c " -from graphify.ingest import save_query_result -from pathlib import Path -save_query_result( - question='Explain NODE_NAME', - answer='ANSWER', - memory_dir=Path('graphify-out/memory'), - query_type='explain', - source_nodes=['NODE_NAME'], -) -print('Explanation saved to graphify-out/memory/') -" +$(cat .graphify_python) -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME ``` --- diff --git a/graphify/skill-trae.md b/graphify/skill-trae.md index 8b2e1ef8..73d2979e 100644 --- a/graphify/skill-trae.md +++ b/graphify/skill-trae.md @@ -915,18 +915,7 @@ Replace `QUESTION` with the user's actual question, `MODE` with `bfs` or `dfs`, After writing the answer, save it back into the graph so it improves future queries: ```bash -$(cat .graphify_python) -c " -from graphify.ingest import save_query_result -from pathlib import Path -save_query_result( - question='QUESTION', - answer='ANSWER', - memory_dir=Path('graphify-out/memory'), - query_type='query', - source_nodes=SOURCE_NODES, -) -print('Query result saved to graphify-out/memory/') -" +$(cat .graphify_python) -m graphify save-result --question "QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 ``` Replace `QUESTION` with the question, `ANSWER` with your full answer text, `SOURCE_NODES` with the list of node labels you cited. This closes the feedback loop: the next `--update` will extract this Q&A as a node in the graph. @@ -1001,18 +990,7 @@ Replace `NODE_A` and `NODE_B` with the actual concept names from the user. Then After writing the explanation, save it back: ```bash -$(cat .graphify_python) -c " -from graphify.ingest import save_query_result -from pathlib import Path -save_query_result( - question='Path from NODE_A to NODE_B', - answer='ANSWER', - memory_dir=Path('graphify-out/memory'), - query_type='path_query', - source_nodes=PATH_NODES, -) -print('Path result saved to graphify-out/memory/') -" +$(cat .graphify_python) -m graphify save-result --question "Path from NODE_A to NODE_B" --answer "ANSWER" --type path_query --nodes NODE_A NODE_B ``` --- @@ -1077,18 +1055,7 @@ Replace `NODE_NAME` with the concept the user asked about. Then write a 3-5 sent After writing the explanation, save it back: ```bash -$(cat .graphify_python) -c " -from graphify.ingest import save_query_result -from pathlib import Path -save_query_result( - question='Explain NODE_NAME', - answer='ANSWER', - memory_dir=Path('graphify-out/memory'), - query_type='explain', - source_nodes=['NODE_NAME'], -) -print('Explanation saved to graphify-out/memory/') -" +$(cat .graphify_python) -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME ``` --- diff --git a/graphify/skill-windows.md b/graphify/skill-windows.md index e8f9c9de..e635c020 100644 --- a/graphify/skill-windows.md +++ b/graphify/skill-windows.md @@ -937,18 +937,7 @@ Replace `QUESTION` with the user's actual question, `MODE` with `bfs` or `dfs`, After writing the answer, save it back into the graph so it improves future queries: ```powershell -python -c " -from graphify.ingest import save_query_result -from pathlib import Path -save_query_result( - question='QUESTION', - answer='ANSWER', - memory_dir=Path('graphify-out/memory'), - query_type='query', - source_nodes=SOURCE_NODES, # list of node labels cited, or [] -) -print('Query result saved to graphify-out/memory/') -" +python -m graphify save-result --question "QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 ``` Replace `QUESTION` with the question, `ANSWER` with your full answer text, `SOURCE_NODES` with the list of node labels you cited. This closes the feedback loop: the next `--update` will extract this Q&A as a node in the graph. @@ -1023,18 +1012,7 @@ Replace `NODE_A` and `NODE_B` with the actual concept names from the user. Then After writing the explanation, save it back: ```powershell -python -c " -from graphify.ingest import save_query_result -from pathlib import Path -save_query_result( - question='Path from NODE_A to NODE_B', - answer='ANSWER', - memory_dir=Path('graphify-out/memory'), - query_type='path_query', - source_nodes=PATH_NODES, # list of node labels on the path -) -print('Path result saved to graphify-out/memory/') -" +python -m graphify save-result --question "Path from NODE_A to NODE_B" --answer "ANSWER" --type path_query --nodes NODE_A NODE_B ``` --- @@ -1100,18 +1078,7 @@ Replace `NODE_NAME` with the concept the user asked about. Then write a 3-5 sent After writing the explanation, save it back: ```powershell -python -c " -from graphify.ingest import save_query_result -from pathlib import Path -save_query_result( - question='Explain NODE_NAME', - answer='ANSWER', - memory_dir=Path('graphify-out/memory'), - query_type='explain', - source_nodes=['NODE_NAME'], -) -print('Explanation saved to graphify-out/memory/') -" +python -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME ``` --- diff --git a/graphify/skill.md b/graphify/skill.md index bd744e78..ec09523d 100644 --- a/graphify/skill.md +++ b/graphify/skill.md @@ -958,18 +958,7 @@ Replace `QUESTION` with the user's actual question, `MODE` with `bfs` or `dfs`, After writing the answer, save it back into the graph so it improves future queries: ```bash -$(cat graphify-out/.graphify_python) -c " -from graphify.ingest import save_query_result -from pathlib import Path -save_query_result( - question='QUESTION', - answer='ANSWER', - memory_dir=Path('graphify-out/memory'), - query_type='query', - source_nodes=SOURCE_NODES, # list of node labels cited, or [] -) -print('Query result saved to graphify-out/memory/') -" +$(cat graphify-out/.graphify_python) -m graphify save-result --question "QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 ``` Replace `QUESTION` with the question, `ANSWER` with your full answer text, `SOURCE_NODES` with the list of node labels you cited. This closes the feedback loop: the next `--update` will extract this Q&A as a node in the graph. @@ -1044,18 +1033,7 @@ Replace `NODE_A` and `NODE_B` with the actual concept names from the user. Then After writing the explanation, save it back: ```bash -$(cat graphify-out/.graphify_python) -c " -from graphify.ingest import save_query_result -from pathlib import Path -save_query_result( - question='Path from NODE_A to NODE_B', - answer='ANSWER', - memory_dir=Path('graphify-out/memory'), - query_type='path_query', - source_nodes=PATH_NODES, # list of node labels on the path -) -print('Path result saved to graphify-out/memory/') -" +$(cat graphify-out/.graphify_python) -m graphify save-result --question "Path from NODE_A to NODE_B" --answer "ANSWER" --type path_query --nodes NODE_A NODE_B ``` --- @@ -1121,18 +1099,7 @@ Replace `NODE_NAME` with the concept the user asked about. Then write a 3-5 sent After writing the explanation, save it back: ```bash -$(cat graphify-out/.graphify_python) -c " -from graphify.ingest import save_query_result -from pathlib import Path -save_query_result( - question='Explain NODE_NAME', - answer='ANSWER', - memory_dir=Path('graphify-out/memory'), - query_type='explain', - source_nodes=['NODE_NAME'], -) -print('Explanation saved to graphify-out/memory/') -" +$(cat graphify-out/.graphify_python) -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME ``` --- diff --git a/graphify/watch.py b/graphify/watch.py index c0cabdde..0d92bd6c 100644 --- a/graphify/watch.py +++ b/graphify/watch.py @@ -24,19 +24,16 @@ def _rebuild_code(watch_path: Path, *, follow_symlinks: bool = False) -> bool: Returns True on success, False on error. """ try: - from graphify.extract import collect_files, extract + from graphify.extract import extract + from graphify.detect import detect, FileType from graphify.build import build_from_json from graphify.cluster import cluster, score_all from graphify.analyze import god_nodes, surprising_connections, suggest_questions from graphify.report import generate from graphify.export import to_json - code_files = collect_files(watch_path, follow_symlinks=follow_symlinks) - code_files = [ - f for f in code_files - if "graphify-out" not in f.parts - and "__pycache__" not in f.parts - ] + detected = detect(watch_path, follow_symlinks=follow_symlinks) + code_files = [Path(f) for f in detected[FileType.CODE]] if not code_files: print("[graphify watch] No code files found - nothing to rebuild.") @@ -47,7 +44,7 @@ def _rebuild_code(watch_path: Path, *, follow_symlinks: bool = False) -> bool: detection = { "files": {"code": [str(f) for f in code_files], "document": [], "paper": [], "image": []}, "total_files": len(code_files), - "total_words": 0, # not needed during watch rebuild + "total_words": detected.get("total_words", 0), } G = build_from_json(result)