From c3a771d49cffe5c771334dd3ee10b772eeec72e5 Mon Sep 17 00:00:00 2001 From: Logan Date: Wed, 2 Sep 2026 03:23:15 -0500 Subject: [PATCH 01/14] fix(install): pin the interpreter and fail open in generated PreToolUse hooks The generated hook resolved its command via shutil.which("graphify"), which returns the console-script shim. For uv-tool and pipx installs that shim is unsigned; on Windows with Smart App Control enabled, code-integrity policy refuses to load it, so the hook can never run. Both ~/.local/bin/graphify.exe and the venv Scripts/graphify.exe are NotSigned, so resolving to a different shim directory does not help. The hook also had no failure guard. A PreToolUse hook that exits non-zero blocks the tool call it was meant to advise, so an advisory graph check could break the host on any launcher failure. Resolve to ' -m graphify' via the existing hooks._pinned_python(), the same reasoning already applied to git hooks, and make both the POSIX and Windows commands fail open with a timeout. The #2165 regression parses the subcommand out of the hook command; it now handles both supported grammars rather than assuming the subcommand is the second token, so it keeps proving the same contract. Refs #3280 --- graphify/install.py | 25 ++++++++++++++++- tests/test_install.py | 64 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 85 insertions(+), 4 deletions(-) diff --git a/graphify/install.py b/graphify/install.py index 048589476..c0bd062d4 100644 --- a/graphify/install.py +++ b/graphify/install.py @@ -1442,6 +1442,17 @@ def _resolve_graphify_exe(project: bool = False) -> str: import shutil if project: return "graphify" + # Prefer the interpreter running this install over any console-script shim. + # For uv-tool and pipx installs the shim is an unsigned executable; on Windows + # with Smart App Control enabled, code-integrity policy refuses to load it, so + # the hook can never run -- while `/Scripts/python.exe -m graphify` does. + # Both ~/.local/bin/graphify.exe and the venv Scripts/graphify.exe are + # NotSigned, so resolving to a different shim directory does not help (#3280). + # This mirrors hooks._pinned_python(), already used for git hooks. + from .hooks import _pinned_python + pinned = _pinned_python() + if pinned: + return f"{pinned.replace(chr(92), '/')} -m graphify" found = shutil.which("graphify") if not found: # Derive from sys.executable: same Scripts/ (Windows) or bin/ (Unix) dir @@ -1469,7 +1480,19 @@ def _install_codex_hook(project_dir: Path, project: bool = False) -> None: "PreToolUse": [ { "matcher": "Bash", - "hooks": [{"type": "command", "command": f"{graphify_exe} hook-check"}], + "hooks": [ + { + "type": "command", + # Fail open: a PreToolUse hook that exits non-zero blocks + # the tool call it was meant to advise. An advisory graph + # check must never be able to break the host (#3280). + "command": f"{graphify_exe} hook-check || true", + "commandWindows": ( + f'cmd /c "{graphify_exe} hook-check" & exit /b 0' + ), + "timeout": 10, + } + ], } ] } diff --git a/tests/test_install.py b/tests/test_install.py index 7637714e5..68c2c06ac 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -1211,9 +1211,17 @@ def test_codex_hook_command_is_a_real_cli_subcommand(tmp_path): assert "hook-check" in dispatched, "sanity: parser must find known commands" for entry in entries: - # command is " [args...]" - parts = entry["command"].split() - subcommand = parts[1] if len(parts) > 1 else "" + # Two supported shapes (#3280): + # direct launcher: " [args...]" + # module launcher: " -m graphify [args...]" + # Trailing "|| true" is a fail-open guard, not an argument. + parts = [p for p in entry["command"].split() if p not in ("||", "true")] + if "-m" in parts: + idx = parts.index("-m") + # skip "-m" and the module name that follows it + subcommand = parts[idx + 2] if len(parts) > idx + 2 else "" + else: + subcommand = parts[1] if len(parts) > 1 else "" assert subcommand in dispatched, ( f"codex hook registers {subcommand!r}, which the CLI does not dispatch " f"(#2165). Known commands: {sorted(dispatched)}" @@ -1341,3 +1349,53 @@ def test_project_uninstall_removes_the_bare_hook_command(tmp_path, monkeypatch): main() assert not [c for c in _hook_commands(settings.read_text(encoding="utf-8")) if "graphify" in c] + + +# --- #3280: generated PreToolUse hook must be runnable and must fail open -------- +# +# Two defects: the command resolved via shutil.which() can be an unsigned console +# shim that hardened Windows refuses to load, and the entry has no guard, so a +# launcher failure exits non-zero -- which blocks the tool call the hook was only +# meant to advise. + + +def test_codex_hook_is_invoked_through_the_running_interpreter(tmp_path): + """#3280: prefer sys.executable -m graphify over the PATH console shim.""" + import json + import sys + from graphify.install import _install_codex_hook + + _install_codex_hook(tmp_path) + hooks = json.loads((tmp_path / ".codex" / "hooks.json").read_text(encoding="utf-8")) + entries = [ + h + for group in hooks["hooks"]["PreToolUse"] + for h in group["hooks"] + if "graphify" in h.get("command", "") + ] + assert entries, "codex install must register a graphify PreToolUse hook" + for entry in entries: + assert "-m graphify" in entry["command"], ( + "hook must invoke the interpreter that installed it, not the PATH shim " + f"(#3280); got {entry['command']!r}" + ) + + +def test_codex_hook_fails_open(tmp_path): + """#3280: a launcher failure must not fail the host's tool call.""" + import json + from graphify.install import _install_codex_hook + + _install_codex_hook(tmp_path) + hooks = json.loads((tmp_path / ".codex" / "hooks.json").read_text(encoding="utf-8")) + for group in hooks["hooks"]["PreToolUse"]: + for entry in group["hooks"]: + if "graphify" not in entry.get("command", ""): + continue + assert entry["command"].rstrip().endswith("|| true"), ( + f"POSIX command must fail open (#3280); got {entry['command']!r}" + ) + win = entry.get("commandWindows", "") + assert "exit /b 0" in win, ( + f"Windows command must fail open (#3280); got {win!r}" + ) From 912f9a4ee38cf3665f9e89a924edf2cd686beb60 Mon Sep 17 00:00:00 2001 From: Logan Date: Wed, 2 Sep 2026 05:10:15 -0500 Subject: [PATCH 02/14] fix(hooks): make graph-first guidance executable --- CHANGELOG.md | 3 + README.md | 2 +- graphify/always_on/agents-md.md | 2 +- graphify/always_on/antigravity-rules.md | 2 +- graphify/always_on/claude-md.md | 2 +- graphify/cli.py | 70 ++++++++----------- graphify/install.py | 58 ++++++++------- graphify/skill-agents.md | 6 +- graphify/skill-amp.md | 6 +- graphify/skill-claw.md | 6 +- graphify/skill-codex.md | 6 +- graphify/skill-copilot.md | 6 +- graphify/skill-droid.md | 6 +- graphify/skill-kilo.md | 6 +- graphify/skill-kiro.md | 6 +- graphify/skill-opencode.md | 6 +- graphify/skill-pi.md | 6 +- graphify/skill-trae.md | 6 +- graphify/skill-vscode.md | 6 +- graphify/skill-windows.md | 6 +- graphify/skill.md | 6 +- graphify/skills/agents/references/query.md | 8 +-- graphify/skills/amp/references/query.md | 8 +-- graphify/skills/claude/references/query.md | 8 +-- graphify/skills/claw/references/query.md | 8 +-- graphify/skills/codex/references/query.md | 8 +-- graphify/skills/copilot/references/query.md | 8 +-- graphify/skills/droid/references/query.md | 8 +-- graphify/skills/kilo/references/query.md | 8 +-- graphify/skills/kiro/references/query.md | 8 +-- graphify/skills/opencode/references/query.md | 8 +-- graphify/skills/pi/references/query.md | 8 +-- graphify/skills/trae/references/query.md | 8 +-- graphify/skills/vscode/references/query.md | 8 +-- graphify/skills/windows/references/query.md | 8 +-- tests/test_hook_guard.py | 1 + tests/test_hook_strict.py | 66 +++++++++++------ tests/test_read_hook.py | 1 + tests/test_search_hook.py | 1 + tests/test_skillgen.py | 18 +++++ .../graphify__always_on__agents-md.md | 2 +- .../graphify__always_on__antigravity-rules.md | 2 +- .../graphify__always_on__claude-md.md | 2 +- .../expected/graphify__skill-agents.md | 6 +- .../skillgen/expected/graphify__skill-amp.md | 6 +- .../skillgen/expected/graphify__skill-claw.md | 6 +- .../expected/graphify__skill-codex.md | 6 +- .../expected/graphify__skill-copilot.md | 6 +- .../expected/graphify__skill-droid.md | 6 +- .../skillgen/expected/graphify__skill-kilo.md | 6 +- .../skillgen/expected/graphify__skill-kiro.md | 6 +- .../expected/graphify__skill-opencode.md | 6 +- tools/skillgen/expected/graphify__skill-pi.md | 6 +- .../skillgen/expected/graphify__skill-trae.md | 6 +- .../expected/graphify__skill-vscode.md | 6 +- .../expected/graphify__skill-windows.md | 6 +- tools/skillgen/expected/graphify__skill.md | 6 +- ...hify__skills__agents__references__query.md | 8 +-- ...raphify__skills__amp__references__query.md | 8 +-- ...hify__skills__claude__references__query.md | 8 +-- ...aphify__skills__claw__references__query.md | 8 +-- ...phify__skills__codex__references__query.md | 8 +-- ...ify__skills__copilot__references__query.md | 8 +-- ...phify__skills__droid__references__query.md | 8 +-- ...aphify__skills__kilo__references__query.md | 8 +-- ...aphify__skills__kiro__references__query.md | 8 +-- ...fy__skills__opencode__references__query.md | 8 +-- ...graphify__skills__pi__references__query.md | 8 +-- ...aphify__skills__trae__references__query.md | 8 +-- ...hify__skills__vscode__references__query.md | 8 +-- ...ify__skills__windows__references__query.md | 8 +-- .../skillgen/fragments/always-on/agents-md.md | 2 +- .../fragments/always-on/antigravity-rules.md | 2 +- .../skillgen/fragments/always-on/claude-md.md | 2 +- .../skillgen/fragments/query-stub/default.md | 6 +- .../fragments/references/query/default.md | 8 +-- 76 files changed, 315 insertions(+), 329 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d818561b..368ca5f5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ Full release notes with details on each version: [GitHub Releases](https://githu ## 0.9.53 (2026-08-30) +- Fix: generated agent guidance now prefers the MCP `query_graph` tool and uses the graph's recorded Python interpreter as the CLI fallback, so hardened Windows hosts do not route agents through an unsigned `graphify.exe` shim. +- Fix: strict Claude hooks no longer use one graph-wide 30-minute query stamp that let any agent disable the first-read block for every other session; deny markers now key subagents by `session_id` plus `agent_id`, so a parent's first read cannot silently consume every subagent's guard. +- Fix: project-scoped hooks now resolve Graphify through each clone's `graphify-out/.graphify_python` sidecar instead of a bare PATH launcher, preserving committed-config portability without producing an unrunnable hook on Application Control hosts (#3280, follow-up to #3129). - Fix: a batch of cross-language inheritance-edge corrections (thanks @Synvoya): JavaScript `class X extends Y` now emits an `inherits` edge (#1790); PHP interfaces, enums, and traits are captured as class-like nodes with their heritage (#1791); Scala `trait` declarations become class-like nodes (#1792) and qualified `extends`/`with` bases resolve to the tail type (#1794); a qualified Kotlin supertype resolves to its tail type instead of the package head (#1793); a C# interface extending an interface is classified as `inherits`, not `implements` (#1817); and a Go interface type-set constraint no longer emits a spurious `embeds` edge (#1818). - Feature: Robot Framework `.robot`/`.resource` files are now extracted (optional `[robot]` extra) — suites, test cases, user keywords, keyword-call edges, and resource/library imports, with case/space/underscore-insensitive keyword resolution (#3192, thanks @nshiveg). - Fix: chat-template control tokens are now defanged by form (`<|…|>`, `[INST]`/`[SYSTEM]`) rather than an enumerated few, closing a prompt-injection gap for attacker-chosen tokens (e.g. `<|eot_id|>`); legitimate content is untouched (#3183, thanks @abhay-codes07). diff --git a/README.md b/README.md index d83ea76c0..e8a3fafc8 100644 --- a/README.md +++ b/README.md @@ -203,7 +203,7 @@ for example `graphify claude install --project` or `graphify codex install --pro > **Git hooks and uv tool / pipx:** `graphify hook install` embeds the current interpreter path directly into the hook scripts at install time, so the post-commit hook fires correctly even in GUI git clients and CI runners where `~/.local/bin` is not on PATH. If you reinstall or upgrade graphify, re-run `graphify hook install` to refresh the embedded path. -> **Strict mode (Claude Code):** `graphify install --project --strict` makes the assistant actually use the graph. The default install *nudges* it to run `graphify query` before reading files; strict mode *blocks* the first raw source read of a session and redirects it to the graph, then reverts to the nudge (so it fires at most once per session and never gets stuck). Toggle at runtime with `GRAPHIFY_HOOK_STRICT=1`/`0`; the default install is unchanged (soft nudge). +> **Strict mode (Claude Code):** `graphify install --project --strict` makes the assistant actually use the graph. The default install *nudges* it to use the MCP `query_graph` tool (or the recorded Python-interpreter fallback) before reading files; strict mode *blocks* the first raw source read of a session and redirects it to the graph, then reverts to the nudge (so it fires at most once per session and never gets stuck). Set `GRAPHIFY_HOOK_STRICT=1`/`0` in the Claude process environment to override the installed mode; the default install is unchanged (soft nudge).
Pick your platform (20+ assistants, click to expand) diff --git a/graphify/always_on/agents-md.md b/graphify/always_on/agents-md.md index 6511cd1dd..6da0539c1 100644 --- a/graphify/always_on/agents-md.md +++ b/graphify/always_on/agents-md.md @@ -5,7 +5,7 @@ This project has a knowledge graph at graphify-out/ with god nodes, community st When the user types `/graphify`, use the installed graphify skill or instructions before doing anything else. Rules: -- For codebase questions, first run `graphify query ""` when graphify-out/graph.json exists. Use `graphify path "" ""` for relationships and `graphify explain ""` for focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. +- For codebase questions, first use the graph when graphify-out/graph.json exists. Prefer the MCP `query_graph` tool when it is available; otherwise follow the installed Graphify skill's interpreter-based CLI fallback. Use `shortest_path` / `get_node` (or their module-invocation CLI equivalents) for relationships and focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. - Dirty graphify-out/ files are expected after hooks or incremental updates; dirty graph files are not a reason to skip graphify. Only skip graphify if the task is about stale or incorrect graph output, or the user explicitly says not to use it. - If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing. - Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context. diff --git a/graphify/always_on/antigravity-rules.md b/graphify/always_on/antigravity-rules.md index 0fc786414..e6b109acc 100644 --- a/graphify/always_on/antigravity-rules.md +++ b/graphify/always_on/antigravity-rules.md @@ -8,7 +8,7 @@ description: Consult the graphify knowledge graph at graphify-out/ for codebase This project has a graphify knowledge graph at graphify-out/. Rules: -- For codebase or architecture questions, when `graphify-out/graph.json` exists, first run `graphify query ""` (CLI) or `query_graph` (MCP). Use `graphify path "" ""` / `shortest_path` for relationships and `graphify explain ""` / `get_node` for focused concepts. These return a scoped subgraph, usually much smaller than `GRAPH_REPORT.md` or raw grep output. +- For codebase or architecture questions, when `graphify-out/graph.json` exists, first use the graph. Prefer the MCP `query_graph` tool when it is available; otherwise follow the installed Graphify skill's interpreter-based CLI fallback. Use `shortest_path` / `get_node` (or their module-invocation CLI equivalents) for relationships and focused concepts. These return a scoped subgraph, usually much smaller than `GRAPH_REPORT.md` or raw grep output. - If graphify-out/wiki/index.md exists, navigate it instead of reading raw files - Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context - After modifying code files in this session, run `graphify update .` to keep the graph current (AST-only, no API cost) diff --git a/graphify/always_on/claude-md.md b/graphify/always_on/claude-md.md index 417efeb27..b62763ded 100644 --- a/graphify/always_on/claude-md.md +++ b/graphify/always_on/claude-md.md @@ -3,7 +3,7 @@ This project has a knowledge graph at graphify-out/ with god nodes, community structure, and cross-file relationships. Rules: -- For codebase questions, first run `graphify query ""` when graphify-out/graph.json exists. Use `graphify path "" ""` for relationships and `graphify explain ""` for focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. +- For codebase questions, first use the graph when graphify-out/graph.json exists. Prefer the MCP `query_graph` tool when it is available; otherwise follow the installed Graphify skill's interpreter-based CLI fallback. Use `shortest_path` / `get_node` (or their module-invocation CLI equivalents) for relationships and focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. - If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing. - Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context. - After modifying code, run `graphify update .` to keep the graph current (AST-only, no API cost). diff --git a/graphify/cli.py b/graphify/cli.py index 6642f57d5..b1395a1b9 100644 --- a/graphify/cli.py +++ b/graphify/cli.py @@ -19,8 +19,9 @@ "hookSpecificOutput": { "hookEventName": "PreToolUse", "additionalContext": ( - 'MANDATORY: graphify-out/graph.json exists. You MUST run ' - '`graphify query ""` before grepping raw files. Only grep ' + 'MANDATORY: graphify-out/graph.json exists. Use the MCP `query_graph` ' + 'tool when available; otherwise run `graphify query ""` ' + 'through the interpreter named by the installed Graphify skill. Only grep ' 'after graphify has oriented you, or to modify/debug specific lines.' ), } @@ -30,7 +31,8 @@ "hookEventName": "PreToolUse", "additionalContext": ( 'MANDATORY: graphify-out/graph.json exists. You MUST run graphify ' - 'before reading source files. Use: `graphify query ""` ' + 'before reading source files. Prefer the MCP `query_graph` tool; ' + 'otherwise use: `graphify query ""` ' '(scoped subgraph), `graphify explain ""`, or ' '`graphify path "" ""`. Only read raw files after graphify has ' 'oriented you, or to modify/debug specific lines. This rule applies to ' @@ -60,7 +62,8 @@ "permissionDecision": "deny", "permissionDecisionReason": ( 'graphify strict mode: this project has a fresh knowledge graph that covers ' - 'this file. Run `graphify query ""` (or `graphify explain` / ' + 'this file. Use the MCP `query_graph` tool when available; otherwise run ' + '`graphify query ""` (or `graphify explain` / ' '`graphify path`) FIRST to orient yourself, then re-issue this Read — it ' 'will be allowed. This block fires at most once per session; reading raw ' 'files to modify or debug specific lines is fine after one query. Apply the ' @@ -74,8 +77,9 @@ '.swift', '.php', '.scala', '.lua', '.sh', '.md', '.rst', '.txt', '.mdx', ) _GEMINI_NUDGE_TEXT = ( - 'graphify: knowledge graph at graphify-out/. For focused questions, run ' - '`graphify query ""` (scoped subgraph, usually much smaller than ' + 'graphify: knowledge graph at graphify-out/. For focused questions, use the ' + 'MCP `query_graph` tool when available; otherwise run `graphify query ' + '""` through the installed Graphify skill (scoped subgraph, usually much smaller than ' 'GRAPH_REPORT.md) instead of grepping raw files. Read GRAPH_REPORT.md only ' 'for broad architecture context.' ) @@ -684,36 +688,19 @@ def _hook_strict_enabled(flag: bool) -> bool: return flag -def _touch_query_stamp(graph_path: "Path") -> None: - """Record that graphify oriented the agent recently, next to the queried graph. - The strict guard suppresses its block while this stamp is fresh. Fail-silent.""" - try: - from graphify.paths import write_text_atomic - stamp = Path(graph_path).parent / "cache" / "last_query_stamp" - stamp.parent.mkdir(parents=True, exist_ok=True) - write_text_atomic(stamp, str(time.time())) - except Exception: - pass +def _mark_session_denied(identity: str) -> bool: + """Atomically claim one strict block per session/agent identity. - -def _query_stamp_fresh() -> bool: - """True if a query/explain/path ran within GRAPHIFY_HOOK_STRICT_TTL (default - 1800s) — recent orientation, so strict mode does not block this read.""" - from graphify.paths import out_path - try: - ttl = float(os.environ.get("GRAPHIFY_HOOK_STRICT_TTL", "1800")) - return (time.time() - out_path("cache", "last_query_stamp").stat().st_mtime) < ttl - except Exception: - return False - - -def _mark_session_denied(session_id: str) -> bool: - """Atomically claim a one-time strict block for this session. Returns True only - on the FIRST call for a given session id (O_EXCL create wins once); every later - call — or any error — returns False, so a session is blocked at most once and an - agent can never be stranded. Best-effort GC of markers older than 24h.""" + Returns True only on the first call for the identity (O_EXCL wins once); every + later call or error returns False, so neither a parent nor subagent can be + stranded. Best-effort GC removes markers older than 24 hours. + """ from graphify.paths import out_path - sid = re.sub(r"[^A-Za-z0-9_-]", "_", str(session_id))[:64] + raw = str(identity) + sid = re.sub(r"[^A-Za-z0-9_-]", "_", raw) + if len(sid) > 64: + import hashlib + sid = hashlib.sha256(raw.encode("utf-8")).hexdigest() if not sid: return False try: @@ -928,13 +915,17 @@ def _run_hook_guard(kind: str, strict: bool = False) -> None: if stale: sys.stdout.write(_READ_NUDGE_STALE) return - # Strict block: Read tool only, first time per session, not recently - # oriented, and the file is demonstrably indexed. + # Strict block: Read tool only, first time per session, and the file + # is demonstrably indexed. Do not suppress this from a graph-wide + # query timestamp: one agent's query must not disable every session + # sharing the same graph (#3280 follow-up). tool_name = d.get("tool_name") + session_key = str(d.get("session_id") or "") + if d.get("agent_id"): + session_key += f"--agent-{d['agent_id']}" if _hook_strict_enabled(strict) and tool_name in (None, "Read") \ - and not _query_stamp_fresh() \ and _target_is_indexed(fp, root) \ - and _mark_session_denied(str(d.get("session_id") or "")): + and _mark_session_denied(session_key): sys.stdout.write(_READ_DENY) return sys.stdout.write(_READ_NUDGE) @@ -1318,7 +1309,6 @@ def dispatch_command(cmd: str) -> None: token_budget=budget, duration_ms=(_time.perf_counter() - _t0) * 1000, ) - _touch_query_stamp(gp) print(_result) elif cmd == "affected": if len(sys.argv) < 3: @@ -1696,7 +1686,6 @@ def dispatch_command(cmd: str) -> None: corpus=str(gp), nodes_returned=hops, ) - _touch_query_stamp(gp) elif cmd == "explain": if len(sys.argv) < 3: @@ -1830,7 +1819,6 @@ def dispatch_command(cmd: str) -> None: corpus=str(gp), nodes_returned=len(connections), ) - _touch_query_stamp(gp) elif cmd == "diagnose": subcmd = sys.argv[2] if len(sys.argv) > 2 else "" diff --git a/graphify/install.py b/graphify/install.py index c0bd062d4..a2bd0aa8f 100644 --- a/graphify/install.py +++ b/graphify/install.py @@ -301,11 +301,10 @@ def _print_project_git_add_hint(paths: list[Path]) -> None: def _claude_pretooluse_hooks(strict: bool = False, project: bool = False) -> "list[dict]": """graphify's Claude/Codebuddy PreToolUse hooks, resolved at install time. - The command invokes `graphify hook-guard ` via the absolute exe - path (`_resolve_graphify_exe`) — or, for a project-scoped install, via the - bare `graphify` command, since that config gets committed (#3129). Either - form parses under sh, cmd.exe and PowerShell alike — this is the #522 fix, - and mirrors the codex hook. Matchers are + User-scoped hooks pin the running interpreter. Project-scoped hooks read + ``graphify-out/.graphify_python`` at runtime, so committed config stays + portable without trusting an unsigned console shim on PATH (#3129, #3280). + Matchers are "Bash|Grep" and "Read|Glob" and the command always contains "graphify", so the existing install/uninstall filters find and replace both old bash hooks and these. "Grep" is in the search matcher because current Claude Code routes @@ -317,14 +316,19 @@ def _claude_pretooluse_hooks(strict: bool = False, project: bool = False) -> "li var can force it on or off at runtime without a reinstall. """ exe = _resolve_graphify_exe(project=project) - if " " in exe and not exe.startswith('"'): - exe = f'"{exe}"' read_cmd = f"{exe} hook-guard read" + (" --strict" if strict else "") + read_args = "hook-guard read" + (" --strict" if strict else "") return [ {"matcher": "Bash|Grep", - "hooks": [{"type": "command", "command": f"{exe} hook-guard search"}]}, + "hooks": [{"type": "command", + "command": f"{exe} hook-guard search || true", + "commandWindows": _graphify_command_windows("hook-guard search", project), + "timeout": 10}]}, {"matcher": "Read|Glob", - "hooks": [{"type": "command", "command": read_cmd}]}, + "hooks": [{"type": "command", + "command": f"{read_cmd} || true", + "commandWindows": _graphify_command_windows(read_args, project), + "timeout": 10}]}, ] def _skill_registration(skill_path: str = "~/.claude/skills/graphify/SKILL.md") -> str: return ( @@ -1419,13 +1423,10 @@ def _uninstall_opencode_plugin(project_dir: Path) -> None: def _resolve_graphify_exe(project: bool = False) -> str: """Return the absolute path to the graphify executable, with forward slashes. - With *project* set, return the bare ``graphify`` command instead. A - project-scoped install writes hook config the installer then tells the user - to commit, so an absolute path resolved from the installing machine is wrong - for every other clone: it names a directory that does not exist there, and - the drive letter and ``.EXE`` casing do not even survive between two Windows - checkouts. A committed hook refers to ``graphify`` the way it would refer to - ``git`` or ``node``, and PATH resolves it per machine (#3129). + With *project* set, read the interpreter sidecar written in each clone's + ``graphify-out`` directory. This keeps committed hook config portable + without routing hardened Windows through an unsigned console shim + (#3129, #3280). Falls back to bare 'graphify' if resolution fails. Using an absolute path ensures the hook works in environments where the venv Scripts/ directory is @@ -1441,7 +1442,7 @@ def _resolve_graphify_exe(project: bool = False) -> str: """ import shutil if project: - return "graphify" + return '"$(cat graphify-out/.graphify_python)" -m graphify' # Prefer the interpreter running this install over any console-script shim. # For uv-tool and pipx installs the shim is an unsigned executable; on Windows # with Smart App Control enabled, code-integrity policy refuses to load it, so @@ -1452,7 +1453,7 @@ def _resolve_graphify_exe(project: bool = False) -> str: from .hooks import _pinned_python pinned = _pinned_python() if pinned: - return f"{pinned.replace(chr(92), '/')} -m graphify" + return f'"{pinned.replace(chr(92), "/")}" -m graphify' found = shutil.which("graphify") if not found: # Derive from sys.executable: same Scripts/ (Windows) or bin/ (Unix) dir @@ -1462,12 +1463,23 @@ def _resolve_graphify_exe(project: bool = False) -> str: if candidate.exists(): found = str(candidate) break - return (found or "graphify").replace("\\", "/") + return f'"{(found or "graphify").replace(chr(92), "/")}"' + + +def _graphify_command_windows(args: str, project: bool = False) -> str: + """Build a fail-open cmd.exe command for a generated Windows hook.""" + if project: + return ( + 'powershell.exe -NoProfile -NonInteractive -Command ' + '"$p=(Get-Content -Raw \'graphify-out\\.graphify_python\').Trim(); ' + f'& $p -m graphify {args}; exit 0"' + ) + return f'cmd /c "{_resolve_graphify_exe()} {args}" & exit /b 0' def _install_codex_hook(project_dir: Path, project: bool = False) -> None: """Add graphify PreToolUse hook to .codex/hooks.json. - A project-scoped install emits the bare command, since .codex/hooks.json is - then committed and an installing machine's path is wrong there (#3129). + A project-scoped install reads ``graphify-out/.graphify_python`` so the + committed config is portable without trusting PATH (#3129, #3280). """ hooks_path = project_dir / ".codex" / "hooks.json" hooks_path.parent.mkdir(parents=True, exist_ok=True) @@ -1487,9 +1499,7 @@ def _install_codex_hook(project_dir: Path, project: bool = False) -> None: # the tool call it was meant to advise. An advisory graph # check must never be able to break the host (#3280). "command": f"{graphify_exe} hook-check || true", - "commandWindows": ( - f'cmd /c "{graphify_exe} hook-check" & exit /b 0' - ), + "commandWindows": _graphify_command_windows("hook-check", project), "timeout": 10, } ], diff --git a/graphify/skill-agents.md b/graphify/skill-agents.md index 190827d9a..a79fb23fc 100644 --- a/graphify/skill-agents.md +++ b/graphify/skill-agents.md @@ -681,11 +681,9 @@ Both are non-default subcommands. `--update` re-extracts only new or changed fil When `graphify-out/graph.json` already exists and the user asks a question about the corpus, answer from the graph rather than rebuilding it: -```bash -graphify query "" -``` +Prefer the MCP `query_graph` tool when it is available; pass the project root as `project_path` when the server supports multiple graphs. Otherwise follow `references/query.md`, which invokes the module through the graph's recorded Python interpreter instead of trusting a console shim on `PATH`. -Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If the `graphify query` CLI is unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. +Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If MCP and the module invocation are unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. --- diff --git a/graphify/skill-amp.md b/graphify/skill-amp.md index 190827d9a..a79fb23fc 100644 --- a/graphify/skill-amp.md +++ b/graphify/skill-amp.md @@ -681,11 +681,9 @@ Both are non-default subcommands. `--update` re-extracts only new or changed fil When `graphify-out/graph.json` already exists and the user asks a question about the corpus, answer from the graph rather than rebuilding it: -```bash -graphify query "" -``` +Prefer the MCP `query_graph` tool when it is available; pass the project root as `project_path` when the server supports multiple graphs. Otherwise follow `references/query.md`, which invokes the module through the graph's recorded Python interpreter instead of trusting a console shim on `PATH`. -Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If the `graphify query` CLI is unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. +Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If MCP and the module invocation are unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. --- diff --git a/graphify/skill-claw.md b/graphify/skill-claw.md index abd2811d2..9d174f1bb 100644 --- a/graphify/skill-claw.md +++ b/graphify/skill-claw.md @@ -684,11 +684,9 @@ Both are non-default subcommands. `--update` re-extracts only new or changed fil When `graphify-out/graph.json` already exists and the user asks a question about the corpus, answer from the graph rather than rebuilding it: -```bash -graphify query "" -``` +Prefer the MCP `query_graph` tool when it is available; pass the project root as `project_path` when the server supports multiple graphs. Otherwise follow `references/query.md`, which invokes the module through the graph's recorded Python interpreter instead of trusting a console shim on `PATH`. -Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If the `graphify query` CLI is unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. +Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If MCP and the module invocation are unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. --- diff --git a/graphify/skill-codex.md b/graphify/skill-codex.md index af3f723c7..de86747b8 100644 --- a/graphify/skill-codex.md +++ b/graphify/skill-codex.md @@ -681,11 +681,9 @@ Both are non-default subcommands. `--update` re-extracts only new or changed fil When `graphify-out/graph.json` already exists and the user asks a question about the corpus, answer from the graph rather than rebuilding it: -```bash -graphify query "" -``` +Prefer the MCP `query_graph` tool when it is available; pass the project root as `project_path` when the server supports multiple graphs. Otherwise follow `references/query.md`, which invokes the module through the graph's recorded Python interpreter instead of trusting a console shim on `PATH`. -Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If the `graphify query` CLI is unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. +Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If MCP and the module invocation are unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. --- diff --git a/graphify/skill-copilot.md b/graphify/skill-copilot.md index abd2811d2..9d174f1bb 100644 --- a/graphify/skill-copilot.md +++ b/graphify/skill-copilot.md @@ -684,11 +684,9 @@ Both are non-default subcommands. `--update` re-extracts only new or changed fil When `graphify-out/graph.json` already exists and the user asks a question about the corpus, answer from the graph rather than rebuilding it: -```bash -graphify query "" -``` +Prefer the MCP `query_graph` tool when it is available; pass the project root as `project_path` when the server supports multiple graphs. Otherwise follow `references/query.md`, which invokes the module through the graph's recorded Python interpreter instead of trusting a console shim on `PATH`. -Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If the `graphify query` CLI is unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. +Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If MCP and the module invocation are unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. --- diff --git a/graphify/skill-droid.md b/graphify/skill-droid.md index fd148d485..821ef56a7 100644 --- a/graphify/skill-droid.md +++ b/graphify/skill-droid.md @@ -681,11 +681,9 @@ Both are non-default subcommands. `--update` re-extracts only new or changed fil When `graphify-out/graph.json` already exists and the user asks a question about the corpus, answer from the graph rather than rebuilding it: -```bash -graphify query "" -``` +Prefer the MCP `query_graph` tool when it is available; pass the project root as `project_path` when the server supports multiple graphs. Otherwise follow `references/query.md`, which invokes the module through the graph's recorded Python interpreter instead of trusting a console shim on `PATH`. -Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If the `graphify query` CLI is unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. +Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If MCP and the module invocation are unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. --- diff --git a/graphify/skill-kilo.md b/graphify/skill-kilo.md index 3e70b050a..e1f1496e9 100644 --- a/graphify/skill-kilo.md +++ b/graphify/skill-kilo.md @@ -684,11 +684,9 @@ Both are non-default subcommands. `--update` re-extracts only new or changed fil When `graphify-out/graph.json` already exists and the user asks a question about the corpus, answer from the graph rather than rebuilding it: -```bash -graphify query "" -``` +Prefer the MCP `query_graph` tool when it is available; pass the project root as `project_path` when the server supports multiple graphs. Otherwise follow `references/query.md`, which invokes the module through the graph's recorded Python interpreter instead of trusting a console shim on `PATH`. -Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If the `graphify query` CLI is unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. +Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If MCP and the module invocation are unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. --- diff --git a/graphify/skill-kiro.md b/graphify/skill-kiro.md index abd2811d2..9d174f1bb 100644 --- a/graphify/skill-kiro.md +++ b/graphify/skill-kiro.md @@ -684,11 +684,9 @@ Both are non-default subcommands. `--update` re-extracts only new or changed fil When `graphify-out/graph.json` already exists and the user asks a question about the corpus, answer from the graph rather than rebuilding it: -```bash -graphify query "" -``` +Prefer the MCP `query_graph` tool when it is available; pass the project root as `project_path` when the server supports multiple graphs. Otherwise follow `references/query.md`, which invokes the module through the graph's recorded Python interpreter instead of trusting a console shim on `PATH`. -Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If the `graphify query` CLI is unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. +Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If MCP and the module invocation are unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. --- diff --git a/graphify/skill-opencode.md b/graphify/skill-opencode.md index 91ced6067..872ad49f7 100644 --- a/graphify/skill-opencode.md +++ b/graphify/skill-opencode.md @@ -676,11 +676,9 @@ Both are non-default subcommands. `--update` re-extracts only new or changed fil When `graphify-out/graph.json` already exists and the user asks a question about the corpus, answer from the graph rather than rebuilding it: -```bash -graphify query "" -``` +Prefer the MCP `query_graph` tool when it is available; pass the project root as `project_path` when the server supports multiple graphs. Otherwise follow `references/query.md`, which invokes the module through the graph's recorded Python interpreter instead of trusting a console shim on `PATH`. -Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If the `graphify query` CLI is unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. +Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If MCP and the module invocation are unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. --- diff --git a/graphify/skill-pi.md b/graphify/skill-pi.md index abd2811d2..9d174f1bb 100644 --- a/graphify/skill-pi.md +++ b/graphify/skill-pi.md @@ -684,11 +684,9 @@ Both are non-default subcommands. `--update` re-extracts only new or changed fil When `graphify-out/graph.json` already exists and the user asks a question about the corpus, answer from the graph rather than rebuilding it: -```bash -graphify query "" -``` +Prefer the MCP `query_graph` tool when it is available; pass the project root as `project_path` when the server supports multiple graphs. Otherwise follow `references/query.md`, which invokes the module through the graph's recorded Python interpreter instead of trusting a console shim on `PATH`. -Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If the `graphify query` CLI is unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. +Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If MCP and the module invocation are unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. --- diff --git a/graphify/skill-trae.md b/graphify/skill-trae.md index 050667bc2..6a76e4a01 100644 --- a/graphify/skill-trae.md +++ b/graphify/skill-trae.md @@ -682,11 +682,9 @@ Both are non-default subcommands. `--update` re-extracts only new or changed fil When `graphify-out/graph.json` already exists and the user asks a question about the corpus, answer from the graph rather than rebuilding it: -```bash -graphify query "" -``` +Prefer the MCP `query_graph` tool when it is available; pass the project root as `project_path` when the server supports multiple graphs. Otherwise follow `references/query.md`, which invokes the module through the graph's recorded Python interpreter instead of trusting a console shim on `PATH`. -Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If the `graphify query` CLI is unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. +Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If MCP and the module invocation are unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. --- diff --git a/graphify/skill-vscode.md b/graphify/skill-vscode.md index 20c7c0835..4d2bf8c1e 100644 --- a/graphify/skill-vscode.md +++ b/graphify/skill-vscode.md @@ -680,11 +680,9 @@ Both are non-default subcommands. `--update` re-extracts only new or changed fil When `graphify-out/graph.json` already exists and the user asks a question about the corpus, answer from the graph rather than rebuilding it: -```bash -graphify query "" -``` +Prefer the MCP `query_graph` tool when it is available; pass the project root as `project_path` when the server supports multiple graphs. Otherwise follow `references/query.md`, which invokes the module through the graph's recorded Python interpreter instead of trusting a console shim on `PATH`. -Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If the `graphify query` CLI is unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. +Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If MCP and the module invocation are unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. --- diff --git a/graphify/skill-windows.md b/graphify/skill-windows.md index b09ecca3c..6f353df57 100644 --- a/graphify/skill-windows.md +++ b/graphify/skill-windows.md @@ -713,11 +713,9 @@ Both are non-default subcommands. `--update` re-extracts only new or changed fil When `graphify-out/graph.json` already exists and the user asks a question about the corpus, answer from the graph rather than rebuilding it: -```powershell -graphify query "" -``` +Prefer the MCP `query_graph` tool when it is available; pass the project root as `project_path` when the server supports multiple graphs. Otherwise follow `references/query.md`, which invokes the module through the graph's recorded Python interpreter instead of trusting a console shim on `PATH`. -Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If the `graphify query` CLI is unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. +Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If MCP and the module invocation are unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. --- diff --git a/graphify/skill.md b/graphify/skill.md index abd2811d2..9d174f1bb 100644 --- a/graphify/skill.md +++ b/graphify/skill.md @@ -684,11 +684,9 @@ Both are non-default subcommands. `--update` re-extracts only new or changed fil When `graphify-out/graph.json` already exists and the user asks a question about the corpus, answer from the graph rather than rebuilding it: -```bash -graphify query "" -``` +Prefer the MCP `query_graph` tool when it is available; pass the project root as `project_path` when the server supports multiple graphs. Otherwise follow `references/query.md`, which invokes the module through the graph's recorded Python interpreter instead of trusting a console shim on `PATH`. -Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If the `graphify query` CLI is unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. +Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If MCP and the module invocation are unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. --- diff --git a/graphify/skills/agents/references/query.md b/graphify/skills/agents/references/query.md index 56565eb78..ab059fac3 100644 --- a/graphify/skills/agents/references/query.md +++ b/graphify/skills/agents/references/query.md @@ -1,6 +1,6 @@ # graphify reference: query, path, explain -Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. These flows use the `graphify query` CLI when it is available and fall back to an inline NetworkX traversal otherwise. +Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. Prefer the MCP `query_graph` tool when it is available. If MCP is unavailable, invoke the module through the Python interpreter recorded in `graphify-out/.graphify_python`; on Windows, never execute a `graphify.exe` console shim. The inline NetworkX traversal remains the final fallback. Two traversal modes - choose based on the question: @@ -62,10 +62,10 @@ If the list is empty, say so plainly and stop — do not proceed to traversal. Build the **expanded query string** by joining the selected tokens with spaces. Use this string as `QUESTION` below — NOT the original user question. (The original question is preserved only for `save-result` at the end.) -Prefer the CLI when it is installed: +If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -graphify query "QUESTION" -# or: graphify query "QUESTION" --dfs --budget 3000 +$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" +# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: diff --git a/graphify/skills/amp/references/query.md b/graphify/skills/amp/references/query.md index 56565eb78..ab059fac3 100644 --- a/graphify/skills/amp/references/query.md +++ b/graphify/skills/amp/references/query.md @@ -1,6 +1,6 @@ # graphify reference: query, path, explain -Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. These flows use the `graphify query` CLI when it is available and fall back to an inline NetworkX traversal otherwise. +Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. Prefer the MCP `query_graph` tool when it is available. If MCP is unavailable, invoke the module through the Python interpreter recorded in `graphify-out/.graphify_python`; on Windows, never execute a `graphify.exe` console shim. The inline NetworkX traversal remains the final fallback. Two traversal modes - choose based on the question: @@ -62,10 +62,10 @@ If the list is empty, say so plainly and stop — do not proceed to traversal. Build the **expanded query string** by joining the selected tokens with spaces. Use this string as `QUESTION` below — NOT the original user question. (The original question is preserved only for `save-result` at the end.) -Prefer the CLI when it is installed: +If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -graphify query "QUESTION" -# or: graphify query "QUESTION" --dfs --budget 3000 +$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" +# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: diff --git a/graphify/skills/claude/references/query.md b/graphify/skills/claude/references/query.md index 56565eb78..ab059fac3 100644 --- a/graphify/skills/claude/references/query.md +++ b/graphify/skills/claude/references/query.md @@ -1,6 +1,6 @@ # graphify reference: query, path, explain -Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. These flows use the `graphify query` CLI when it is available and fall back to an inline NetworkX traversal otherwise. +Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. Prefer the MCP `query_graph` tool when it is available. If MCP is unavailable, invoke the module through the Python interpreter recorded in `graphify-out/.graphify_python`; on Windows, never execute a `graphify.exe` console shim. The inline NetworkX traversal remains the final fallback. Two traversal modes - choose based on the question: @@ -62,10 +62,10 @@ If the list is empty, say so plainly and stop — do not proceed to traversal. Build the **expanded query string** by joining the selected tokens with spaces. Use this string as `QUESTION` below — NOT the original user question. (The original question is preserved only for `save-result` at the end.) -Prefer the CLI when it is installed: +If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -graphify query "QUESTION" -# or: graphify query "QUESTION" --dfs --budget 3000 +$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" +# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: diff --git a/graphify/skills/claw/references/query.md b/graphify/skills/claw/references/query.md index 56565eb78..ab059fac3 100644 --- a/graphify/skills/claw/references/query.md +++ b/graphify/skills/claw/references/query.md @@ -1,6 +1,6 @@ # graphify reference: query, path, explain -Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. These flows use the `graphify query` CLI when it is available and fall back to an inline NetworkX traversal otherwise. +Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. Prefer the MCP `query_graph` tool when it is available. If MCP is unavailable, invoke the module through the Python interpreter recorded in `graphify-out/.graphify_python`; on Windows, never execute a `graphify.exe` console shim. The inline NetworkX traversal remains the final fallback. Two traversal modes - choose based on the question: @@ -62,10 +62,10 @@ If the list is empty, say so plainly and stop — do not proceed to traversal. Build the **expanded query string** by joining the selected tokens with spaces. Use this string as `QUESTION` below — NOT the original user question. (The original question is preserved only for `save-result` at the end.) -Prefer the CLI when it is installed: +If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -graphify query "QUESTION" -# or: graphify query "QUESTION" --dfs --budget 3000 +$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" +# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: diff --git a/graphify/skills/codex/references/query.md b/graphify/skills/codex/references/query.md index 56565eb78..ab059fac3 100644 --- a/graphify/skills/codex/references/query.md +++ b/graphify/skills/codex/references/query.md @@ -1,6 +1,6 @@ # graphify reference: query, path, explain -Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. These flows use the `graphify query` CLI when it is available and fall back to an inline NetworkX traversal otherwise. +Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. Prefer the MCP `query_graph` tool when it is available. If MCP is unavailable, invoke the module through the Python interpreter recorded in `graphify-out/.graphify_python`; on Windows, never execute a `graphify.exe` console shim. The inline NetworkX traversal remains the final fallback. Two traversal modes - choose based on the question: @@ -62,10 +62,10 @@ If the list is empty, say so plainly and stop — do not proceed to traversal. Build the **expanded query string** by joining the selected tokens with spaces. Use this string as `QUESTION` below — NOT the original user question. (The original question is preserved only for `save-result` at the end.) -Prefer the CLI when it is installed: +If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -graphify query "QUESTION" -# or: graphify query "QUESTION" --dfs --budget 3000 +$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" +# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: diff --git a/graphify/skills/copilot/references/query.md b/graphify/skills/copilot/references/query.md index 56565eb78..ab059fac3 100644 --- a/graphify/skills/copilot/references/query.md +++ b/graphify/skills/copilot/references/query.md @@ -1,6 +1,6 @@ # graphify reference: query, path, explain -Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. These flows use the `graphify query` CLI when it is available and fall back to an inline NetworkX traversal otherwise. +Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. Prefer the MCP `query_graph` tool when it is available. If MCP is unavailable, invoke the module through the Python interpreter recorded in `graphify-out/.graphify_python`; on Windows, never execute a `graphify.exe` console shim. The inline NetworkX traversal remains the final fallback. Two traversal modes - choose based on the question: @@ -62,10 +62,10 @@ If the list is empty, say so plainly and stop — do not proceed to traversal. Build the **expanded query string** by joining the selected tokens with spaces. Use this string as `QUESTION` below — NOT the original user question. (The original question is preserved only for `save-result` at the end.) -Prefer the CLI when it is installed: +If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -graphify query "QUESTION" -# or: graphify query "QUESTION" --dfs --budget 3000 +$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" +# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: diff --git a/graphify/skills/droid/references/query.md b/graphify/skills/droid/references/query.md index 56565eb78..ab059fac3 100644 --- a/graphify/skills/droid/references/query.md +++ b/graphify/skills/droid/references/query.md @@ -1,6 +1,6 @@ # graphify reference: query, path, explain -Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. These flows use the `graphify query` CLI when it is available and fall back to an inline NetworkX traversal otherwise. +Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. Prefer the MCP `query_graph` tool when it is available. If MCP is unavailable, invoke the module through the Python interpreter recorded in `graphify-out/.graphify_python`; on Windows, never execute a `graphify.exe` console shim. The inline NetworkX traversal remains the final fallback. Two traversal modes - choose based on the question: @@ -62,10 +62,10 @@ If the list is empty, say so plainly and stop — do not proceed to traversal. Build the **expanded query string** by joining the selected tokens with spaces. Use this string as `QUESTION` below — NOT the original user question. (The original question is preserved only for `save-result` at the end.) -Prefer the CLI when it is installed: +If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -graphify query "QUESTION" -# or: graphify query "QUESTION" --dfs --budget 3000 +$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" +# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: diff --git a/graphify/skills/kilo/references/query.md b/graphify/skills/kilo/references/query.md index 56565eb78..ab059fac3 100644 --- a/graphify/skills/kilo/references/query.md +++ b/graphify/skills/kilo/references/query.md @@ -1,6 +1,6 @@ # graphify reference: query, path, explain -Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. These flows use the `graphify query` CLI when it is available and fall back to an inline NetworkX traversal otherwise. +Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. Prefer the MCP `query_graph` tool when it is available. If MCP is unavailable, invoke the module through the Python interpreter recorded in `graphify-out/.graphify_python`; on Windows, never execute a `graphify.exe` console shim. The inline NetworkX traversal remains the final fallback. Two traversal modes - choose based on the question: @@ -62,10 +62,10 @@ If the list is empty, say so plainly and stop — do not proceed to traversal. Build the **expanded query string** by joining the selected tokens with spaces. Use this string as `QUESTION` below — NOT the original user question. (The original question is preserved only for `save-result` at the end.) -Prefer the CLI when it is installed: +If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -graphify query "QUESTION" -# or: graphify query "QUESTION" --dfs --budget 3000 +$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" +# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: diff --git a/graphify/skills/kiro/references/query.md b/graphify/skills/kiro/references/query.md index 56565eb78..ab059fac3 100644 --- a/graphify/skills/kiro/references/query.md +++ b/graphify/skills/kiro/references/query.md @@ -1,6 +1,6 @@ # graphify reference: query, path, explain -Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. These flows use the `graphify query` CLI when it is available and fall back to an inline NetworkX traversal otherwise. +Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. Prefer the MCP `query_graph` tool when it is available. If MCP is unavailable, invoke the module through the Python interpreter recorded in `graphify-out/.graphify_python`; on Windows, never execute a `graphify.exe` console shim. The inline NetworkX traversal remains the final fallback. Two traversal modes - choose based on the question: @@ -62,10 +62,10 @@ If the list is empty, say so plainly and stop — do not proceed to traversal. Build the **expanded query string** by joining the selected tokens with spaces. Use this string as `QUESTION` below — NOT the original user question. (The original question is preserved only for `save-result` at the end.) -Prefer the CLI when it is installed: +If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -graphify query "QUESTION" -# or: graphify query "QUESTION" --dfs --budget 3000 +$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" +# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: diff --git a/graphify/skills/opencode/references/query.md b/graphify/skills/opencode/references/query.md index 56565eb78..ab059fac3 100644 --- a/graphify/skills/opencode/references/query.md +++ b/graphify/skills/opencode/references/query.md @@ -1,6 +1,6 @@ # graphify reference: query, path, explain -Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. These flows use the `graphify query` CLI when it is available and fall back to an inline NetworkX traversal otherwise. +Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. Prefer the MCP `query_graph` tool when it is available. If MCP is unavailable, invoke the module through the Python interpreter recorded in `graphify-out/.graphify_python`; on Windows, never execute a `graphify.exe` console shim. The inline NetworkX traversal remains the final fallback. Two traversal modes - choose based on the question: @@ -62,10 +62,10 @@ If the list is empty, say so plainly and stop — do not proceed to traversal. Build the **expanded query string** by joining the selected tokens with spaces. Use this string as `QUESTION` below — NOT the original user question. (The original question is preserved only for `save-result` at the end.) -Prefer the CLI when it is installed: +If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -graphify query "QUESTION" -# or: graphify query "QUESTION" --dfs --budget 3000 +$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" +# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: diff --git a/graphify/skills/pi/references/query.md b/graphify/skills/pi/references/query.md index 56565eb78..ab059fac3 100644 --- a/graphify/skills/pi/references/query.md +++ b/graphify/skills/pi/references/query.md @@ -1,6 +1,6 @@ # graphify reference: query, path, explain -Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. These flows use the `graphify query` CLI when it is available and fall back to an inline NetworkX traversal otherwise. +Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. Prefer the MCP `query_graph` tool when it is available. If MCP is unavailable, invoke the module through the Python interpreter recorded in `graphify-out/.graphify_python`; on Windows, never execute a `graphify.exe` console shim. The inline NetworkX traversal remains the final fallback. Two traversal modes - choose based on the question: @@ -62,10 +62,10 @@ If the list is empty, say so plainly and stop — do not proceed to traversal. Build the **expanded query string** by joining the selected tokens with spaces. Use this string as `QUESTION` below — NOT the original user question. (The original question is preserved only for `save-result` at the end.) -Prefer the CLI when it is installed: +If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -graphify query "QUESTION" -# or: graphify query "QUESTION" --dfs --budget 3000 +$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" +# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: diff --git a/graphify/skills/trae/references/query.md b/graphify/skills/trae/references/query.md index 56565eb78..ab059fac3 100644 --- a/graphify/skills/trae/references/query.md +++ b/graphify/skills/trae/references/query.md @@ -1,6 +1,6 @@ # graphify reference: query, path, explain -Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. These flows use the `graphify query` CLI when it is available and fall back to an inline NetworkX traversal otherwise. +Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. Prefer the MCP `query_graph` tool when it is available. If MCP is unavailable, invoke the module through the Python interpreter recorded in `graphify-out/.graphify_python`; on Windows, never execute a `graphify.exe` console shim. The inline NetworkX traversal remains the final fallback. Two traversal modes - choose based on the question: @@ -62,10 +62,10 @@ If the list is empty, say so plainly and stop — do not proceed to traversal. Build the **expanded query string** by joining the selected tokens with spaces. Use this string as `QUESTION` below — NOT the original user question. (The original question is preserved only for `save-result` at the end.) -Prefer the CLI when it is installed: +If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -graphify query "QUESTION" -# or: graphify query "QUESTION" --dfs --budget 3000 +$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" +# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: diff --git a/graphify/skills/vscode/references/query.md b/graphify/skills/vscode/references/query.md index 56565eb78..ab059fac3 100644 --- a/graphify/skills/vscode/references/query.md +++ b/graphify/skills/vscode/references/query.md @@ -1,6 +1,6 @@ # graphify reference: query, path, explain -Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. These flows use the `graphify query` CLI when it is available and fall back to an inline NetworkX traversal otherwise. +Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. Prefer the MCP `query_graph` tool when it is available. If MCP is unavailable, invoke the module through the Python interpreter recorded in `graphify-out/.graphify_python`; on Windows, never execute a `graphify.exe` console shim. The inline NetworkX traversal remains the final fallback. Two traversal modes - choose based on the question: @@ -62,10 +62,10 @@ If the list is empty, say so plainly and stop — do not proceed to traversal. Build the **expanded query string** by joining the selected tokens with spaces. Use this string as `QUESTION` below — NOT the original user question. (The original question is preserved only for `save-result` at the end.) -Prefer the CLI when it is installed: +If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -graphify query "QUESTION" -# or: graphify query "QUESTION" --dfs --budget 3000 +$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" +# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: diff --git a/graphify/skills/windows/references/query.md b/graphify/skills/windows/references/query.md index 56565eb78..ab059fac3 100644 --- a/graphify/skills/windows/references/query.md +++ b/graphify/skills/windows/references/query.md @@ -1,6 +1,6 @@ # graphify reference: query, path, explain -Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. These flows use the `graphify query` CLI when it is available and fall back to an inline NetworkX traversal otherwise. +Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. Prefer the MCP `query_graph` tool when it is available. If MCP is unavailable, invoke the module through the Python interpreter recorded in `graphify-out/.graphify_python`; on Windows, never execute a `graphify.exe` console shim. The inline NetworkX traversal remains the final fallback. Two traversal modes - choose based on the question: @@ -62,10 +62,10 @@ If the list is empty, say so plainly and stop — do not proceed to traversal. Build the **expanded query string** by joining the selected tokens with spaces. Use this string as `QUESTION` below — NOT the original user question. (The original question is preserved only for `save-result` at the end.) -Prefer the CLI when it is installed: +If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -graphify query "QUESTION" -# or: graphify query "QUESTION" --dfs --budget 3000 +$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" +# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: diff --git a/tests/test_hook_guard.py b/tests/test_hook_guard.py index 869f7ccb8..486b21ed7 100644 --- a/tests/test_hook_guard.py +++ b/tests/test_hook_guard.py @@ -210,6 +210,7 @@ def test_gemini_allow_with_nudge(tmp_path, monkeypatch): payload = json.loads(out) assert payload["decision"] == "allow" assert "graphify query" in payload["additionalContext"] + assert "MCP `query_graph`" in payload["additionalContext"] def test_gemini_allow_without_graph(tmp_path, monkeypatch): diff --git a/tests/test_hook_strict.py b/tests/test_hook_strict.py index 1b34d13f9..b00dcea64 100644 --- a/tests/test_hook_strict.py +++ b/tests/test_hook_strict.py @@ -9,6 +9,7 @@ import io import json import os +import subprocess import sys import time @@ -66,6 +67,7 @@ def test_strict_first_read_denies_then_nudges(tmp_path, monkeypatch): out1 = _invoke("read", _read(f), tmp_path, monkeypatch, strict=True) assert _is_deny(out1) assert "graphify query" in json.loads(out1)["hookSpecificOutput"]["permissionDecisionReason"] + assert "MCP `query_graph`" in json.loads(out1)["hookSpecificOutput"]["permissionDecisionReason"] # marker created assert (tmp_path / "graphify-out" / "cache" / "hook_sessions" / "s1.denied").exists() # same session again -> soft nudge, not a second deny @@ -80,24 +82,31 @@ def test_strict_new_session_denies_again(tmp_path, monkeypatch): assert _is_deny(out) -def test_fresh_query_stamp_suppresses_deny(tmp_path, monkeypatch): +def test_subagent_gets_its_own_deny_with_shared_session_id(tmp_path, monkeypatch): + """A parent's deny must not consume the subagent's first-read guard.""" f = _fixture(tmp_path) - stamp = tmp_path / "graphify-out" / "cache" / "last_query_stamp" - stamp.parent.mkdir(parents=True, exist_ok=True) - stamp.write_text(str(time.time()), encoding="utf-8") - out = _invoke("read", _read(f), tmp_path, monkeypatch, strict=True) - assert not _is_deny(out) and "MANDATORY" in out + assert _is_deny(_invoke("read", _read(f, "shared"), tmp_path, monkeypatch, strict=True)) + child = _read(f, "shared") + child["agent_id"] = "child-1" + assert _is_deny(_invoke("read", child, tmp_path, monkeypatch, strict=True)) + + +def test_long_sibling_agent_ids_do_not_collide(tmp_path, monkeypatch): + """Sibling identities differing past the marker prefix stay distinct.""" + f = _fixture(tmp_path) + for suffix in ("A", "B"): + child = _read(f, "12345678-1234-1234-1234-123456789abc") + child["agent_id"] = "x" * 30 + suffix + assert _is_deny(_invoke("read", child, tmp_path, monkeypatch, strict=True)) -def test_expired_query_stamp_still_denies(tmp_path, monkeypatch): +def test_query_stamp_from_another_session_does_not_suppress_deny(tmp_path, monkeypatch): + """One agent's query must not disable strict mode for another session.""" f = _fixture(tmp_path) stamp = tmp_path / "graphify-out" / "cache" / "last_query_stamp" stamp.parent.mkdir(parents=True, exist_ok=True) - stamp.write_text("old", encoding="utf-8") - old = time.time() - 10_000 - os.utime(stamp, (old, old)) - out = _invoke("read", _read(f), tmp_path, monkeypatch, strict=True, env={"GRAPHIFY_HOOK_STRICT_TTL": "1800"}) - assert _is_deny(out) + stamp.write_text(str(time.time()), encoding="utf-8") + assert _is_deny(_invoke("read", _read(f, "new-session"), tmp_path, monkeypatch, strict=True)) def test_soft_mode_never_denies(tmp_path, monkeypatch): @@ -189,14 +198,27 @@ def test_strict_enabled_env_precedence(): _os.environ["GRAPHIFY_HOOK_STRICT"] = saved -def test_install_hook_carries_strict_flag(): +def test_installed_strict_hook_executes_and_denies(tmp_path): from graphify.install import _claude_pretooluse_hooks - soft = _claude_pretooluse_hooks(strict=False) - strict = _claude_pretooluse_hooks(strict=True) - read_soft = next(h for h in soft if h["matcher"] == "Read|Glob")["hooks"][0]["command"] - read_strict = next(h for h in strict if h["matcher"] == "Read|Glob")["hooks"][0]["command"] - assert read_soft.endswith("hook-guard read") - assert read_strict.endswith("hook-guard read --strict") - # search hook is unchanged either way - for hooks in (soft, strict): - assert next(h for h in hooks if h["matcher"] == "Bash|Grep")["hooks"][0]["command"].endswith("hook-guard search") + + f = _fixture(tmp_path) + sidecar = tmp_path / "graphify-out" / ".graphify_python" + sidecar.write_text(sys.executable, encoding="utf-8") + for project in (False, True): + payload = json.dumps(_read(f, f"installed-command-{int(project)}")) + entry = next( + h for h in _claude_pretooluse_hooks(strict=True, project=project) + if h["matcher"] == "Read|Glob" + )["hooks"][0] + command = entry["commandWindows"] if os.name == "nt" else entry["command"] + result = subprocess.run( + command, + input=payload, + text=True, + capture_output=True, + shell=True, + cwd=tmp_path, + timeout=15, + ) + assert result.returncode == 0, result.stderr + assert _is_deny(result.stdout), result.stdout or result.stderr diff --git a/tests/test_read_hook.py b/tests/test_read_hook.py index 82a514d92..56793a615 100644 --- a/tests/test_read_hook.py +++ b/tests/test_read_hook.py @@ -66,6 +66,7 @@ def test_nudge_payload_is_valid_pretooluse_json(tmp_path): payload = json.loads(out) assert payload["hookSpecificOutput"]["hookEventName"] == "PreToolUse" assert "graphify query" in payload["hookSpecificOutput"]["additionalContext"] + assert "MCP `query_graph`" in payload["hookSpecificOutput"]["additionalContext"] def test_silent_on_graphify_out_targets(tmp_path): diff --git a/tests/test_search_hook.py b/tests/test_search_hook.py index 93c492375..3b5916b9c 100644 --- a/tests/test_search_hook.py +++ b/tests/test_search_hook.py @@ -103,6 +103,7 @@ def test_nudge_payload_is_valid_pretooluse_json(tmp_path): payload = json.loads(out) assert payload["hookSpecificOutput"]["hookEventName"] == "PreToolUse" assert "graphify query" in payload["hookSpecificOutput"]["additionalContext"] + assert "MCP `query_graph`" in payload["hookSpecificOutput"]["additionalContext"] def test_fails_open_on_malformed_stdin(tmp_path): diff --git a/tests/test_skillgen.py b/tests/test_skillgen.py index 63a6f7c17..18bd93254 100644 --- a/tests/test_skillgen.py +++ b/tests/test_skillgen.py @@ -58,6 +58,24 @@ def test_render_output_is_lf_only(): assert not art.content.endswith("\n\n"), art.path +def test_query_guidance_prefers_mcp_and_never_runs_windows_console_shim(): + """Agent retrieval must not route Windows through an unsigned graphify.exe.""" + for key in ("claude", "codex", "windows", "opencode"): + core, refs = _platform_artifacts(key) + query = refs["query.md"] + assert "Prefer the MCP `query_graph` tool when it is available" in core, key + assert "Prefer the MCP `query_graph` tool when it is available" in query, key + assert "never execute a `graphify.exe` console shim" in query, key + + artifacts = gen.render_all(gen.load_platforms()) + antigravity = next( + artifact.content + for artifact in artifacts + if artifact.path == "graphify/always_on/antigravity-rules.md" + ) + assert "Prefer the MCP `query_graph` tool when it is available" in antigravity + + def test_no_version_or_timestamp_in_output(): """No generated artifact carries the package version string.""" from graphify.__main__ import __version__ diff --git a/tools/skillgen/expected/graphify__always_on__agents-md.md b/tools/skillgen/expected/graphify__always_on__agents-md.md index 6511cd1dd..6da0539c1 100644 --- a/tools/skillgen/expected/graphify__always_on__agents-md.md +++ b/tools/skillgen/expected/graphify__always_on__agents-md.md @@ -5,7 +5,7 @@ This project has a knowledge graph at graphify-out/ with god nodes, community st When the user types `/graphify`, use the installed graphify skill or instructions before doing anything else. Rules: -- For codebase questions, first run `graphify query ""` when graphify-out/graph.json exists. Use `graphify path "" ""` for relationships and `graphify explain ""` for focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. +- For codebase questions, first use the graph when graphify-out/graph.json exists. Prefer the MCP `query_graph` tool when it is available; otherwise follow the installed Graphify skill's interpreter-based CLI fallback. Use `shortest_path` / `get_node` (or their module-invocation CLI equivalents) for relationships and focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. - Dirty graphify-out/ files are expected after hooks or incremental updates; dirty graph files are not a reason to skip graphify. Only skip graphify if the task is about stale or incorrect graph output, or the user explicitly says not to use it. - If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing. - Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context. diff --git a/tools/skillgen/expected/graphify__always_on__antigravity-rules.md b/tools/skillgen/expected/graphify__always_on__antigravity-rules.md index 0fc786414..e6b109acc 100644 --- a/tools/skillgen/expected/graphify__always_on__antigravity-rules.md +++ b/tools/skillgen/expected/graphify__always_on__antigravity-rules.md @@ -8,7 +8,7 @@ description: Consult the graphify knowledge graph at graphify-out/ for codebase This project has a graphify knowledge graph at graphify-out/. Rules: -- For codebase or architecture questions, when `graphify-out/graph.json` exists, first run `graphify query ""` (CLI) or `query_graph` (MCP). Use `graphify path "" ""` / `shortest_path` for relationships and `graphify explain ""` / `get_node` for focused concepts. These return a scoped subgraph, usually much smaller than `GRAPH_REPORT.md` or raw grep output. +- For codebase or architecture questions, when `graphify-out/graph.json` exists, first use the graph. Prefer the MCP `query_graph` tool when it is available; otherwise follow the installed Graphify skill's interpreter-based CLI fallback. Use `shortest_path` / `get_node` (or their module-invocation CLI equivalents) for relationships and focused concepts. These return a scoped subgraph, usually much smaller than `GRAPH_REPORT.md` or raw grep output. - If graphify-out/wiki/index.md exists, navigate it instead of reading raw files - Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context - After modifying code files in this session, run `graphify update .` to keep the graph current (AST-only, no API cost) diff --git a/tools/skillgen/expected/graphify__always_on__claude-md.md b/tools/skillgen/expected/graphify__always_on__claude-md.md index 417efeb27..b62763ded 100644 --- a/tools/skillgen/expected/graphify__always_on__claude-md.md +++ b/tools/skillgen/expected/graphify__always_on__claude-md.md @@ -3,7 +3,7 @@ This project has a knowledge graph at graphify-out/ with god nodes, community structure, and cross-file relationships. Rules: -- For codebase questions, first run `graphify query ""` when graphify-out/graph.json exists. Use `graphify path "" ""` for relationships and `graphify explain ""` for focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. +- For codebase questions, first use the graph when graphify-out/graph.json exists. Prefer the MCP `query_graph` tool when it is available; otherwise follow the installed Graphify skill's interpreter-based CLI fallback. Use `shortest_path` / `get_node` (or their module-invocation CLI equivalents) for relationships and focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. - If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing. - Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context. - After modifying code, run `graphify update .` to keep the graph current (AST-only, no API cost). diff --git a/tools/skillgen/expected/graphify__skill-agents.md b/tools/skillgen/expected/graphify__skill-agents.md index 190827d9a..a79fb23fc 100644 --- a/tools/skillgen/expected/graphify__skill-agents.md +++ b/tools/skillgen/expected/graphify__skill-agents.md @@ -681,11 +681,9 @@ Both are non-default subcommands. `--update` re-extracts only new or changed fil When `graphify-out/graph.json` already exists and the user asks a question about the corpus, answer from the graph rather than rebuilding it: -```bash -graphify query "" -``` +Prefer the MCP `query_graph` tool when it is available; pass the project root as `project_path` when the server supports multiple graphs. Otherwise follow `references/query.md`, which invokes the module through the graph's recorded Python interpreter instead of trusting a console shim on `PATH`. -Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If the `graphify query` CLI is unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. +Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If MCP and the module invocation are unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. --- diff --git a/tools/skillgen/expected/graphify__skill-amp.md b/tools/skillgen/expected/graphify__skill-amp.md index 190827d9a..a79fb23fc 100644 --- a/tools/skillgen/expected/graphify__skill-amp.md +++ b/tools/skillgen/expected/graphify__skill-amp.md @@ -681,11 +681,9 @@ Both are non-default subcommands. `--update` re-extracts only new or changed fil When `graphify-out/graph.json` already exists and the user asks a question about the corpus, answer from the graph rather than rebuilding it: -```bash -graphify query "" -``` +Prefer the MCP `query_graph` tool when it is available; pass the project root as `project_path` when the server supports multiple graphs. Otherwise follow `references/query.md`, which invokes the module through the graph's recorded Python interpreter instead of trusting a console shim on `PATH`. -Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If the `graphify query` CLI is unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. +Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If MCP and the module invocation are unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. --- diff --git a/tools/skillgen/expected/graphify__skill-claw.md b/tools/skillgen/expected/graphify__skill-claw.md index abd2811d2..9d174f1bb 100644 --- a/tools/skillgen/expected/graphify__skill-claw.md +++ b/tools/skillgen/expected/graphify__skill-claw.md @@ -684,11 +684,9 @@ Both are non-default subcommands. `--update` re-extracts only new or changed fil When `graphify-out/graph.json` already exists and the user asks a question about the corpus, answer from the graph rather than rebuilding it: -```bash -graphify query "" -``` +Prefer the MCP `query_graph` tool when it is available; pass the project root as `project_path` when the server supports multiple graphs. Otherwise follow `references/query.md`, which invokes the module through the graph's recorded Python interpreter instead of trusting a console shim on `PATH`. -Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If the `graphify query` CLI is unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. +Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If MCP and the module invocation are unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. --- diff --git a/tools/skillgen/expected/graphify__skill-codex.md b/tools/skillgen/expected/graphify__skill-codex.md index af3f723c7..de86747b8 100644 --- a/tools/skillgen/expected/graphify__skill-codex.md +++ b/tools/skillgen/expected/graphify__skill-codex.md @@ -681,11 +681,9 @@ Both are non-default subcommands. `--update` re-extracts only new or changed fil When `graphify-out/graph.json` already exists and the user asks a question about the corpus, answer from the graph rather than rebuilding it: -```bash -graphify query "" -``` +Prefer the MCP `query_graph` tool when it is available; pass the project root as `project_path` when the server supports multiple graphs. Otherwise follow `references/query.md`, which invokes the module through the graph's recorded Python interpreter instead of trusting a console shim on `PATH`. -Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If the `graphify query` CLI is unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. +Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If MCP and the module invocation are unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. --- diff --git a/tools/skillgen/expected/graphify__skill-copilot.md b/tools/skillgen/expected/graphify__skill-copilot.md index abd2811d2..9d174f1bb 100644 --- a/tools/skillgen/expected/graphify__skill-copilot.md +++ b/tools/skillgen/expected/graphify__skill-copilot.md @@ -684,11 +684,9 @@ Both are non-default subcommands. `--update` re-extracts only new or changed fil When `graphify-out/graph.json` already exists and the user asks a question about the corpus, answer from the graph rather than rebuilding it: -```bash -graphify query "" -``` +Prefer the MCP `query_graph` tool when it is available; pass the project root as `project_path` when the server supports multiple graphs. Otherwise follow `references/query.md`, which invokes the module through the graph's recorded Python interpreter instead of trusting a console shim on `PATH`. -Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If the `graphify query` CLI is unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. +Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If MCP and the module invocation are unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. --- diff --git a/tools/skillgen/expected/graphify__skill-droid.md b/tools/skillgen/expected/graphify__skill-droid.md index fd148d485..821ef56a7 100644 --- a/tools/skillgen/expected/graphify__skill-droid.md +++ b/tools/skillgen/expected/graphify__skill-droid.md @@ -681,11 +681,9 @@ Both are non-default subcommands. `--update` re-extracts only new or changed fil When `graphify-out/graph.json` already exists and the user asks a question about the corpus, answer from the graph rather than rebuilding it: -```bash -graphify query "" -``` +Prefer the MCP `query_graph` tool when it is available; pass the project root as `project_path` when the server supports multiple graphs. Otherwise follow `references/query.md`, which invokes the module through the graph's recorded Python interpreter instead of trusting a console shim on `PATH`. -Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If the `graphify query` CLI is unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. +Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If MCP and the module invocation are unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. --- diff --git a/tools/skillgen/expected/graphify__skill-kilo.md b/tools/skillgen/expected/graphify__skill-kilo.md index 3e70b050a..e1f1496e9 100644 --- a/tools/skillgen/expected/graphify__skill-kilo.md +++ b/tools/skillgen/expected/graphify__skill-kilo.md @@ -684,11 +684,9 @@ Both are non-default subcommands. `--update` re-extracts only new or changed fil When `graphify-out/graph.json` already exists and the user asks a question about the corpus, answer from the graph rather than rebuilding it: -```bash -graphify query "" -``` +Prefer the MCP `query_graph` tool when it is available; pass the project root as `project_path` when the server supports multiple graphs. Otherwise follow `references/query.md`, which invokes the module through the graph's recorded Python interpreter instead of trusting a console shim on `PATH`. -Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If the `graphify query` CLI is unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. +Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If MCP and the module invocation are unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. --- diff --git a/tools/skillgen/expected/graphify__skill-kiro.md b/tools/skillgen/expected/graphify__skill-kiro.md index abd2811d2..9d174f1bb 100644 --- a/tools/skillgen/expected/graphify__skill-kiro.md +++ b/tools/skillgen/expected/graphify__skill-kiro.md @@ -684,11 +684,9 @@ Both are non-default subcommands. `--update` re-extracts only new or changed fil When `graphify-out/graph.json` already exists and the user asks a question about the corpus, answer from the graph rather than rebuilding it: -```bash -graphify query "" -``` +Prefer the MCP `query_graph` tool when it is available; pass the project root as `project_path` when the server supports multiple graphs. Otherwise follow `references/query.md`, which invokes the module through the graph's recorded Python interpreter instead of trusting a console shim on `PATH`. -Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If the `graphify query` CLI is unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. +Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If MCP and the module invocation are unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. --- diff --git a/tools/skillgen/expected/graphify__skill-opencode.md b/tools/skillgen/expected/graphify__skill-opencode.md index 91ced6067..872ad49f7 100644 --- a/tools/skillgen/expected/graphify__skill-opencode.md +++ b/tools/skillgen/expected/graphify__skill-opencode.md @@ -676,11 +676,9 @@ Both are non-default subcommands. `--update` re-extracts only new or changed fil When `graphify-out/graph.json` already exists and the user asks a question about the corpus, answer from the graph rather than rebuilding it: -```bash -graphify query "" -``` +Prefer the MCP `query_graph` tool when it is available; pass the project root as `project_path` when the server supports multiple graphs. Otherwise follow `references/query.md`, which invokes the module through the graph's recorded Python interpreter instead of trusting a console shim on `PATH`. -Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If the `graphify query` CLI is unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. +Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If MCP and the module invocation are unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. --- diff --git a/tools/skillgen/expected/graphify__skill-pi.md b/tools/skillgen/expected/graphify__skill-pi.md index abd2811d2..9d174f1bb 100644 --- a/tools/skillgen/expected/graphify__skill-pi.md +++ b/tools/skillgen/expected/graphify__skill-pi.md @@ -684,11 +684,9 @@ Both are non-default subcommands. `--update` re-extracts only new or changed fil When `graphify-out/graph.json` already exists and the user asks a question about the corpus, answer from the graph rather than rebuilding it: -```bash -graphify query "" -``` +Prefer the MCP `query_graph` tool when it is available; pass the project root as `project_path` when the server supports multiple graphs. Otherwise follow `references/query.md`, which invokes the module through the graph's recorded Python interpreter instead of trusting a console shim on `PATH`. -Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If the `graphify query` CLI is unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. +Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If MCP and the module invocation are unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. --- diff --git a/tools/skillgen/expected/graphify__skill-trae.md b/tools/skillgen/expected/graphify__skill-trae.md index 050667bc2..6a76e4a01 100644 --- a/tools/skillgen/expected/graphify__skill-trae.md +++ b/tools/skillgen/expected/graphify__skill-trae.md @@ -682,11 +682,9 @@ Both are non-default subcommands. `--update` re-extracts only new or changed fil When `graphify-out/graph.json` already exists and the user asks a question about the corpus, answer from the graph rather than rebuilding it: -```bash -graphify query "" -``` +Prefer the MCP `query_graph` tool when it is available; pass the project root as `project_path` when the server supports multiple graphs. Otherwise follow `references/query.md`, which invokes the module through the graph's recorded Python interpreter instead of trusting a console shim on `PATH`. -Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If the `graphify query` CLI is unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. +Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If MCP and the module invocation are unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. --- diff --git a/tools/skillgen/expected/graphify__skill-vscode.md b/tools/skillgen/expected/graphify__skill-vscode.md index 20c7c0835..4d2bf8c1e 100644 --- a/tools/skillgen/expected/graphify__skill-vscode.md +++ b/tools/skillgen/expected/graphify__skill-vscode.md @@ -680,11 +680,9 @@ Both are non-default subcommands. `--update` re-extracts only new or changed fil When `graphify-out/graph.json` already exists and the user asks a question about the corpus, answer from the graph rather than rebuilding it: -```bash -graphify query "" -``` +Prefer the MCP `query_graph` tool when it is available; pass the project root as `project_path` when the server supports multiple graphs. Otherwise follow `references/query.md`, which invokes the module through the graph's recorded Python interpreter instead of trusting a console shim on `PATH`. -Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If the `graphify query` CLI is unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. +Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If MCP and the module invocation are unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. --- diff --git a/tools/skillgen/expected/graphify__skill-windows.md b/tools/skillgen/expected/graphify__skill-windows.md index b09ecca3c..6f353df57 100644 --- a/tools/skillgen/expected/graphify__skill-windows.md +++ b/tools/skillgen/expected/graphify__skill-windows.md @@ -713,11 +713,9 @@ Both are non-default subcommands. `--update` re-extracts only new or changed fil When `graphify-out/graph.json` already exists and the user asks a question about the corpus, answer from the graph rather than rebuilding it: -```powershell -graphify query "" -``` +Prefer the MCP `query_graph` tool when it is available; pass the project root as `project_path` when the server supports multiple graphs. Otherwise follow `references/query.md`, which invokes the module through the graph's recorded Python interpreter instead of trusting a console shim on `PATH`. -Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If the `graphify query` CLI is unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. +Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If MCP and the module invocation are unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. --- diff --git a/tools/skillgen/expected/graphify__skill.md b/tools/skillgen/expected/graphify__skill.md index abd2811d2..9d174f1bb 100644 --- a/tools/skillgen/expected/graphify__skill.md +++ b/tools/skillgen/expected/graphify__skill.md @@ -684,11 +684,9 @@ Both are non-default subcommands. `--update` re-extracts only new or changed fil When `graphify-out/graph.json` already exists and the user asks a question about the corpus, answer from the graph rather than rebuilding it: -```bash -graphify query "" -``` +Prefer the MCP `query_graph` tool when it is available; pass the project root as `project_path` when the server supports multiple graphs. Otherwise follow `references/query.md`, which invokes the module through the graph's recorded Python interpreter instead of trusting a console shim on `PATH`. -Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If the `graphify query` CLI is unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. +Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If MCP and the module invocation are unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. --- diff --git a/tools/skillgen/expected/graphify__skills__agents__references__query.md b/tools/skillgen/expected/graphify__skills__agents__references__query.md index 56565eb78..ab059fac3 100644 --- a/tools/skillgen/expected/graphify__skills__agents__references__query.md +++ b/tools/skillgen/expected/graphify__skills__agents__references__query.md @@ -1,6 +1,6 @@ # graphify reference: query, path, explain -Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. These flows use the `graphify query` CLI when it is available and fall back to an inline NetworkX traversal otherwise. +Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. Prefer the MCP `query_graph` tool when it is available. If MCP is unavailable, invoke the module through the Python interpreter recorded in `graphify-out/.graphify_python`; on Windows, never execute a `graphify.exe` console shim. The inline NetworkX traversal remains the final fallback. Two traversal modes - choose based on the question: @@ -62,10 +62,10 @@ If the list is empty, say so plainly and stop — do not proceed to traversal. Build the **expanded query string** by joining the selected tokens with spaces. Use this string as `QUESTION` below — NOT the original user question. (The original question is preserved only for `save-result` at the end.) -Prefer the CLI when it is installed: +If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -graphify query "QUESTION" -# or: graphify query "QUESTION" --dfs --budget 3000 +$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" +# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: diff --git a/tools/skillgen/expected/graphify__skills__amp__references__query.md b/tools/skillgen/expected/graphify__skills__amp__references__query.md index 56565eb78..ab059fac3 100644 --- a/tools/skillgen/expected/graphify__skills__amp__references__query.md +++ b/tools/skillgen/expected/graphify__skills__amp__references__query.md @@ -1,6 +1,6 @@ # graphify reference: query, path, explain -Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. These flows use the `graphify query` CLI when it is available and fall back to an inline NetworkX traversal otherwise. +Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. Prefer the MCP `query_graph` tool when it is available. If MCP is unavailable, invoke the module through the Python interpreter recorded in `graphify-out/.graphify_python`; on Windows, never execute a `graphify.exe` console shim. The inline NetworkX traversal remains the final fallback. Two traversal modes - choose based on the question: @@ -62,10 +62,10 @@ If the list is empty, say so plainly and stop — do not proceed to traversal. Build the **expanded query string** by joining the selected tokens with spaces. Use this string as `QUESTION` below — NOT the original user question. (The original question is preserved only for `save-result` at the end.) -Prefer the CLI when it is installed: +If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -graphify query "QUESTION" -# or: graphify query "QUESTION" --dfs --budget 3000 +$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" +# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: diff --git a/tools/skillgen/expected/graphify__skills__claude__references__query.md b/tools/skillgen/expected/graphify__skills__claude__references__query.md index 56565eb78..ab059fac3 100644 --- a/tools/skillgen/expected/graphify__skills__claude__references__query.md +++ b/tools/skillgen/expected/graphify__skills__claude__references__query.md @@ -1,6 +1,6 @@ # graphify reference: query, path, explain -Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. These flows use the `graphify query` CLI when it is available and fall back to an inline NetworkX traversal otherwise. +Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. Prefer the MCP `query_graph` tool when it is available. If MCP is unavailable, invoke the module through the Python interpreter recorded in `graphify-out/.graphify_python`; on Windows, never execute a `graphify.exe` console shim. The inline NetworkX traversal remains the final fallback. Two traversal modes - choose based on the question: @@ -62,10 +62,10 @@ If the list is empty, say so plainly and stop — do not proceed to traversal. Build the **expanded query string** by joining the selected tokens with spaces. Use this string as `QUESTION` below — NOT the original user question. (The original question is preserved only for `save-result` at the end.) -Prefer the CLI when it is installed: +If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -graphify query "QUESTION" -# or: graphify query "QUESTION" --dfs --budget 3000 +$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" +# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: diff --git a/tools/skillgen/expected/graphify__skills__claw__references__query.md b/tools/skillgen/expected/graphify__skills__claw__references__query.md index 56565eb78..ab059fac3 100644 --- a/tools/skillgen/expected/graphify__skills__claw__references__query.md +++ b/tools/skillgen/expected/graphify__skills__claw__references__query.md @@ -1,6 +1,6 @@ # graphify reference: query, path, explain -Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. These flows use the `graphify query` CLI when it is available and fall back to an inline NetworkX traversal otherwise. +Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. Prefer the MCP `query_graph` tool when it is available. If MCP is unavailable, invoke the module through the Python interpreter recorded in `graphify-out/.graphify_python`; on Windows, never execute a `graphify.exe` console shim. The inline NetworkX traversal remains the final fallback. Two traversal modes - choose based on the question: @@ -62,10 +62,10 @@ If the list is empty, say so plainly and stop — do not proceed to traversal. Build the **expanded query string** by joining the selected tokens with spaces. Use this string as `QUESTION` below — NOT the original user question. (The original question is preserved only for `save-result` at the end.) -Prefer the CLI when it is installed: +If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -graphify query "QUESTION" -# or: graphify query "QUESTION" --dfs --budget 3000 +$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" +# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: diff --git a/tools/skillgen/expected/graphify__skills__codex__references__query.md b/tools/skillgen/expected/graphify__skills__codex__references__query.md index 56565eb78..ab059fac3 100644 --- a/tools/skillgen/expected/graphify__skills__codex__references__query.md +++ b/tools/skillgen/expected/graphify__skills__codex__references__query.md @@ -1,6 +1,6 @@ # graphify reference: query, path, explain -Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. These flows use the `graphify query` CLI when it is available and fall back to an inline NetworkX traversal otherwise. +Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. Prefer the MCP `query_graph` tool when it is available. If MCP is unavailable, invoke the module through the Python interpreter recorded in `graphify-out/.graphify_python`; on Windows, never execute a `graphify.exe` console shim. The inline NetworkX traversal remains the final fallback. Two traversal modes - choose based on the question: @@ -62,10 +62,10 @@ If the list is empty, say so plainly and stop — do not proceed to traversal. Build the **expanded query string** by joining the selected tokens with spaces. Use this string as `QUESTION` below — NOT the original user question. (The original question is preserved only for `save-result` at the end.) -Prefer the CLI when it is installed: +If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -graphify query "QUESTION" -# or: graphify query "QUESTION" --dfs --budget 3000 +$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" +# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: diff --git a/tools/skillgen/expected/graphify__skills__copilot__references__query.md b/tools/skillgen/expected/graphify__skills__copilot__references__query.md index 56565eb78..ab059fac3 100644 --- a/tools/skillgen/expected/graphify__skills__copilot__references__query.md +++ b/tools/skillgen/expected/graphify__skills__copilot__references__query.md @@ -1,6 +1,6 @@ # graphify reference: query, path, explain -Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. These flows use the `graphify query` CLI when it is available and fall back to an inline NetworkX traversal otherwise. +Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. Prefer the MCP `query_graph` tool when it is available. If MCP is unavailable, invoke the module through the Python interpreter recorded in `graphify-out/.graphify_python`; on Windows, never execute a `graphify.exe` console shim. The inline NetworkX traversal remains the final fallback. Two traversal modes - choose based on the question: @@ -62,10 +62,10 @@ If the list is empty, say so plainly and stop — do not proceed to traversal. Build the **expanded query string** by joining the selected tokens with spaces. Use this string as `QUESTION` below — NOT the original user question. (The original question is preserved only for `save-result` at the end.) -Prefer the CLI when it is installed: +If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -graphify query "QUESTION" -# or: graphify query "QUESTION" --dfs --budget 3000 +$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" +# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: diff --git a/tools/skillgen/expected/graphify__skills__droid__references__query.md b/tools/skillgen/expected/graphify__skills__droid__references__query.md index 56565eb78..ab059fac3 100644 --- a/tools/skillgen/expected/graphify__skills__droid__references__query.md +++ b/tools/skillgen/expected/graphify__skills__droid__references__query.md @@ -1,6 +1,6 @@ # graphify reference: query, path, explain -Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. These flows use the `graphify query` CLI when it is available and fall back to an inline NetworkX traversal otherwise. +Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. Prefer the MCP `query_graph` tool when it is available. If MCP is unavailable, invoke the module through the Python interpreter recorded in `graphify-out/.graphify_python`; on Windows, never execute a `graphify.exe` console shim. The inline NetworkX traversal remains the final fallback. Two traversal modes - choose based on the question: @@ -62,10 +62,10 @@ If the list is empty, say so plainly and stop — do not proceed to traversal. Build the **expanded query string** by joining the selected tokens with spaces. Use this string as `QUESTION` below — NOT the original user question. (The original question is preserved only for `save-result` at the end.) -Prefer the CLI when it is installed: +If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -graphify query "QUESTION" -# or: graphify query "QUESTION" --dfs --budget 3000 +$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" +# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: diff --git a/tools/skillgen/expected/graphify__skills__kilo__references__query.md b/tools/skillgen/expected/graphify__skills__kilo__references__query.md index 56565eb78..ab059fac3 100644 --- a/tools/skillgen/expected/graphify__skills__kilo__references__query.md +++ b/tools/skillgen/expected/graphify__skills__kilo__references__query.md @@ -1,6 +1,6 @@ # graphify reference: query, path, explain -Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. These flows use the `graphify query` CLI when it is available and fall back to an inline NetworkX traversal otherwise. +Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. Prefer the MCP `query_graph` tool when it is available. If MCP is unavailable, invoke the module through the Python interpreter recorded in `graphify-out/.graphify_python`; on Windows, never execute a `graphify.exe` console shim. The inline NetworkX traversal remains the final fallback. Two traversal modes - choose based on the question: @@ -62,10 +62,10 @@ If the list is empty, say so plainly and stop — do not proceed to traversal. Build the **expanded query string** by joining the selected tokens with spaces. Use this string as `QUESTION` below — NOT the original user question. (The original question is preserved only for `save-result` at the end.) -Prefer the CLI when it is installed: +If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -graphify query "QUESTION" -# or: graphify query "QUESTION" --dfs --budget 3000 +$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" +# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: diff --git a/tools/skillgen/expected/graphify__skills__kiro__references__query.md b/tools/skillgen/expected/graphify__skills__kiro__references__query.md index 56565eb78..ab059fac3 100644 --- a/tools/skillgen/expected/graphify__skills__kiro__references__query.md +++ b/tools/skillgen/expected/graphify__skills__kiro__references__query.md @@ -1,6 +1,6 @@ # graphify reference: query, path, explain -Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. These flows use the `graphify query` CLI when it is available and fall back to an inline NetworkX traversal otherwise. +Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. Prefer the MCP `query_graph` tool when it is available. If MCP is unavailable, invoke the module through the Python interpreter recorded in `graphify-out/.graphify_python`; on Windows, never execute a `graphify.exe` console shim. The inline NetworkX traversal remains the final fallback. Two traversal modes - choose based on the question: @@ -62,10 +62,10 @@ If the list is empty, say so plainly and stop — do not proceed to traversal. Build the **expanded query string** by joining the selected tokens with spaces. Use this string as `QUESTION` below — NOT the original user question. (The original question is preserved only for `save-result` at the end.) -Prefer the CLI when it is installed: +If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -graphify query "QUESTION" -# or: graphify query "QUESTION" --dfs --budget 3000 +$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" +# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: diff --git a/tools/skillgen/expected/graphify__skills__opencode__references__query.md b/tools/skillgen/expected/graphify__skills__opencode__references__query.md index 56565eb78..ab059fac3 100644 --- a/tools/skillgen/expected/graphify__skills__opencode__references__query.md +++ b/tools/skillgen/expected/graphify__skills__opencode__references__query.md @@ -1,6 +1,6 @@ # graphify reference: query, path, explain -Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. These flows use the `graphify query` CLI when it is available and fall back to an inline NetworkX traversal otherwise. +Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. Prefer the MCP `query_graph` tool when it is available. If MCP is unavailable, invoke the module through the Python interpreter recorded in `graphify-out/.graphify_python`; on Windows, never execute a `graphify.exe` console shim. The inline NetworkX traversal remains the final fallback. Two traversal modes - choose based on the question: @@ -62,10 +62,10 @@ If the list is empty, say so plainly and stop — do not proceed to traversal. Build the **expanded query string** by joining the selected tokens with spaces. Use this string as `QUESTION` below — NOT the original user question. (The original question is preserved only for `save-result` at the end.) -Prefer the CLI when it is installed: +If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -graphify query "QUESTION" -# or: graphify query "QUESTION" --dfs --budget 3000 +$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" +# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: diff --git a/tools/skillgen/expected/graphify__skills__pi__references__query.md b/tools/skillgen/expected/graphify__skills__pi__references__query.md index 56565eb78..ab059fac3 100644 --- a/tools/skillgen/expected/graphify__skills__pi__references__query.md +++ b/tools/skillgen/expected/graphify__skills__pi__references__query.md @@ -1,6 +1,6 @@ # graphify reference: query, path, explain -Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. These flows use the `graphify query` CLI when it is available and fall back to an inline NetworkX traversal otherwise. +Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. Prefer the MCP `query_graph` tool when it is available. If MCP is unavailable, invoke the module through the Python interpreter recorded in `graphify-out/.graphify_python`; on Windows, never execute a `graphify.exe` console shim. The inline NetworkX traversal remains the final fallback. Two traversal modes - choose based on the question: @@ -62,10 +62,10 @@ If the list is empty, say so plainly and stop — do not proceed to traversal. Build the **expanded query string** by joining the selected tokens with spaces. Use this string as `QUESTION` below — NOT the original user question. (The original question is preserved only for `save-result` at the end.) -Prefer the CLI when it is installed: +If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -graphify query "QUESTION" -# or: graphify query "QUESTION" --dfs --budget 3000 +$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" +# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: diff --git a/tools/skillgen/expected/graphify__skills__trae__references__query.md b/tools/skillgen/expected/graphify__skills__trae__references__query.md index 56565eb78..ab059fac3 100644 --- a/tools/skillgen/expected/graphify__skills__trae__references__query.md +++ b/tools/skillgen/expected/graphify__skills__trae__references__query.md @@ -1,6 +1,6 @@ # graphify reference: query, path, explain -Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. These flows use the `graphify query` CLI when it is available and fall back to an inline NetworkX traversal otherwise. +Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. Prefer the MCP `query_graph` tool when it is available. If MCP is unavailable, invoke the module through the Python interpreter recorded in `graphify-out/.graphify_python`; on Windows, never execute a `graphify.exe` console shim. The inline NetworkX traversal remains the final fallback. Two traversal modes - choose based on the question: @@ -62,10 +62,10 @@ If the list is empty, say so plainly and stop — do not proceed to traversal. Build the **expanded query string** by joining the selected tokens with spaces. Use this string as `QUESTION` below — NOT the original user question. (The original question is preserved only for `save-result` at the end.) -Prefer the CLI when it is installed: +If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -graphify query "QUESTION" -# or: graphify query "QUESTION" --dfs --budget 3000 +$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" +# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: diff --git a/tools/skillgen/expected/graphify__skills__vscode__references__query.md b/tools/skillgen/expected/graphify__skills__vscode__references__query.md index 56565eb78..ab059fac3 100644 --- a/tools/skillgen/expected/graphify__skills__vscode__references__query.md +++ b/tools/skillgen/expected/graphify__skills__vscode__references__query.md @@ -1,6 +1,6 @@ # graphify reference: query, path, explain -Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. These flows use the `graphify query` CLI when it is available and fall back to an inline NetworkX traversal otherwise. +Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. Prefer the MCP `query_graph` tool when it is available. If MCP is unavailable, invoke the module through the Python interpreter recorded in `graphify-out/.graphify_python`; on Windows, never execute a `graphify.exe` console shim. The inline NetworkX traversal remains the final fallback. Two traversal modes - choose based on the question: @@ -62,10 +62,10 @@ If the list is empty, say so plainly and stop — do not proceed to traversal. Build the **expanded query string** by joining the selected tokens with spaces. Use this string as `QUESTION` below — NOT the original user question. (The original question is preserved only for `save-result` at the end.) -Prefer the CLI when it is installed: +If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -graphify query "QUESTION" -# or: graphify query "QUESTION" --dfs --budget 3000 +$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" +# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: diff --git a/tools/skillgen/expected/graphify__skills__windows__references__query.md b/tools/skillgen/expected/graphify__skills__windows__references__query.md index 56565eb78..ab059fac3 100644 --- a/tools/skillgen/expected/graphify__skills__windows__references__query.md +++ b/tools/skillgen/expected/graphify__skills__windows__references__query.md @@ -1,6 +1,6 @@ # graphify reference: query, path, explain -Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. These flows use the `graphify query` CLI when it is available and fall back to an inline NetworkX traversal otherwise. +Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. Prefer the MCP `query_graph` tool when it is available. If MCP is unavailable, invoke the module through the Python interpreter recorded in `graphify-out/.graphify_python`; on Windows, never execute a `graphify.exe` console shim. The inline NetworkX traversal remains the final fallback. Two traversal modes - choose based on the question: @@ -62,10 +62,10 @@ If the list is empty, say so plainly and stop — do not proceed to traversal. Build the **expanded query string** by joining the selected tokens with spaces. Use this string as `QUESTION` below — NOT the original user question. (The original question is preserved only for `save-result` at the end.) -Prefer the CLI when it is installed: +If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -graphify query "QUESTION" -# or: graphify query "QUESTION" --dfs --budget 3000 +$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" +# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: diff --git a/tools/skillgen/fragments/always-on/agents-md.md b/tools/skillgen/fragments/always-on/agents-md.md index 6511cd1dd..6da0539c1 100644 --- a/tools/skillgen/fragments/always-on/agents-md.md +++ b/tools/skillgen/fragments/always-on/agents-md.md @@ -5,7 +5,7 @@ This project has a knowledge graph at graphify-out/ with god nodes, community st When the user types `/graphify`, use the installed graphify skill or instructions before doing anything else. Rules: -- For codebase questions, first run `graphify query ""` when graphify-out/graph.json exists. Use `graphify path "" ""` for relationships and `graphify explain ""` for focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. +- For codebase questions, first use the graph when graphify-out/graph.json exists. Prefer the MCP `query_graph` tool when it is available; otherwise follow the installed Graphify skill's interpreter-based CLI fallback. Use `shortest_path` / `get_node` (or their module-invocation CLI equivalents) for relationships and focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. - Dirty graphify-out/ files are expected after hooks or incremental updates; dirty graph files are not a reason to skip graphify. Only skip graphify if the task is about stale or incorrect graph output, or the user explicitly says not to use it. - If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing. - Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context. diff --git a/tools/skillgen/fragments/always-on/antigravity-rules.md b/tools/skillgen/fragments/always-on/antigravity-rules.md index 0fc786414..e6b109acc 100644 --- a/tools/skillgen/fragments/always-on/antigravity-rules.md +++ b/tools/skillgen/fragments/always-on/antigravity-rules.md @@ -8,7 +8,7 @@ description: Consult the graphify knowledge graph at graphify-out/ for codebase This project has a graphify knowledge graph at graphify-out/. Rules: -- For codebase or architecture questions, when `graphify-out/graph.json` exists, first run `graphify query ""` (CLI) or `query_graph` (MCP). Use `graphify path "" ""` / `shortest_path` for relationships and `graphify explain ""` / `get_node` for focused concepts. These return a scoped subgraph, usually much smaller than `GRAPH_REPORT.md` or raw grep output. +- For codebase or architecture questions, when `graphify-out/graph.json` exists, first use the graph. Prefer the MCP `query_graph` tool when it is available; otherwise follow the installed Graphify skill's interpreter-based CLI fallback. Use `shortest_path` / `get_node` (or their module-invocation CLI equivalents) for relationships and focused concepts. These return a scoped subgraph, usually much smaller than `GRAPH_REPORT.md` or raw grep output. - If graphify-out/wiki/index.md exists, navigate it instead of reading raw files - Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context - After modifying code files in this session, run `graphify update .` to keep the graph current (AST-only, no API cost) diff --git a/tools/skillgen/fragments/always-on/claude-md.md b/tools/skillgen/fragments/always-on/claude-md.md index 417efeb27..b62763ded 100644 --- a/tools/skillgen/fragments/always-on/claude-md.md +++ b/tools/skillgen/fragments/always-on/claude-md.md @@ -3,7 +3,7 @@ This project has a knowledge graph at graphify-out/ with god nodes, community structure, and cross-file relationships. Rules: -- For codebase questions, first run `graphify query ""` when graphify-out/graph.json exists. Use `graphify path "" ""` for relationships and `graphify explain ""` for focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. +- For codebase questions, first use the graph when graphify-out/graph.json exists. Prefer the MCP `query_graph` tool when it is available; otherwise follow the installed Graphify skill's interpreter-based CLI fallback. Use `shortest_path` / `get_node` (or their module-invocation CLI equivalents) for relationships and focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. - If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing. - Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context. - After modifying code, run `graphify update .` to keep the graph current (AST-only, no API cost). diff --git a/tools/skillgen/fragments/query-stub/default.md b/tools/skillgen/fragments/query-stub/default.md index 696796ec5..07ee97a90 100644 --- a/tools/skillgen/fragments/query-stub/default.md +++ b/tools/skillgen/fragments/query-stub/default.md @@ -1,7 +1,5 @@ When `graphify-out/graph.json` already exists and the user asks a question about the corpus, answer from the graph rather than rebuilding it: -```bash -graphify query "" -``` +Prefer the MCP `query_graph` tool when it is available; pass the project root as `project_path` when the server supports multiple graphs. Otherwise follow `references/query.md`, which invokes the module through the graph's recorded Python interpreter instead of trusting a console shim on `PATH`. -Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If the `graphify query` CLI is unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. +Before traversal, expand the question against the graph's own vocabulary so a wording mismatch does not collapse the answer to noise. If MCP and the module invocation are unavailable, fall back to an inline NetworkX traversal of `graphify-out/graph.json`. Answer using only what the graph output contains, and quote `source_location` when citing a specific fact. For that vocab-expansion step, the BFS/DFS traversal modes, the `--budget` cap, the NetworkX fallback, `save-result` feedback, and the `/graphify path` and `/graphify explain` flows, see `references/query.md`. diff --git a/tools/skillgen/fragments/references/query/default.md b/tools/skillgen/fragments/references/query/default.md index 56565eb78..ab059fac3 100644 --- a/tools/skillgen/fragments/references/query/default.md +++ b/tools/skillgen/fragments/references/query/default.md @@ -1,6 +1,6 @@ # graphify reference: query, path, explain -Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. These flows use the `graphify query` CLI when it is available and fall back to an inline NetworkX traversal otherwise. +Load this when the user asks a question against an existing graph, or runs `/graphify path` or `/graphify explain`. The core's query stub points here for the full traversal flow. Prefer the MCP `query_graph` tool when it is available. If MCP is unavailable, invoke the module through the Python interpreter recorded in `graphify-out/.graphify_python`; on Windows, never execute a `graphify.exe` console shim. The inline NetworkX traversal remains the final fallback. Two traversal modes - choose based on the question: @@ -62,10 +62,10 @@ If the list is empty, say so plainly and stop — do not proceed to traversal. Build the **expanded query string** by joining the selected tokens with spaces. Use this string as `QUESTION` below — NOT the original user question. (The original question is preserved only for `save-result` at the end.) -Prefer the CLI when it is installed: +If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -graphify query "QUESTION" -# or: graphify query "QUESTION" --dfs --budget 3000 +$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" +# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: From 352b21e78eb2806add4f7b80e0fc75260946e611 Mon Sep 17 00:00:00 2001 From: Logan Date: Wed, 2 Sep 2026 05:31:18 -0500 Subject: [PATCH 03/14] fix(hooks): close generated guidance regressions --- CHANGELOG.md | 5 ++- README.md | 2 + graphify/always_on/agents-md.md | 2 +- graphify/always_on/antigravity-rules.md | 2 +- graphify/always_on/claude-md.md | 2 +- graphify/always_on/gemini-md.md | 2 +- graphify/always_on/kiro-steering.md | 2 +- graphify/always_on/vscode-instructions.md | 9 ++-- graphify/skill-agents.md | 2 +- graphify/skill-amp.md | 2 +- graphify/skill-claw.md | 2 +- graphify/skill-codex.md | 2 +- graphify/skill-copilot.md | 2 +- graphify/skill-droid.md | 2 +- graphify/skill-kilo.md | 2 +- graphify/skill-kiro.md | 2 +- graphify/skill-opencode.md | 2 +- graphify/skill-pi.md | 2 +- graphify/skill-trae.md | 2 +- graphify/skill-vscode.md | 2 +- graphify/skill-windows.md | 2 +- graphify/skill.md | 2 +- graphify/skills/agents/references/query.md | 24 +++++------ graphify/skills/amp/references/query.md | 24 +++++------ graphify/skills/claude/references/query.md | 24 +++++------ graphify/skills/claw/references/query.md | 24 +++++------ graphify/skills/codex/references/query.md | 24 +++++------ graphify/skills/copilot/references/query.md | 24 +++++------ graphify/skills/droid/references/query.md | 24 +++++------ graphify/skills/kilo/references/query.md | 24 +++++------ graphify/skills/kiro/references/query.md | 24 +++++------ graphify/skills/opencode/references/query.md | 24 +++++------ graphify/skills/pi/references/query.md | 24 +++++------ graphify/skills/trae/references/query.md | 24 +++++------ graphify/skills/vscode/references/query.md | 24 +++++------ graphify/skills/windows/references/query.md | 24 +++++------ tests/test_hook_guard.py | 1 - tests/test_hook_strict.py | 1 - tests/test_read_hook.py | 9 ++-- tests/test_search_hook.py | 9 ++-- tests/test_skillgen.py | 28 ++++++++----- .../graphify__always_on__agents-md.md | 2 +- .../graphify__always_on__antigravity-rules.md | 2 +- .../graphify__always_on__claude-md.md | 2 +- .../graphify__always_on__gemini-md.md | 2 +- .../graphify__always_on__kiro-steering.md | 2 +- ...raphify__always_on__vscode-instructions.md | 9 ++-- .../expected/graphify__skill-agents.md | 2 +- .../skillgen/expected/graphify__skill-amp.md | 2 +- .../skillgen/expected/graphify__skill-claw.md | 2 +- .../expected/graphify__skill-codex.md | 2 +- .../expected/graphify__skill-copilot.md | 2 +- .../expected/graphify__skill-droid.md | 2 +- .../skillgen/expected/graphify__skill-kilo.md | 2 +- .../skillgen/expected/graphify__skill-kiro.md | 2 +- .../expected/graphify__skill-opencode.md | 2 +- tools/skillgen/expected/graphify__skill-pi.md | 2 +- .../skillgen/expected/graphify__skill-trae.md | 2 +- .../expected/graphify__skill-vscode.md | 2 +- .../expected/graphify__skill-windows.md | 2 +- tools/skillgen/expected/graphify__skill.md | 2 +- ...hify__skills__agents__references__query.md | 24 +++++------ ...raphify__skills__amp__references__query.md | 24 +++++------ ...hify__skills__claude__references__query.md | 24 +++++------ ...aphify__skills__claw__references__query.md | 24 +++++------ ...phify__skills__codex__references__query.md | 24 +++++------ ...ify__skills__copilot__references__query.md | 24 +++++------ ...phify__skills__droid__references__query.md | 24 +++++------ ...aphify__skills__kilo__references__query.md | 24 +++++------ ...aphify__skills__kiro__references__query.md | 24 +++++------ ...fy__skills__opencode__references__query.md | 24 +++++------ ...graphify__skills__pi__references__query.md | 24 +++++------ ...aphify__skills__trae__references__query.md | 24 +++++------ ...hify__skills__vscode__references__query.md | 24 +++++------ ...ify__skills__windows__references__query.md | 24 +++++------ .../skillgen/fragments/always-on/agents-md.md | 2 +- .../fragments/always-on/antigravity-rules.md | 2 +- .../skillgen/fragments/always-on/claude-md.md | 2 +- .../skillgen/fragments/always-on/gemini-md.md | 2 +- .../fragments/always-on/kiro-steering.md | 2 +- .../always-on/vscode-instructions.md | 9 ++-- tools/skillgen/fragments/core/core.md | 2 +- .../fragments/references/query/default.md | 24 +++++------ tools/skillgen/gen.py | 41 +++++++++++++++++++ 84 files changed, 482 insertions(+), 425 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 368ca5f5d..c9b3fcbb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,14 @@ Full release notes with details on each version: [GitHub Releases](https://github.com/safishamsi/graphify/releases) -## 0.9.53 (2026-08-30) +## Unreleased - Fix: generated agent guidance now prefers the MCP `query_graph` tool and uses the graph's recorded Python interpreter as the CLI fallback, so hardened Windows hosts do not route agents through an unsigned `graphify.exe` shim. - Fix: strict Claude hooks no longer use one graph-wide 30-minute query stamp that let any agent disable the first-read block for every other session; deny markers now key subagents by `session_id` plus `agent_id`, so a parent's first read cannot silently consume every subagent's guard. - Fix: project-scoped hooks now resolve Graphify through each clone's `graphify-out/.graphify_python` sidecar instead of a bare PATH launcher, preserving committed-config portability without producing an unrunnable hook on Application Control hosts (#3280, follow-up to #3129). + +## 0.9.53 (2026-08-30) + - Fix: a batch of cross-language inheritance-edge corrections (thanks @Synvoya): JavaScript `class X extends Y` now emits an `inherits` edge (#1790); PHP interfaces, enums, and traits are captured as class-like nodes with their heritage (#1791); Scala `trait` declarations become class-like nodes (#1792) and qualified `extends`/`with` bases resolve to the tail type (#1794); a qualified Kotlin supertype resolves to its tail type instead of the package head (#1793); a C# interface extending an interface is classified as `inherits`, not `implements` (#1817); and a Go interface type-set constraint no longer emits a spurious `embeds` edge (#1818). - Feature: Robot Framework `.robot`/`.resource` files are now extracted (optional `[robot]` extra) — suites, test cases, user keywords, keyword-call edges, and resource/library imports, with case/space/underscore-insensitive keyword resolution (#3192, thanks @nshiveg). - Fix: chat-template control tokens are now defanged by form (`<|…|>`, `[INST]`/`[SYSTEM]`) rather than an enumerated few, closing a prompt-injection gap for attacker-chosen tokens (e.g. `<|eot_id|>`); legitimate content is untouched (#3183, thanks @abhay-codes07). diff --git a/README.md b/README.md index e8a3fafc8..2c883b023 100644 --- a/README.md +++ b/README.md @@ -205,6 +205,8 @@ for example `graphify claude install --project` or `graphify codex install --pro > **Strict mode (Claude Code):** `graphify install --project --strict` makes the assistant actually use the graph. The default install *nudges* it to use the MCP `query_graph` tool (or the recorded Python-interpreter fallback) before reading files; strict mode *blocks* the first raw source read of a session and redirects it to the graph, then reverts to the nudge (so it fires at most once per session and never gets stuck). Set `GRAPHIFY_HOOK_STRICT=1`/`0` in the Claude process environment to override the installed mode; the default install is unchanged (soft nudge). +> **Project hook interpreter:** committed project hooks read `graphify-out/.graphify_python`, which each clone writes when Graphify resolves its environment. Until that sidecar exists, the hook fails open and emits no graph decision; run the installed Graphify skill once in a fresh clone before relying on strict enforcement. +
Pick your platform (20+ assistants, click to expand) diff --git a/graphify/always_on/agents-md.md b/graphify/always_on/agents-md.md index 6da0539c1..8bea68aa0 100644 --- a/graphify/always_on/agents-md.md +++ b/graphify/always_on/agents-md.md @@ -5,7 +5,7 @@ This project has a knowledge graph at graphify-out/ with god nodes, community st When the user types `/graphify`, use the installed graphify skill or instructions before doing anything else. Rules: -- For codebase questions, first use the graph when graphify-out/graph.json exists. Prefer the MCP `query_graph` tool when it is available; otherwise follow the installed Graphify skill's interpreter-based CLI fallback. Use `shortest_path` / `get_node` (or their module-invocation CLI equivalents) for relationships and focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. +- For codebase questions, first use the graph when graphify-out/graph.json exists. Prefer the MCP `query_graph` tool. CLI fallback: `"$(cat graphify-out/.graphify_python)" -m graphify query ""` (PowerShell: `& (Get-Content graphify-out\.graphify_python) -m graphify query ""`); use the same recorded interpreter with `path` or `explain` for relationships and focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. - Dirty graphify-out/ files are expected after hooks or incremental updates; dirty graph files are not a reason to skip graphify. Only skip graphify if the task is about stale or incorrect graph output, or the user explicitly says not to use it. - If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing. - Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context. diff --git a/graphify/always_on/antigravity-rules.md b/graphify/always_on/antigravity-rules.md index e6b109acc..baf9f98e4 100644 --- a/graphify/always_on/antigravity-rules.md +++ b/graphify/always_on/antigravity-rules.md @@ -8,7 +8,7 @@ description: Consult the graphify knowledge graph at graphify-out/ for codebase This project has a graphify knowledge graph at graphify-out/. Rules: -- For codebase or architecture questions, when `graphify-out/graph.json` exists, first use the graph. Prefer the MCP `query_graph` tool when it is available; otherwise follow the installed Graphify skill's interpreter-based CLI fallback. Use `shortest_path` / `get_node` (or their module-invocation CLI equivalents) for relationships and focused concepts. These return a scoped subgraph, usually much smaller than `GRAPH_REPORT.md` or raw grep output. +- For codebase or architecture questions, when `graphify-out/graph.json` exists, first use the graph. Prefer the MCP `query_graph` tool. CLI fallback: `"$(cat graphify-out/.graphify_python)" -m graphify query ""` (PowerShell: `& (Get-Content graphify-out\.graphify_python) -m graphify query ""`); use the same recorded interpreter with `path` or `explain` for relationships and focused concepts. These return a scoped subgraph, usually much smaller than `GRAPH_REPORT.md` or raw grep output. - If graphify-out/wiki/index.md exists, navigate it instead of reading raw files - Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context - After modifying code files in this session, run `graphify update .` to keep the graph current (AST-only, no API cost) diff --git a/graphify/always_on/claude-md.md b/graphify/always_on/claude-md.md index b62763ded..d1928c7a2 100644 --- a/graphify/always_on/claude-md.md +++ b/graphify/always_on/claude-md.md @@ -3,7 +3,7 @@ This project has a knowledge graph at graphify-out/ with god nodes, community structure, and cross-file relationships. Rules: -- For codebase questions, first use the graph when graphify-out/graph.json exists. Prefer the MCP `query_graph` tool when it is available; otherwise follow the installed Graphify skill's interpreter-based CLI fallback. Use `shortest_path` / `get_node` (or their module-invocation CLI equivalents) for relationships and focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. +- For codebase questions, first use the graph when graphify-out/graph.json exists. Prefer the MCP `query_graph` tool. CLI fallback: `"$(cat graphify-out/.graphify_python)" -m graphify query ""` (PowerShell: `& (Get-Content graphify-out\.graphify_python) -m graphify query ""`); use the same recorded interpreter with `path` or `explain` for relationships and focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. - If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing. - Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context. - After modifying code, run `graphify update .` to keep the graph current (AST-only, no API cost). diff --git a/graphify/always_on/gemini-md.md b/graphify/always_on/gemini-md.md index 417efeb27..d1928c7a2 100644 --- a/graphify/always_on/gemini-md.md +++ b/graphify/always_on/gemini-md.md @@ -3,7 +3,7 @@ This project has a knowledge graph at graphify-out/ with god nodes, community structure, and cross-file relationships. Rules: -- For codebase questions, first run `graphify query ""` when graphify-out/graph.json exists. Use `graphify path "" ""` for relationships and `graphify explain ""` for focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. +- For codebase questions, first use the graph when graphify-out/graph.json exists. Prefer the MCP `query_graph` tool. CLI fallback: `"$(cat graphify-out/.graphify_python)" -m graphify query ""` (PowerShell: `& (Get-Content graphify-out\.graphify_python) -m graphify query ""`); use the same recorded interpreter with `path` or `explain` for relationships and focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. - If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing. - Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context. - After modifying code, run `graphify update .` to keep the graph current (AST-only, no API cost). diff --git a/graphify/always_on/kiro-steering.md b/graphify/always_on/kiro-steering.md index cb6f4543d..87a8e8dea 100644 --- a/graphify/always_on/kiro-steering.md +++ b/graphify/always_on/kiro-steering.md @@ -2,4 +2,4 @@ inclusion: always --- -graphify: A knowledge graph of this project lives in `graphify-out/`. For codebase, architecture, or dependency questions, when `graphify-out/graph.json` exists, first run `graphify query ""` (or `graphify path "" ""` / `graphify explain ""`). These return a scoped subgraph, usually much smaller than `GRAPH_REPORT.md` or raw grep output. Read `GRAPH_REPORT.md` only for broad architecture review or when those commands do not surface enough context. +graphify: A knowledge graph of this project lives in `graphify-out/`. For codebase, architecture, or dependency questions, when `graphify-out/graph.json` exists, prefer the MCP `query_graph` tool. CLI fallback: `"$(cat graphify-out/.graphify_python)" -m graphify query ""` (PowerShell: `& (Get-Content graphify-out\.graphify_python) -m graphify query ""`); use the same recorded interpreter with `path` or `explain`. These return a scoped subgraph, usually much smaller than `GRAPH_REPORT.md` or raw grep output. Read `GRAPH_REPORT.md` only for broad architecture review or when those commands do not surface enough context. diff --git a/graphify/always_on/vscode-instructions.md b/graphify/always_on/vscode-instructions.md index 9cb983c95..5f9181c1b 100644 --- a/graphify/always_on/vscode-instructions.md +++ b/graphify/always_on/vscode-instructions.md @@ -1,10 +1,11 @@ ## graphify For any question about this repo's architecture, structure, components, or how to add/modify/find -code, your first action should be `graphify query ""` when `graphify-out/graph.json` -exists. Use `graphify path "" ""` for relationship questions and `graphify explain ""` -for focused-concept questions. These return a scoped subgraph, usually much smaller than the full -report or raw grep output. +code, first use the graph when `graphify-out/graph.json` exists. Prefer the MCP `query_graph` tool. +CLI fallback: `"$(cat graphify-out/.graphify_python)" -m graphify query ""` (PowerShell: +`& (Get-Content graphify-out\.graphify_python) -m graphify query ""`); use the same recorded +interpreter with `path` or `explain`. These return a scoped subgraph, usually much smaller than the +full report or raw grep output. Triggers: "how do I…", "where is…", "what does … do", "add/modify a ", "explain the architecture", or anything that depends on how files or classes relate. diff --git a/graphify/skill-agents.md b/graphify/skill-agents.md index a79fb23fc..48689345a 100644 --- a/graphify/skill-agents.md +++ b/graphify/skill-agents.md @@ -50,7 +50,7 @@ Drop any folder of code, docs, papers, images, or video into graphify and get a If the user invoked `/graphify --help` or `/graphify -h` (with no other arguments), print the contents of the `## Usage` section above verbatim and stop. Do not run any commands, do not detect files, do not default the path to `.`. Just print the Usage block and return. -**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Run `graphify query ""` immediately. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. +**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Prefer the MCP `query_graph` tool immediately; otherwise use the recorded-interpreter command in `references/query.md`. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. If no path was given, use `.` (current directory). Do not ask the user for a path. diff --git a/graphify/skill-amp.md b/graphify/skill-amp.md index a79fb23fc..48689345a 100644 --- a/graphify/skill-amp.md +++ b/graphify/skill-amp.md @@ -50,7 +50,7 @@ Drop any folder of code, docs, papers, images, or video into graphify and get a If the user invoked `/graphify --help` or `/graphify -h` (with no other arguments), print the contents of the `## Usage` section above verbatim and stop. Do not run any commands, do not detect files, do not default the path to `.`. Just print the Usage block and return. -**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Run `graphify query ""` immediately. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. +**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Prefer the MCP `query_graph` tool immediately; otherwise use the recorded-interpreter command in `references/query.md`. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. If no path was given, use `.` (current directory). Do not ask the user for a path. diff --git a/graphify/skill-claw.md b/graphify/skill-claw.md index 9d174f1bb..238d3ccff 100644 --- a/graphify/skill-claw.md +++ b/graphify/skill-claw.md @@ -50,7 +50,7 @@ Drop any folder of code, docs, papers, images, or video into graphify and get a If the user invoked `/graphify --help` or `/graphify -h` (with no other arguments), print the contents of the `## Usage` section above verbatim and stop. Do not run any commands, do not detect files, do not default the path to `.`. Just print the Usage block and return. -**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Run `graphify query ""` immediately. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. +**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Prefer the MCP `query_graph` tool immediately; otherwise use the recorded-interpreter command in `references/query.md`. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. If no path was given, use `.` (current directory). Do not ask the user for a path. diff --git a/graphify/skill-codex.md b/graphify/skill-codex.md index de86747b8..9f9cbc652 100644 --- a/graphify/skill-codex.md +++ b/graphify/skill-codex.md @@ -50,7 +50,7 @@ Drop any folder of code, docs, papers, images, or video into graphify and get a If the user invoked `/graphify --help` or `/graphify -h` (with no other arguments), print the contents of the `## Usage` section above verbatim and stop. Do not run any commands, do not detect files, do not default the path to `.`. Just print the Usage block and return. -**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Run `graphify query ""` immediately. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. +**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Prefer the MCP `query_graph` tool immediately; otherwise use the recorded-interpreter command in `references/query.md`. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. If no path was given, use `.` (current directory). Do not ask the user for a path. diff --git a/graphify/skill-copilot.md b/graphify/skill-copilot.md index 9d174f1bb..238d3ccff 100644 --- a/graphify/skill-copilot.md +++ b/graphify/skill-copilot.md @@ -50,7 +50,7 @@ Drop any folder of code, docs, papers, images, or video into graphify and get a If the user invoked `/graphify --help` or `/graphify -h` (with no other arguments), print the contents of the `## Usage` section above verbatim and stop. Do not run any commands, do not detect files, do not default the path to `.`. Just print the Usage block and return. -**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Run `graphify query ""` immediately. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. +**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Prefer the MCP `query_graph` tool immediately; otherwise use the recorded-interpreter command in `references/query.md`. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. If no path was given, use `.` (current directory). Do not ask the user for a path. diff --git a/graphify/skill-droid.md b/graphify/skill-droid.md index 821ef56a7..b0879f6c8 100644 --- a/graphify/skill-droid.md +++ b/graphify/skill-droid.md @@ -50,7 +50,7 @@ Drop any folder of code, docs, papers, images, or video into graphify and get a If the user invoked `/graphify --help` or `/graphify -h` (with no other arguments), print the contents of the `## Usage` section above verbatim and stop. Do not run any commands, do not detect files, do not default the path to `.`. Just print the Usage block and return. -**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Run `graphify query ""` immediately. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. +**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Prefer the MCP `query_graph` tool immediately; otherwise use the recorded-interpreter command in `references/query.md`. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. If no path was given, use `.` (current directory). Do not ask the user for a path. diff --git a/graphify/skill-kilo.md b/graphify/skill-kilo.md index e1f1496e9..71e6fe485 100644 --- a/graphify/skill-kilo.md +++ b/graphify/skill-kilo.md @@ -50,7 +50,7 @@ Drop any folder of code, docs, papers, images, or video into graphify and get a If the user invoked `/graphify --help` or `/graphify -h` (with no other arguments), print the contents of the `## Usage` section above verbatim and stop. Do not run any commands, do not detect files, do not default the path to `.`. Just print the Usage block and return. -**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Run `graphify query ""` immediately. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. +**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Prefer the MCP `query_graph` tool immediately; otherwise use the recorded-interpreter command in `references/query.md`. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. If no path was given, use `.` (current directory). Do not ask the user for a path. diff --git a/graphify/skill-kiro.md b/graphify/skill-kiro.md index 9d174f1bb..238d3ccff 100644 --- a/graphify/skill-kiro.md +++ b/graphify/skill-kiro.md @@ -50,7 +50,7 @@ Drop any folder of code, docs, papers, images, or video into graphify and get a If the user invoked `/graphify --help` or `/graphify -h` (with no other arguments), print the contents of the `## Usage` section above verbatim and stop. Do not run any commands, do not detect files, do not default the path to `.`. Just print the Usage block and return. -**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Run `graphify query ""` immediately. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. +**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Prefer the MCP `query_graph` tool immediately; otherwise use the recorded-interpreter command in `references/query.md`. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. If no path was given, use `.` (current directory). Do not ask the user for a path. diff --git a/graphify/skill-opencode.md b/graphify/skill-opencode.md index 872ad49f7..7efbd23a9 100644 --- a/graphify/skill-opencode.md +++ b/graphify/skill-opencode.md @@ -50,7 +50,7 @@ Drop any folder of code, docs, papers, images, or video into graphify and get a If the user invoked `/graphify --help` or `/graphify -h` (with no other arguments), print the contents of the `## Usage` section above verbatim and stop. Do not run any commands, do not detect files, do not default the path to `.`. Just print the Usage block and return. -**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Run `graphify query ""` immediately. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. +**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Prefer the MCP `query_graph` tool immediately; otherwise use the recorded-interpreter command in `references/query.md`. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. If no path was given, use `.` (current directory). Do not ask the user for a path. diff --git a/graphify/skill-pi.md b/graphify/skill-pi.md index 9d174f1bb..238d3ccff 100644 --- a/graphify/skill-pi.md +++ b/graphify/skill-pi.md @@ -50,7 +50,7 @@ Drop any folder of code, docs, papers, images, or video into graphify and get a If the user invoked `/graphify --help` or `/graphify -h` (with no other arguments), print the contents of the `## Usage` section above verbatim and stop. Do not run any commands, do not detect files, do not default the path to `.`. Just print the Usage block and return. -**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Run `graphify query ""` immediately. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. +**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Prefer the MCP `query_graph` tool immediately; otherwise use the recorded-interpreter command in `references/query.md`. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. If no path was given, use `.` (current directory). Do not ask the user for a path. diff --git a/graphify/skill-trae.md b/graphify/skill-trae.md index 6a76e4a01..098cb33aa 100644 --- a/graphify/skill-trae.md +++ b/graphify/skill-trae.md @@ -50,7 +50,7 @@ Drop any folder of code, docs, papers, images, or video into graphify and get a If the user invoked `/graphify --help` or `/graphify -h` (with no other arguments), print the contents of the `## Usage` section above verbatim and stop. Do not run any commands, do not detect files, do not default the path to `.`. Just print the Usage block and return. -**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Run `graphify query ""` immediately. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. +**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Prefer the MCP `query_graph` tool immediately; otherwise use the recorded-interpreter command in `references/query.md`. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. If no path was given, use `.` (current directory). Do not ask the user for a path. diff --git a/graphify/skill-vscode.md b/graphify/skill-vscode.md index 4d2bf8c1e..d98332a2b 100644 --- a/graphify/skill-vscode.md +++ b/graphify/skill-vscode.md @@ -50,7 +50,7 @@ Drop any folder of code, docs, papers, images, or video into graphify and get a If the user invoked `/graphify --help` or `/graphify -h` (with no other arguments), print the contents of the `## Usage` section above verbatim and stop. Do not run any commands, do not detect files, do not default the path to `.`. Just print the Usage block and return. -**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Run `graphify query ""` immediately. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. +**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Prefer the MCP `query_graph` tool immediately; otherwise use the recorded-interpreter command in `references/query.md`. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. If no path was given, use `.` (current directory). Do not ask the user for a path. diff --git a/graphify/skill-windows.md b/graphify/skill-windows.md index 6f353df57..7f8d9d6e2 100644 --- a/graphify/skill-windows.md +++ b/graphify/skill-windows.md @@ -50,7 +50,7 @@ Drop any folder of code, docs, papers, images, or video into graphify and get a If the user invoked `/graphify --help` or `/graphify -h` (with no other arguments), print the contents of the `## Usage` section above verbatim and stop. Do not run any commands, do not detect files, do not default the path to `.`. Just print the Usage block and return. -**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Run `graphify query ""` immediately. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. +**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Prefer the MCP `query_graph` tool immediately; otherwise use the recorded-interpreter command in `references/query.md`. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. If no path was given, use `.` (current directory). Do not ask the user for a path. diff --git a/graphify/skill.md b/graphify/skill.md index 9d174f1bb..238d3ccff 100644 --- a/graphify/skill.md +++ b/graphify/skill.md @@ -50,7 +50,7 @@ Drop any folder of code, docs, papers, images, or video into graphify and get a If the user invoked `/graphify --help` or `/graphify -h` (with no other arguments), print the contents of the `## Usage` section above verbatim and stop. Do not run any commands, do not detect files, do not default the path to `.`. Just print the Usage block and return. -**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Run `graphify query ""` immediately. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. +**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Prefer the MCP `query_graph` tool immediately; otherwise use the recorded-interpreter command in `references/query.md`. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. If no path was given, use `.` (current directory). Do not ask the user for a path. diff --git a/graphify/skills/agents/references/query.md b/graphify/skills/agents/references/query.md index ab059fac3..5ce535433 100644 --- a/graphify/skills/agents/references/query.md +++ b/graphify/skills/agents/references/query.md @@ -11,7 +11,7 @@ Two traversal modes - choose based on the question: First check the graph exists: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " from pathlib import Path if not Path('graphify-out/graph.json').exists(): print('ERROR: No graph found. Run /graphify first to build the graph.') @@ -28,7 +28,7 @@ Fix this **without inventing tokens** by expanding the query against the actual 1. Extract the token vocabulary from node labels: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, re from pathlib import Path data = json.loads(Path('graphify-out/graph.json').read_text(encoding='utf-8')) @@ -64,8 +64,8 @@ Build the **expanded query string** by joining the selected tokens with spaces. If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" -# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 +"$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" +# or: "$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: @@ -77,7 +77,7 @@ If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal 5. If the graph lacks enough information, say so - do not hallucinate edges. ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import sys, json from networkx.readwrite import json_graph import networkx as nx @@ -168,7 +168,7 @@ Replace `QUESTION` with the **expanded** query string, `MODE` with `bfs` or `dfs After writing the answer, save it back into the graph so it improves future queries. Include the expanded tokens inside the `--answer` text (e.g. `"Expanded from original query via vocab: [tokens]. Then traversed..."`) so the next `--update` extracts the expansion history as a graph node: ```bash -$(cat graphify-out/.graphify_python) -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 ``` Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with your full answer text (containing the expanded-token trace), `NODE1 NODE2` 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. @@ -188,13 +188,13 @@ At the **start** of graph work, refresh and read the lessons: run `graphify refl Find the shortest path between two named concepts in the graph. Prefer the CLI when installed: ```bash -graphify path "NODE_A" "NODE_B" +"$(cat graphify-out/.graphify_python)" -m graphify path "NODE_A" "NODE_B" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -246,7 +246,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) -m graphify save-result --question "Path from NODE_A to NODE_B" --answer "ANSWER" --type path_query --nodes NODE_A NODE_B +"$(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 ``` --- @@ -256,13 +256,13 @@ $(cat graphify-out/.graphify_python) -m graphify save-result --question "Path fr Give a plain-language explanation of a single node - everything connected to it. Prefer the CLI when installed: ```bash -graphify explain "NODE_NAME" +"$(cat graphify-out/.graphify_python)" -m graphify explain "NODE_NAME" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -307,5 +307,5 @@ 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) -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME ``` diff --git a/graphify/skills/amp/references/query.md b/graphify/skills/amp/references/query.md index ab059fac3..5ce535433 100644 --- a/graphify/skills/amp/references/query.md +++ b/graphify/skills/amp/references/query.md @@ -11,7 +11,7 @@ Two traversal modes - choose based on the question: First check the graph exists: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " from pathlib import Path if not Path('graphify-out/graph.json').exists(): print('ERROR: No graph found. Run /graphify first to build the graph.') @@ -28,7 +28,7 @@ Fix this **without inventing tokens** by expanding the query against the actual 1. Extract the token vocabulary from node labels: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, re from pathlib import Path data = json.loads(Path('graphify-out/graph.json').read_text(encoding='utf-8')) @@ -64,8 +64,8 @@ Build the **expanded query string** by joining the selected tokens with spaces. If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" -# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 +"$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" +# or: "$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: @@ -77,7 +77,7 @@ If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal 5. If the graph lacks enough information, say so - do not hallucinate edges. ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import sys, json from networkx.readwrite import json_graph import networkx as nx @@ -168,7 +168,7 @@ Replace `QUESTION` with the **expanded** query string, `MODE` with `bfs` or `dfs After writing the answer, save it back into the graph so it improves future queries. Include the expanded tokens inside the `--answer` text (e.g. `"Expanded from original query via vocab: [tokens]. Then traversed..."`) so the next `--update` extracts the expansion history as a graph node: ```bash -$(cat graphify-out/.graphify_python) -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 ``` Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with your full answer text (containing the expanded-token trace), `NODE1 NODE2` 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. @@ -188,13 +188,13 @@ At the **start** of graph work, refresh and read the lessons: run `graphify refl Find the shortest path between two named concepts in the graph. Prefer the CLI when installed: ```bash -graphify path "NODE_A" "NODE_B" +"$(cat graphify-out/.graphify_python)" -m graphify path "NODE_A" "NODE_B" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -246,7 +246,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) -m graphify save-result --question "Path from NODE_A to NODE_B" --answer "ANSWER" --type path_query --nodes NODE_A NODE_B +"$(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 ``` --- @@ -256,13 +256,13 @@ $(cat graphify-out/.graphify_python) -m graphify save-result --question "Path fr Give a plain-language explanation of a single node - everything connected to it. Prefer the CLI when installed: ```bash -graphify explain "NODE_NAME" +"$(cat graphify-out/.graphify_python)" -m graphify explain "NODE_NAME" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -307,5 +307,5 @@ 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) -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME ``` diff --git a/graphify/skills/claude/references/query.md b/graphify/skills/claude/references/query.md index ab059fac3..5ce535433 100644 --- a/graphify/skills/claude/references/query.md +++ b/graphify/skills/claude/references/query.md @@ -11,7 +11,7 @@ Two traversal modes - choose based on the question: First check the graph exists: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " from pathlib import Path if not Path('graphify-out/graph.json').exists(): print('ERROR: No graph found. Run /graphify first to build the graph.') @@ -28,7 +28,7 @@ Fix this **without inventing tokens** by expanding the query against the actual 1. Extract the token vocabulary from node labels: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, re from pathlib import Path data = json.loads(Path('graphify-out/graph.json').read_text(encoding='utf-8')) @@ -64,8 +64,8 @@ Build the **expanded query string** by joining the selected tokens with spaces. If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" -# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 +"$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" +# or: "$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: @@ -77,7 +77,7 @@ If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal 5. If the graph lacks enough information, say so - do not hallucinate edges. ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import sys, json from networkx.readwrite import json_graph import networkx as nx @@ -168,7 +168,7 @@ Replace `QUESTION` with the **expanded** query string, `MODE` with `bfs` or `dfs After writing the answer, save it back into the graph so it improves future queries. Include the expanded tokens inside the `--answer` text (e.g. `"Expanded from original query via vocab: [tokens]. Then traversed..."`) so the next `--update` extracts the expansion history as a graph node: ```bash -$(cat graphify-out/.graphify_python) -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 ``` Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with your full answer text (containing the expanded-token trace), `NODE1 NODE2` 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. @@ -188,13 +188,13 @@ At the **start** of graph work, refresh and read the lessons: run `graphify refl Find the shortest path between two named concepts in the graph. Prefer the CLI when installed: ```bash -graphify path "NODE_A" "NODE_B" +"$(cat graphify-out/.graphify_python)" -m graphify path "NODE_A" "NODE_B" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -246,7 +246,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) -m graphify save-result --question "Path from NODE_A to NODE_B" --answer "ANSWER" --type path_query --nodes NODE_A NODE_B +"$(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 ``` --- @@ -256,13 +256,13 @@ $(cat graphify-out/.graphify_python) -m graphify save-result --question "Path fr Give a plain-language explanation of a single node - everything connected to it. Prefer the CLI when installed: ```bash -graphify explain "NODE_NAME" +"$(cat graphify-out/.graphify_python)" -m graphify explain "NODE_NAME" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -307,5 +307,5 @@ 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) -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME ``` diff --git a/graphify/skills/claw/references/query.md b/graphify/skills/claw/references/query.md index ab059fac3..5ce535433 100644 --- a/graphify/skills/claw/references/query.md +++ b/graphify/skills/claw/references/query.md @@ -11,7 +11,7 @@ Two traversal modes - choose based on the question: First check the graph exists: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " from pathlib import Path if not Path('graphify-out/graph.json').exists(): print('ERROR: No graph found. Run /graphify first to build the graph.') @@ -28,7 +28,7 @@ Fix this **without inventing tokens** by expanding the query against the actual 1. Extract the token vocabulary from node labels: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, re from pathlib import Path data = json.loads(Path('graphify-out/graph.json').read_text(encoding='utf-8')) @@ -64,8 +64,8 @@ Build the **expanded query string** by joining the selected tokens with spaces. If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" -# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 +"$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" +# or: "$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: @@ -77,7 +77,7 @@ If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal 5. If the graph lacks enough information, say so - do not hallucinate edges. ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import sys, json from networkx.readwrite import json_graph import networkx as nx @@ -168,7 +168,7 @@ Replace `QUESTION` with the **expanded** query string, `MODE` with `bfs` or `dfs After writing the answer, save it back into the graph so it improves future queries. Include the expanded tokens inside the `--answer` text (e.g. `"Expanded from original query via vocab: [tokens]. Then traversed..."`) so the next `--update` extracts the expansion history as a graph node: ```bash -$(cat graphify-out/.graphify_python) -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 ``` Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with your full answer text (containing the expanded-token trace), `NODE1 NODE2` 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. @@ -188,13 +188,13 @@ At the **start** of graph work, refresh and read the lessons: run `graphify refl Find the shortest path between two named concepts in the graph. Prefer the CLI when installed: ```bash -graphify path "NODE_A" "NODE_B" +"$(cat graphify-out/.graphify_python)" -m graphify path "NODE_A" "NODE_B" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -246,7 +246,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) -m graphify save-result --question "Path from NODE_A to NODE_B" --answer "ANSWER" --type path_query --nodes NODE_A NODE_B +"$(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 ``` --- @@ -256,13 +256,13 @@ $(cat graphify-out/.graphify_python) -m graphify save-result --question "Path fr Give a plain-language explanation of a single node - everything connected to it. Prefer the CLI when installed: ```bash -graphify explain "NODE_NAME" +"$(cat graphify-out/.graphify_python)" -m graphify explain "NODE_NAME" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -307,5 +307,5 @@ 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) -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME ``` diff --git a/graphify/skills/codex/references/query.md b/graphify/skills/codex/references/query.md index ab059fac3..5ce535433 100644 --- a/graphify/skills/codex/references/query.md +++ b/graphify/skills/codex/references/query.md @@ -11,7 +11,7 @@ Two traversal modes - choose based on the question: First check the graph exists: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " from pathlib import Path if not Path('graphify-out/graph.json').exists(): print('ERROR: No graph found. Run /graphify first to build the graph.') @@ -28,7 +28,7 @@ Fix this **without inventing tokens** by expanding the query against the actual 1. Extract the token vocabulary from node labels: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, re from pathlib import Path data = json.loads(Path('graphify-out/graph.json').read_text(encoding='utf-8')) @@ -64,8 +64,8 @@ Build the **expanded query string** by joining the selected tokens with spaces. If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" -# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 +"$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" +# or: "$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: @@ -77,7 +77,7 @@ If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal 5. If the graph lacks enough information, say so - do not hallucinate edges. ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import sys, json from networkx.readwrite import json_graph import networkx as nx @@ -168,7 +168,7 @@ Replace `QUESTION` with the **expanded** query string, `MODE` with `bfs` or `dfs After writing the answer, save it back into the graph so it improves future queries. Include the expanded tokens inside the `--answer` text (e.g. `"Expanded from original query via vocab: [tokens]. Then traversed..."`) so the next `--update` extracts the expansion history as a graph node: ```bash -$(cat graphify-out/.graphify_python) -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 ``` Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with your full answer text (containing the expanded-token trace), `NODE1 NODE2` 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. @@ -188,13 +188,13 @@ At the **start** of graph work, refresh and read the lessons: run `graphify refl Find the shortest path between two named concepts in the graph. Prefer the CLI when installed: ```bash -graphify path "NODE_A" "NODE_B" +"$(cat graphify-out/.graphify_python)" -m graphify path "NODE_A" "NODE_B" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -246,7 +246,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) -m graphify save-result --question "Path from NODE_A to NODE_B" --answer "ANSWER" --type path_query --nodes NODE_A NODE_B +"$(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 ``` --- @@ -256,13 +256,13 @@ $(cat graphify-out/.graphify_python) -m graphify save-result --question "Path fr Give a plain-language explanation of a single node - everything connected to it. Prefer the CLI when installed: ```bash -graphify explain "NODE_NAME" +"$(cat graphify-out/.graphify_python)" -m graphify explain "NODE_NAME" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -307,5 +307,5 @@ 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) -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME ``` diff --git a/graphify/skills/copilot/references/query.md b/graphify/skills/copilot/references/query.md index ab059fac3..5ce535433 100644 --- a/graphify/skills/copilot/references/query.md +++ b/graphify/skills/copilot/references/query.md @@ -11,7 +11,7 @@ Two traversal modes - choose based on the question: First check the graph exists: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " from pathlib import Path if not Path('graphify-out/graph.json').exists(): print('ERROR: No graph found. Run /graphify first to build the graph.') @@ -28,7 +28,7 @@ Fix this **without inventing tokens** by expanding the query against the actual 1. Extract the token vocabulary from node labels: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, re from pathlib import Path data = json.loads(Path('graphify-out/graph.json').read_text(encoding='utf-8')) @@ -64,8 +64,8 @@ Build the **expanded query string** by joining the selected tokens with spaces. If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" -# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 +"$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" +# or: "$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: @@ -77,7 +77,7 @@ If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal 5. If the graph lacks enough information, say so - do not hallucinate edges. ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import sys, json from networkx.readwrite import json_graph import networkx as nx @@ -168,7 +168,7 @@ Replace `QUESTION` with the **expanded** query string, `MODE` with `bfs` or `dfs After writing the answer, save it back into the graph so it improves future queries. Include the expanded tokens inside the `--answer` text (e.g. `"Expanded from original query via vocab: [tokens]. Then traversed..."`) so the next `--update` extracts the expansion history as a graph node: ```bash -$(cat graphify-out/.graphify_python) -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 ``` Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with your full answer text (containing the expanded-token trace), `NODE1 NODE2` 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. @@ -188,13 +188,13 @@ At the **start** of graph work, refresh and read the lessons: run `graphify refl Find the shortest path between two named concepts in the graph. Prefer the CLI when installed: ```bash -graphify path "NODE_A" "NODE_B" +"$(cat graphify-out/.graphify_python)" -m graphify path "NODE_A" "NODE_B" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -246,7 +246,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) -m graphify save-result --question "Path from NODE_A to NODE_B" --answer "ANSWER" --type path_query --nodes NODE_A NODE_B +"$(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 ``` --- @@ -256,13 +256,13 @@ $(cat graphify-out/.graphify_python) -m graphify save-result --question "Path fr Give a plain-language explanation of a single node - everything connected to it. Prefer the CLI when installed: ```bash -graphify explain "NODE_NAME" +"$(cat graphify-out/.graphify_python)" -m graphify explain "NODE_NAME" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -307,5 +307,5 @@ 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) -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME ``` diff --git a/graphify/skills/droid/references/query.md b/graphify/skills/droid/references/query.md index ab059fac3..5ce535433 100644 --- a/graphify/skills/droid/references/query.md +++ b/graphify/skills/droid/references/query.md @@ -11,7 +11,7 @@ Two traversal modes - choose based on the question: First check the graph exists: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " from pathlib import Path if not Path('graphify-out/graph.json').exists(): print('ERROR: No graph found. Run /graphify first to build the graph.') @@ -28,7 +28,7 @@ Fix this **without inventing tokens** by expanding the query against the actual 1. Extract the token vocabulary from node labels: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, re from pathlib import Path data = json.loads(Path('graphify-out/graph.json').read_text(encoding='utf-8')) @@ -64,8 +64,8 @@ Build the **expanded query string** by joining the selected tokens with spaces. If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" -# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 +"$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" +# or: "$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: @@ -77,7 +77,7 @@ If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal 5. If the graph lacks enough information, say so - do not hallucinate edges. ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import sys, json from networkx.readwrite import json_graph import networkx as nx @@ -168,7 +168,7 @@ Replace `QUESTION` with the **expanded** query string, `MODE` with `bfs` or `dfs After writing the answer, save it back into the graph so it improves future queries. Include the expanded tokens inside the `--answer` text (e.g. `"Expanded from original query via vocab: [tokens]. Then traversed..."`) so the next `--update` extracts the expansion history as a graph node: ```bash -$(cat graphify-out/.graphify_python) -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 ``` Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with your full answer text (containing the expanded-token trace), `NODE1 NODE2` 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. @@ -188,13 +188,13 @@ At the **start** of graph work, refresh and read the lessons: run `graphify refl Find the shortest path between two named concepts in the graph. Prefer the CLI when installed: ```bash -graphify path "NODE_A" "NODE_B" +"$(cat graphify-out/.graphify_python)" -m graphify path "NODE_A" "NODE_B" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -246,7 +246,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) -m graphify save-result --question "Path from NODE_A to NODE_B" --answer "ANSWER" --type path_query --nodes NODE_A NODE_B +"$(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 ``` --- @@ -256,13 +256,13 @@ $(cat graphify-out/.graphify_python) -m graphify save-result --question "Path fr Give a plain-language explanation of a single node - everything connected to it. Prefer the CLI when installed: ```bash -graphify explain "NODE_NAME" +"$(cat graphify-out/.graphify_python)" -m graphify explain "NODE_NAME" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -307,5 +307,5 @@ 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) -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME ``` diff --git a/graphify/skills/kilo/references/query.md b/graphify/skills/kilo/references/query.md index ab059fac3..5ce535433 100644 --- a/graphify/skills/kilo/references/query.md +++ b/graphify/skills/kilo/references/query.md @@ -11,7 +11,7 @@ Two traversal modes - choose based on the question: First check the graph exists: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " from pathlib import Path if not Path('graphify-out/graph.json').exists(): print('ERROR: No graph found. Run /graphify first to build the graph.') @@ -28,7 +28,7 @@ Fix this **without inventing tokens** by expanding the query against the actual 1. Extract the token vocabulary from node labels: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, re from pathlib import Path data = json.loads(Path('graphify-out/graph.json').read_text(encoding='utf-8')) @@ -64,8 +64,8 @@ Build the **expanded query string** by joining the selected tokens with spaces. If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" -# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 +"$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" +# or: "$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: @@ -77,7 +77,7 @@ If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal 5. If the graph lacks enough information, say so - do not hallucinate edges. ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import sys, json from networkx.readwrite import json_graph import networkx as nx @@ -168,7 +168,7 @@ Replace `QUESTION` with the **expanded** query string, `MODE` with `bfs` or `dfs After writing the answer, save it back into the graph so it improves future queries. Include the expanded tokens inside the `--answer` text (e.g. `"Expanded from original query via vocab: [tokens]. Then traversed..."`) so the next `--update` extracts the expansion history as a graph node: ```bash -$(cat graphify-out/.graphify_python) -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 ``` Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with your full answer text (containing the expanded-token trace), `NODE1 NODE2` 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. @@ -188,13 +188,13 @@ At the **start** of graph work, refresh and read the lessons: run `graphify refl Find the shortest path between two named concepts in the graph. Prefer the CLI when installed: ```bash -graphify path "NODE_A" "NODE_B" +"$(cat graphify-out/.graphify_python)" -m graphify path "NODE_A" "NODE_B" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -246,7 +246,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) -m graphify save-result --question "Path from NODE_A to NODE_B" --answer "ANSWER" --type path_query --nodes NODE_A NODE_B +"$(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 ``` --- @@ -256,13 +256,13 @@ $(cat graphify-out/.graphify_python) -m graphify save-result --question "Path fr Give a plain-language explanation of a single node - everything connected to it. Prefer the CLI when installed: ```bash -graphify explain "NODE_NAME" +"$(cat graphify-out/.graphify_python)" -m graphify explain "NODE_NAME" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -307,5 +307,5 @@ 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) -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME ``` diff --git a/graphify/skills/kiro/references/query.md b/graphify/skills/kiro/references/query.md index ab059fac3..5ce535433 100644 --- a/graphify/skills/kiro/references/query.md +++ b/graphify/skills/kiro/references/query.md @@ -11,7 +11,7 @@ Two traversal modes - choose based on the question: First check the graph exists: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " from pathlib import Path if not Path('graphify-out/graph.json').exists(): print('ERROR: No graph found. Run /graphify first to build the graph.') @@ -28,7 +28,7 @@ Fix this **without inventing tokens** by expanding the query against the actual 1. Extract the token vocabulary from node labels: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, re from pathlib import Path data = json.loads(Path('graphify-out/graph.json').read_text(encoding='utf-8')) @@ -64,8 +64,8 @@ Build the **expanded query string** by joining the selected tokens with spaces. If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" -# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 +"$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" +# or: "$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: @@ -77,7 +77,7 @@ If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal 5. If the graph lacks enough information, say so - do not hallucinate edges. ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import sys, json from networkx.readwrite import json_graph import networkx as nx @@ -168,7 +168,7 @@ Replace `QUESTION` with the **expanded** query string, `MODE` with `bfs` or `dfs After writing the answer, save it back into the graph so it improves future queries. Include the expanded tokens inside the `--answer` text (e.g. `"Expanded from original query via vocab: [tokens]. Then traversed..."`) so the next `--update` extracts the expansion history as a graph node: ```bash -$(cat graphify-out/.graphify_python) -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 ``` Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with your full answer text (containing the expanded-token trace), `NODE1 NODE2` 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. @@ -188,13 +188,13 @@ At the **start** of graph work, refresh and read the lessons: run `graphify refl Find the shortest path between two named concepts in the graph. Prefer the CLI when installed: ```bash -graphify path "NODE_A" "NODE_B" +"$(cat graphify-out/.graphify_python)" -m graphify path "NODE_A" "NODE_B" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -246,7 +246,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) -m graphify save-result --question "Path from NODE_A to NODE_B" --answer "ANSWER" --type path_query --nodes NODE_A NODE_B +"$(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 ``` --- @@ -256,13 +256,13 @@ $(cat graphify-out/.graphify_python) -m graphify save-result --question "Path fr Give a plain-language explanation of a single node - everything connected to it. Prefer the CLI when installed: ```bash -graphify explain "NODE_NAME" +"$(cat graphify-out/.graphify_python)" -m graphify explain "NODE_NAME" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -307,5 +307,5 @@ 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) -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME ``` diff --git a/graphify/skills/opencode/references/query.md b/graphify/skills/opencode/references/query.md index ab059fac3..5ce535433 100644 --- a/graphify/skills/opencode/references/query.md +++ b/graphify/skills/opencode/references/query.md @@ -11,7 +11,7 @@ Two traversal modes - choose based on the question: First check the graph exists: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " from pathlib import Path if not Path('graphify-out/graph.json').exists(): print('ERROR: No graph found. Run /graphify first to build the graph.') @@ -28,7 +28,7 @@ Fix this **without inventing tokens** by expanding the query against the actual 1. Extract the token vocabulary from node labels: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, re from pathlib import Path data = json.loads(Path('graphify-out/graph.json').read_text(encoding='utf-8')) @@ -64,8 +64,8 @@ Build the **expanded query string** by joining the selected tokens with spaces. If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" -# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 +"$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" +# or: "$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: @@ -77,7 +77,7 @@ If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal 5. If the graph lacks enough information, say so - do not hallucinate edges. ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import sys, json from networkx.readwrite import json_graph import networkx as nx @@ -168,7 +168,7 @@ Replace `QUESTION` with the **expanded** query string, `MODE` with `bfs` or `dfs After writing the answer, save it back into the graph so it improves future queries. Include the expanded tokens inside the `--answer` text (e.g. `"Expanded from original query via vocab: [tokens]. Then traversed..."`) so the next `--update` extracts the expansion history as a graph node: ```bash -$(cat graphify-out/.graphify_python) -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 ``` Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with your full answer text (containing the expanded-token trace), `NODE1 NODE2` 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. @@ -188,13 +188,13 @@ At the **start** of graph work, refresh and read the lessons: run `graphify refl Find the shortest path between two named concepts in the graph. Prefer the CLI when installed: ```bash -graphify path "NODE_A" "NODE_B" +"$(cat graphify-out/.graphify_python)" -m graphify path "NODE_A" "NODE_B" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -246,7 +246,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) -m graphify save-result --question "Path from NODE_A to NODE_B" --answer "ANSWER" --type path_query --nodes NODE_A NODE_B +"$(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 ``` --- @@ -256,13 +256,13 @@ $(cat graphify-out/.graphify_python) -m graphify save-result --question "Path fr Give a plain-language explanation of a single node - everything connected to it. Prefer the CLI when installed: ```bash -graphify explain "NODE_NAME" +"$(cat graphify-out/.graphify_python)" -m graphify explain "NODE_NAME" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -307,5 +307,5 @@ 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) -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME ``` diff --git a/graphify/skills/pi/references/query.md b/graphify/skills/pi/references/query.md index ab059fac3..5ce535433 100644 --- a/graphify/skills/pi/references/query.md +++ b/graphify/skills/pi/references/query.md @@ -11,7 +11,7 @@ Two traversal modes - choose based on the question: First check the graph exists: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " from pathlib import Path if not Path('graphify-out/graph.json').exists(): print('ERROR: No graph found. Run /graphify first to build the graph.') @@ -28,7 +28,7 @@ Fix this **without inventing tokens** by expanding the query against the actual 1. Extract the token vocabulary from node labels: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, re from pathlib import Path data = json.loads(Path('graphify-out/graph.json').read_text(encoding='utf-8')) @@ -64,8 +64,8 @@ Build the **expanded query string** by joining the selected tokens with spaces. If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" -# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 +"$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" +# or: "$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: @@ -77,7 +77,7 @@ If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal 5. If the graph lacks enough information, say so - do not hallucinate edges. ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import sys, json from networkx.readwrite import json_graph import networkx as nx @@ -168,7 +168,7 @@ Replace `QUESTION` with the **expanded** query string, `MODE` with `bfs` or `dfs After writing the answer, save it back into the graph so it improves future queries. Include the expanded tokens inside the `--answer` text (e.g. `"Expanded from original query via vocab: [tokens]. Then traversed..."`) so the next `--update` extracts the expansion history as a graph node: ```bash -$(cat graphify-out/.graphify_python) -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 ``` Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with your full answer text (containing the expanded-token trace), `NODE1 NODE2` 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. @@ -188,13 +188,13 @@ At the **start** of graph work, refresh and read the lessons: run `graphify refl Find the shortest path between two named concepts in the graph. Prefer the CLI when installed: ```bash -graphify path "NODE_A" "NODE_B" +"$(cat graphify-out/.graphify_python)" -m graphify path "NODE_A" "NODE_B" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -246,7 +246,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) -m graphify save-result --question "Path from NODE_A to NODE_B" --answer "ANSWER" --type path_query --nodes NODE_A NODE_B +"$(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 ``` --- @@ -256,13 +256,13 @@ $(cat graphify-out/.graphify_python) -m graphify save-result --question "Path fr Give a plain-language explanation of a single node - everything connected to it. Prefer the CLI when installed: ```bash -graphify explain "NODE_NAME" +"$(cat graphify-out/.graphify_python)" -m graphify explain "NODE_NAME" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -307,5 +307,5 @@ 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) -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME ``` diff --git a/graphify/skills/trae/references/query.md b/graphify/skills/trae/references/query.md index ab059fac3..5ce535433 100644 --- a/graphify/skills/trae/references/query.md +++ b/graphify/skills/trae/references/query.md @@ -11,7 +11,7 @@ Two traversal modes - choose based on the question: First check the graph exists: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " from pathlib import Path if not Path('graphify-out/graph.json').exists(): print('ERROR: No graph found. Run /graphify first to build the graph.') @@ -28,7 +28,7 @@ Fix this **without inventing tokens** by expanding the query against the actual 1. Extract the token vocabulary from node labels: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, re from pathlib import Path data = json.loads(Path('graphify-out/graph.json').read_text(encoding='utf-8')) @@ -64,8 +64,8 @@ Build the **expanded query string** by joining the selected tokens with spaces. If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" -# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 +"$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" +# or: "$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: @@ -77,7 +77,7 @@ If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal 5. If the graph lacks enough information, say so - do not hallucinate edges. ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import sys, json from networkx.readwrite import json_graph import networkx as nx @@ -168,7 +168,7 @@ Replace `QUESTION` with the **expanded** query string, `MODE` with `bfs` or `dfs After writing the answer, save it back into the graph so it improves future queries. Include the expanded tokens inside the `--answer` text (e.g. `"Expanded from original query via vocab: [tokens]. Then traversed..."`) so the next `--update` extracts the expansion history as a graph node: ```bash -$(cat graphify-out/.graphify_python) -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 ``` Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with your full answer text (containing the expanded-token trace), `NODE1 NODE2` 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. @@ -188,13 +188,13 @@ At the **start** of graph work, refresh and read the lessons: run `graphify refl Find the shortest path between two named concepts in the graph. Prefer the CLI when installed: ```bash -graphify path "NODE_A" "NODE_B" +"$(cat graphify-out/.graphify_python)" -m graphify path "NODE_A" "NODE_B" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -246,7 +246,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) -m graphify save-result --question "Path from NODE_A to NODE_B" --answer "ANSWER" --type path_query --nodes NODE_A NODE_B +"$(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 ``` --- @@ -256,13 +256,13 @@ $(cat graphify-out/.graphify_python) -m graphify save-result --question "Path fr Give a plain-language explanation of a single node - everything connected to it. Prefer the CLI when installed: ```bash -graphify explain "NODE_NAME" +"$(cat graphify-out/.graphify_python)" -m graphify explain "NODE_NAME" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -307,5 +307,5 @@ 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) -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME ``` diff --git a/graphify/skills/vscode/references/query.md b/graphify/skills/vscode/references/query.md index ab059fac3..5ce535433 100644 --- a/graphify/skills/vscode/references/query.md +++ b/graphify/skills/vscode/references/query.md @@ -11,7 +11,7 @@ Two traversal modes - choose based on the question: First check the graph exists: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " from pathlib import Path if not Path('graphify-out/graph.json').exists(): print('ERROR: No graph found. Run /graphify first to build the graph.') @@ -28,7 +28,7 @@ Fix this **without inventing tokens** by expanding the query against the actual 1. Extract the token vocabulary from node labels: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, re from pathlib import Path data = json.loads(Path('graphify-out/graph.json').read_text(encoding='utf-8')) @@ -64,8 +64,8 @@ Build the **expanded query string** by joining the selected tokens with spaces. If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" -# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 +"$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" +# or: "$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: @@ -77,7 +77,7 @@ If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal 5. If the graph lacks enough information, say so - do not hallucinate edges. ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import sys, json from networkx.readwrite import json_graph import networkx as nx @@ -168,7 +168,7 @@ Replace `QUESTION` with the **expanded** query string, `MODE` with `bfs` or `dfs After writing the answer, save it back into the graph so it improves future queries. Include the expanded tokens inside the `--answer` text (e.g. `"Expanded from original query via vocab: [tokens]. Then traversed..."`) so the next `--update` extracts the expansion history as a graph node: ```bash -$(cat graphify-out/.graphify_python) -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 ``` Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with your full answer text (containing the expanded-token trace), `NODE1 NODE2` 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. @@ -188,13 +188,13 @@ At the **start** of graph work, refresh and read the lessons: run `graphify refl Find the shortest path between two named concepts in the graph. Prefer the CLI when installed: ```bash -graphify path "NODE_A" "NODE_B" +"$(cat graphify-out/.graphify_python)" -m graphify path "NODE_A" "NODE_B" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -246,7 +246,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) -m graphify save-result --question "Path from NODE_A to NODE_B" --answer "ANSWER" --type path_query --nodes NODE_A NODE_B +"$(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 ``` --- @@ -256,13 +256,13 @@ $(cat graphify-out/.graphify_python) -m graphify save-result --question "Path fr Give a plain-language explanation of a single node - everything connected to it. Prefer the CLI when installed: ```bash -graphify explain "NODE_NAME" +"$(cat graphify-out/.graphify_python)" -m graphify explain "NODE_NAME" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -307,5 +307,5 @@ 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) -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME ``` diff --git a/graphify/skills/windows/references/query.md b/graphify/skills/windows/references/query.md index ab059fac3..5ce535433 100644 --- a/graphify/skills/windows/references/query.md +++ b/graphify/skills/windows/references/query.md @@ -11,7 +11,7 @@ Two traversal modes - choose based on the question: First check the graph exists: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " from pathlib import Path if not Path('graphify-out/graph.json').exists(): print('ERROR: No graph found. Run /graphify first to build the graph.') @@ -28,7 +28,7 @@ Fix this **without inventing tokens** by expanding the query against the actual 1. Extract the token vocabulary from node labels: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, re from pathlib import Path data = json.loads(Path('graphify-out/graph.json').read_text(encoding='utf-8')) @@ -64,8 +64,8 @@ Build the **expanded query string** by joining the selected tokens with spaces. If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" -# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 +"$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" +# or: "$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: @@ -77,7 +77,7 @@ If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal 5. If the graph lacks enough information, say so - do not hallucinate edges. ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import sys, json from networkx.readwrite import json_graph import networkx as nx @@ -168,7 +168,7 @@ Replace `QUESTION` with the **expanded** query string, `MODE` with `bfs` or `dfs After writing the answer, save it back into the graph so it improves future queries. Include the expanded tokens inside the `--answer` text (e.g. `"Expanded from original query via vocab: [tokens]. Then traversed..."`) so the next `--update` extracts the expansion history as a graph node: ```bash -$(cat graphify-out/.graphify_python) -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 ``` Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with your full answer text (containing the expanded-token trace), `NODE1 NODE2` 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. @@ -188,13 +188,13 @@ At the **start** of graph work, refresh and read the lessons: run `graphify refl Find the shortest path between two named concepts in the graph. Prefer the CLI when installed: ```bash -graphify path "NODE_A" "NODE_B" +"$(cat graphify-out/.graphify_python)" -m graphify path "NODE_A" "NODE_B" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -246,7 +246,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) -m graphify save-result --question "Path from NODE_A to NODE_B" --answer "ANSWER" --type path_query --nodes NODE_A NODE_B +"$(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 ``` --- @@ -256,13 +256,13 @@ $(cat graphify-out/.graphify_python) -m graphify save-result --question "Path fr Give a plain-language explanation of a single node - everything connected to it. Prefer the CLI when installed: ```bash -graphify explain "NODE_NAME" +"$(cat graphify-out/.graphify_python)" -m graphify explain "NODE_NAME" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -307,5 +307,5 @@ 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) -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME ``` diff --git a/tests/test_hook_guard.py b/tests/test_hook_guard.py index 486b21ed7..869f7ccb8 100644 --- a/tests/test_hook_guard.py +++ b/tests/test_hook_guard.py @@ -210,7 +210,6 @@ def test_gemini_allow_with_nudge(tmp_path, monkeypatch): payload = json.loads(out) assert payload["decision"] == "allow" assert "graphify query" in payload["additionalContext"] - assert "MCP `query_graph`" in payload["additionalContext"] def test_gemini_allow_without_graph(tmp_path, monkeypatch): diff --git a/tests/test_hook_strict.py b/tests/test_hook_strict.py index b00dcea64..c9454aeb8 100644 --- a/tests/test_hook_strict.py +++ b/tests/test_hook_strict.py @@ -67,7 +67,6 @@ def test_strict_first_read_denies_then_nudges(tmp_path, monkeypatch): out1 = _invoke("read", _read(f), tmp_path, monkeypatch, strict=True) assert _is_deny(out1) assert "graphify query" in json.loads(out1)["hookSpecificOutput"]["permissionDecisionReason"] - assert "MCP `query_graph`" in json.loads(out1)["hookSpecificOutput"]["permissionDecisionReason"] # marker created assert (tmp_path / "graphify-out" / "cache" / "hook_sessions" / "s1.denied").exists() # same session again -> soft nudge, not a second deny diff --git a/tests/test_read_hook.py b/tests/test_read_hook.py index 56793a615..a260021cb 100644 --- a/tests/test_read_hook.py +++ b/tests/test_read_hook.py @@ -44,11 +44,13 @@ def test_matcher_targets_read_and_glob(): def test_command_has_no_shell_syntax(): - # #522: the command must be a plain exe invocation, not POSIX bash. + # Claude runs `command` through a POSIX shell (Git Bash on Windows), while + # Codex uses its separate commandWindows field. Keep the body simple and + # require the one fail-open operator added by #3280. cmd = _read_matcher()["hooks"][0]["command"] - for token in ("$(", "case ", "[ -f", "&&", "||", ";;", "echo '"): + for token in ("$(", "case ", "[ -f", "&&", ";;", "echo '"): assert token not in cmd, f"shell syntax {token!r} leaked into the hook" - assert "graphify" in cmd and "hook-guard read" in cmd + assert "graphify" in cmd and cmd.endswith("hook-guard read || true") def test_silent_without_graph(tmp_path): @@ -66,7 +68,6 @@ def test_nudge_payload_is_valid_pretooluse_json(tmp_path): payload = json.loads(out) assert payload["hookSpecificOutput"]["hookEventName"] == "PreToolUse" assert "graphify query" in payload["hookSpecificOutput"]["additionalContext"] - assert "MCP `query_graph`" in payload["hookSpecificOutput"]["additionalContext"] def test_silent_on_graphify_out_targets(tmp_path): diff --git a/tests/test_search_hook.py b/tests/test_search_hook.py index 3b5916b9c..10e54a9f5 100644 --- a/tests/test_search_hook.py +++ b/tests/test_search_hook.py @@ -66,11 +66,13 @@ def test_hook_command_has_no_backslashes(monkeypatch): def test_command_has_no_shell_syntax(): - # #522: no POSIX bash that Windows cmd.exe/PowerShell can't parse. + # Claude runs `command` through a POSIX shell (Git Bash on Windows), while + # Codex uses its separate commandWindows field. Keep the body simple and + # require the one fail-open operator added by #3280. cmd = _search_matcher()["hooks"][0]["command"] - for token in ("$(", "case ", "[ -f", "&&", "||", ";;", "echo '"): + for token in ("$(", "case ", "[ -f", "&&", ";;", "echo '"): assert token not in cmd, f"shell syntax {token!r} leaked into the hook" - assert "graphify" in cmd and "hook-guard search" in cmd + assert "graphify" in cmd and cmd.endswith("hook-guard search || true") def test_nudges_on_search_commands_with_graph(tmp_path): @@ -103,7 +105,6 @@ def test_nudge_payload_is_valid_pretooluse_json(tmp_path): payload = json.loads(out) assert payload["hookSpecificOutput"]["hookEventName"] == "PreToolUse" assert "graphify query" in payload["hookSpecificOutput"]["additionalContext"] - assert "MCP `query_graph`" in payload["hookSpecificOutput"]["additionalContext"] def test_fails_open_on_malformed_stdin(tmp_path): diff --git a/tests/test_skillgen.py b/tests/test_skillgen.py index 18bd93254..a25d155ca 100644 --- a/tests/test_skillgen.py +++ b/tests/test_skillgen.py @@ -9,6 +9,7 @@ from __future__ import annotations import sys +import re from pathlib import Path import pytest @@ -63,9 +64,15 @@ def test_query_guidance_prefers_mcp_and_never_runs_windows_console_shim(): for key in ("claude", "codex", "windows", "opencode"): core, refs = _platform_artifacts(key) query = refs["query.md"] - assert "Prefer the MCP `query_graph` tool when it is available" in core, key - assert "Prefer the MCP `query_graph` tool when it is available" in query, key + assert "query_graph" in core, key + assert "query_graph" in query, key assert "never execute a `graphify.exe` console shim" in query, key + guidance = core + "\n" + query + assert not re.search( + r"`graphify (?:query|path|explain) (?=[\"<])|^(?:# or: )?graphify (?:query|path|explain) (?=[\"<])", + guidance, + re.MULTILINE, + ), key artifacts = gen.render_all(gen.load_platforms()) antigravity = next( @@ -73,7 +80,8 @@ def test_query_guidance_prefers_mcp_and_never_runs_windows_console_shim(): for artifact in artifacts if artifact.path == "graphify/always_on/antigravity-rules.md" ) - assert "Prefer the MCP `query_graph` tool when it is available" in antigravity + assert "query_graph" in antigravity + assert not re.search(r"`graphify (?:query|path|explain) (?=[\"<])", antigravity) def test_no_version_or_timestamp_in_output(): @@ -768,15 +776,15 @@ def test_always_on_roundtrip_is_byte_faithful(): "When the user types `/graphify`, use the installed graphify skill or instructions " "before doing anything else." ) - # The sanctioned-edit registry holds exactly this single old->new substitution. - assert gen.ALWAYS_ON_SANCTIONED_EDITS["_AGENTS_MD_SECTION"] == ( - (old_instruction, new_instruction), - ) + assert (old_instruction, new_instruction) in gen.ALWAYS_ON_SANCTIONED_EDITS["_AGENTS_MD_SECTION"] baseline_agents = gen._always_on_constants(gen.ALWAYS_ON_BASELINE_REF)["_AGENTS_MD_SECTION"] - # The ONLY divergence from the frozen baseline is the sanctioned sentence — - # any other byte drift would have surfaced as a problem above. + # Every divergence from the frozen baseline must be named in the sanctioned + # edit registry; applying that registry must reproduce the rendered bytes. assert old_instruction in baseline_agents - assert baseline_agents.replace(old_instruction, new_instruction) == rendered_agents + expected_agents = baseline_agents + for old, new in gen.ALWAYS_ON_SANCTIONED_EDITS["_AGENTS_MD_SECTION"]: + expected_agents = expected_agents.replace(old, new) + assert expected_agents == rendered_agents assert "`skill` tool" not in rendered_agents assert 'skill: "graphify"' not in rendered_agents diff --git a/tools/skillgen/expected/graphify__always_on__agents-md.md b/tools/skillgen/expected/graphify__always_on__agents-md.md index 6da0539c1..8bea68aa0 100644 --- a/tools/skillgen/expected/graphify__always_on__agents-md.md +++ b/tools/skillgen/expected/graphify__always_on__agents-md.md @@ -5,7 +5,7 @@ This project has a knowledge graph at graphify-out/ with god nodes, community st When the user types `/graphify`, use the installed graphify skill or instructions before doing anything else. Rules: -- For codebase questions, first use the graph when graphify-out/graph.json exists. Prefer the MCP `query_graph` tool when it is available; otherwise follow the installed Graphify skill's interpreter-based CLI fallback. Use `shortest_path` / `get_node` (or their module-invocation CLI equivalents) for relationships and focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. +- For codebase questions, first use the graph when graphify-out/graph.json exists. Prefer the MCP `query_graph` tool. CLI fallback: `"$(cat graphify-out/.graphify_python)" -m graphify query ""` (PowerShell: `& (Get-Content graphify-out\.graphify_python) -m graphify query ""`); use the same recorded interpreter with `path` or `explain` for relationships and focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. - Dirty graphify-out/ files are expected after hooks or incremental updates; dirty graph files are not a reason to skip graphify. Only skip graphify if the task is about stale or incorrect graph output, or the user explicitly says not to use it. - If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing. - Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context. diff --git a/tools/skillgen/expected/graphify__always_on__antigravity-rules.md b/tools/skillgen/expected/graphify__always_on__antigravity-rules.md index e6b109acc..baf9f98e4 100644 --- a/tools/skillgen/expected/graphify__always_on__antigravity-rules.md +++ b/tools/skillgen/expected/graphify__always_on__antigravity-rules.md @@ -8,7 +8,7 @@ description: Consult the graphify knowledge graph at graphify-out/ for codebase This project has a graphify knowledge graph at graphify-out/. Rules: -- For codebase or architecture questions, when `graphify-out/graph.json` exists, first use the graph. Prefer the MCP `query_graph` tool when it is available; otherwise follow the installed Graphify skill's interpreter-based CLI fallback. Use `shortest_path` / `get_node` (or their module-invocation CLI equivalents) for relationships and focused concepts. These return a scoped subgraph, usually much smaller than `GRAPH_REPORT.md` or raw grep output. +- For codebase or architecture questions, when `graphify-out/graph.json` exists, first use the graph. Prefer the MCP `query_graph` tool. CLI fallback: `"$(cat graphify-out/.graphify_python)" -m graphify query ""` (PowerShell: `& (Get-Content graphify-out\.graphify_python) -m graphify query ""`); use the same recorded interpreter with `path` or `explain` for relationships and focused concepts. These return a scoped subgraph, usually much smaller than `GRAPH_REPORT.md` or raw grep output. - If graphify-out/wiki/index.md exists, navigate it instead of reading raw files - Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context - After modifying code files in this session, run `graphify update .` to keep the graph current (AST-only, no API cost) diff --git a/tools/skillgen/expected/graphify__always_on__claude-md.md b/tools/skillgen/expected/graphify__always_on__claude-md.md index b62763ded..d1928c7a2 100644 --- a/tools/skillgen/expected/graphify__always_on__claude-md.md +++ b/tools/skillgen/expected/graphify__always_on__claude-md.md @@ -3,7 +3,7 @@ This project has a knowledge graph at graphify-out/ with god nodes, community structure, and cross-file relationships. Rules: -- For codebase questions, first use the graph when graphify-out/graph.json exists. Prefer the MCP `query_graph` tool when it is available; otherwise follow the installed Graphify skill's interpreter-based CLI fallback. Use `shortest_path` / `get_node` (or their module-invocation CLI equivalents) for relationships and focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. +- For codebase questions, first use the graph when graphify-out/graph.json exists. Prefer the MCP `query_graph` tool. CLI fallback: `"$(cat graphify-out/.graphify_python)" -m graphify query ""` (PowerShell: `& (Get-Content graphify-out\.graphify_python) -m graphify query ""`); use the same recorded interpreter with `path` or `explain` for relationships and focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. - If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing. - Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context. - After modifying code, run `graphify update .` to keep the graph current (AST-only, no API cost). diff --git a/tools/skillgen/expected/graphify__always_on__gemini-md.md b/tools/skillgen/expected/graphify__always_on__gemini-md.md index 417efeb27..d1928c7a2 100644 --- a/tools/skillgen/expected/graphify__always_on__gemini-md.md +++ b/tools/skillgen/expected/graphify__always_on__gemini-md.md @@ -3,7 +3,7 @@ This project has a knowledge graph at graphify-out/ with god nodes, community structure, and cross-file relationships. Rules: -- For codebase questions, first run `graphify query ""` when graphify-out/graph.json exists. Use `graphify path "" ""` for relationships and `graphify explain ""` for focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. +- For codebase questions, first use the graph when graphify-out/graph.json exists. Prefer the MCP `query_graph` tool. CLI fallback: `"$(cat graphify-out/.graphify_python)" -m graphify query ""` (PowerShell: `& (Get-Content graphify-out\.graphify_python) -m graphify query ""`); use the same recorded interpreter with `path` or `explain` for relationships and focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. - If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing. - Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context. - After modifying code, run `graphify update .` to keep the graph current (AST-only, no API cost). diff --git a/tools/skillgen/expected/graphify__always_on__kiro-steering.md b/tools/skillgen/expected/graphify__always_on__kiro-steering.md index cb6f4543d..87a8e8dea 100644 --- a/tools/skillgen/expected/graphify__always_on__kiro-steering.md +++ b/tools/skillgen/expected/graphify__always_on__kiro-steering.md @@ -2,4 +2,4 @@ inclusion: always --- -graphify: A knowledge graph of this project lives in `graphify-out/`. For codebase, architecture, or dependency questions, when `graphify-out/graph.json` exists, first run `graphify query ""` (or `graphify path "" ""` / `graphify explain ""`). These return a scoped subgraph, usually much smaller than `GRAPH_REPORT.md` or raw grep output. Read `GRAPH_REPORT.md` only for broad architecture review or when those commands do not surface enough context. +graphify: A knowledge graph of this project lives in `graphify-out/`. For codebase, architecture, or dependency questions, when `graphify-out/graph.json` exists, prefer the MCP `query_graph` tool. CLI fallback: `"$(cat graphify-out/.graphify_python)" -m graphify query ""` (PowerShell: `& (Get-Content graphify-out\.graphify_python) -m graphify query ""`); use the same recorded interpreter with `path` or `explain`. These return a scoped subgraph, usually much smaller than `GRAPH_REPORT.md` or raw grep output. Read `GRAPH_REPORT.md` only for broad architecture review or when those commands do not surface enough context. diff --git a/tools/skillgen/expected/graphify__always_on__vscode-instructions.md b/tools/skillgen/expected/graphify__always_on__vscode-instructions.md index 9cb983c95..5f9181c1b 100644 --- a/tools/skillgen/expected/graphify__always_on__vscode-instructions.md +++ b/tools/skillgen/expected/graphify__always_on__vscode-instructions.md @@ -1,10 +1,11 @@ ## graphify For any question about this repo's architecture, structure, components, or how to add/modify/find -code, your first action should be `graphify query ""` when `graphify-out/graph.json` -exists. Use `graphify path "" ""` for relationship questions and `graphify explain ""` -for focused-concept questions. These return a scoped subgraph, usually much smaller than the full -report or raw grep output. +code, first use the graph when `graphify-out/graph.json` exists. Prefer the MCP `query_graph` tool. +CLI fallback: `"$(cat graphify-out/.graphify_python)" -m graphify query ""` (PowerShell: +`& (Get-Content graphify-out\.graphify_python) -m graphify query ""`); use the same recorded +interpreter with `path` or `explain`. These return a scoped subgraph, usually much smaller than the +full report or raw grep output. Triggers: "how do I…", "where is…", "what does … do", "add/modify a ", "explain the architecture", or anything that depends on how files or classes relate. diff --git a/tools/skillgen/expected/graphify__skill-agents.md b/tools/skillgen/expected/graphify__skill-agents.md index a79fb23fc..48689345a 100644 --- a/tools/skillgen/expected/graphify__skill-agents.md +++ b/tools/skillgen/expected/graphify__skill-agents.md @@ -50,7 +50,7 @@ Drop any folder of code, docs, papers, images, or video into graphify and get a If the user invoked `/graphify --help` or `/graphify -h` (with no other arguments), print the contents of the `## Usage` section above verbatim and stop. Do not run any commands, do not detect files, do not default the path to `.`. Just print the Usage block and return. -**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Run `graphify query ""` immediately. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. +**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Prefer the MCP `query_graph` tool immediately; otherwise use the recorded-interpreter command in `references/query.md`. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. If no path was given, use `.` (current directory). Do not ask the user for a path. diff --git a/tools/skillgen/expected/graphify__skill-amp.md b/tools/skillgen/expected/graphify__skill-amp.md index a79fb23fc..48689345a 100644 --- a/tools/skillgen/expected/graphify__skill-amp.md +++ b/tools/skillgen/expected/graphify__skill-amp.md @@ -50,7 +50,7 @@ Drop any folder of code, docs, papers, images, or video into graphify and get a If the user invoked `/graphify --help` or `/graphify -h` (with no other arguments), print the contents of the `## Usage` section above verbatim and stop. Do not run any commands, do not detect files, do not default the path to `.`. Just print the Usage block and return. -**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Run `graphify query ""` immediately. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. +**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Prefer the MCP `query_graph` tool immediately; otherwise use the recorded-interpreter command in `references/query.md`. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. If no path was given, use `.` (current directory). Do not ask the user for a path. diff --git a/tools/skillgen/expected/graphify__skill-claw.md b/tools/skillgen/expected/graphify__skill-claw.md index 9d174f1bb..238d3ccff 100644 --- a/tools/skillgen/expected/graphify__skill-claw.md +++ b/tools/skillgen/expected/graphify__skill-claw.md @@ -50,7 +50,7 @@ Drop any folder of code, docs, papers, images, or video into graphify and get a If the user invoked `/graphify --help` or `/graphify -h` (with no other arguments), print the contents of the `## Usage` section above verbatim and stop. Do not run any commands, do not detect files, do not default the path to `.`. Just print the Usage block and return. -**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Run `graphify query ""` immediately. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. +**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Prefer the MCP `query_graph` tool immediately; otherwise use the recorded-interpreter command in `references/query.md`. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. If no path was given, use `.` (current directory). Do not ask the user for a path. diff --git a/tools/skillgen/expected/graphify__skill-codex.md b/tools/skillgen/expected/graphify__skill-codex.md index de86747b8..9f9cbc652 100644 --- a/tools/skillgen/expected/graphify__skill-codex.md +++ b/tools/skillgen/expected/graphify__skill-codex.md @@ -50,7 +50,7 @@ Drop any folder of code, docs, papers, images, or video into graphify and get a If the user invoked `/graphify --help` or `/graphify -h` (with no other arguments), print the contents of the `## Usage` section above verbatim and stop. Do not run any commands, do not detect files, do not default the path to `.`. Just print the Usage block and return. -**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Run `graphify query ""` immediately. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. +**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Prefer the MCP `query_graph` tool immediately; otherwise use the recorded-interpreter command in `references/query.md`. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. If no path was given, use `.` (current directory). Do not ask the user for a path. diff --git a/tools/skillgen/expected/graphify__skill-copilot.md b/tools/skillgen/expected/graphify__skill-copilot.md index 9d174f1bb..238d3ccff 100644 --- a/tools/skillgen/expected/graphify__skill-copilot.md +++ b/tools/skillgen/expected/graphify__skill-copilot.md @@ -50,7 +50,7 @@ Drop any folder of code, docs, papers, images, or video into graphify and get a If the user invoked `/graphify --help` or `/graphify -h` (with no other arguments), print the contents of the `## Usage` section above verbatim and stop. Do not run any commands, do not detect files, do not default the path to `.`. Just print the Usage block and return. -**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Run `graphify query ""` immediately. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. +**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Prefer the MCP `query_graph` tool immediately; otherwise use the recorded-interpreter command in `references/query.md`. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. If no path was given, use `.` (current directory). Do not ask the user for a path. diff --git a/tools/skillgen/expected/graphify__skill-droid.md b/tools/skillgen/expected/graphify__skill-droid.md index 821ef56a7..b0879f6c8 100644 --- a/tools/skillgen/expected/graphify__skill-droid.md +++ b/tools/skillgen/expected/graphify__skill-droid.md @@ -50,7 +50,7 @@ Drop any folder of code, docs, papers, images, or video into graphify and get a If the user invoked `/graphify --help` or `/graphify -h` (with no other arguments), print the contents of the `## Usage` section above verbatim and stop. Do not run any commands, do not detect files, do not default the path to `.`. Just print the Usage block and return. -**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Run `graphify query ""` immediately. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. +**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Prefer the MCP `query_graph` tool immediately; otherwise use the recorded-interpreter command in `references/query.md`. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. If no path was given, use `.` (current directory). Do not ask the user for a path. diff --git a/tools/skillgen/expected/graphify__skill-kilo.md b/tools/skillgen/expected/graphify__skill-kilo.md index e1f1496e9..71e6fe485 100644 --- a/tools/skillgen/expected/graphify__skill-kilo.md +++ b/tools/skillgen/expected/graphify__skill-kilo.md @@ -50,7 +50,7 @@ Drop any folder of code, docs, papers, images, or video into graphify and get a If the user invoked `/graphify --help` or `/graphify -h` (with no other arguments), print the contents of the `## Usage` section above verbatim and stop. Do not run any commands, do not detect files, do not default the path to `.`. Just print the Usage block and return. -**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Run `graphify query ""` immediately. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. +**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Prefer the MCP `query_graph` tool immediately; otherwise use the recorded-interpreter command in `references/query.md`. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. If no path was given, use `.` (current directory). Do not ask the user for a path. diff --git a/tools/skillgen/expected/graphify__skill-kiro.md b/tools/skillgen/expected/graphify__skill-kiro.md index 9d174f1bb..238d3ccff 100644 --- a/tools/skillgen/expected/graphify__skill-kiro.md +++ b/tools/skillgen/expected/graphify__skill-kiro.md @@ -50,7 +50,7 @@ Drop any folder of code, docs, papers, images, or video into graphify and get a If the user invoked `/graphify --help` or `/graphify -h` (with no other arguments), print the contents of the `## Usage` section above verbatim and stop. Do not run any commands, do not detect files, do not default the path to `.`. Just print the Usage block and return. -**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Run `graphify query ""` immediately. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. +**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Prefer the MCP `query_graph` tool immediately; otherwise use the recorded-interpreter command in `references/query.md`. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. If no path was given, use `.` (current directory). Do not ask the user for a path. diff --git a/tools/skillgen/expected/graphify__skill-opencode.md b/tools/skillgen/expected/graphify__skill-opencode.md index 872ad49f7..7efbd23a9 100644 --- a/tools/skillgen/expected/graphify__skill-opencode.md +++ b/tools/skillgen/expected/graphify__skill-opencode.md @@ -50,7 +50,7 @@ Drop any folder of code, docs, papers, images, or video into graphify and get a If the user invoked `/graphify --help` or `/graphify -h` (with no other arguments), print the contents of the `## Usage` section above verbatim and stop. Do not run any commands, do not detect files, do not default the path to `.`. Just print the Usage block and return. -**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Run `graphify query ""` immediately. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. +**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Prefer the MCP `query_graph` tool immediately; otherwise use the recorded-interpreter command in `references/query.md`. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. If no path was given, use `.` (current directory). Do not ask the user for a path. diff --git a/tools/skillgen/expected/graphify__skill-pi.md b/tools/skillgen/expected/graphify__skill-pi.md index 9d174f1bb..238d3ccff 100644 --- a/tools/skillgen/expected/graphify__skill-pi.md +++ b/tools/skillgen/expected/graphify__skill-pi.md @@ -50,7 +50,7 @@ Drop any folder of code, docs, papers, images, or video into graphify and get a If the user invoked `/graphify --help` or `/graphify -h` (with no other arguments), print the contents of the `## Usage` section above verbatim and stop. Do not run any commands, do not detect files, do not default the path to `.`. Just print the Usage block and return. -**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Run `graphify query ""` immediately. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. +**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Prefer the MCP `query_graph` tool immediately; otherwise use the recorded-interpreter command in `references/query.md`. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. If no path was given, use `.` (current directory). Do not ask the user for a path. diff --git a/tools/skillgen/expected/graphify__skill-trae.md b/tools/skillgen/expected/graphify__skill-trae.md index 6a76e4a01..098cb33aa 100644 --- a/tools/skillgen/expected/graphify__skill-trae.md +++ b/tools/skillgen/expected/graphify__skill-trae.md @@ -50,7 +50,7 @@ Drop any folder of code, docs, papers, images, or video into graphify and get a If the user invoked `/graphify --help` or `/graphify -h` (with no other arguments), print the contents of the `## Usage` section above verbatim and stop. Do not run any commands, do not detect files, do not default the path to `.`. Just print the Usage block and return. -**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Run `graphify query ""` immediately. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. +**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Prefer the MCP `query_graph` tool immediately; otherwise use the recorded-interpreter command in `references/query.md`. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. If no path was given, use `.` (current directory). Do not ask the user for a path. diff --git a/tools/skillgen/expected/graphify__skill-vscode.md b/tools/skillgen/expected/graphify__skill-vscode.md index 4d2bf8c1e..d98332a2b 100644 --- a/tools/skillgen/expected/graphify__skill-vscode.md +++ b/tools/skillgen/expected/graphify__skill-vscode.md @@ -50,7 +50,7 @@ Drop any folder of code, docs, papers, images, or video into graphify and get a If the user invoked `/graphify --help` or `/graphify -h` (with no other arguments), print the contents of the `## Usage` section above verbatim and stop. Do not run any commands, do not detect files, do not default the path to `.`. Just print the Usage block and return. -**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Run `graphify query ""` immediately. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. +**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Prefer the MCP `query_graph` tool immediately; otherwise use the recorded-interpreter command in `references/query.md`. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. If no path was given, use `.` (current directory). Do not ask the user for a path. diff --git a/tools/skillgen/expected/graphify__skill-windows.md b/tools/skillgen/expected/graphify__skill-windows.md index 6f353df57..7f8d9d6e2 100644 --- a/tools/skillgen/expected/graphify__skill-windows.md +++ b/tools/skillgen/expected/graphify__skill-windows.md @@ -50,7 +50,7 @@ Drop any folder of code, docs, papers, images, or video into graphify and get a If the user invoked `/graphify --help` or `/graphify -h` (with no other arguments), print the contents of the `## Usage` section above verbatim and stop. Do not run any commands, do not detect files, do not default the path to `.`. Just print the Usage block and return. -**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Run `graphify query ""` immediately. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. +**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Prefer the MCP `query_graph` tool immediately; otherwise use the recorded-interpreter command in `references/query.md`. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. If no path was given, use `.` (current directory). Do not ask the user for a path. diff --git a/tools/skillgen/expected/graphify__skill.md b/tools/skillgen/expected/graphify__skill.md index 9d174f1bb..238d3ccff 100644 --- a/tools/skillgen/expected/graphify__skill.md +++ b/tools/skillgen/expected/graphify__skill.md @@ -50,7 +50,7 @@ Drop any folder of code, docs, papers, images, or video into graphify and get a If the user invoked `/graphify --help` or `/graphify -h` (with no other arguments), print the contents of the `## Usage` section above verbatim and stop. Do not run any commands, do not detect files, do not default the path to `.`. Just print the Usage block and return. -**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Run `graphify query ""` immediately. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. +**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Prefer the MCP `query_graph` tool immediately; otherwise use the recorded-interpreter command in `references/query.md`. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. If no path was given, use `.` (current directory). Do not ask the user for a path. diff --git a/tools/skillgen/expected/graphify__skills__agents__references__query.md b/tools/skillgen/expected/graphify__skills__agents__references__query.md index ab059fac3..5ce535433 100644 --- a/tools/skillgen/expected/graphify__skills__agents__references__query.md +++ b/tools/skillgen/expected/graphify__skills__agents__references__query.md @@ -11,7 +11,7 @@ Two traversal modes - choose based on the question: First check the graph exists: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " from pathlib import Path if not Path('graphify-out/graph.json').exists(): print('ERROR: No graph found. Run /graphify first to build the graph.') @@ -28,7 +28,7 @@ Fix this **without inventing tokens** by expanding the query against the actual 1. Extract the token vocabulary from node labels: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, re from pathlib import Path data = json.loads(Path('graphify-out/graph.json').read_text(encoding='utf-8')) @@ -64,8 +64,8 @@ Build the **expanded query string** by joining the selected tokens with spaces. If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" -# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 +"$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" +# or: "$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: @@ -77,7 +77,7 @@ If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal 5. If the graph lacks enough information, say so - do not hallucinate edges. ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import sys, json from networkx.readwrite import json_graph import networkx as nx @@ -168,7 +168,7 @@ Replace `QUESTION` with the **expanded** query string, `MODE` with `bfs` or `dfs After writing the answer, save it back into the graph so it improves future queries. Include the expanded tokens inside the `--answer` text (e.g. `"Expanded from original query via vocab: [tokens]. Then traversed..."`) so the next `--update` extracts the expansion history as a graph node: ```bash -$(cat graphify-out/.graphify_python) -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 ``` Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with your full answer text (containing the expanded-token trace), `NODE1 NODE2` 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. @@ -188,13 +188,13 @@ At the **start** of graph work, refresh and read the lessons: run `graphify refl Find the shortest path between two named concepts in the graph. Prefer the CLI when installed: ```bash -graphify path "NODE_A" "NODE_B" +"$(cat graphify-out/.graphify_python)" -m graphify path "NODE_A" "NODE_B" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -246,7 +246,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) -m graphify save-result --question "Path from NODE_A to NODE_B" --answer "ANSWER" --type path_query --nodes NODE_A NODE_B +"$(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 ``` --- @@ -256,13 +256,13 @@ $(cat graphify-out/.graphify_python) -m graphify save-result --question "Path fr Give a plain-language explanation of a single node - everything connected to it. Prefer the CLI when installed: ```bash -graphify explain "NODE_NAME" +"$(cat graphify-out/.graphify_python)" -m graphify explain "NODE_NAME" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -307,5 +307,5 @@ 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) -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME ``` diff --git a/tools/skillgen/expected/graphify__skills__amp__references__query.md b/tools/skillgen/expected/graphify__skills__amp__references__query.md index ab059fac3..5ce535433 100644 --- a/tools/skillgen/expected/graphify__skills__amp__references__query.md +++ b/tools/skillgen/expected/graphify__skills__amp__references__query.md @@ -11,7 +11,7 @@ Two traversal modes - choose based on the question: First check the graph exists: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " from pathlib import Path if not Path('graphify-out/graph.json').exists(): print('ERROR: No graph found. Run /graphify first to build the graph.') @@ -28,7 +28,7 @@ Fix this **without inventing tokens** by expanding the query against the actual 1. Extract the token vocabulary from node labels: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, re from pathlib import Path data = json.loads(Path('graphify-out/graph.json').read_text(encoding='utf-8')) @@ -64,8 +64,8 @@ Build the **expanded query string** by joining the selected tokens with spaces. If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" -# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 +"$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" +# or: "$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: @@ -77,7 +77,7 @@ If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal 5. If the graph lacks enough information, say so - do not hallucinate edges. ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import sys, json from networkx.readwrite import json_graph import networkx as nx @@ -168,7 +168,7 @@ Replace `QUESTION` with the **expanded** query string, `MODE` with `bfs` or `dfs After writing the answer, save it back into the graph so it improves future queries. Include the expanded tokens inside the `--answer` text (e.g. `"Expanded from original query via vocab: [tokens]. Then traversed..."`) so the next `--update` extracts the expansion history as a graph node: ```bash -$(cat graphify-out/.graphify_python) -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 ``` Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with your full answer text (containing the expanded-token trace), `NODE1 NODE2` 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. @@ -188,13 +188,13 @@ At the **start** of graph work, refresh and read the lessons: run `graphify refl Find the shortest path between two named concepts in the graph. Prefer the CLI when installed: ```bash -graphify path "NODE_A" "NODE_B" +"$(cat graphify-out/.graphify_python)" -m graphify path "NODE_A" "NODE_B" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -246,7 +246,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) -m graphify save-result --question "Path from NODE_A to NODE_B" --answer "ANSWER" --type path_query --nodes NODE_A NODE_B +"$(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 ``` --- @@ -256,13 +256,13 @@ $(cat graphify-out/.graphify_python) -m graphify save-result --question "Path fr Give a plain-language explanation of a single node - everything connected to it. Prefer the CLI when installed: ```bash -graphify explain "NODE_NAME" +"$(cat graphify-out/.graphify_python)" -m graphify explain "NODE_NAME" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -307,5 +307,5 @@ 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) -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME ``` diff --git a/tools/skillgen/expected/graphify__skills__claude__references__query.md b/tools/skillgen/expected/graphify__skills__claude__references__query.md index ab059fac3..5ce535433 100644 --- a/tools/skillgen/expected/graphify__skills__claude__references__query.md +++ b/tools/skillgen/expected/graphify__skills__claude__references__query.md @@ -11,7 +11,7 @@ Two traversal modes - choose based on the question: First check the graph exists: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " from pathlib import Path if not Path('graphify-out/graph.json').exists(): print('ERROR: No graph found. Run /graphify first to build the graph.') @@ -28,7 +28,7 @@ Fix this **without inventing tokens** by expanding the query against the actual 1. Extract the token vocabulary from node labels: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, re from pathlib import Path data = json.loads(Path('graphify-out/graph.json').read_text(encoding='utf-8')) @@ -64,8 +64,8 @@ Build the **expanded query string** by joining the selected tokens with spaces. If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" -# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 +"$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" +# or: "$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: @@ -77,7 +77,7 @@ If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal 5. If the graph lacks enough information, say so - do not hallucinate edges. ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import sys, json from networkx.readwrite import json_graph import networkx as nx @@ -168,7 +168,7 @@ Replace `QUESTION` with the **expanded** query string, `MODE` with `bfs` or `dfs After writing the answer, save it back into the graph so it improves future queries. Include the expanded tokens inside the `--answer` text (e.g. `"Expanded from original query via vocab: [tokens]. Then traversed..."`) so the next `--update` extracts the expansion history as a graph node: ```bash -$(cat graphify-out/.graphify_python) -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 ``` Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with your full answer text (containing the expanded-token trace), `NODE1 NODE2` 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. @@ -188,13 +188,13 @@ At the **start** of graph work, refresh and read the lessons: run `graphify refl Find the shortest path between two named concepts in the graph. Prefer the CLI when installed: ```bash -graphify path "NODE_A" "NODE_B" +"$(cat graphify-out/.graphify_python)" -m graphify path "NODE_A" "NODE_B" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -246,7 +246,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) -m graphify save-result --question "Path from NODE_A to NODE_B" --answer "ANSWER" --type path_query --nodes NODE_A NODE_B +"$(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 ``` --- @@ -256,13 +256,13 @@ $(cat graphify-out/.graphify_python) -m graphify save-result --question "Path fr Give a plain-language explanation of a single node - everything connected to it. Prefer the CLI when installed: ```bash -graphify explain "NODE_NAME" +"$(cat graphify-out/.graphify_python)" -m graphify explain "NODE_NAME" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -307,5 +307,5 @@ 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) -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME ``` diff --git a/tools/skillgen/expected/graphify__skills__claw__references__query.md b/tools/skillgen/expected/graphify__skills__claw__references__query.md index ab059fac3..5ce535433 100644 --- a/tools/skillgen/expected/graphify__skills__claw__references__query.md +++ b/tools/skillgen/expected/graphify__skills__claw__references__query.md @@ -11,7 +11,7 @@ Two traversal modes - choose based on the question: First check the graph exists: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " from pathlib import Path if not Path('graphify-out/graph.json').exists(): print('ERROR: No graph found. Run /graphify first to build the graph.') @@ -28,7 +28,7 @@ Fix this **without inventing tokens** by expanding the query against the actual 1. Extract the token vocabulary from node labels: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, re from pathlib import Path data = json.loads(Path('graphify-out/graph.json').read_text(encoding='utf-8')) @@ -64,8 +64,8 @@ Build the **expanded query string** by joining the selected tokens with spaces. If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" -# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 +"$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" +# or: "$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: @@ -77,7 +77,7 @@ If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal 5. If the graph lacks enough information, say so - do not hallucinate edges. ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import sys, json from networkx.readwrite import json_graph import networkx as nx @@ -168,7 +168,7 @@ Replace `QUESTION` with the **expanded** query string, `MODE` with `bfs` or `dfs After writing the answer, save it back into the graph so it improves future queries. Include the expanded tokens inside the `--answer` text (e.g. `"Expanded from original query via vocab: [tokens]. Then traversed..."`) so the next `--update` extracts the expansion history as a graph node: ```bash -$(cat graphify-out/.graphify_python) -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 ``` Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with your full answer text (containing the expanded-token trace), `NODE1 NODE2` 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. @@ -188,13 +188,13 @@ At the **start** of graph work, refresh and read the lessons: run `graphify refl Find the shortest path between two named concepts in the graph. Prefer the CLI when installed: ```bash -graphify path "NODE_A" "NODE_B" +"$(cat graphify-out/.graphify_python)" -m graphify path "NODE_A" "NODE_B" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -246,7 +246,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) -m graphify save-result --question "Path from NODE_A to NODE_B" --answer "ANSWER" --type path_query --nodes NODE_A NODE_B +"$(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 ``` --- @@ -256,13 +256,13 @@ $(cat graphify-out/.graphify_python) -m graphify save-result --question "Path fr Give a plain-language explanation of a single node - everything connected to it. Prefer the CLI when installed: ```bash -graphify explain "NODE_NAME" +"$(cat graphify-out/.graphify_python)" -m graphify explain "NODE_NAME" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -307,5 +307,5 @@ 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) -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME ``` diff --git a/tools/skillgen/expected/graphify__skills__codex__references__query.md b/tools/skillgen/expected/graphify__skills__codex__references__query.md index ab059fac3..5ce535433 100644 --- a/tools/skillgen/expected/graphify__skills__codex__references__query.md +++ b/tools/skillgen/expected/graphify__skills__codex__references__query.md @@ -11,7 +11,7 @@ Two traversal modes - choose based on the question: First check the graph exists: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " from pathlib import Path if not Path('graphify-out/graph.json').exists(): print('ERROR: No graph found. Run /graphify first to build the graph.') @@ -28,7 +28,7 @@ Fix this **without inventing tokens** by expanding the query against the actual 1. Extract the token vocabulary from node labels: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, re from pathlib import Path data = json.loads(Path('graphify-out/graph.json').read_text(encoding='utf-8')) @@ -64,8 +64,8 @@ Build the **expanded query string** by joining the selected tokens with spaces. If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" -# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 +"$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" +# or: "$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: @@ -77,7 +77,7 @@ If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal 5. If the graph lacks enough information, say so - do not hallucinate edges. ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import sys, json from networkx.readwrite import json_graph import networkx as nx @@ -168,7 +168,7 @@ Replace `QUESTION` with the **expanded** query string, `MODE` with `bfs` or `dfs After writing the answer, save it back into the graph so it improves future queries. Include the expanded tokens inside the `--answer` text (e.g. `"Expanded from original query via vocab: [tokens]. Then traversed..."`) so the next `--update` extracts the expansion history as a graph node: ```bash -$(cat graphify-out/.graphify_python) -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 ``` Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with your full answer text (containing the expanded-token trace), `NODE1 NODE2` 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. @@ -188,13 +188,13 @@ At the **start** of graph work, refresh and read the lessons: run `graphify refl Find the shortest path between two named concepts in the graph. Prefer the CLI when installed: ```bash -graphify path "NODE_A" "NODE_B" +"$(cat graphify-out/.graphify_python)" -m graphify path "NODE_A" "NODE_B" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -246,7 +246,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) -m graphify save-result --question "Path from NODE_A to NODE_B" --answer "ANSWER" --type path_query --nodes NODE_A NODE_B +"$(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 ``` --- @@ -256,13 +256,13 @@ $(cat graphify-out/.graphify_python) -m graphify save-result --question "Path fr Give a plain-language explanation of a single node - everything connected to it. Prefer the CLI when installed: ```bash -graphify explain "NODE_NAME" +"$(cat graphify-out/.graphify_python)" -m graphify explain "NODE_NAME" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -307,5 +307,5 @@ 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) -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME ``` diff --git a/tools/skillgen/expected/graphify__skills__copilot__references__query.md b/tools/skillgen/expected/graphify__skills__copilot__references__query.md index ab059fac3..5ce535433 100644 --- a/tools/skillgen/expected/graphify__skills__copilot__references__query.md +++ b/tools/skillgen/expected/graphify__skills__copilot__references__query.md @@ -11,7 +11,7 @@ Two traversal modes - choose based on the question: First check the graph exists: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " from pathlib import Path if not Path('graphify-out/graph.json').exists(): print('ERROR: No graph found. Run /graphify first to build the graph.') @@ -28,7 +28,7 @@ Fix this **without inventing tokens** by expanding the query against the actual 1. Extract the token vocabulary from node labels: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, re from pathlib import Path data = json.loads(Path('graphify-out/graph.json').read_text(encoding='utf-8')) @@ -64,8 +64,8 @@ Build the **expanded query string** by joining the selected tokens with spaces. If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" -# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 +"$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" +# or: "$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: @@ -77,7 +77,7 @@ If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal 5. If the graph lacks enough information, say so - do not hallucinate edges. ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import sys, json from networkx.readwrite import json_graph import networkx as nx @@ -168,7 +168,7 @@ Replace `QUESTION` with the **expanded** query string, `MODE` with `bfs` or `dfs After writing the answer, save it back into the graph so it improves future queries. Include the expanded tokens inside the `--answer` text (e.g. `"Expanded from original query via vocab: [tokens]. Then traversed..."`) so the next `--update` extracts the expansion history as a graph node: ```bash -$(cat graphify-out/.graphify_python) -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 ``` Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with your full answer text (containing the expanded-token trace), `NODE1 NODE2` 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. @@ -188,13 +188,13 @@ At the **start** of graph work, refresh and read the lessons: run `graphify refl Find the shortest path between two named concepts in the graph. Prefer the CLI when installed: ```bash -graphify path "NODE_A" "NODE_B" +"$(cat graphify-out/.graphify_python)" -m graphify path "NODE_A" "NODE_B" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -246,7 +246,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) -m graphify save-result --question "Path from NODE_A to NODE_B" --answer "ANSWER" --type path_query --nodes NODE_A NODE_B +"$(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 ``` --- @@ -256,13 +256,13 @@ $(cat graphify-out/.graphify_python) -m graphify save-result --question "Path fr Give a plain-language explanation of a single node - everything connected to it. Prefer the CLI when installed: ```bash -graphify explain "NODE_NAME" +"$(cat graphify-out/.graphify_python)" -m graphify explain "NODE_NAME" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -307,5 +307,5 @@ 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) -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME ``` diff --git a/tools/skillgen/expected/graphify__skills__droid__references__query.md b/tools/skillgen/expected/graphify__skills__droid__references__query.md index ab059fac3..5ce535433 100644 --- a/tools/skillgen/expected/graphify__skills__droid__references__query.md +++ b/tools/skillgen/expected/graphify__skills__droid__references__query.md @@ -11,7 +11,7 @@ Two traversal modes - choose based on the question: First check the graph exists: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " from pathlib import Path if not Path('graphify-out/graph.json').exists(): print('ERROR: No graph found. Run /graphify first to build the graph.') @@ -28,7 +28,7 @@ Fix this **without inventing tokens** by expanding the query against the actual 1. Extract the token vocabulary from node labels: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, re from pathlib import Path data = json.loads(Path('graphify-out/graph.json').read_text(encoding='utf-8')) @@ -64,8 +64,8 @@ Build the **expanded query string** by joining the selected tokens with spaces. If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" -# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 +"$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" +# or: "$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: @@ -77,7 +77,7 @@ If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal 5. If the graph lacks enough information, say so - do not hallucinate edges. ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import sys, json from networkx.readwrite import json_graph import networkx as nx @@ -168,7 +168,7 @@ Replace `QUESTION` with the **expanded** query string, `MODE` with `bfs` or `dfs After writing the answer, save it back into the graph so it improves future queries. Include the expanded tokens inside the `--answer` text (e.g. `"Expanded from original query via vocab: [tokens]. Then traversed..."`) so the next `--update` extracts the expansion history as a graph node: ```bash -$(cat graphify-out/.graphify_python) -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 ``` Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with your full answer text (containing the expanded-token trace), `NODE1 NODE2` 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. @@ -188,13 +188,13 @@ At the **start** of graph work, refresh and read the lessons: run `graphify refl Find the shortest path between two named concepts in the graph. Prefer the CLI when installed: ```bash -graphify path "NODE_A" "NODE_B" +"$(cat graphify-out/.graphify_python)" -m graphify path "NODE_A" "NODE_B" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -246,7 +246,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) -m graphify save-result --question "Path from NODE_A to NODE_B" --answer "ANSWER" --type path_query --nodes NODE_A NODE_B +"$(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 ``` --- @@ -256,13 +256,13 @@ $(cat graphify-out/.graphify_python) -m graphify save-result --question "Path fr Give a plain-language explanation of a single node - everything connected to it. Prefer the CLI when installed: ```bash -graphify explain "NODE_NAME" +"$(cat graphify-out/.graphify_python)" -m graphify explain "NODE_NAME" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -307,5 +307,5 @@ 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) -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME ``` diff --git a/tools/skillgen/expected/graphify__skills__kilo__references__query.md b/tools/skillgen/expected/graphify__skills__kilo__references__query.md index ab059fac3..5ce535433 100644 --- a/tools/skillgen/expected/graphify__skills__kilo__references__query.md +++ b/tools/skillgen/expected/graphify__skills__kilo__references__query.md @@ -11,7 +11,7 @@ Two traversal modes - choose based on the question: First check the graph exists: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " from pathlib import Path if not Path('graphify-out/graph.json').exists(): print('ERROR: No graph found. Run /graphify first to build the graph.') @@ -28,7 +28,7 @@ Fix this **without inventing tokens** by expanding the query against the actual 1. Extract the token vocabulary from node labels: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, re from pathlib import Path data = json.loads(Path('graphify-out/graph.json').read_text(encoding='utf-8')) @@ -64,8 +64,8 @@ Build the **expanded query string** by joining the selected tokens with spaces. If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" -# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 +"$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" +# or: "$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: @@ -77,7 +77,7 @@ If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal 5. If the graph lacks enough information, say so - do not hallucinate edges. ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import sys, json from networkx.readwrite import json_graph import networkx as nx @@ -168,7 +168,7 @@ Replace `QUESTION` with the **expanded** query string, `MODE` with `bfs` or `dfs After writing the answer, save it back into the graph so it improves future queries. Include the expanded tokens inside the `--answer` text (e.g. `"Expanded from original query via vocab: [tokens]. Then traversed..."`) so the next `--update` extracts the expansion history as a graph node: ```bash -$(cat graphify-out/.graphify_python) -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 ``` Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with your full answer text (containing the expanded-token trace), `NODE1 NODE2` 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. @@ -188,13 +188,13 @@ At the **start** of graph work, refresh and read the lessons: run `graphify refl Find the shortest path between two named concepts in the graph. Prefer the CLI when installed: ```bash -graphify path "NODE_A" "NODE_B" +"$(cat graphify-out/.graphify_python)" -m graphify path "NODE_A" "NODE_B" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -246,7 +246,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) -m graphify save-result --question "Path from NODE_A to NODE_B" --answer "ANSWER" --type path_query --nodes NODE_A NODE_B +"$(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 ``` --- @@ -256,13 +256,13 @@ $(cat graphify-out/.graphify_python) -m graphify save-result --question "Path fr Give a plain-language explanation of a single node - everything connected to it. Prefer the CLI when installed: ```bash -graphify explain "NODE_NAME" +"$(cat graphify-out/.graphify_python)" -m graphify explain "NODE_NAME" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -307,5 +307,5 @@ 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) -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME ``` diff --git a/tools/skillgen/expected/graphify__skills__kiro__references__query.md b/tools/skillgen/expected/graphify__skills__kiro__references__query.md index ab059fac3..5ce535433 100644 --- a/tools/skillgen/expected/graphify__skills__kiro__references__query.md +++ b/tools/skillgen/expected/graphify__skills__kiro__references__query.md @@ -11,7 +11,7 @@ Two traversal modes - choose based on the question: First check the graph exists: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " from pathlib import Path if not Path('graphify-out/graph.json').exists(): print('ERROR: No graph found. Run /graphify first to build the graph.') @@ -28,7 +28,7 @@ Fix this **without inventing tokens** by expanding the query against the actual 1. Extract the token vocabulary from node labels: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, re from pathlib import Path data = json.loads(Path('graphify-out/graph.json').read_text(encoding='utf-8')) @@ -64,8 +64,8 @@ Build the **expanded query string** by joining the selected tokens with spaces. If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" -# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 +"$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" +# or: "$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: @@ -77,7 +77,7 @@ If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal 5. If the graph lacks enough information, say so - do not hallucinate edges. ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import sys, json from networkx.readwrite import json_graph import networkx as nx @@ -168,7 +168,7 @@ Replace `QUESTION` with the **expanded** query string, `MODE` with `bfs` or `dfs After writing the answer, save it back into the graph so it improves future queries. Include the expanded tokens inside the `--answer` text (e.g. `"Expanded from original query via vocab: [tokens]. Then traversed..."`) so the next `--update` extracts the expansion history as a graph node: ```bash -$(cat graphify-out/.graphify_python) -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 ``` Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with your full answer text (containing the expanded-token trace), `NODE1 NODE2` 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. @@ -188,13 +188,13 @@ At the **start** of graph work, refresh and read the lessons: run `graphify refl Find the shortest path between two named concepts in the graph. Prefer the CLI when installed: ```bash -graphify path "NODE_A" "NODE_B" +"$(cat graphify-out/.graphify_python)" -m graphify path "NODE_A" "NODE_B" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -246,7 +246,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) -m graphify save-result --question "Path from NODE_A to NODE_B" --answer "ANSWER" --type path_query --nodes NODE_A NODE_B +"$(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 ``` --- @@ -256,13 +256,13 @@ $(cat graphify-out/.graphify_python) -m graphify save-result --question "Path fr Give a plain-language explanation of a single node - everything connected to it. Prefer the CLI when installed: ```bash -graphify explain "NODE_NAME" +"$(cat graphify-out/.graphify_python)" -m graphify explain "NODE_NAME" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -307,5 +307,5 @@ 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) -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME ``` diff --git a/tools/skillgen/expected/graphify__skills__opencode__references__query.md b/tools/skillgen/expected/graphify__skills__opencode__references__query.md index ab059fac3..5ce535433 100644 --- a/tools/skillgen/expected/graphify__skills__opencode__references__query.md +++ b/tools/skillgen/expected/graphify__skills__opencode__references__query.md @@ -11,7 +11,7 @@ Two traversal modes - choose based on the question: First check the graph exists: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " from pathlib import Path if not Path('graphify-out/graph.json').exists(): print('ERROR: No graph found. Run /graphify first to build the graph.') @@ -28,7 +28,7 @@ Fix this **without inventing tokens** by expanding the query against the actual 1. Extract the token vocabulary from node labels: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, re from pathlib import Path data = json.loads(Path('graphify-out/graph.json').read_text(encoding='utf-8')) @@ -64,8 +64,8 @@ Build the **expanded query string** by joining the selected tokens with spaces. If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" -# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 +"$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" +# or: "$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: @@ -77,7 +77,7 @@ If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal 5. If the graph lacks enough information, say so - do not hallucinate edges. ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import sys, json from networkx.readwrite import json_graph import networkx as nx @@ -168,7 +168,7 @@ Replace `QUESTION` with the **expanded** query string, `MODE` with `bfs` or `dfs After writing the answer, save it back into the graph so it improves future queries. Include the expanded tokens inside the `--answer` text (e.g. `"Expanded from original query via vocab: [tokens]. Then traversed..."`) so the next `--update` extracts the expansion history as a graph node: ```bash -$(cat graphify-out/.graphify_python) -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 ``` Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with your full answer text (containing the expanded-token trace), `NODE1 NODE2` 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. @@ -188,13 +188,13 @@ At the **start** of graph work, refresh and read the lessons: run `graphify refl Find the shortest path between two named concepts in the graph. Prefer the CLI when installed: ```bash -graphify path "NODE_A" "NODE_B" +"$(cat graphify-out/.graphify_python)" -m graphify path "NODE_A" "NODE_B" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -246,7 +246,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) -m graphify save-result --question "Path from NODE_A to NODE_B" --answer "ANSWER" --type path_query --nodes NODE_A NODE_B +"$(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 ``` --- @@ -256,13 +256,13 @@ $(cat graphify-out/.graphify_python) -m graphify save-result --question "Path fr Give a plain-language explanation of a single node - everything connected to it. Prefer the CLI when installed: ```bash -graphify explain "NODE_NAME" +"$(cat graphify-out/.graphify_python)" -m graphify explain "NODE_NAME" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -307,5 +307,5 @@ 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) -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME ``` diff --git a/tools/skillgen/expected/graphify__skills__pi__references__query.md b/tools/skillgen/expected/graphify__skills__pi__references__query.md index ab059fac3..5ce535433 100644 --- a/tools/skillgen/expected/graphify__skills__pi__references__query.md +++ b/tools/skillgen/expected/graphify__skills__pi__references__query.md @@ -11,7 +11,7 @@ Two traversal modes - choose based on the question: First check the graph exists: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " from pathlib import Path if not Path('graphify-out/graph.json').exists(): print('ERROR: No graph found. Run /graphify first to build the graph.') @@ -28,7 +28,7 @@ Fix this **without inventing tokens** by expanding the query against the actual 1. Extract the token vocabulary from node labels: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, re from pathlib import Path data = json.loads(Path('graphify-out/graph.json').read_text(encoding='utf-8')) @@ -64,8 +64,8 @@ Build the **expanded query string** by joining the selected tokens with spaces. If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" -# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 +"$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" +# or: "$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: @@ -77,7 +77,7 @@ If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal 5. If the graph lacks enough information, say so - do not hallucinate edges. ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import sys, json from networkx.readwrite import json_graph import networkx as nx @@ -168,7 +168,7 @@ Replace `QUESTION` with the **expanded** query string, `MODE` with `bfs` or `dfs After writing the answer, save it back into the graph so it improves future queries. Include the expanded tokens inside the `--answer` text (e.g. `"Expanded from original query via vocab: [tokens]. Then traversed..."`) so the next `--update` extracts the expansion history as a graph node: ```bash -$(cat graphify-out/.graphify_python) -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 ``` Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with your full answer text (containing the expanded-token trace), `NODE1 NODE2` 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. @@ -188,13 +188,13 @@ At the **start** of graph work, refresh and read the lessons: run `graphify refl Find the shortest path between two named concepts in the graph. Prefer the CLI when installed: ```bash -graphify path "NODE_A" "NODE_B" +"$(cat graphify-out/.graphify_python)" -m graphify path "NODE_A" "NODE_B" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -246,7 +246,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) -m graphify save-result --question "Path from NODE_A to NODE_B" --answer "ANSWER" --type path_query --nodes NODE_A NODE_B +"$(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 ``` --- @@ -256,13 +256,13 @@ $(cat graphify-out/.graphify_python) -m graphify save-result --question "Path fr Give a plain-language explanation of a single node - everything connected to it. Prefer the CLI when installed: ```bash -graphify explain "NODE_NAME" +"$(cat graphify-out/.graphify_python)" -m graphify explain "NODE_NAME" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -307,5 +307,5 @@ 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) -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME ``` diff --git a/tools/skillgen/expected/graphify__skills__trae__references__query.md b/tools/skillgen/expected/graphify__skills__trae__references__query.md index ab059fac3..5ce535433 100644 --- a/tools/skillgen/expected/graphify__skills__trae__references__query.md +++ b/tools/skillgen/expected/graphify__skills__trae__references__query.md @@ -11,7 +11,7 @@ Two traversal modes - choose based on the question: First check the graph exists: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " from pathlib import Path if not Path('graphify-out/graph.json').exists(): print('ERROR: No graph found. Run /graphify first to build the graph.') @@ -28,7 +28,7 @@ Fix this **without inventing tokens** by expanding the query against the actual 1. Extract the token vocabulary from node labels: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, re from pathlib import Path data = json.loads(Path('graphify-out/graph.json').read_text(encoding='utf-8')) @@ -64,8 +64,8 @@ Build the **expanded query string** by joining the selected tokens with spaces. If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" -# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 +"$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" +# or: "$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: @@ -77,7 +77,7 @@ If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal 5. If the graph lacks enough information, say so - do not hallucinate edges. ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import sys, json from networkx.readwrite import json_graph import networkx as nx @@ -168,7 +168,7 @@ Replace `QUESTION` with the **expanded** query string, `MODE` with `bfs` or `dfs After writing the answer, save it back into the graph so it improves future queries. Include the expanded tokens inside the `--answer` text (e.g. `"Expanded from original query via vocab: [tokens]. Then traversed..."`) so the next `--update` extracts the expansion history as a graph node: ```bash -$(cat graphify-out/.graphify_python) -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 ``` Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with your full answer text (containing the expanded-token trace), `NODE1 NODE2` 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. @@ -188,13 +188,13 @@ At the **start** of graph work, refresh and read the lessons: run `graphify refl Find the shortest path between two named concepts in the graph. Prefer the CLI when installed: ```bash -graphify path "NODE_A" "NODE_B" +"$(cat graphify-out/.graphify_python)" -m graphify path "NODE_A" "NODE_B" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -246,7 +246,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) -m graphify save-result --question "Path from NODE_A to NODE_B" --answer "ANSWER" --type path_query --nodes NODE_A NODE_B +"$(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 ``` --- @@ -256,13 +256,13 @@ $(cat graphify-out/.graphify_python) -m graphify save-result --question "Path fr Give a plain-language explanation of a single node - everything connected to it. Prefer the CLI when installed: ```bash -graphify explain "NODE_NAME" +"$(cat graphify-out/.graphify_python)" -m graphify explain "NODE_NAME" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -307,5 +307,5 @@ 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) -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME ``` diff --git a/tools/skillgen/expected/graphify__skills__vscode__references__query.md b/tools/skillgen/expected/graphify__skills__vscode__references__query.md index ab059fac3..5ce535433 100644 --- a/tools/skillgen/expected/graphify__skills__vscode__references__query.md +++ b/tools/skillgen/expected/graphify__skills__vscode__references__query.md @@ -11,7 +11,7 @@ Two traversal modes - choose based on the question: First check the graph exists: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " from pathlib import Path if not Path('graphify-out/graph.json').exists(): print('ERROR: No graph found. Run /graphify first to build the graph.') @@ -28,7 +28,7 @@ Fix this **without inventing tokens** by expanding the query against the actual 1. Extract the token vocabulary from node labels: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, re from pathlib import Path data = json.loads(Path('graphify-out/graph.json').read_text(encoding='utf-8')) @@ -64,8 +64,8 @@ Build the **expanded query string** by joining the selected tokens with spaces. If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" -# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 +"$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" +# or: "$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: @@ -77,7 +77,7 @@ If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal 5. If the graph lacks enough information, say so - do not hallucinate edges. ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import sys, json from networkx.readwrite import json_graph import networkx as nx @@ -168,7 +168,7 @@ Replace `QUESTION` with the **expanded** query string, `MODE` with `bfs` or `dfs After writing the answer, save it back into the graph so it improves future queries. Include the expanded tokens inside the `--answer` text (e.g. `"Expanded from original query via vocab: [tokens]. Then traversed..."`) so the next `--update` extracts the expansion history as a graph node: ```bash -$(cat graphify-out/.graphify_python) -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 ``` Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with your full answer text (containing the expanded-token trace), `NODE1 NODE2` 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. @@ -188,13 +188,13 @@ At the **start** of graph work, refresh and read the lessons: run `graphify refl Find the shortest path between two named concepts in the graph. Prefer the CLI when installed: ```bash -graphify path "NODE_A" "NODE_B" +"$(cat graphify-out/.graphify_python)" -m graphify path "NODE_A" "NODE_B" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -246,7 +246,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) -m graphify save-result --question "Path from NODE_A to NODE_B" --answer "ANSWER" --type path_query --nodes NODE_A NODE_B +"$(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 ``` --- @@ -256,13 +256,13 @@ $(cat graphify-out/.graphify_python) -m graphify save-result --question "Path fr Give a plain-language explanation of a single node - everything connected to it. Prefer the CLI when installed: ```bash -graphify explain "NODE_NAME" +"$(cat graphify-out/.graphify_python)" -m graphify explain "NODE_NAME" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -307,5 +307,5 @@ 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) -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME ``` diff --git a/tools/skillgen/expected/graphify__skills__windows__references__query.md b/tools/skillgen/expected/graphify__skills__windows__references__query.md index ab059fac3..5ce535433 100644 --- a/tools/skillgen/expected/graphify__skills__windows__references__query.md +++ b/tools/skillgen/expected/graphify__skills__windows__references__query.md @@ -11,7 +11,7 @@ Two traversal modes - choose based on the question: First check the graph exists: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " from pathlib import Path if not Path('graphify-out/graph.json').exists(): print('ERROR: No graph found. Run /graphify first to build the graph.') @@ -28,7 +28,7 @@ Fix this **without inventing tokens** by expanding the query against the actual 1. Extract the token vocabulary from node labels: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, re from pathlib import Path data = json.loads(Path('graphify-out/graph.json').read_text(encoding='utf-8')) @@ -64,8 +64,8 @@ Build the **expanded query string** by joining the selected tokens with spaces. If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" -# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 +"$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" +# or: "$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: @@ -77,7 +77,7 @@ If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal 5. If the graph lacks enough information, say so - do not hallucinate edges. ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import sys, json from networkx.readwrite import json_graph import networkx as nx @@ -168,7 +168,7 @@ Replace `QUESTION` with the **expanded** query string, `MODE` with `bfs` or `dfs After writing the answer, save it back into the graph so it improves future queries. Include the expanded tokens inside the `--answer` text (e.g. `"Expanded from original query via vocab: [tokens]. Then traversed..."`) so the next `--update` extracts the expansion history as a graph node: ```bash -$(cat graphify-out/.graphify_python) -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 ``` Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with your full answer text (containing the expanded-token trace), `NODE1 NODE2` 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. @@ -188,13 +188,13 @@ At the **start** of graph work, refresh and read the lessons: run `graphify refl Find the shortest path between two named concepts in the graph. Prefer the CLI when installed: ```bash -graphify path "NODE_A" "NODE_B" +"$(cat graphify-out/.graphify_python)" -m graphify path "NODE_A" "NODE_B" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -246,7 +246,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) -m graphify save-result --question "Path from NODE_A to NODE_B" --answer "ANSWER" --type path_query --nodes NODE_A NODE_B +"$(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 ``` --- @@ -256,13 +256,13 @@ $(cat graphify-out/.graphify_python) -m graphify save-result --question "Path fr Give a plain-language explanation of a single node - everything connected to it. Prefer the CLI when installed: ```bash -graphify explain "NODE_NAME" +"$(cat graphify-out/.graphify_python)" -m graphify explain "NODE_NAME" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -307,5 +307,5 @@ 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) -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME ``` diff --git a/tools/skillgen/fragments/always-on/agents-md.md b/tools/skillgen/fragments/always-on/agents-md.md index 6da0539c1..8bea68aa0 100644 --- a/tools/skillgen/fragments/always-on/agents-md.md +++ b/tools/skillgen/fragments/always-on/agents-md.md @@ -5,7 +5,7 @@ This project has a knowledge graph at graphify-out/ with god nodes, community st When the user types `/graphify`, use the installed graphify skill or instructions before doing anything else. Rules: -- For codebase questions, first use the graph when graphify-out/graph.json exists. Prefer the MCP `query_graph` tool when it is available; otherwise follow the installed Graphify skill's interpreter-based CLI fallback. Use `shortest_path` / `get_node` (or their module-invocation CLI equivalents) for relationships and focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. +- For codebase questions, first use the graph when graphify-out/graph.json exists. Prefer the MCP `query_graph` tool. CLI fallback: `"$(cat graphify-out/.graphify_python)" -m graphify query ""` (PowerShell: `& (Get-Content graphify-out\.graphify_python) -m graphify query ""`); use the same recorded interpreter with `path` or `explain` for relationships and focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. - Dirty graphify-out/ files are expected after hooks or incremental updates; dirty graph files are not a reason to skip graphify. Only skip graphify if the task is about stale or incorrect graph output, or the user explicitly says not to use it. - If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing. - Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context. diff --git a/tools/skillgen/fragments/always-on/antigravity-rules.md b/tools/skillgen/fragments/always-on/antigravity-rules.md index e6b109acc..baf9f98e4 100644 --- a/tools/skillgen/fragments/always-on/antigravity-rules.md +++ b/tools/skillgen/fragments/always-on/antigravity-rules.md @@ -8,7 +8,7 @@ description: Consult the graphify knowledge graph at graphify-out/ for codebase This project has a graphify knowledge graph at graphify-out/. Rules: -- For codebase or architecture questions, when `graphify-out/graph.json` exists, first use the graph. Prefer the MCP `query_graph` tool when it is available; otherwise follow the installed Graphify skill's interpreter-based CLI fallback. Use `shortest_path` / `get_node` (or their module-invocation CLI equivalents) for relationships and focused concepts. These return a scoped subgraph, usually much smaller than `GRAPH_REPORT.md` or raw grep output. +- For codebase or architecture questions, when `graphify-out/graph.json` exists, first use the graph. Prefer the MCP `query_graph` tool. CLI fallback: `"$(cat graphify-out/.graphify_python)" -m graphify query ""` (PowerShell: `& (Get-Content graphify-out\.graphify_python) -m graphify query ""`); use the same recorded interpreter with `path` or `explain` for relationships and focused concepts. These return a scoped subgraph, usually much smaller than `GRAPH_REPORT.md` or raw grep output. - If graphify-out/wiki/index.md exists, navigate it instead of reading raw files - Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context - After modifying code files in this session, run `graphify update .` to keep the graph current (AST-only, no API cost) diff --git a/tools/skillgen/fragments/always-on/claude-md.md b/tools/skillgen/fragments/always-on/claude-md.md index b62763ded..d1928c7a2 100644 --- a/tools/skillgen/fragments/always-on/claude-md.md +++ b/tools/skillgen/fragments/always-on/claude-md.md @@ -3,7 +3,7 @@ This project has a knowledge graph at graphify-out/ with god nodes, community structure, and cross-file relationships. Rules: -- For codebase questions, first use the graph when graphify-out/graph.json exists. Prefer the MCP `query_graph` tool when it is available; otherwise follow the installed Graphify skill's interpreter-based CLI fallback. Use `shortest_path` / `get_node` (or their module-invocation CLI equivalents) for relationships and focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. +- For codebase questions, first use the graph when graphify-out/graph.json exists. Prefer the MCP `query_graph` tool. CLI fallback: `"$(cat graphify-out/.graphify_python)" -m graphify query ""` (PowerShell: `& (Get-Content graphify-out\.graphify_python) -m graphify query ""`); use the same recorded interpreter with `path` or `explain` for relationships and focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. - If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing. - Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context. - After modifying code, run `graphify update .` to keep the graph current (AST-only, no API cost). diff --git a/tools/skillgen/fragments/always-on/gemini-md.md b/tools/skillgen/fragments/always-on/gemini-md.md index 417efeb27..d1928c7a2 100644 --- a/tools/skillgen/fragments/always-on/gemini-md.md +++ b/tools/skillgen/fragments/always-on/gemini-md.md @@ -3,7 +3,7 @@ This project has a knowledge graph at graphify-out/ with god nodes, community structure, and cross-file relationships. Rules: -- For codebase questions, first run `graphify query ""` when graphify-out/graph.json exists. Use `graphify path "" ""` for relationships and `graphify explain ""` for focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. +- For codebase questions, first use the graph when graphify-out/graph.json exists. Prefer the MCP `query_graph` tool. CLI fallback: `"$(cat graphify-out/.graphify_python)" -m graphify query ""` (PowerShell: `& (Get-Content graphify-out\.graphify_python) -m graphify query ""`); use the same recorded interpreter with `path` or `explain` for relationships and focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. - If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing. - Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context. - After modifying code, run `graphify update .` to keep the graph current (AST-only, no API cost). diff --git a/tools/skillgen/fragments/always-on/kiro-steering.md b/tools/skillgen/fragments/always-on/kiro-steering.md index cb6f4543d..87a8e8dea 100644 --- a/tools/skillgen/fragments/always-on/kiro-steering.md +++ b/tools/skillgen/fragments/always-on/kiro-steering.md @@ -2,4 +2,4 @@ inclusion: always --- -graphify: A knowledge graph of this project lives in `graphify-out/`. For codebase, architecture, or dependency questions, when `graphify-out/graph.json` exists, first run `graphify query ""` (or `graphify path "" ""` / `graphify explain ""`). These return a scoped subgraph, usually much smaller than `GRAPH_REPORT.md` or raw grep output. Read `GRAPH_REPORT.md` only for broad architecture review or when those commands do not surface enough context. +graphify: A knowledge graph of this project lives in `graphify-out/`. For codebase, architecture, or dependency questions, when `graphify-out/graph.json` exists, prefer the MCP `query_graph` tool. CLI fallback: `"$(cat graphify-out/.graphify_python)" -m graphify query ""` (PowerShell: `& (Get-Content graphify-out\.graphify_python) -m graphify query ""`); use the same recorded interpreter with `path` or `explain`. These return a scoped subgraph, usually much smaller than `GRAPH_REPORT.md` or raw grep output. Read `GRAPH_REPORT.md` only for broad architecture review or when those commands do not surface enough context. diff --git a/tools/skillgen/fragments/always-on/vscode-instructions.md b/tools/skillgen/fragments/always-on/vscode-instructions.md index 9cb983c95..5f9181c1b 100644 --- a/tools/skillgen/fragments/always-on/vscode-instructions.md +++ b/tools/skillgen/fragments/always-on/vscode-instructions.md @@ -1,10 +1,11 @@ ## graphify For any question about this repo's architecture, structure, components, or how to add/modify/find -code, your first action should be `graphify query ""` when `graphify-out/graph.json` -exists. Use `graphify path "" ""` for relationship questions and `graphify explain ""` -for focused-concept questions. These return a scoped subgraph, usually much smaller than the full -report or raw grep output. +code, first use the graph when `graphify-out/graph.json` exists. Prefer the MCP `query_graph` tool. +CLI fallback: `"$(cat graphify-out/.graphify_python)" -m graphify query ""` (PowerShell: +`& (Get-Content graphify-out\.graphify_python) -m graphify query ""`); use the same recorded +interpreter with `path` or `explain`. These return a scoped subgraph, usually much smaller than the +full report or raw grep output. Triggers: "how do I…", "where is…", "what does … do", "add/modify a ", "explain the architecture", or anything that depends on how files or classes relate. diff --git a/tools/skillgen/fragments/core/core.md b/tools/skillgen/fragments/core/core.md index c527a1256..b1f111a8d 100644 --- a/tools/skillgen/fragments/core/core.md +++ b/tools/skillgen/fragments/core/core.md @@ -47,7 +47,7 @@ Drop any folder of code, docs, papers, images, or video into graphify and get a If the user invoked `/graphify --help` or `/graphify -h` (with no other arguments), print the contents of the `## Usage` section above verbatim and stop. Do not run any commands, do not detect files, do not default the path to `.`. Just print the Usage block and return. -**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Run `graphify query ""` immediately. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. +**Fast path — existing graph:** Before doing anything else, check whether `graphify-out/graph.json` exists. The expected location is `graphify-out/graph.json` relative to the **current working directory** (i.e. the project root where you are running commands). If it exists AND the user's request is a natural-language question about the codebase (e.g. "How does X work?", "What calls Y?", "Trace the data flow through Z") and NOT an explicit rebuild command (`--update`, `--cluster-only`, or a bare path/URL that implies fresh extraction): **skip Steps 1–5 entirely and jump straight to `## For /graphify query`.** Prefer the MCP `query_graph` tool immediately; otherwise use the recorded-interpreter command in `references/query.md`. Do not run detect. Do not check corpus size. Do not ask the user to narrow. The graph is already built — use it. If no path was given, use `.` (current directory). Do not ask the user for a path. diff --git a/tools/skillgen/fragments/references/query/default.md b/tools/skillgen/fragments/references/query/default.md index ab059fac3..5ce535433 100644 --- a/tools/skillgen/fragments/references/query/default.md +++ b/tools/skillgen/fragments/references/query/default.md @@ -11,7 +11,7 @@ Two traversal modes - choose based on the question: First check the graph exists: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " from pathlib import Path if not Path('graphify-out/graph.json').exists(): print('ERROR: No graph found. Run /graphify first to build the graph.') @@ -28,7 +28,7 @@ Fix this **without inventing tokens** by expanding the query against the actual 1. Extract the token vocabulary from node labels: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, re from pathlib import Path data = json.loads(Path('graphify-out/graph.json').read_text(encoding='utf-8')) @@ -64,8 +64,8 @@ Build the **expanded query string** by joining the selected tokens with spaces. If the MCP `query_graph` tool is available, call it with the expanded question, traversal mode, token budget, and the project root as `project_path`, then continue with the returned scoped subgraph. Otherwise invoke the module through the graph's recorded interpreter: ```bash -$(cat graphify-out/.graphify_python) -m graphify query "QUESTION" -# or: $(cat graphify-out/.graphify_python) -m graphify query "QUESTION" --dfs --budget 3000 +"$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" +# or: "$(cat graphify-out/.graphify_python)" -m graphify query "QUESTION" --dfs --budget 3000 ``` If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal inline: @@ -77,7 +77,7 @@ If the CLI is unavailable, load `graphify-out/graph.json` and run the traversal 5. If the graph lacks enough information, say so - do not hallucinate edges. ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import sys, json from networkx.readwrite import json_graph import networkx as nx @@ -168,7 +168,7 @@ Replace `QUESTION` with the **expanded** query string, `MODE` with `bfs` or `dfs After writing the answer, save it back into the graph so it improves future queries. Include the expanded tokens inside the `--answer` text (e.g. `"Expanded from original query via vocab: [tokens]. Then traversed..."`) so the next `--update` extracts the expansion history as a graph node: ```bash -$(cat graphify-out/.graphify_python) -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "ORIGINAL_QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2 ``` Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with your full answer text (containing the expanded-token trace), `NODE1 NODE2` 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. @@ -188,13 +188,13 @@ At the **start** of graph work, refresh and read the lessons: run `graphify refl Find the shortest path between two named concepts in the graph. Prefer the CLI when installed: ```bash -graphify path "NODE_A" "NODE_B" +"$(cat graphify-out/.graphify_python)" -m graphify path "NODE_A" "NODE_B" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -246,7 +246,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) -m graphify save-result --question "Path from NODE_A to NODE_B" --answer "ANSWER" --type path_query --nodes NODE_A NODE_B +"$(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 ``` --- @@ -256,13 +256,13 @@ $(cat graphify-out/.graphify_python) -m graphify save-result --question "Path fr Give a plain-language explanation of a single node - everything connected to it. Prefer the CLI when installed: ```bash -graphify explain "NODE_NAME" +"$(cat graphify-out/.graphify_python)" -m graphify explain "NODE_NAME" ``` If the CLI is unavailable, run it inline: ```bash -$(cat graphify-out/.graphify_python) -c " +"$(cat graphify-out/.graphify_python)" -c " import json, sys import networkx as nx from networkx.readwrite import json_graph @@ -307,5 +307,5 @@ 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) -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME +"$(cat graphify-out/.graphify_python)" -m graphify save-result --question "Explain NODE_NAME" --answer "ANSWER" --type explain --nodes NODE_NAME ``` diff --git a/tools/skillgen/gen.py b/tools/skillgen/gen.py index 09e19ede0..82fcb07d5 100644 --- a/tools/skillgen/gen.py +++ b/tools/skillgen/gen.py @@ -110,6 +110,47 @@ def _v8_baseline_ref(platform_key: str) -> str: "When the user types `/graphify`, use the installed graphify skill or instructions " "before doing anything else.", ), + ( + "- For codebase questions, first run `graphify query \"\"` when graphify-out/graph.json exists. Use `graphify path \"\" \"\"` for relationships and `graphify explain \"\"` for focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output.", + "- For codebase questions, first use the graph when graphify-out/graph.json exists. Prefer the MCP `query_graph` tool. CLI fallback: `\"$(cat graphify-out/.graphify_python)\" -m graphify query \"\"` (PowerShell: `& (Get-Content graphify-out\\.graphify_python) -m graphify query \"\"`); use the same recorded interpreter with `path` or `explain` for relationships and focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output.", + ), + ), + "_CLAUDE_MD_SECTION": ( + ( + "- For codebase questions, first run `graphify query \"\"` when graphify-out/graph.json exists. Use `graphify path \"\" \"\"` for relationships and `graphify explain \"\"` for focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output.", + "- For codebase questions, first use the graph when graphify-out/graph.json exists. Prefer the MCP `query_graph` tool. CLI fallback: `\"$(cat graphify-out/.graphify_python)\" -m graphify query \"\"` (PowerShell: `& (Get-Content graphify-out\\.graphify_python) -m graphify query \"\"`); use the same recorded interpreter with `path` or `explain` for relationships and focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output.", + ), + ), + "_GEMINI_MD_SECTION": ( + ( + "- For codebase questions, first run `graphify query \"\"` when graphify-out/graph.json exists. Use `graphify path \"\" \"\"` for relationships and `graphify explain \"\"` for focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output.", + "- For codebase questions, first use the graph when graphify-out/graph.json exists. Prefer the MCP `query_graph` tool. CLI fallback: `\"$(cat graphify-out/.graphify_python)\" -m graphify query \"\"` (PowerShell: `& (Get-Content graphify-out\\.graphify_python) -m graphify query \"\"`); use the same recorded interpreter with `path` or `explain` for relationships and focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output.", + ), + ), + "_ANTIGRAVITY_RULES": ( + ( + "- For codebase or architecture questions, when `graphify-out/graph.json` exists, first run `graphify query \"\"` (CLI) or `query_graph` (MCP). Use `graphify path \"\" \"\"` / `shortest_path` for relationships and `graphify explain \"\"` / `get_node` for focused concepts. These return a scoped subgraph, usually much smaller than `GRAPH_REPORT.md` or raw grep output.", + "- For codebase or architecture questions, when `graphify-out/graph.json` exists, first use the graph. Prefer the MCP `query_graph` tool. CLI fallback: `\"$(cat graphify-out/.graphify_python)\" -m graphify query \"\"` (PowerShell: `& (Get-Content graphify-out\\.graphify_python) -m graphify query \"\"`); use the same recorded interpreter with `path` or `explain` for relationships and focused concepts. These return a scoped subgraph, usually much smaller than `GRAPH_REPORT.md` or raw grep output.", + ), + ), + "_KIRO_STEERING": ( + ( + "graphify: A knowledge graph of this project lives in `graphify-out/`. For codebase, architecture, or dependency questions, when `graphify-out/graph.json` exists, first run `graphify query \"\"` (or `graphify path \"\" \"\"` / `graphify explain \"\"`). These return a scoped subgraph, usually much smaller than `GRAPH_REPORT.md` or raw grep output. Read `GRAPH_REPORT.md` only for broad architecture review or when those commands do not surface enough context.", + "graphify: A knowledge graph of this project lives in `graphify-out/`. For codebase, architecture, or dependency questions, when `graphify-out/graph.json` exists, prefer the MCP `query_graph` tool. CLI fallback: `\"$(cat graphify-out/.graphify_python)\" -m graphify query \"\"` (PowerShell: `& (Get-Content graphify-out\\.graphify_python) -m graphify query \"\"`); use the same recorded interpreter with `path` or `explain`. These return a scoped subgraph, usually much smaller than `GRAPH_REPORT.md` or raw grep output. Read `GRAPH_REPORT.md` only for broad architecture review or when those commands do not surface enough context.", + ), + ), + "_VSCODE_INSTRUCTIONS_SECTION": ( + ( + "code, your first action should be `graphify query \"\"` when `graphify-out/graph.json`\n" + "exists. Use `graphify path \"\" \"\"` for relationship questions and `graphify explain \"\"`\n" + "for focused-concept questions. These return a scoped subgraph, usually much smaller than the full\n" + "report or raw grep output.", + "code, first use the graph when `graphify-out/graph.json` exists. Prefer the MCP `query_graph` tool.\n" + "CLI fallback: `\"$(cat graphify-out/.graphify_python)\" -m graphify query \"\"` (PowerShell:\n" + "`& (Get-Content graphify-out\\.graphify_python) -m graphify query \"\"`); use the same recorded\n" + "interpreter with `path` or `explain`. These return a scoped subgraph, usually much smaller than the\n" + "full report or raw grep output.", + ), ), } From 85921806be8cd4b91cfe32a4c1e1a3bf1e122760 Mon Sep 17 00:00:00 2001 From: Logan Date: Wed, 2 Sep 2026 05:46:26 -0500 Subject: [PATCH 04/14] fix(guidance): remove remaining blocked invocations --- graphify/cli.py | 30 +++++++++++++++--------------- graphify/install.py | 9 +++------ tests/test_hook_guard.py | 2 +- tests/test_hook_strict.py | 2 +- tests/test_read_hook.py | 2 +- tests/test_search_hook.py | 5 +++-- tests/test_skillgen.py | 9 +++++++++ 7 files changed, 33 insertions(+), 26 deletions(-) diff --git a/graphify/cli.py b/graphify/cli.py index b1395a1b9..9e5771378 100644 --- a/graphify/cli.py +++ b/graphify/cli.py @@ -20,8 +20,8 @@ "hookEventName": "PreToolUse", "additionalContext": ( 'MANDATORY: graphify-out/graph.json exists. Use the MCP `query_graph` ' - 'tool when available; otherwise run `graphify query ""` ' - 'through the interpreter named by the installed Graphify skill. Only grep ' + 'tool when available; otherwise use the recorded-interpreter query ' + 'command in the installed Graphify skill. Only grep ' 'after graphify has oriented you, or to modify/debug specific lines.' ), } @@ -32,9 +32,8 @@ "additionalContext": ( 'MANDATORY: graphify-out/graph.json exists. You MUST run graphify ' 'before reading source files. Prefer the MCP `query_graph` tool; ' - 'otherwise use: `graphify query ""` ' - '(scoped subgraph), `graphify explain ""`, or ' - '`graphify path "" ""`. Only read raw files after graphify has ' + 'otherwise use the recorded-interpreter query, explain, or path ' + 'command in the installed Graphify skill. Only read raw files after graphify has ' 'oriented you, or to modify/debug specific lines. This rule applies to ' 'subagents too — include it in every subagent prompt involving code ' 'exploration.' @@ -46,8 +45,9 @@ "hookEventName": "PreToolUse", "additionalContext": ( 'graphify-out/graph.json exists but may be STALE for this file (the file ' - 'changed after the last build). Prefer `graphify query ""` for ' - 'orientation, and run `graphify update` to refresh the graph. Reading the ' + 'changed after the last build). Prefer the MCP `query_graph` tool or the ' + 'installed skill\'s recorded-interpreter query for orientation, and use ' + 'that interpreter to update the graph. Reading the ' 'file directly is fine.' ), } @@ -62,9 +62,9 @@ "permissionDecision": "deny", "permissionDecisionReason": ( 'graphify strict mode: this project has a fresh knowledge graph that covers ' - 'this file. Use the MCP `query_graph` tool when available; otherwise run ' - '`graphify query ""` (or `graphify explain` / ' - '`graphify path`) FIRST to orient yourself, then re-issue this Read — it ' + 'this file. Use the MCP `query_graph` tool when available; otherwise use ' + 'the recorded-interpreter query, explain, or path command in the installed ' + 'Graphify skill FIRST, then re-issue this Read — it ' 'will be allowed. This block fires at most once per session; reading raw ' 'files to modify or debug specific lines is fine after one query. Apply the ' 'same rule in any subagent prompt that explores code.' @@ -78,8 +78,8 @@ ) _GEMINI_NUDGE_TEXT = ( 'graphify: knowledge graph at graphify-out/. For focused questions, use the ' - 'MCP `query_graph` tool when available; otherwise run `graphify query ' - '""` through the installed Graphify skill (scoped subgraph, usually much smaller than ' + 'MCP `query_graph` tool when available; otherwise use the recorded-interpreter ' + 'query command in the installed Graphify skill (scoped subgraph, usually much smaller than ' 'GRAPH_REPORT.md) instead of grepping raw files. Read GRAPH_REPORT.md only ' 'for broad architecture context.' ) @@ -1192,7 +1192,7 @@ def dispatch_command(cmd: str) -> None: sys.exit(1) elif cmd == "query": if len(sys.argv) < 3: - print("Usage: graphify query \"\" [--dfs] [--context C] [--budget N] [--graph path]", file=sys.stderr) + print("Usage: python -m graphify query \"\" [--dfs] [--context C] [--budget N] [--graph path]", file=sys.stderr) sys.exit(1) from graphify.serve import _query_graph_text from graphify.security import sanitize_label @@ -1524,7 +1524,7 @@ def dispatch_command(cmd: str) -> None: elif cmd == "path": if len(sys.argv) < 4: print( - 'Usage: graphify path "" "" [--graph path] ' + 'Usage: python -m graphify path "" "" [--graph path] ' "[--directed|--undirected]", file=sys.stderr, ) @@ -1689,7 +1689,7 @@ def dispatch_command(cmd: str) -> None: elif cmd == "explain": if len(sys.argv) < 3: - print('Usage: graphify explain "" [--graph path]', file=sys.stderr) + print('Usage: python -m graphify explain "" [--graph path]', file=sys.stderr) sys.exit(1) from graphify.serve import _find_node, find_node_ambiguity from networkx.readwrite import json_graph diff --git a/graphify/install.py b/graphify/install.py index a2bd0aa8f..ee13a8bcf 100644 --- a/graphify/install.py +++ b/graphify/install.py @@ -1111,10 +1111,7 @@ def _antigravity_uninstall(project_dir: Path, *, project: bool = False) -> None: This project has a graphify knowledge graph at graphify-out/. -**MANDATORY: Before using Read, Grep, Glob, or Bash to explore the codebase, you MUST run graphify first:** -- `graphify query ""` — scoped subgraph for any codebase or architecture question -- `graphify path "" ""` — dependency path between two symbols -- `graphify explain ""` — all nodes related to a concept +**MANDATORY: Before using Read, Grep, Glob, or Bash to explore the codebase, use the graph first.** Prefer the MCP `query_graph` tool. CLI fallback: `"$(cat graphify-out/.graphify_python)" -m graphify query ""` (PowerShell: `& (Get-Content graphify-out\\.graphify_python) -m graphify query ""`); use the same recorded interpreter with `path` or `explain`. This applies to YOU and to every subagent you spawn. Include this rule explicitly in every subagent prompt that involves code exploration. Do not skip graphify because files are "already known" or because you are executing a plan — the graph surfaces cross-file dependencies and INFERRED edges that grep and Read cannot find. @@ -1158,7 +1155,7 @@ def _cursor_uninstall(project_dir: Path) -> None: This project has a graphify knowledge graph at graphify-out/. Rules: -- For codebase or architecture questions, when `graphify-out/graph.json` exists, first run `graphify query ""` (or `graphify path "" ""` / `graphify explain ""`). These return a scoped subgraph, usually much smaller than `GRAPH_REPORT.md` or raw grep output. +- For codebase or architecture questions, when `graphify-out/graph.json` exists, prefer the MCP `query_graph` tool. CLI fallback: `"$(cat graphify-out/.graphify_python)" -m graphify query ""` (PowerShell: `& (Get-Content graphify-out\\.graphify_python) -m graphify query ""`); use the same recorded interpreter with `path` or `explain`. These return a scoped subgraph, usually much smaller than `GRAPH_REPORT.md` or raw grep output. - If graphify-out/wiki/index.md exists, navigate it instead of reading raw files - Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context - After modifying code files in this session, run `graphify update .` to keep the graph current (AST-only, no API cost) @@ -1364,7 +1361,7 @@ def _uninstall_kilo_plugin(project_dir: Path) -> None: // ';' not '&&' — Windows PowerShell 5.1 rejects '&&' as a statement // separator, breaking the first bash command of the session (#1646). output.args.command = - 'echo "[graphify] knowledge graph at graphify-out/. For focused questions, run graphify query with your question (scoped subgraph, usually much smaller than GRAPH_REPORT.md) instead of grepping raw files. Read GRAPH_REPORT.md only for broad architecture context." ; ' + + 'echo "[graphify] knowledge graph at graphify-out/. For focused questions, use MCP query_graph or the recorded-interpreter query command in the installed Graphify skill instead of grepping raw files. Read GRAPH_REPORT.md only for broad architecture context." ; ' + output.args.command; reminded = true; } diff --git a/tests/test_hook_guard.py b/tests/test_hook_guard.py index 869f7ccb8..d624a43d2 100644 --- a/tests/test_hook_guard.py +++ b/tests/test_hook_guard.py @@ -209,7 +209,7 @@ def test_gemini_allow_with_nudge(tmp_path, monkeypatch): out = _invoke("gemini", None, tmp_path, monkeypatch, graph=True) payload = json.loads(out) assert payload["decision"] == "allow" - assert "graphify query" in payload["additionalContext"] + assert "query_graph" in payload["additionalContext"] def test_gemini_allow_without_graph(tmp_path, monkeypatch): diff --git a/tests/test_hook_strict.py b/tests/test_hook_strict.py index c9454aeb8..48a0a2302 100644 --- a/tests/test_hook_strict.py +++ b/tests/test_hook_strict.py @@ -66,7 +66,7 @@ def test_strict_first_read_denies_then_nudges(tmp_path, monkeypatch): f = _fixture(tmp_path) out1 = _invoke("read", _read(f), tmp_path, monkeypatch, strict=True) assert _is_deny(out1) - assert "graphify query" in json.loads(out1)["hookSpecificOutput"]["permissionDecisionReason"] + assert "query_graph" in json.loads(out1)["hookSpecificOutput"]["permissionDecisionReason"] # marker created assert (tmp_path / "graphify-out" / "cache" / "hook_sessions" / "s1.denied").exists() # same session again -> soft nudge, not a second deny diff --git a/tests/test_read_hook.py b/tests/test_read_hook.py index a260021cb..79f96f69a 100644 --- a/tests/test_read_hook.py +++ b/tests/test_read_hook.py @@ -67,7 +67,7 @@ def test_nudge_payload_is_valid_pretooluse_json(tmp_path): out = _run({"file_path": "pkg/mod.ts"}, tmp_path, graph=True).stdout payload = json.loads(out) assert payload["hookSpecificOutput"]["hookEventName"] == "PreToolUse" - assert "graphify query" in payload["hookSpecificOutput"]["additionalContext"] + assert "query_graph" in payload["hookSpecificOutput"]["additionalContext"] def test_silent_on_graphify_out_targets(tmp_path): diff --git a/tests/test_search_hook.py b/tests/test_search_hook.py index 10e54a9f5..6a98f582e 100644 --- a/tests/test_search_hook.py +++ b/tests/test_search_hook.py @@ -59,8 +59,9 @@ def test_hook_command_has_no_backslashes(monkeypatch): # escape character and strips it (C:\Users\me\graphify.EXE -> C:Usersme...), # breaking every guard. The emitted command must use forward slashes. from graphify.__main__ import _resolve_graphify_exe + monkeypatch.setattr("graphify.hooks._pinned_python", lambda: "") monkeypatch.setattr("shutil.which", lambda _name: r"C:\Users\me\graphify.EXE") - assert _resolve_graphify_exe() == "C:/Users/me/graphify.EXE" + assert _resolve_graphify_exe() == '"C:/Users/me/graphify.EXE"' for h in _claude_pretooluse_hooks(): assert "\\" not in h["hooks"][0]["command"] @@ -104,7 +105,7 @@ def test_nudge_payload_is_valid_pretooluse_json(tmp_path): out = _run("grep -rn foo .", tmp_path, graph=True).stdout payload = json.loads(out) assert payload["hookSpecificOutput"]["hookEventName"] == "PreToolUse" - assert "graphify query" in payload["hookSpecificOutput"]["additionalContext"] + assert "query_graph" in payload["hookSpecificOutput"]["additionalContext"] def test_fails_open_on_malformed_stdin(tmp_path): diff --git a/tests/test_skillgen.py b/tests/test_skillgen.py index a25d155ca..3fc094611 100644 --- a/tests/test_skillgen.py +++ b/tests/test_skillgen.py @@ -83,6 +83,15 @@ def test_query_guidance_prefers_mcp_and_never_runs_windows_console_shim(): assert "query_graph" in antigravity assert not re.search(r"`graphify (?:query|path|explain) (?=[\"<])", antigravity) + forbidden = re.compile( + r"(? Date: Wed, 2 Sep 2026 06:00:27 -0500 Subject: [PATCH 05/14] fix(guidance): align emitters and owned tests --- graphify/always_on/agents-md.md | 2 +- graphify/always_on/antigravity-rules.md | 2 +- graphify/always_on/claude-md.md | 2 +- graphify/always_on/gemini-md.md | 2 +- graphify/install.py | 4 ++-- graphify/skill-kilo.md | 2 +- tests/test_codebuddy.py | 6 +++--- tests/test_devin.py | 10 +++++----- tests/test_gemini_hook.py | 4 ++-- tests/test_hook_guard.py | 8 ++++---- tests/test_install.py | 16 +++++++++------- tests/test_install_strings.py | 8 ++++---- tests/test_install_upgrade.py | 4 ++-- tests/test_read_hook.py | 12 ++++++------ tests/test_search_hook.py | 11 +++++++---- tests/test_skillgen.py | 6 +++--- .../expected/graphify__always_on__agents-md.md | 2 +- .../graphify__always_on__antigravity-rules.md | 2 +- .../expected/graphify__always_on__claude-md.md | 2 +- .../expected/graphify__always_on__gemini-md.md | 2 +- tools/skillgen/expected/graphify__skill-kilo.md | 2 +- tools/skillgen/fragments/always-on/agents-md.md | 2 +- .../fragments/always-on/antigravity-rules.md | 2 +- tools/skillgen/fragments/always-on/claude-md.md | 2 +- tools/skillgen/fragments/always-on/gemini-md.md | 2 +- tools/skillgen/fragments/extra/kilo-rules.md | 2 +- tools/skillgen/gen.py | 16 ++++++++++++++++ 27 files changed, 78 insertions(+), 57 deletions(-) diff --git a/graphify/always_on/agents-md.md b/graphify/always_on/agents-md.md index 8bea68aa0..6fb941954 100644 --- a/graphify/always_on/agents-md.md +++ b/graphify/always_on/agents-md.md @@ -9,4 +9,4 @@ Rules: - Dirty graphify-out/ files are expected after hooks or incremental updates; dirty graph files are not a reason to skip graphify. Only skip graphify if the task is about stale or incorrect graph output, or the user explicitly says not to use it. - If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing. - Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context. -- After modifying code, run `graphify update .` to keep the graph current (AST-only, no API cost). +- After modifying code, run `"$(cat graphify-out/.graphify_python)" -m graphify update .` (PowerShell: `& (Get-Content graphify-out\.graphify_python) -m graphify update .`) to keep the graph current (AST-only, no API cost). diff --git a/graphify/always_on/antigravity-rules.md b/graphify/always_on/antigravity-rules.md index baf9f98e4..28f540dd7 100644 --- a/graphify/always_on/antigravity-rules.md +++ b/graphify/always_on/antigravity-rules.md @@ -11,4 +11,4 @@ Rules: - For codebase or architecture questions, when `graphify-out/graph.json` exists, first use the graph. Prefer the MCP `query_graph` tool. CLI fallback: `"$(cat graphify-out/.graphify_python)" -m graphify query ""` (PowerShell: `& (Get-Content graphify-out\.graphify_python) -m graphify query ""`); use the same recorded interpreter with `path` or `explain` for relationships and focused concepts. These return a scoped subgraph, usually much smaller than `GRAPH_REPORT.md` or raw grep output. - If graphify-out/wiki/index.md exists, navigate it instead of reading raw files - Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context -- After modifying code files in this session, run `graphify update .` to keep the graph current (AST-only, no API cost) +- After modifying code files in this session, run `"$(cat graphify-out/.graphify_python)" -m graphify update .` (PowerShell: `& (Get-Content graphify-out\.graphify_python) -m graphify update .`) to keep the graph current (AST-only, no API cost) diff --git a/graphify/always_on/claude-md.md b/graphify/always_on/claude-md.md index d1928c7a2..53d52fe75 100644 --- a/graphify/always_on/claude-md.md +++ b/graphify/always_on/claude-md.md @@ -6,4 +6,4 @@ Rules: - For codebase questions, first use the graph when graphify-out/graph.json exists. Prefer the MCP `query_graph` tool. CLI fallback: `"$(cat graphify-out/.graphify_python)" -m graphify query ""` (PowerShell: `& (Get-Content graphify-out\.graphify_python) -m graphify query ""`); use the same recorded interpreter with `path` or `explain` for relationships and focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. - If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing. - Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context. -- After modifying code, run `graphify update .` to keep the graph current (AST-only, no API cost). +- After modifying code, run `"$(cat graphify-out/.graphify_python)" -m graphify update .` (PowerShell: `& (Get-Content graphify-out\.graphify_python) -m graphify update .`) to keep the graph current (AST-only, no API cost). diff --git a/graphify/always_on/gemini-md.md b/graphify/always_on/gemini-md.md index d1928c7a2..53d52fe75 100644 --- a/graphify/always_on/gemini-md.md +++ b/graphify/always_on/gemini-md.md @@ -6,4 +6,4 @@ Rules: - For codebase questions, first use the graph when graphify-out/graph.json exists. Prefer the MCP `query_graph` tool. CLI fallback: `"$(cat graphify-out/.graphify_python)" -m graphify query ""` (PowerShell: `& (Get-Content graphify-out\.graphify_python) -m graphify query ""`); use the same recorded interpreter with `path` or `explain` for relationships and focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. - If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing. - Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context. -- After modifying code, run `graphify update .` to keep the graph current (AST-only, no API cost). +- After modifying code, run `"$(cat graphify-out/.graphify_python)" -m graphify update .` (PowerShell: `& (Get-Content graphify-out\.graphify_python) -m graphify update .`) to keep the graph current (AST-only, no API cost). diff --git a/graphify/install.py b/graphify/install.py index ee13a8bcf..a3b24342e 100644 --- a/graphify/install.py +++ b/graphify/install.py @@ -1121,7 +1121,7 @@ def _antigravity_uninstall(project_dir: Path, *, project: bool = False) -> None: - If `graphify-out/wiki/index.md` exists, navigate it instead of reading raw files - Read `graphify-out/GRAPH_REPORT.md` only for broad architecture review when query/path/explain do not surface enough context -- After modifying code files, run `graphify update .` to keep the graph current (AST-only, no API cost) +- After modifying code files, run `"$(cat graphify-out/.graphify_python)" -m graphify update .` (PowerShell: `& (Get-Content graphify-out\\.graphify_python) -m graphify update .`) to keep the graph current (AST-only, no API cost) """ def _cursor_install(project_dir: Path) -> None: """Write .cursor/rules/graphify.mdc with alwaysApply: true.""" @@ -1158,7 +1158,7 @@ def _cursor_uninstall(project_dir: Path) -> None: - For codebase or architecture questions, when `graphify-out/graph.json` exists, prefer the MCP `query_graph` tool. CLI fallback: `"$(cat graphify-out/.graphify_python)" -m graphify query ""` (PowerShell: `& (Get-Content graphify-out\\.graphify_python) -m graphify query ""`); use the same recorded interpreter with `path` or `explain`. These return a scoped subgraph, usually much smaller than `GRAPH_REPORT.md` or raw grep output. - If graphify-out/wiki/index.md exists, navigate it instead of reading raw files - Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context -- After modifying code files in this session, run `graphify update .` to keep the graph current (AST-only, no API cost) +- After modifying code files in this session, run `"$(cat graphify-out/.graphify_python)" -m graphify update .` (PowerShell: `& (Get-Content graphify-out\\.graphify_python) -m graphify update .`) to keep the graph current (AST-only, no API cost) """ def _devin_rules_install(project_dir: Path) -> None: """Write .windsurf/rules/graphify.md for always-on Devin context.""" diff --git a/graphify/skill-kilo.md b/graphify/skill-kilo.md index 71e6fe485..0c1ffe082 100644 --- a/graphify/skill-kilo.md +++ b/graphify/skill-kilo.md @@ -707,7 +707,7 @@ When the user asks to install the post-commit auto-rebuild hook or wire graphify - Use the native `Task` tool for semantic extraction fan-out. - Launch all chunk tasks in the same response so they run in parallel. - Always use `subagent_type="general"` for extraction chunks. -- After modifying code files during the session, run `graphify update .`. +- After modifying code files during the session, run `"$(cat graphify-out/.graphify_python)" -m graphify update .` (PowerShell: `& (Get-Content graphify-out\.graphify_python) -m graphify update .`). --- diff --git a/tests/test_codebuddy.py b/tests/test_codebuddy.py index 62a7e6c02..ac1aa52cf 100644 --- a/tests/test_codebuddy.py +++ b/tests/test_codebuddy.py @@ -59,11 +59,11 @@ def test_codebuddy_skill_file_contains_frontmatter(tmp_path): assert "description:" in content -def test_codebuddy_skill_file_references_graphify_query(tmp_path): - """/graphify skill must mention graphify query (query-first policy).""" +def test_codebuddy_skill_file_references_query_graph(tmp_path): + """The skill must name the MCP-first query path.""" _codebuddy_install_user(tmp_path) content = _skill_path_user(tmp_path).read_text() - assert "graphify query" in content or "/graphify query" in content + assert "query_graph" in content # --------------------------------------------------------------------------- diff --git a/tests/test_devin.py b/tests/test_devin.py index a3bca5d05..88c400ef8 100644 --- a/tests/test_devin.py +++ b/tests/test_devin.py @@ -54,11 +54,11 @@ def test_devin_skill_file_contains_frontmatter(tmp_path): assert "triggers:" in content -def test_devin_skill_file_references_graphify_query(tmp_path): - """/graphify skill must mention graphify query (query-first policy).""" +def test_devin_skill_file_references_query_graph(tmp_path): + """The skill must name the MCP-first query path.""" _devin_install_user(tmp_path) content = _skill_path_user(tmp_path).read_text() - assert "graphify query" in content or "/graphify query" in content + assert "query_graph" in content def test_devin_install_user_does_not_write_rules(tmp_path): @@ -101,12 +101,12 @@ def test_devin_install_project_creates_rules_file(tmp_path, monkeypatch): assert "GRAPH_REPORT.md" in rules.read_text() -def test_devin_rules_content_recommends_graphify_query(tmp_path): +def test_devin_rules_content_recommends_query_graph(tmp_path): """The rules file installed by devin must use query-first policy.""" from graphify.__main__ import _devin_rules_install _devin_rules_install(tmp_path) content = _rules_path(tmp_path).read_text() - assert "graphify query" in content + assert "query_graph" in content def test_devin_rules_install_idempotent(tmp_path, capsys): diff --git a/tests/test_gemini_hook.py b/tests/test_gemini_hook.py index aa84117dd..017fa2308 100644 --- a/tests/test_gemini_hook.py +++ b/tests/test_gemini_hook.py @@ -42,7 +42,7 @@ def test_allows_and_nudges_with_graph(tmp_path): out = _run(tmp_path, graph=True).stdout payload = json.loads(out) assert payload["decision"] == "allow" - assert "graphify query" in payload["additionalContext"] + assert "query_graph" in payload["additionalContext"] def test_allows_without_nudge_when_no_graph(tmp_path): @@ -68,4 +68,4 @@ def test_honors_graphify_out_override(tmp_path): [sys.executable, "-m", "graphify", "hook-guard", "gemini"], input="", capture_output=True, text=True, cwd=tmp_path, env=env, ) - assert "graphify query" in json.loads(r.stdout).get("additionalContext", "") + assert "query_graph" in json.loads(r.stdout).get("additionalContext", "") diff --git a/tests/test_hook_guard.py b/tests/test_hook_guard.py index d624a43d2..32b968882 100644 --- a/tests/test_hook_guard.py +++ b/tests/test_hook_guard.py @@ -63,7 +63,7 @@ def __init__(self, b): ]) def test_search_nudges(command, tmp_path, monkeypatch): out = _invoke("search", {"tool_input": {"command": command}}, tmp_path, monkeypatch) - assert "graphify query" in out, f"{command!r} should nudge" + assert "query_graph" in out, f"{command!r} should nudge" assert json.loads(out)["hookSpecificOutput"]["hookEventName"] == "PreToolUse" @@ -107,7 +107,7 @@ def test_search_non_string_command_is_silent(tmp_path, monkeypatch): def test_search_top_level_command_without_tool_input(tmp_path, monkeypatch): # Some hosts pass the tool payload flat (no "tool_input" wrapper). out = _invoke("search", {"command": "grep x"}, tmp_path, monkeypatch) - assert "graphify query" in out + assert "query_graph" in out def test_search_non_dict_tool_input_is_silent(tmp_path, monkeypatch): @@ -133,7 +133,7 @@ def test_search_non_dict_tool_input_is_silent(tmp_path, monkeypatch): ]) def test_read_nudges(tool_input, tmp_path, monkeypatch): out = _invoke("read", {"tool_input": tool_input}, tmp_path, monkeypatch) - assert "graphify query" in out, f"{tool_input!r} should nudge" + assert "query_graph" in out, f"{tool_input!r} should nudge" # --------------------------------------------------------------------------- # @@ -179,7 +179,7 @@ def test_read_respects_custom_output_dir_name(tmp_path, monkeypatch): def test_read_nudges_source_outside_custom_output_dir(tmp_path, monkeypatch): out = _invoke("read", {"tool_input": {"file_path": "src/app.py"}}, tmp_path, monkeypatch, graph=True, out_name="build-out") - assert "graphify query" in out + assert "query_graph" in out # --------------------------------------------------------------------------- # diff --git a/tests/test_install.py b/tests/test_install.py index 68c2c06ac..e47a212f6 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -274,9 +274,7 @@ def test_codex_skill_uses_graphify_with_existing_graph(): skill = (Path(graphify.__file__).parent / "skill-codex.md").read_text() assert "Fast path — existing graph" in skill assert "skip Steps 1–5 entirely and jump straight to `## For /graphify query`" in skill - assert "graphify query" in skill - assert "graphify explain" in skill - assert "graphify path" in skill + assert "query_graph" in skill def test_codex_agents_install_mentions_dirty_graph_output(tmp_path): @@ -1278,6 +1276,7 @@ def test_project_install_hook_command_is_portable(tmp_path, monkeypatch, platfor monkeypatch.chdir(project) # Resolution would otherwise find a real graphify on this machine; pin it so # the assertion fails loudly if the project path ever resolves again. + monkeypatch.setattr("graphify.hooks._pinned_python", lambda: "") monkeypatch.setattr("shutil.which", lambda _name: r"C:\Users\installer\graphify.EXE") _run_project_install(project, home, platform) @@ -1285,7 +1284,7 @@ def test_project_install_hook_command_is_portable(tmp_path, monkeypatch, platfor commands = _hook_commands((project / _PROJECT_HOOK_FILES[platform]).read_text(encoding="utf-8")) assert commands, f"{platform} project install registered no hook command" for command in commands: - assert command.startswith("graphify "), command + assert ".graphify_python" in command, command assert ":" not in command, f"drive letter / absolute path leaked: {command}" assert "\\" not in command, f"backslash path leaked: {command}" assert ".exe" not in command.lower(), f"platform exe casing leaked: {command}" @@ -1299,6 +1298,7 @@ def test_user_profile_install_still_resolves_absolute_path(tmp_path, monkeypatch project = tmp_path / "project" project.mkdir() monkeypatch.chdir(project) + monkeypatch.setattr("graphify.hooks._pinned_python", lambda: "") monkeypatch.setattr("shutil.which", lambda _name: r"C:\Users\installer\graphify.EXE") from graphify.__main__ import main @@ -1310,7 +1310,7 @@ def test_user_profile_install_still_resolves_absolute_path(tmp_path, monkeypatch commands = _hook_commands((project / _PROJECT_HOOK_FILES[platform]).read_text(encoding="utf-8")) assert commands, f"{platform} install registered no hook command" for command in commands: - assert command.startswith("C:/Users/installer/graphify.EXE "), command + assert command.startswith('"C:/Users/installer/graphify.EXE" '), command @pytest.mark.parametrize("platform", sorted(_PROJECT_HOOK_FILES)) @@ -1320,6 +1320,7 @@ def test_project_install_is_idempotent(tmp_path, monkeypatch, platform): project = tmp_path / "project" project.mkdir() monkeypatch.chdir(project) + monkeypatch.setattr("graphify.hooks._pinned_python", lambda: "") monkeypatch.setattr("shutil.which", lambda _name: r"C:\Users\installer\graphify.EXE") target = project / _PROJECT_HOOK_FILES[platform] @@ -1330,14 +1331,15 @@ def test_project_install_is_idempotent(tmp_path, monkeypatch, platform): assert target.read_text(encoding="utf-8") == first -def test_project_uninstall_removes_the_bare_hook_command(tmp_path, monkeypatch): - """The uninstall filter matches on "graphify", so a bare command still goes.""" +def test_project_uninstall_removes_the_hook_command(tmp_path, monkeypatch): + """The uninstall filter removes the recorded-interpreter hook command.""" from graphify.__main__ import main home = tmp_path / "home" project = tmp_path / "project" project.mkdir() monkeypatch.chdir(project) + monkeypatch.setattr("graphify.hooks._pinned_python", lambda: "") monkeypatch.setattr("shutil.which", lambda _name: r"C:\Users\installer\graphify.EXE") _run_project_install(project, home, "claude") diff --git a/tests/test_install_strings.py b/tests/test_install_strings.py index 7e65b768e..5e8fa1f2a 100644 --- a/tests/test_install_strings.py +++ b/tests/test_install_strings.py @@ -47,18 +47,18 @@ } -def test_every_install_surface_recommends_graphify_query(): - """All ten install surfaces must point the assistant at `graphify query` +def test_every_install_surface_recommends_query_graph(): + """All ten install surfaces must point the assistant at `query_graph` as the first action for codebase questions. This is the load-bearing fix for issue #580 — the alternative (reading GRAPH_REPORT.md) costs ~10x more tokens per question and made the project worse-than-baseline in real Claude Code sessions.""" missing: list[str] = [] for name, text in _INSTALL_TEXTS.items(): - if "graphify query" not in text: + if "query_graph" not in text: missing.append(name) assert not missing, ( - f"these install surfaces no longer mention `graphify query`: {missing}. " + f"these install surfaces no longer mention `query_graph`: {missing}. " f"If you removed it intentionally, consider whether issue #580 is back." ) diff --git a/tests/test_install_upgrade.py b/tests/test_install_upgrade.py index e85add452..8e66d7ad8 100644 --- a/tests/test_install_upgrade.py +++ b/tests/test_install_upgrade.py @@ -85,8 +85,8 @@ def _assert_no_report_first(text: str, ctx: str) -> None: def _assert_query_first(text: str, ctx: str) -> None: - assert "graphify query" in text, ( - f"{ctx}: new 'graphify query' guidance missing after upgrade" + assert "query_graph" in text, ( + f"{ctx}: new 'query_graph' guidance missing after upgrade" ) diff --git a/tests/test_read_hook.py b/tests/test_read_hook.py index 79f96f69a..baa3cbf82 100644 --- a/tests/test_read_hook.py +++ b/tests/test_read_hook.py @@ -60,7 +60,7 @@ def test_silent_without_graph(tmp_path): def test_nudges_on_source_read_with_graph(tmp_path): out = _run({"file_path": "src/app.py"}, tmp_path, graph=True).stdout - assert "graphify query" in out + assert "query_graph" in out def test_nudge_payload_is_valid_pretooluse_json(tmp_path): @@ -84,19 +84,19 @@ def test_silent_on_non_source_files(tmp_path): def test_glob_pattern_nudges(tmp_path): out = _run({"pattern": "**/*.py", "path": "src"}, tmp_path, graph=True).stdout - assert "graphify query" in out + assert "query_graph" in out def test_nudges_on_framework_source(tmp_path): """.astro/.vue/.svelte are real source types and must nudge (regression).""" for path in ("src/components/Hero.astro", "src/App.vue", "src/Card.svelte"): out = _run({"file_path": path}, tmp_path, graph=True).stdout - assert "graphify query" in out, f"{path} should nudge" + assert "query_graph" in out, f"{path} should nudge" def test_astro_glob_nudges(tmp_path): out = _run({"pattern": "**/*.astro"}, tmp_path, graph=True).stdout - assert "graphify query" in out + assert "query_graph" in out def test_silent_on_json_config(tmp_path): @@ -111,13 +111,13 @@ def test_nudges_on_multi_dot_source(tmp_path): a.test.tsx -> .tsx (nudge), foo.min.js -> .js (nudge).""" for path in ("src/a.test.tsx", "lib/foo.min.js"): out = _run({"file_path": path}, tmp_path, graph=True).stdout - assert "graphify query" in out, f"{path} should nudge" + assert "query_graph" in out, f"{path} should nudge" def test_windows_path_nudges(tmp_path): """Backslash-separated paths split on the real final segment, then its ext.""" out = _run({"file_path": r"src\components\app.py"}, tmp_path, graph=True).stdout - assert "graphify query" in out + assert "query_graph" in out def test_silent_when_extension_is_on_a_directory_segment(tmp_path): diff --git a/tests/test_search_hook.py b/tests/test_search_hook.py index 6a98f582e..e074ef9e6 100644 --- a/tests/test_search_hook.py +++ b/tests/test_search_hook.py @@ -65,6 +65,9 @@ def test_hook_command_has_no_backslashes(monkeypatch): for h in _claude_pretooluse_hooks(): assert "\\" not in h["hooks"][0]["command"] + monkeypatch.setattr("graphify.hooks._pinned_python", lambda: r"C:\Tools\Graphify\python.exe") + assert _resolve_graphify_exe() == '"C:/Tools/Graphify/python.exe" -m graphify' + def test_command_has_no_shell_syntax(): # Claude runs `command` through a POSIX shell (Git Bash on Windows), while @@ -87,7 +90,7 @@ def test_nudges_on_search_commands_with_graph(tmp_path): "ag needle", ): out = _run(command, tmp_path, graph=True).stdout - assert "graphify query" in out, f"{command!r} should nudge" + assert "query_graph" in out, f"{command!r} should nudge" def test_silent_without_graph(tmp_path): @@ -137,7 +140,7 @@ def test_honors_graphify_out_override(tmp_path): [sys.executable, "-m", "graphify", "hook-guard", "search"], input=stdin, capture_output=True, text=True, cwd=tmp_path, env=env, ) - assert "graphify query" in r.stdout + assert "query_graph" in r.stdout # --------------------------------------------------------------------------- @@ -153,7 +156,7 @@ def test_grep_tool_input_nudges_with_graph(tmp_path): {"pattern": "foo", "path": "src/", "glob": "**/*.ts"}, ): out = _run_grep_tool(tool_input, tmp_path, graph=True).stdout - assert "graphify query" in out, f"Grep input {tool_input!r} should nudge" + assert "query_graph" in out, f"Grep input {tool_input!r} should nudge" def test_grep_tool_input_silent_without_graph(tmp_path): @@ -165,7 +168,7 @@ def test_grep_tool_nudge_is_valid_pretooluse_json(tmp_path): out = _run_grep_tool({"pattern": "foo", "path": "."}, tmp_path, graph=True).stdout payload = json.loads(out) assert payload["hookSpecificOutput"]["hookEventName"] == "PreToolUse" - assert "graphify query" in payload["hookSpecificOutput"]["additionalContext"] + assert "query_graph" in payload["hookSpecificOutput"]["additionalContext"] def test_grep_tool_never_blocks(tmp_path): diff --git a/tests/test_skillgen.py b/tests/test_skillgen.py index 3fc094611..04a117f12 100644 --- a/tests/test_skillgen.py +++ b/tests/test_skillgen.py @@ -69,7 +69,7 @@ def test_query_guidance_prefers_mcp_and_never_runs_windows_console_shim(): assert "never execute a `graphify.exe` console shim" in query, key guidance = core + "\n" + query assert not re.search( - r"`graphify (?:query|path|explain) (?=[\"<])|^(?:# or: )?graphify (?:query|path|explain) (?=[\"<])", + r"`graphify (?:query|path|explain|update) (?=[\"<.])|^(?:# or: )?graphify (?:query|path|explain|update) (?=[\"<.])", guidance, re.MULTILINE, ), key @@ -81,10 +81,10 @@ def test_query_guidance_prefers_mcp_and_never_runs_windows_console_shim(): if artifact.path == "graphify/always_on/antigravity-rules.md" ) assert "query_graph" in antigravity - assert not re.search(r"`graphify (?:query|path|explain) (?=[\"<])", antigravity) + assert not re.search(r"`graphify (?:query|path|explain|update) (?=[\"<.])", antigravity) forbidden = re.compile( - r"(?"` (PowerShell: `& (Get-Content graphify-out\.graphify_python) -m graphify query ""`); use the same recorded interpreter with `path` or `explain` for relationships and focused concepts. These return a scoped subgraph, usually much smaller than `GRAPH_REPORT.md` or raw grep output. - If graphify-out/wiki/index.md exists, navigate it instead of reading raw files - Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context -- After modifying code files in this session, run `graphify update .` to keep the graph current (AST-only, no API cost) +- After modifying code files in this session, run `"$(cat graphify-out/.graphify_python)" -m graphify update .` (PowerShell: `& (Get-Content graphify-out\.graphify_python) -m graphify update .`) to keep the graph current (AST-only, no API cost) diff --git a/tools/skillgen/expected/graphify__always_on__claude-md.md b/tools/skillgen/expected/graphify__always_on__claude-md.md index d1928c7a2..53d52fe75 100644 --- a/tools/skillgen/expected/graphify__always_on__claude-md.md +++ b/tools/skillgen/expected/graphify__always_on__claude-md.md @@ -6,4 +6,4 @@ Rules: - For codebase questions, first use the graph when graphify-out/graph.json exists. Prefer the MCP `query_graph` tool. CLI fallback: `"$(cat graphify-out/.graphify_python)" -m graphify query ""` (PowerShell: `& (Get-Content graphify-out\.graphify_python) -m graphify query ""`); use the same recorded interpreter with `path` or `explain` for relationships and focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. - If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing. - Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context. -- After modifying code, run `graphify update .` to keep the graph current (AST-only, no API cost). +- After modifying code, run `"$(cat graphify-out/.graphify_python)" -m graphify update .` (PowerShell: `& (Get-Content graphify-out\.graphify_python) -m graphify update .`) to keep the graph current (AST-only, no API cost). diff --git a/tools/skillgen/expected/graphify__always_on__gemini-md.md b/tools/skillgen/expected/graphify__always_on__gemini-md.md index d1928c7a2..53d52fe75 100644 --- a/tools/skillgen/expected/graphify__always_on__gemini-md.md +++ b/tools/skillgen/expected/graphify__always_on__gemini-md.md @@ -6,4 +6,4 @@ Rules: - For codebase questions, first use the graph when graphify-out/graph.json exists. Prefer the MCP `query_graph` tool. CLI fallback: `"$(cat graphify-out/.graphify_python)" -m graphify query ""` (PowerShell: `& (Get-Content graphify-out\.graphify_python) -m graphify query ""`); use the same recorded interpreter with `path` or `explain` for relationships and focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. - If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing. - Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context. -- After modifying code, run `graphify update .` to keep the graph current (AST-only, no API cost). +- After modifying code, run `"$(cat graphify-out/.graphify_python)" -m graphify update .` (PowerShell: `& (Get-Content graphify-out\.graphify_python) -m graphify update .`) to keep the graph current (AST-only, no API cost). diff --git a/tools/skillgen/expected/graphify__skill-kilo.md b/tools/skillgen/expected/graphify__skill-kilo.md index 71e6fe485..0c1ffe082 100644 --- a/tools/skillgen/expected/graphify__skill-kilo.md +++ b/tools/skillgen/expected/graphify__skill-kilo.md @@ -707,7 +707,7 @@ When the user asks to install the post-commit auto-rebuild hook or wire graphify - Use the native `Task` tool for semantic extraction fan-out. - Launch all chunk tasks in the same response so they run in parallel. - Always use `subagent_type="general"` for extraction chunks. -- After modifying code files during the session, run `graphify update .`. +- After modifying code files during the session, run `"$(cat graphify-out/.graphify_python)" -m graphify update .` (PowerShell: `& (Get-Content graphify-out\.graphify_python) -m graphify update .`). --- diff --git a/tools/skillgen/fragments/always-on/agents-md.md b/tools/skillgen/fragments/always-on/agents-md.md index 8bea68aa0..6fb941954 100644 --- a/tools/skillgen/fragments/always-on/agents-md.md +++ b/tools/skillgen/fragments/always-on/agents-md.md @@ -9,4 +9,4 @@ Rules: - Dirty graphify-out/ files are expected after hooks or incremental updates; dirty graph files are not a reason to skip graphify. Only skip graphify if the task is about stale or incorrect graph output, or the user explicitly says not to use it. - If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing. - Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context. -- After modifying code, run `graphify update .` to keep the graph current (AST-only, no API cost). +- After modifying code, run `"$(cat graphify-out/.graphify_python)" -m graphify update .` (PowerShell: `& (Get-Content graphify-out\.graphify_python) -m graphify update .`) to keep the graph current (AST-only, no API cost). diff --git a/tools/skillgen/fragments/always-on/antigravity-rules.md b/tools/skillgen/fragments/always-on/antigravity-rules.md index baf9f98e4..28f540dd7 100644 --- a/tools/skillgen/fragments/always-on/antigravity-rules.md +++ b/tools/skillgen/fragments/always-on/antigravity-rules.md @@ -11,4 +11,4 @@ Rules: - For codebase or architecture questions, when `graphify-out/graph.json` exists, first use the graph. Prefer the MCP `query_graph` tool. CLI fallback: `"$(cat graphify-out/.graphify_python)" -m graphify query ""` (PowerShell: `& (Get-Content graphify-out\.graphify_python) -m graphify query ""`); use the same recorded interpreter with `path` or `explain` for relationships and focused concepts. These return a scoped subgraph, usually much smaller than `GRAPH_REPORT.md` or raw grep output. - If graphify-out/wiki/index.md exists, navigate it instead of reading raw files - Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context -- After modifying code files in this session, run `graphify update .` to keep the graph current (AST-only, no API cost) +- After modifying code files in this session, run `"$(cat graphify-out/.graphify_python)" -m graphify update .` (PowerShell: `& (Get-Content graphify-out\.graphify_python) -m graphify update .`) to keep the graph current (AST-only, no API cost) diff --git a/tools/skillgen/fragments/always-on/claude-md.md b/tools/skillgen/fragments/always-on/claude-md.md index d1928c7a2..53d52fe75 100644 --- a/tools/skillgen/fragments/always-on/claude-md.md +++ b/tools/skillgen/fragments/always-on/claude-md.md @@ -6,4 +6,4 @@ Rules: - For codebase questions, first use the graph when graphify-out/graph.json exists. Prefer the MCP `query_graph` tool. CLI fallback: `"$(cat graphify-out/.graphify_python)" -m graphify query ""` (PowerShell: `& (Get-Content graphify-out\.graphify_python) -m graphify query ""`); use the same recorded interpreter with `path` or `explain` for relationships and focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. - If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing. - Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context. -- After modifying code, run `graphify update .` to keep the graph current (AST-only, no API cost). +- After modifying code, run `"$(cat graphify-out/.graphify_python)" -m graphify update .` (PowerShell: `& (Get-Content graphify-out\.graphify_python) -m graphify update .`) to keep the graph current (AST-only, no API cost). diff --git a/tools/skillgen/fragments/always-on/gemini-md.md b/tools/skillgen/fragments/always-on/gemini-md.md index d1928c7a2..53d52fe75 100644 --- a/tools/skillgen/fragments/always-on/gemini-md.md +++ b/tools/skillgen/fragments/always-on/gemini-md.md @@ -6,4 +6,4 @@ Rules: - For codebase questions, first use the graph when graphify-out/graph.json exists. Prefer the MCP `query_graph` tool. CLI fallback: `"$(cat graphify-out/.graphify_python)" -m graphify query ""` (PowerShell: `& (Get-Content graphify-out\.graphify_python) -m graphify query ""`); use the same recorded interpreter with `path` or `explain` for relationships and focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. - If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing. - Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context. -- After modifying code, run `graphify update .` to keep the graph current (AST-only, no API cost). +- After modifying code, run `"$(cat graphify-out/.graphify_python)" -m graphify update .` (PowerShell: `& (Get-Content graphify-out\.graphify_python) -m graphify update .`) to keep the graph current (AST-only, no API cost). diff --git a/tools/skillgen/fragments/extra/kilo-rules.md b/tools/skillgen/fragments/extra/kilo-rules.md index c42881efe..e31a8b786 100644 --- a/tools/skillgen/fragments/extra/kilo-rules.md +++ b/tools/skillgen/fragments/extra/kilo-rules.md @@ -3,6 +3,6 @@ - Use the native `Task` tool for semantic extraction fan-out. - Launch all chunk tasks in the same response so they run in parallel. - Always use `subagent_type="general"` for extraction chunks. -- After modifying code files during the session, run `graphify update .`. +- After modifying code files during the session, run `"$(cat graphify-out/.graphify_python)" -m graphify update .` (PowerShell: `& (Get-Content graphify-out\.graphify_python) -m graphify update .`). --- diff --git a/tools/skillgen/gen.py b/tools/skillgen/gen.py index 82fcb07d5..1e5b240a4 100644 --- a/tools/skillgen/gen.py +++ b/tools/skillgen/gen.py @@ -114,24 +114,40 @@ def _v8_baseline_ref(platform_key: str) -> str: "- For codebase questions, first run `graphify query \"\"` when graphify-out/graph.json exists. Use `graphify path \"\" \"\"` for relationships and `graphify explain \"\"` for focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output.", "- For codebase questions, first use the graph when graphify-out/graph.json exists. Prefer the MCP `query_graph` tool. CLI fallback: `\"$(cat graphify-out/.graphify_python)\" -m graphify query \"\"` (PowerShell: `& (Get-Content graphify-out\\.graphify_python) -m graphify query \"\"`); use the same recorded interpreter with `path` or `explain` for relationships and focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output.", ), + ( + "- After modifying code, run `graphify update .` to keep the graph current (AST-only, no API cost).", + "- After modifying code, run `\"$(cat graphify-out/.graphify_python)\" -m graphify update .` (PowerShell: `& (Get-Content graphify-out\\.graphify_python) -m graphify update .`) to keep the graph current (AST-only, no API cost).", + ), ), "_CLAUDE_MD_SECTION": ( ( "- For codebase questions, first run `graphify query \"\"` when graphify-out/graph.json exists. Use `graphify path \"\" \"\"` for relationships and `graphify explain \"\"` for focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output.", "- For codebase questions, first use the graph when graphify-out/graph.json exists. Prefer the MCP `query_graph` tool. CLI fallback: `\"$(cat graphify-out/.graphify_python)\" -m graphify query \"\"` (PowerShell: `& (Get-Content graphify-out\\.graphify_python) -m graphify query \"\"`); use the same recorded interpreter with `path` or `explain` for relationships and focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output.", ), + ( + "- After modifying code, run `graphify update .` to keep the graph current (AST-only, no API cost).", + "- After modifying code, run `\"$(cat graphify-out/.graphify_python)\" -m graphify update .` (PowerShell: `& (Get-Content graphify-out\\.graphify_python) -m graphify update .`) to keep the graph current (AST-only, no API cost).", + ), ), "_GEMINI_MD_SECTION": ( ( "- For codebase questions, first run `graphify query \"\"` when graphify-out/graph.json exists. Use `graphify path \"\" \"\"` for relationships and `graphify explain \"\"` for focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output.", "- For codebase questions, first use the graph when graphify-out/graph.json exists. Prefer the MCP `query_graph` tool. CLI fallback: `\"$(cat graphify-out/.graphify_python)\" -m graphify query \"\"` (PowerShell: `& (Get-Content graphify-out\\.graphify_python) -m graphify query \"\"`); use the same recorded interpreter with `path` or `explain` for relationships and focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output.", ), + ( + "- After modifying code, run `graphify update .` to keep the graph current (AST-only, no API cost).", + "- After modifying code, run `\"$(cat graphify-out/.graphify_python)\" -m graphify update .` (PowerShell: `& (Get-Content graphify-out\\.graphify_python) -m graphify update .`) to keep the graph current (AST-only, no API cost).", + ), ), "_ANTIGRAVITY_RULES": ( ( "- For codebase or architecture questions, when `graphify-out/graph.json` exists, first run `graphify query \"\"` (CLI) or `query_graph` (MCP). Use `graphify path \"\" \"\"` / `shortest_path` for relationships and `graphify explain \"\"` / `get_node` for focused concepts. These return a scoped subgraph, usually much smaller than `GRAPH_REPORT.md` or raw grep output.", "- For codebase or architecture questions, when `graphify-out/graph.json` exists, first use the graph. Prefer the MCP `query_graph` tool. CLI fallback: `\"$(cat graphify-out/.graphify_python)\" -m graphify query \"\"` (PowerShell: `& (Get-Content graphify-out\\.graphify_python) -m graphify query \"\"`); use the same recorded interpreter with `path` or `explain` for relationships and focused concepts. These return a scoped subgraph, usually much smaller than `GRAPH_REPORT.md` or raw grep output.", ), + ( + "- After modifying code files in this session, run `graphify update .` to keep the graph current (AST-only, no API cost)", + "- After modifying code files in this session, run `\"$(cat graphify-out/.graphify_python)\" -m graphify update .` (PowerShell: `& (Get-Content graphify-out\\.graphify_python) -m graphify update .`) to keep the graph current (AST-only, no API cost)", + ), ), "_KIRO_STEERING": ( ( From c908c336325fe772b5511d2273ee6b417bb7adea Mon Sep 17 00:00:00 2001 From: Logan Date: Wed, 2 Sep 2026 06:19:23 -0500 Subject: [PATCH 06/14] test(hooks): align Claude fail-open contract --- tests/test_install.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_install.py b/tests/test_install.py index e47a212f6..a0ef5b6e9 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -404,8 +404,8 @@ def test_codebuddy_install_writes_hook(tmp_path): def test_claude_hook_is_shell_agnostic(tmp_path): - # #522: the installed PreToolUse hooks must be plain exe invocations, not - # POSIX bash (which fails on Windows cmd.exe/PowerShell). + # Claude runs command hooks through a POSIX shell, including Git Bash on + # Windows; keep the command simple while preserving #3280 fail-open. import json as _json from graphify.__main__ import _install_claude_hook _install_claude_hook(tmp_path) @@ -414,9 +414,9 @@ def test_claude_hook_is_shell_agnostic(tmp_path): assert {"Bash|Grep", "Read|Glob"} <= matchers # Grep in the search matcher: #1986 for h in hooks: cmd = h["hooks"][0]["command"] - for token in ("$(", "case ", "[ -f", "&&", "||", ";;", "echo '"): + for token in ("$(", "case ", "[ -f", "&&", ";;", "echo '"): assert token not in cmd, f"shell syntax {token!r} in {cmd!r}" - assert "graphify" in cmd and "hook-guard" in cmd + assert "graphify" in cmd and "hook-guard" in cmd and cmd.endswith("|| true") def test_claude_hook_install_idempotent_and_replaces_old_bash_hook(tmp_path): From 679512c812412f0528e7e7a6a6b1e3f7cd3b51bc Mon Sep 17 00:00:00 2001 From: Logan Date: Wed, 2 Sep 2026 06:24:51 -0500 Subject: [PATCH 07/14] test(settings): align strict fail-open suffix --- tests/test_settings_merge.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_settings_merge.py b/tests/test_settings_merge.py index ab4cd562b..f3642ca74 100644 --- a/tests/test_settings_merge.py +++ b/tests/test_settings_merge.py @@ -89,7 +89,7 @@ def test_claude_install_preserves_existing_settings(tmp_path): graphify_hooks = [h for h in pre_tool if "graphify" in str(h)] assert len(graphify_hooks) == 2 # strict=True lands on the read guard - assert any(h["hooks"][0]["command"].endswith("--strict") for h in graphify_hooks) + assert any(h["hooks"][0]["command"].endswith("--strict || true") for h in graphify_hooks) # ---------------------------------------------------------------- BOM From 2a837253cbe746ae85625f974a4ab1fc451770c6 Mon Sep 17 00:00:00 2001 From: Logan Date: Wed, 2 Sep 2026 08:13:10 -0500 Subject: [PATCH 08/14] fix(hooks): name the interpreter in the generated Windows hook command The generated Windows command may be run by cmd.exe or by PowerShell, and no bare fail-open suffix is correct in both. `& exit /b 0` is cmd syntax; run by PowerShell the bare `&` is the background operator, so the hook spawns a job and exits 1 - the fail-open suffix caused the very failure it was added to prevent (#3280). `; exit 0` has the mirror problem: valid in PowerShell, rejected by cmd.exe. Naming the interpreter removes the ambiguity. Measured on Windows, the generated string now exits 0 under both pwsh and cmd.exe; the previous form exited 1 under pwsh. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01PPaGbwQeyWLwbVm9uRgHtU --- graphify/install.py | 11 ++++++++++- tests/test_install.py | 10 +++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/graphify/install.py b/graphify/install.py index a3b24342e..9f8832d44 100644 --- a/graphify/install.py +++ b/graphify/install.py @@ -1471,7 +1471,16 @@ def _graphify_command_windows(args: str, project: bool = False) -> str: '"$p=(Get-Content -Raw \'graphify-out\\.graphify_python\').Trim(); ' f'& $p -m graphify {args}; exit 0"' ) - return f'cmd /c "{_resolve_graphify_exe()} {args}" & exit /b 0' + # The generated string may be run by cmd.exe or by PowerShell, and no bare + # suffix is fail-open in both: `& exit /b 0` is cmd syntax that PowerShell + # reads as its background operator (spawns a job, exits 1), while `; exit 0` + # is PowerShell syntax that cmd.exe rejects. Naming the interpreter removes + # the ambiguity, so the advisory hook can never break the host (#3280). + inner = f"cmd /c '{_resolve_graphify_exe()} {args}'" + return ( + 'powershell.exe -NoProfile -NonInteractive -Command ' + f'"{inner}; exit 0"' + ) def _install_codex_hook(project_dir: Path, project: bool = False) -> None: """Add graphify PreToolUse hook to .codex/hooks.json. diff --git a/tests/test_install.py b/tests/test_install.py index a0ef5b6e9..c350b1455 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -1398,6 +1398,14 @@ def test_codex_hook_fails_open(tmp_path): f"POSIX command must fail open (#3280); got {entry['command']!r}" ) win = entry.get("commandWindows", "") - assert "exit /b 0" in win, ( + assert win.startswith("powershell.exe -NoProfile -NonInteractive"), ( + f"Windows command must name its interpreter (#3280); got {win!r}" + ) + assert win.rstrip().endswith('; exit 0"'), ( f"Windows command must fail open (#3280); got {win!r}" ) + # `& exit /b 0` is cmd-only: run by PowerShell the bare `&` is the + # background operator, which spawns a job and exits 1. + assert "& exit /b 0" not in win, ( + f"Windows fail-open suffix must not use cmd-only syntax; got {win!r}" + ) From a0a4f1dfa5ec38bf2b0bef8bdced6d45c2cc1336 Mon Sep 17 00:00:00 2001 From: Logan Date: Wed, 2 Sep 2026 09:25:22 -0500 Subject: [PATCH 09/14] feat(hooks): strict search gate with per-agent query evidence Strict Claude hooks recorded no query evidence after the graph-wide stamp was removed: the Read deny fired once per session unconditionally and the retry was unconditional, and Bash/Grep search was nudge-only by design. An agent instructed to read and search through Bash never met a block, and a nudge that costs nothing is learned in one turn to cost nothing. - PostToolUse `hook-guard mark-queried` writes cache/hook_sessions/ [--agent-].queried after mcp__graphify__{query_graph,get_node, get_neighbors,get_community,god_nodes,shortest_path} or a Bash command that runs `-m graphify query|explain|path` (project sidecar form included). - `hook-guard search --strict` denies a recursive in-project corpus search (grep -r/-R, rg, find, fd, ack, ag, or the Grep tool on a directory) until that marker exists. Not once-per-session: the way out is a query, not a retry. Exact-file grep, stdin grep, git grep, out-of-project targets, Glob, soft mode, no session_id, malformed stdin and GRAPHIFY_HOOK_STRICT=0 never block. Deny text is constant; parsing never executes the command. - Quoted spans are protected before the operator split so a regex with parens or a "$(cat graphify-out/.graphify_python)" interpreter cannot fragment its segment. - install: --strict now applies to Bash|Grep; the PostToolUse marker hook is registered and removed with the others. Tests: tests/test_search_strict.py (RED on 2a83725: 12 failed / 5 passed; GREEN: 301 passed across the hook/install suites), skillgen --check 134 OK, ruff clean. Live proof: a fresh `claude -p --model claude-opus-5` session in a scratch project with the candidate hooks had its second tool call (grep -rn ... .) denied, queried the graph next, and was then allowed. Stacked on #3281 (pr/hook-fail-open); follow-up to #3280. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01HNTa1HY67hwopSPQtffAPy --- CHANGELOG.md | 1 + README.md | 2 +- graphify/cli.py | 268 ++++++++++++++++++++++++++++++---- graphify/install.py | 79 +++++++--- tests/test_hook_strict.py | 6 +- tests/test_search_hook.py | 4 +- tests/test_search_strict.py | 272 +++++++++++++++++++++++++++++++++++ tests/test_settings_merge.py | 10 +- 8 files changed, 586 insertions(+), 56 deletions(-) create mode 100644 tests/test_search_strict.py diff --git a/CHANGELOG.md b/CHANGELOG.md index c9b3fcbb6..9744eab93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Full release notes with details on each version: [GitHub Releases](https://githu - Fix: generated agent guidance now prefers the MCP `query_graph` tool and uses the graph's recorded Python interpreter as the CLI fallback, so hardened Windows hosts do not route agents through an unsigned `graphify.exe` shim. - Fix: strict Claude hooks no longer use one graph-wide 30-minute query stamp that let any agent disable the first-read block for every other session; deny markers now key subagents by `session_id` plus `agent_id`, so a parent's first read cannot silently consume every subagent's guard. - Fix: project-scoped hooks now resolve Graphify through each clone's `graphify-out/.graphify_python` sidecar instead of a bare PATH launcher, preserving committed-config portability without producing an unrunnable hook on Application Control hosts (#3280, follow-up to #3129). +- Feature: strict Claude hooks now record query evidence per session and agent (a PostToolUse `hook-guard mark-queried` on the MCP graph tools and on `graphify query|explain|path`) and deny a recursive in-project search issued through Bash or the Grep tool until that evidence exists; the retry after one traversal is allowed, and exact-file grep, stdin grep, `git grep`, out-of-project targets, Glob, soft mode and malformed input never block. Closes the escape where an agent instructed to read and search through Bash never met the Read-only strict block. ## 0.9.53 (2026-08-30) diff --git a/README.md b/README.md index 2c883b023..73dc999c4 100644 --- a/README.md +++ b/README.md @@ -203,7 +203,7 @@ for example `graphify claude install --project` or `graphify codex install --pro > **Git hooks and uv tool / pipx:** `graphify hook install` embeds the current interpreter path directly into the hook scripts at install time, so the post-commit hook fires correctly even in GUI git clients and CI runners where `~/.local/bin` is not on PATH. If you reinstall or upgrade graphify, re-run `graphify hook install` to refresh the embedded path. -> **Strict mode (Claude Code):** `graphify install --project --strict` makes the assistant actually use the graph. The default install *nudges* it to use the MCP `query_graph` tool (or the recorded Python-interpreter fallback) before reading files; strict mode *blocks* the first raw source read of a session and redirects it to the graph, then reverts to the nudge (so it fires at most once per session and never gets stuck). Set `GRAPHIFY_HOOK_STRICT=1`/`0` in the Claude process environment to override the installed mode; the default install is unchanged (soft nudge). +> **Strict mode (Claude Code):** `graphify install --project --strict` makes the assistant actually use the graph. The default install *nudges* it to use the MCP `query_graph` tool (or the recorded Python-interpreter fallback) before reading files; strict mode *blocks* the first raw source read of a session and redirects it to the graph, then reverts to the nudge (so it fires at most once per session and never gets stuck). Strict mode also *blocks* a recursive in-project search (`grep -r`, `rg`, `find`, `ack`, `ag`, or the Grep tool on a directory) until the session has run one graph traversal — a PostToolUse hook records the MCP graph tools and `graphify query|explain|path` per session and agent; the retry after a query is allowed, and searching a single named file, piping into grep, or paths outside the project are never blocked. Set `GRAPHIFY_HOOK_STRICT=1`/`0` in the Claude process environment to override the installed mode; the default install is unchanged (soft nudge). > **Project hook interpreter:** committed project hooks read `graphify-out/.graphify_python`, which each clone writes when Graphify resolves its environment. Until that sidecar exists, the hook fails open and emits no graph decision; run the installed Graphify skill once in a fresh clone before relying on strict enforcement. diff --git a/graphify/cli.py b/graphify/cli.py index 9e5771378..8a60ea139 100644 --- a/graphify/cli.py +++ b/graphify/cli.py @@ -71,6 +71,25 @@ ), } }, ensure_ascii=False, separators=(",", ":")) + "\n" +# Strict search gate. Unlike the read block above this is NOT once-per-session: +# a recursive in-project corpus search stays denied until this session/agent has +# recorded one graph traversal (see _mark_session_queried). The way out is a +# query, not a retry. Constant text — nothing from the command is echoed back. +_SEARCH_DENY = json.dumps({ + "hookSpecificOutput": { + "hookEventName": "PreToolUse", + "permissionDecision": "deny", + "permissionDecisionReason": ( + 'graphify strict mode: this project has a knowledge graph and this session ' + 'has not queried it yet. Recursive search of the project (grep -r, rg, find, ' + 'ack, ag) is denied until one graph traversal is recorded. Use the MCP ' + '`query_graph` tool when available; otherwise run the recorded-interpreter ' + 'query, explain, or path command in the installed Graphify skill. Then ' + 're-issue this search — it will be allowed. Searching a single named file, ' + 'piping into grep, and paths outside the project are never blocked.' + ), + } +}, ensure_ascii=False, separators=(",", ":")) + "\n" _HOOK_SOURCE_EXTS = ( '.py', '.js', '.cjs', '.ts', '.tsx', '.jsx', '.astro', '.vue', '.svelte', '.go', '.rs', '.java', '.rb', '.c', '.h', '.cpp', '.hpp', '.cc', '.cs', '.kt', @@ -688,6 +707,55 @@ def _hook_strict_enabled(flag: bool) -> bool: return flag +def _session_marker_id(identity: str) -> str: + """Filesystem-safe marker name for a session/agent identity: allowlisted + characters, hashed when long so sibling identities cannot collide on a prefix. + Empty when the identity is empty — callers treat that as "no session".""" + raw = str(identity) + sid = re.sub(r"[^A-Za-z0-9_-]", "_", raw) + if len(sid) > 64: + import hashlib + sid = hashlib.sha256(raw.encode("utf-8")).hexdigest() + return sid + + +def _hook_session_key(d: dict) -> str: + """session_id plus agent_id from a Claude Code hook payload, so a subagent + sharing its parent's session_id keeps its own markers (#3280 follow-up).""" + key = str(d.get("session_id") or "") + if d.get("agent_id"): + key += f"--agent-{d['agent_id']}" + return key + + +def _queried_marker_path(identity: str) -> "Path | None": + from graphify.paths import out_path + sid = _session_marker_id(identity) + return out_path("cache", "hook_sessions", f"{sid}.queried") if sid else None + + +def _mark_session_queried(identity: str) -> bool: + """Record that this session/agent ran an accepted graph traversal (query / + explain / path, MCP or CLI). Idempotent; fails open (returns False).""" + p = _queried_marker_path(identity) + if p is None: + return False + try: + p.parent.mkdir(parents=True, exist_ok=True) + p.touch() + return True + except Exception: + return False + + +def _session_has_queried(identity: str) -> bool: + p = _queried_marker_path(identity) + try: + return p is not None and p.is_file() + except Exception: + return False + + def _mark_session_denied(identity: str) -> bool: """Atomically claim one strict block per session/agent identity. @@ -696,11 +764,7 @@ def _mark_session_denied(identity: str) -> bool: stranded. Best-effort GC removes markers older than 24 hours. """ from graphify.paths import out_path - raw = str(identity) - sid = re.sub(r"[^A-Za-z0-9_-]", "_", raw) - if len(sid) > 64: - import hashlib - sid = hashlib.sha256(raw.encode("utf-8")).hexdigest() + sid = _session_marker_id(identity) if not sid: return False try: @@ -751,22 +815,7 @@ def _bash_invokes_search(cmd_str: str) -> bool: command position (wrappers like sudo/xargs/env skipped; `git grep` and `VAR=x grep ...` still count; a search-tool name inside prose does not). """ - text = cmd_str - # Drop heredoc bodies: from the line after `< bool: return False +# Search tools that walk a directory tree by default; grep needs -r/-R. +_RECURSIVE_BY_DEFAULT = frozenset({"rg", "ripgrep", "find", "fd", "ack", "ag"}) +_GREP_FAMILY = frozenset({"grep", "egrep", "fgrep", "zgrep"}) +# Short flags that consume the next token, so it must not be read as a path. +_SEARCH_FLAGS_WITH_ARG = frozenset({"-e", "-f", "-g", "-t", "-T", "-A", "-B", "-C", "-m"}) +_SEGMENT_SPLIT_RE = re.compile(r"[|;&\n]|\$\(|`|\(|\)|\{|\}") + + +def _strip_heredoc_bodies(text: str) -> str: + """Drop heredoc bodies so their prose never counts as an executed command.""" + m = _HEREDOC_OPEN_RE.search(text) + while m: + nl_idx = text.find("\n", m.end()) + if nl_idx == -1: + break + term = re.compile(r"^\s*" + re.escape(m.group(2)) + r"\s*$", re.MULTILINE) + t = term.search(text, nl_idx + 1) + if t is None: + return text[: nl_idx + 1] + text = text[: nl_idx + 1] + text[t.end():] + m = _HEREDOC_OPEN_RE.search(text, nl_idx + 1) + return text + + +def _bash_command_segments(cmd_str: str) -> "list[list[str]]": + """Executed command segments as argument lists, quotes stripped but their + contents kept (unlike _bash_invokes_search, which discards quoted spans and + therefore loses a quoted path argument). Wrappers and VAR=x prefixes removed, + so tokens[0] is the executable's base name.""" + # Protect quoted spans before splitting on operators, so a regex containing + # `(` or `|`, or a `"$(cat ...)"` interpreter, cannot fragment its segment. + quoted = [] + + def _hold(m): + quoted.append(m.group(0)[1:-1]) + return f"\x00{len(quoted) - 1}\x00" + + text = re.sub(r"'[^']*'|\"[^\"]*\"", _hold, _strip_heredoc_bodies(cmd_str)) + out = [] + for segment in _SEGMENT_SPLIT_RE.split(text): + tokens = [re.sub(r"\x00(\d+)\x00", lambda m: quoted[int(m.group(1))], t) + for t in segment.split()] + i = 0 + while i < len(tokens): + tok = tokens[i] + if "=" in tok.split("/")[-1] and not tok.startswith(("-", "/")): + i += 1 + continue + name = tok.replace("\\", "/").rsplit("/", 1)[-1].lower() + name = name[:-4] if name.endswith(".exe") else name + if name in _COMMAND_WRAPPERS: + i += 1 + while i < len(tokens) and tokens[i].startswith("-"): + i += 1 + continue + out.append([name] + tokens[i + 1:]) + break + return out + + +def _bash_recursive_search_targets(cmd_str: str, root: "Path") -> "list[Path]": + """In-project DIRECTORIES that a recursive search segment would walk. + + Recursive = grep with -r/-R/--recursive, or a tool that recurses by default + (rg, find, fd, ack, ag). Targets are the segment's path arguments; a recursive + tool with no path argument walks cwd. A path that is a regular file makes that + search bounded; a path that does not exist is ignored (probably a flag value); + a path outside *root* is out-of-project. `git grep` stays a nudge. Never + executes anything. + """ + found = [] + for tokens in _bash_command_segments(cmd_str): + name = tokens[0] + if name not in _SEARCH_COMMANDS: + continue + args = tokens[1:] + if name in _GREP_FAMILY: + recursive = any( + a in ("--recursive", "--dereference-recursive") + or (a.startswith("-") and not a.startswith("--") and ("r" in a or "R" in a)) + for a in args + ) + if not recursive: + continue + # The first positional is the pattern, except for find (paths come first) + # and grep given its pattern through -e/-f. + pattern_pending = name != "find" and not any(a in ("-e", "-f") for a in args) + paths, skip_next = [], False + for a in args: + if skip_next: + skip_next = False + continue + if a == "--": + continue + if a.startswith("-"): + if a in _SEARCH_FLAGS_WITH_ARG: + skip_next = True + elif name == "find": + break # find's expression starts here; paths precede it + continue + if pattern_pending: + pattern_pending = False + continue + paths.append(a) + if not paths: + paths = ["."] + for p in paths: + try: + resolved = Path(p).resolve() + if not resolved.is_dir(): + continue + resolved.relative_to(root) + except (ValueError, OSError, RuntimeError): + continue + found.append(resolved) + return found + + +_GRAPH_QUERY_SUBCOMMANDS = frozenset({"query", "explain", "path"}) +_GRAPH_QUERY_MCP_TOOLS = frozenset({ + "query_graph", "get_node", "get_neighbors", "get_community", "god_nodes", "shortest_path", +}) + + +def _bash_invokes_graphify_query(cmd_str: str) -> bool: + """Whether a Bash command RUNS a graph traversal: `... -m graphify ` + through any interpreter, or `graphify ` directly.""" + for tokens in _bash_command_segments(cmd_str): + args = tokens[1:] + if tokens[0] == "graphify" and args[:1] and args[0] in _GRAPH_QUERY_SUBCOMMANDS: + return True + for i, a in enumerate(args): + if a == "-m" and args[i + 1:i + 3][:1] == ["graphify"] \ + and args[i + 2:i + 3] and args[i + 2] in _GRAPH_QUERY_SUBCOMMANDS: + return True + return False + + def _run_hook_guard(kind: str, strict: bool = False) -> None: """Shell-agnostic PreToolUse guard (#522). @@ -853,8 +1040,39 @@ def _run_hook_guard(kind: str, strict: bool = False) -> None: # grep. Nudge-only, even in strict mode — see the docstring. is_grep_tool = not cmd_str and bool(t.get("pattern")) is_bash_search = bool(cmd_str) and _bash_invokes_search(cmd_str) - if (is_grep_tool or is_bash_search) and out_path("graph.json").is_file(): - sys.stdout.write(_SEARCH_NUDGE) + if not (is_grep_tool or is_bash_search) or not out_path("graph.json").is_file(): + return + # Strict search gate: deny a recursive in-project corpus search until + # this session/agent has recorded one graph traversal. Not once per + # session — the way out is a query, not a retry (see _SEARCH_DENY). + session_key = _hook_session_key(d) + if _hook_strict_enabled(strict) and session_key and not _session_has_queried(session_key): + root = Path(os.environ.get("CLAUDE_PROJECT_DIR") or os.getcwd()) + try: + root = root.resolve() + except (OSError, RuntimeError): + pass + if is_grep_tool: + target = Path(str(t.get("path") or ".")).resolve() + try: + target.relative_to(root) + recursive = target.is_dir() + except (ValueError, OSError, RuntimeError): + recursive = False + else: + recursive = bool(_bash_recursive_search_targets(cmd_str, root)) + if recursive: + sys.stdout.write(_SEARCH_DENY) + return + sys.stdout.write(_SEARCH_NUDGE) + elif kind == "mark-queried": + # PostToolUse: record query evidence for the strict search gate. + name = str(d.get("tool_name") or "") + queried = ( + name.startswith("mcp__graphify__") and name[len("mcp__graphify__"):] in _GRAPH_QUERY_MCP_TOOLS + ) or (name == "Bash" and _bash_invokes_graphify_query(str(t.get("command", "") or ""))) + if queried: + _mark_session_queried(_hook_session_key(d)) elif kind == "read": vals = [str(t.get("file_path") or ""), str(t.get("pattern") or ""), str(t.get("path") or "")] j = " ".join(vals).lower().replace("\\", "/") @@ -920,9 +1138,7 @@ def _run_hook_guard(kind: str, strict: bool = False) -> None: # query timestamp: one agent's query must not disable every session # sharing the same graph (#3280 follow-up). tool_name = d.get("tool_name") - session_key = str(d.get("session_id") or "") - if d.get("agent_id"): - session_key += f"--agent-{d['agent_id']}" + session_key = _hook_session_key(d) if _hook_strict_enabled(strict) and tool_name in (None, "Read") \ and _target_is_indexed(fp, root) \ and _mark_session_denied(session_key): diff --git a/graphify/install.py b/graphify/install.py index 9f8832d44..4548bc509 100644 --- a/graphify/install.py +++ b/graphify/install.py @@ -311,25 +311,51 @@ def _claude_pretooluse_hooks(strict: bool = False, project: bool = False) -> "li content search through its dedicated Grep tool, not Bash (#1986) — a Bash-only matcher never fired on the agent's primary search path. - When ``strict`` is set, the read hook carries ``--strict`` so it blocks the - first raw read per session (Claude Code only). The ``GRAPHIFY_HOOK_STRICT`` env - var can force it on or off at runtime without a reinstall. + When ``strict`` is set, both hooks carry ``--strict``: the read hook blocks the + first raw read per session, and the search hook blocks a recursive in-project + search until this session has recorded one graph traversal (Claude Code only; + see ``_claude_posttooluse_hooks`` for the marker). The ``GRAPHIFY_HOOK_STRICT`` + env var can force it on or off at runtime without a reinstall. """ exe = _resolve_graphify_exe(project=project) - read_cmd = f"{exe} hook-guard read" + (" --strict" if strict else "") - read_args = "hook-guard read" + (" --strict" if strict else "") + flag = " --strict" if strict else "" return [ {"matcher": "Bash|Grep", "hooks": [{"type": "command", - "command": f"{exe} hook-guard search || true", - "commandWindows": _graphify_command_windows("hook-guard search", project), + "command": f"{exe} hook-guard search{flag} || true", + "commandWindows": _graphify_command_windows("hook-guard search" + flag, project), "timeout": 10}]}, {"matcher": "Read|Glob", "hooks": [{"type": "command", - "command": f"{read_cmd} || true", - "commandWindows": _graphify_command_windows(read_args, project), + "command": f"{exe} hook-guard read{flag} || true", + "commandWindows": _graphify_command_windows("hook-guard read" + flag, project), "timeout": 10}]}, ] + + +# Matchers graphify registers; the install/uninstall filters key on these plus +# "graphify" in the entry so a user's unrelated hooks are never touched. +_CLAUDE_PRETOOLUSE_MATCHERS = ("Glob|Grep", "Bash", "Bash|Grep", "Read|Glob") +_CLAUDE_POSTTOOLUSE_MATCHER = "Bash|mcp__graphify__.*" +_CLAUDE_POSTTOOLUSE_MATCHERS = (_CLAUDE_POSTTOOLUSE_MATCHER,) + + +def _claude_posttooluse_hooks(project: bool = False) -> "list[dict]": + """PostToolUse hook that records query evidence for the strict search gate: + fires after the MCP graph tools and after Bash, and writes a per-session/agent + marker only when the call was a graph traversal (query/explain/path).""" + exe = _resolve_graphify_exe(project=project) + return [ + {"matcher": _CLAUDE_POSTTOOLUSE_MATCHER, + "hooks": [{"type": "command", + "command": f"{exe} hook-guard mark-queried || true", + "commandWindows": _graphify_command_windows("hook-guard mark-queried", project), + "timeout": 10}]}, + ] + + +def _is_graphify_hook(h, matchers) -> bool: + return isinstance(h, dict) and h.get("matcher") in matchers and "graphify" in str(h) def _skill_registration(skill_path: str = "~/.claude/skills/graphify/SKILL.md") -> str: return ( "\n# graphify\n" @@ -702,8 +728,9 @@ def _print_install_usage() -> None: platforms = ", ".join([*_PLATFORM_CONFIG, "gemini", "cursor"]) print("Usage: graphify install [--project] [--strict] [--platform P|P]") print(f"Platforms: {platforms}") - print(" --strict block the first raw file read per session until one " - "`graphify query` runs (Claude Code project hook only; needs --project)") + print(" --strict block the first raw file read per session, and every recursive " + "in-project search until this session has run one graph traversal " + "(Claude Code project hook only; needs --project)") _CLAUDE_MD_MARKER = "## graphify" _CODEBUDDY_MD_MARKER = "## graphify" _AGENTS_MD_MARKER = "## graphify" @@ -1806,8 +1833,9 @@ def claude_install(project_dir: Path | None = None, strict: bool = False, projec print("Claude Code will now check the knowledge graph before answering") print("codebase questions and rebuild it after code changes.") if strict: - print("Strict mode: the first raw file read per session is blocked until") - print("one `graphify query` runs (toggle with GRAPHIFY_HOOK_STRICT=0).") + print("Strict mode: the first raw file read per session is blocked until one") + print("graph traversal runs, and recursive in-project searches stay blocked until") + print("this session has run one (toggle with GRAPHIFY_HOOK_STRICT=0).") def _install_claude_hook(project_dir: Path, strict: bool = False, project: bool = False) -> None: """Add graphify PreToolUse hook to .claude/settings.json. @@ -1826,11 +1854,17 @@ def _install_claude_hook(project_dir: Path, strict: bool = False, project: bool if not isinstance(pre_tool, list): _refuse_to_modify(settings_path) - hooks["PreToolUse"] = [h for h in pre_tool if not (isinstance(h, dict) and h.get("matcher") in ("Glob|Grep", "Bash", "Bash|Grep", "Read|Glob") and "graphify" in str(h))] + post_tool = hooks.setdefault("PostToolUse", []) + if not isinstance(post_tool, list): + _refuse_to_modify(settings_path) + + hooks["PreToolUse"] = [h for h in pre_tool if not _is_graphify_hook(h, _CLAUDE_PRETOOLUSE_MATCHERS)] hooks["PreToolUse"].extend(_claude_pretooluse_hooks(strict=strict, project=project)) + hooks["PostToolUse"] = [h for h in post_tool if not _is_graphify_hook(h, _CLAUDE_POSTTOOLUSE_MATCHERS)] + hooks["PostToolUse"].extend(_claude_posttooluse_hooks(project=project)) _write_settings_with_backup(settings_path, settings) _mode = " (strict)" if strict else "" - print(f" .claude/settings.json -> PreToolUse hooks registered (Bash|Grep search + Read/Glob){_mode}") + print(f" .claude/settings.json -> PreToolUse hooks registered (Bash|Grep search + Read/Glob){_mode}; PostToolUse query marker registered") def _uninstall_claude_hook(project_dir: Path) -> None: """Remove the graphify PreToolUse hook from .claude/settings.json and its local-only sibling .claude/settings.local.json. @@ -1849,13 +1883,18 @@ def _strip_graphify_hook(settings_path: Path) -> None: settings = json.loads(settings_path.read_text(encoding="utf-8")) except json.JSONDecodeError: return - pre_tool = settings.get("hooks", {}).get("PreToolUse", []) - filtered = [h for h in pre_tool if not (h.get("matcher") in ("Glob|Grep", "Bash", "Bash|Grep", "Read|Glob") and "graphify" in str(h))] - if len(filtered) == len(pre_tool): + hooks = settings.get("hooks", {}) + pre_tool = hooks.get("PreToolUse", []) + post_tool = hooks.get("PostToolUse", []) + pre_kept = [h for h in pre_tool if not _is_graphify_hook(h, _CLAUDE_PRETOOLUSE_MATCHERS)] + post_kept = [h for h in post_tool if not _is_graphify_hook(h, _CLAUDE_POSTTOOLUSE_MATCHERS)] + if len(pre_kept) == len(pre_tool) and len(post_kept) == len(post_tool): return - settings["hooks"]["PreToolUse"] = filtered + hooks["PreToolUse"] = pre_kept + if len(post_kept) != len(post_tool): + hooks["PostToolUse"] = post_kept settings_path.write_text(json.dumps(settings, indent=2), encoding="utf-8") - print(f" .claude/{settings_path.name} -> PreToolUse hook removed") + print(f" .claude/{settings_path.name} -> graphify hooks removed") def uninstall_all(project_dir: Path | None = None, purge: bool = False) -> None: """Remove graphify from every platform detected in the current project.""" pd = project_dir or Path(".") diff --git a/tests/test_hook_strict.py b/tests/test_hook_strict.py index 48a0a2302..61809a4f8 100644 --- a/tests/test_hook_strict.py +++ b/tests/test_hook_strict.py @@ -155,11 +155,11 @@ def test_glob_never_denies(tmp_path, monkeypatch): assert not _is_deny(_invoke("read", payload, tmp_path, monkeypatch, strict=True)) -def test_search_never_denies(tmp_path, monkeypatch): +def test_search_soft_mode_never_denies(tmp_path, monkeypatch): _fixture(tmp_path) out = _invoke("search", {"session_id": "s1", "tool_input": {"command": "grep -rn foo ."}}, - tmp_path, monkeypatch, strict=True) - assert not _is_deny(out) # search stays a nudge even in strict mode + tmp_path, monkeypatch, strict=False) + assert not _is_deny(out) # soft mode stays a nudge; the strict search gate is test_search_strict.py def test_no_session_id_never_denies(tmp_path, monkeypatch): diff --git a/tests/test_search_hook.py b/tests/test_search_hook.py index e074ef9e6..d870c8a4d 100644 --- a/tests/test_search_hook.py +++ b/tests/test_search_hook.py @@ -122,7 +122,7 @@ def test_fails_open_on_malformed_stdin(tmp_path): assert r.stdout.strip() == "" -def test_never_blocks(tmp_path): +def test_never_blocks_in_soft_mode(tmp_path): r = _run("grep -rn foo .", tmp_path, graph=True) assert r.returncode == 0 assert '"permissionDecision"' not in r.stdout @@ -171,7 +171,7 @@ def test_grep_tool_nudge_is_valid_pretooluse_json(tmp_path): assert "query_graph" in payload["hookSpecificOutput"]["additionalContext"] -def test_grep_tool_never_blocks(tmp_path): +def test_grep_tool_never_blocks_in_soft_mode(tmp_path): r = _run_grep_tool({"pattern": "foo", "path": "."}, tmp_path, graph=True) assert r.returncode == 0 assert '"permissionDecision"' not in r.stdout diff --git a/tests/test_search_strict.py b/tests/test_search_strict.py new file mode 100644 index 000000000..02a81c1f8 --- /dev/null +++ b/tests/test_search_strict.py @@ -0,0 +1,272 @@ +"""Strict search gate: a recursive in-project corpus search issued through Bash or the +Grep tool is denied until this session/agent has recorded one accepted graph traversal. + +The read gate (test_hook_strict.py) fires once per session unconditionally. This gate is +different on purpose: the way out is a query, not a retry. Query evidence is written by +`hook-guard mark-queried` (a PostToolUse hook on the MCP graph tools and on Bash commands +that run the recorded-interpreter query/explain/path) and keyed by session_id + agent_id. +Exact-file grep, stdin grep, out-of-project targets, git grep, prose, heredocs, malformed +input, soft mode, the env kill switch and a missing session_id never deny. +""" +import io +import json +import os +import subprocess +import sys +import time + +import graphify.cli as cli + + +def _fixture(tmp_path): + src = tmp_path / "src" + src.mkdir() + f = src / "mod.py" + f.write_text("def x():\n return 1\n", encoding="utf-8") + out = tmp_path / "graphify-out" + out.mkdir() + (out / "manifest.json").write_text(json.dumps({"src/mod.py": {"mtime": 1}}), encoding="utf-8") + time.sleep(0.02) + (out / "graph.json").write_text('{"nodes":[],"links":[]}', encoding="utf-8") + return f + + +def _invoke(kind, payload, tmp_path, monkeypatch, *, strict=True, env=None): + monkeypatch.chdir(tmp_path) + monkeypatch.delenv("CLAUDE_PROJECT_DIR", raising=False) + monkeypatch.delenv("GRAPHIFY_HOOK_STRICT", raising=False) + for k, v in (env or {}).items(): + monkeypatch.setenv(k, v) + data = json.dumps(payload).encode() if not isinstance(payload, (bytes, bytearray)) else bytes(payload) + + class _Stdin: + buffer = io.BytesIO(data) + monkeypatch.setattr(sys, "stdin", _Stdin()) + buf = io.StringIO() + monkeypatch.setattr(sys, "stdout", buf) + cli._run_hook_guard(kind, strict=strict) + return buf.getvalue() + + +def _search(command, sid="s1", agent=None): + p = {"session_id": sid, "tool_name": "Bash", "tool_input": {"command": command}} + if agent: + p["agent_id"] = agent + return p + + +def _grep_tool(tool_input, sid="s1"): + return {"session_id": sid, "tool_name": "Grep", "tool_input": tool_input} + + +def _mark(payload, tmp_path, monkeypatch): + return _invoke("mark-queried", payload, tmp_path, monkeypatch) + + +def _queried_marker(tmp_path, sid): + return tmp_path / "graphify-out" / "cache" / "hook_sessions" / f"{sid}.queried" + + +def _is_deny(out): + return out.strip() != "" and json.loads(out).get("hookSpecificOutput", {}).get("permissionDecision") == "deny" + + +def test_denies_recursive_grep_in_project(tmp_path, monkeypatch): + _fixture(tmp_path) + out = _invoke("search", _search("grep -rn foo ."), tmp_path, monkeypatch) + assert _is_deny(out) + assert "query_graph" in json.loads(out)["hookSpecificOutput"]["permissionDecisionReason"] + + +def test_denies_quoted_directory_target(tmp_path, monkeypatch): + """A quoted path argument must survive tokenizing (the nudge parser drops quoted spans).""" + _fixture(tmp_path) + assert _is_deny(_invoke("search", _search('grep -R foo "src/"'), tmp_path, monkeypatch)) + + +def test_denies_default_recursive_tools(tmp_path, monkeypatch): + _fixture(tmp_path) + for command in ("rg foo", "rg foo src", "find . -name x", "fd bar", "ack needle", "ag needle"): + assert _is_deny(_invoke("search", _search(command), tmp_path, monkeypatch)), command + + +def test_denies_again_before_query(tmp_path, monkeypatch): + """Not once-per-session: without query evidence every recursive search is denied.""" + _fixture(tmp_path) + assert _is_deny(_invoke("search", _search("grep -rn foo ."), tmp_path, monkeypatch)) + assert _is_deny(_invoke("search", _search("grep -rn foo ."), tmp_path, monkeypatch)) + + +def test_allows_after_mcp_query(tmp_path, monkeypatch): + _fixture(tmp_path) + _mark({"session_id": "s1", "tool_name": "mcp__graphify__query_graph", + "tool_input": {"question": "x"}, "tool_response": {}}, tmp_path, monkeypatch) + assert _queried_marker(tmp_path, "s1").exists() + out = _invoke("search", _search("grep -rn foo ."), tmp_path, monkeypatch) + assert not _is_deny(out) and "MANDATORY" in out + + +def test_allows_after_cli_query_via_bash(tmp_path, monkeypatch): + _fixture(tmp_path) + commands = ( + '"C:/py/python.exe" -m graphify query "hook guard"', + "python -m graphify explain 'Some label'", + "graphify path A B", + ) + for i, command in enumerate(commands): + sid = f"cli-{i}" + _mark({"session_id": sid, "tool_name": "Bash", "tool_input": {"command": command}, + "tool_response": {}}, tmp_path, monkeypatch) + out = _invoke("search", _search("grep -rn foo .", sid), tmp_path, monkeypatch) + assert not _is_deny(out), command + + +def test_allows_after_project_sidecar_cli_query(tmp_path, monkeypatch): + """The exact command the project-scoped skill emits must count as evidence.""" + _fixture(tmp_path) + for i, command in enumerate(( + '"$(cat graphify-out/.graphify_python)" -m graphify query "hook guard"', + 'graphify update . && graphify query "x"', + )): + sid = f"sidecar-{i}" + _mark({"session_id": sid, "tool_name": "Bash", "tool_input": {"command": command}, + "tool_response": {}}, tmp_path, monkeypatch) + assert _queried_marker(tmp_path, sid).exists(), command + + +def test_quoted_parens_do_not_fragment_the_segment(tmp_path, monkeypatch): + _fixture(tmp_path) + # out-of-project target must survive a regex with parens -> nudge, not deny + out = _invoke("search", _search('rg "def (a|b)" /somewhere/else'), tmp_path, monkeypatch) + assert not _is_deny(out) and "MANDATORY" in out + # in-project with quoted parens is still recognised as recursive -> deny + assert _is_deny(_invoke("search", _search('grep -rn "foo(bar)" .'), tmp_path, monkeypatch)) + + +def test_mark_queried_ignores_non_query_calls(tmp_path, monkeypatch): + _fixture(tmp_path) + for payload in ( + {"session_id": "n1", "tool_name": "mcp__graphify__graph_stats", "tool_input": {}}, + {"session_id": "n1", "tool_name": "Bash", "tool_input": {"command": "python -m graphify update ."}}, + {"session_id": "n1", "tool_name": "Bash", "tool_input": {"command": "echo graphify query"}}, + {"session_id": "n1", "tool_name": "Read", "tool_input": {"file_path": "x.py"}}, + ): + _mark(payload, tmp_path, monkeypatch) + assert not _queried_marker(tmp_path, "n1").exists() + + +def test_sibling_agents_have_own_query_evidence(tmp_path, monkeypatch): + _fixture(tmp_path) + _mark({"session_id": "shared", "tool_name": "mcp__graphify__query_graph", + "tool_input": {}}, tmp_path, monkeypatch) # parent queried + parent = _invoke("search", _search("grep -rn foo .", "shared"), tmp_path, monkeypatch) + child = _invoke("search", _search("grep -rn foo .", "shared", agent="child-1"), tmp_path, monkeypatch) + assert not _is_deny(parent) + assert _is_deny(child) + + +def test_denies_compound_with_recursive_segment(tmp_path, monkeypatch): + _fixture(tmp_path) + assert _is_deny(_invoke("search", _search("ls; grep -rn foo ."), tmp_path, monkeypatch)) + + +def test_bounded_searches_only_nudge(tmp_path, monkeypatch): + f = _fixture(tmp_path) + for command in ( + f"grep -n foo {f}", # exact file, absolute + "grep -n foo src/mod.py", # exact file, relative + "cat src/mod.py | grep foo", # stdin grep + "git grep foo", # repo-scoped, stays a nudge + "grep -rn foo /somewhere/else", # outside project + ): + out = _invoke("search", _search(command), tmp_path, monkeypatch) + assert not _is_deny(out), command + assert "MANDATORY" in out, command + + +def test_prose_and_heredoc_never_deny(tmp_path, monkeypatch): + _fixture(tmp_path) + heredoc = "cat > notes.md <<'EOT'\nrun grep -rn foo .\nEOT\n" + for command in ('git commit -m "grep -r everything"', heredoc): + assert _invoke("search", _search(command), tmp_path, monkeypatch).strip() == "", command + + +def test_grep_tool_directory_denies_file_nudges(tmp_path, monkeypatch): + f = _fixture(tmp_path) + assert _is_deny(_invoke("search", _grep_tool({"pattern": "foo"}), tmp_path, monkeypatch)) + assert _is_deny(_invoke("search", _grep_tool({"pattern": "foo", "path": "src"}), tmp_path, monkeypatch)) + out = _invoke("search", _grep_tool({"pattern": "foo", "path": str(f)}), tmp_path, monkeypatch) + assert not _is_deny(out) and "MANDATORY" in out + + +def test_safety_valves_never_deny(tmp_path, monkeypatch): + _fixture(tmp_path) + no_sid = {"tool_name": "Bash", "tool_input": {"command": "grep -rn foo ."}} + assert not _is_deny(_invoke("search", no_sid, tmp_path, monkeypatch)) + assert _invoke("search", b"{not json", tmp_path, monkeypatch) == "" + assert not _is_deny(_invoke("search", _search("grep -rn foo ."), tmp_path, monkeypatch, + env={"GRAPHIFY_HOOK_STRICT": "0"})) + assert not _is_deny(_invoke("search", _search("grep -rn foo ."), tmp_path, monkeypatch, strict=False)) + + +def test_deny_reason_is_constant(tmp_path, monkeypatch): + """Nothing from the command may be echoed back into the hook payload.""" + _fixture(tmp_path) + out = _invoke("search", _search('grep -rn "$(rm -rf /)" .'), tmp_path, monkeypatch) + assert _is_deny(out) and "rm -rf" not in out + + +def test_installed_strict_search_hook_executes_and_denies(tmp_path): + from graphify.install import _claude_pretooluse_hooks + + _fixture(tmp_path) + (tmp_path / "graphify-out" / ".graphify_python").write_text(sys.executable, encoding="utf-8") + env = dict(os.environ) + env.pop("CLAUDE_PROJECT_DIR", None) + env.pop("GRAPHIFY_HOOK_STRICT", None) + for project in (False, True): + entry = next( + h for h in _claude_pretooluse_hooks(strict=True, project=project) + if h["matcher"] == "Bash|Grep" + )["hooks"][0] + command = entry["commandWindows"] if os.name == "nt" else entry["command"] + result = subprocess.run( + command, input=json.dumps(_search("grep -rn foo .", f"inst-{int(project)}")), + text=True, capture_output=True, shell=True, cwd=tmp_path, timeout=15, env=env, + ) + assert result.returncode == 0, result.stderr + assert _is_deny(result.stdout), result.stdout or result.stderr + + +def test_installed_posttooluse_marks_query(tmp_path): + from graphify.install import _claude_posttooluse_hooks + + _fixture(tmp_path) + (tmp_path / "graphify-out" / ".graphify_python").write_text(sys.executable, encoding="utf-8") + for project in (False, True): + entry = _claude_posttooluse_hooks(project=project)[0] + assert "mcp__graphify__" in entry["matcher"] and "Bash" in entry["matcher"] + hook = entry["hooks"][0] + command = hook["commandWindows"] if os.name == "nt" else hook["command"] + sid = f"post-{int(project)}" + payload = {"session_id": sid, "tool_name": "mcp__graphify__query_graph", + "tool_input": {"question": "x"}, "tool_response": {}} + result = subprocess.run(command, input=json.dumps(payload), text=True, capture_output=True, + shell=True, cwd=tmp_path, timeout=15) + assert result.returncode == 0, result.stderr + assert _queried_marker(tmp_path, sid).exists() + + +def test_install_and_uninstall_round_trip(tmp_path, monkeypatch): + from graphify.install import _install_claude_hook, _uninstall_claude_hook + + monkeypatch.chdir(tmp_path) + _install_claude_hook(tmp_path, strict=True) + settings = json.loads((tmp_path / ".claude" / "settings.json").read_text(encoding="utf-8")) + search = next(h for h in settings["hooks"]["PreToolUse"] if h["matcher"] == "Bash|Grep") + assert "hook-guard search --strict" in search["hooks"][0]["command"] + assert any("graphify" in str(h) for h in settings["hooks"]["PostToolUse"]) + _uninstall_claude_hook(tmp_path) + settings = json.loads((tmp_path / ".claude" / "settings.json").read_text(encoding="utf-8")) + assert not any("graphify" in str(h) for h in settings["hooks"].get("PreToolUse", [])) + assert not any("graphify" in str(h) for h in settings["hooks"].get("PostToolUse", [])) diff --git a/tests/test_settings_merge.py b/tests/test_settings_merge.py index f3642ca74..ec4120a34 100644 --- a/tests/test_settings_merge.py +++ b/tests/test_settings_merge.py @@ -81,15 +81,17 @@ def test_claude_install_preserves_existing_settings(tmp_path): assert result["mcpServers"] == seeded["mcpServers"] assert result["enabledPlugins"] == seeded["enabledPlugins"] assert result["theme"] == "dark" - # hooks sections graphify does not manage are untouched - assert result["hooks"]["PostToolUse"] == seeded["hooks"]["PostToolUse"] + # the user's own PostToolUse entry survives alongside graphify's query marker + post_tool = result["hooks"]["PostToolUse"] + assert seeded["hooks"]["PostToolUse"][0] in post_tool + assert len([h for h in post_tool if "graphify" in str(h)]) == 1 # the user's own PreToolUse entry survives alongside graphify's pre_tool = result["hooks"]["PreToolUse"] assert seeded["hooks"]["PreToolUse"][0] in pre_tool graphify_hooks = [h for h in pre_tool if "graphify" in str(h)] assert len(graphify_hooks) == 2 - # strict=True lands on the read guard - assert any(h["hooks"][0]["command"].endswith("--strict || true") for h in graphify_hooks) + # strict=True lands on both guards + assert all(h["hooks"][0]["command"].endswith("--strict || true") for h in graphify_hooks) # ---------------------------------------------------------------- BOM From f4ae3dd96fb46f87e9346dfee043258f3ec4973a Mon Sep 17 00:00:00 2001 From: Logan Date: Wed, 2 Sep 2026 09:35:39 -0500 Subject: [PATCH 10/14] fix(hooks): expire query markers and gate PowerShell searches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round 1 review findings on the strict search gate: - F1: a Bash-only session never reached the 24-hour marker sweep that lived only inside _mark_session_denied, so .queried markers accumulated and a reused session id stayed pre-authorized. Both writers now call one shared _gc_session_markers. - F2: Claude Code on Windows exposes a PowerShell tool (tool_name "PowerShell", tool_input.command; PreToolUse:PowerShell hook events were measured in a live transcript). Its recursive in-project searches now reach the same guard: matcher Bash|Grep|PowerShell, and two measured shapes are denied until query evidence exists — `Get-ChildItem|gci|ls|dir ... -Recurse ... | Select-String|sls` and `Select-String ... -Path `. Exact-file -Path, Get-Content | Select-String and out-of-project targets only nudge; non-search PowerShell stays silent. Tests: 5 RED on a0a4f1d, 306 passed after; skillgen --check 134 OK; ruff clean. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01HNTa1HY67hwopSPQtffAPy --- CHANGELOG.md | 2 +- README.md | 2 +- graphify/cli.py | 93 ++++++++++++++++++++++++++++++++----- graphify/install.py | 8 ++-- tests/test_codebuddy.py | 2 +- tests/test_install.py | 2 +- tests/test_search_hook.py | 7 +-- tests/test_search_strict.py | 67 +++++++++++++++++++++++++- 8 files changed, 159 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9744eab93..1f94dd803 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ Full release notes with details on each version: [GitHub Releases](https://githu - Fix: generated agent guidance now prefers the MCP `query_graph` tool and uses the graph's recorded Python interpreter as the CLI fallback, so hardened Windows hosts do not route agents through an unsigned `graphify.exe` shim. - Fix: strict Claude hooks no longer use one graph-wide 30-minute query stamp that let any agent disable the first-read block for every other session; deny markers now key subagents by `session_id` plus `agent_id`, so a parent's first read cannot silently consume every subagent's guard. - Fix: project-scoped hooks now resolve Graphify through each clone's `graphify-out/.graphify_python` sidecar instead of a bare PATH launcher, preserving committed-config portability without producing an unrunnable hook on Application Control hosts (#3280, follow-up to #3129). -- Feature: strict Claude hooks now record query evidence per session and agent (a PostToolUse `hook-guard mark-queried` on the MCP graph tools and on `graphify query|explain|path`) and deny a recursive in-project search issued through Bash or the Grep tool until that evidence exists; the retry after one traversal is allowed, and exact-file grep, stdin grep, `git grep`, out-of-project targets, Glob, soft mode and malformed input never block. Closes the escape where an agent instructed to read and search through Bash never met the Read-only strict block. +- Feature: strict Claude hooks now record query evidence per session and agent (a PostToolUse `hook-guard mark-queried` on the MCP graph tools and on `graphify query|explain|path`) and deny a recursive in-project search issued through Bash, the Grep tool, or Claude Code's PowerShell tool (`Get-ChildItem -Recurse | Select-String`, `Select-String -Path `) until that evidence exists; both marker writers garbage-collect markers older than 24 hours; the retry after one traversal is allowed, and exact-file grep, stdin grep, `git grep`, out-of-project targets, Glob, soft mode and malformed input never block. Closes the escape where an agent instructed to read and search through Bash never met the Read-only strict block. ## 0.9.53 (2026-08-30) diff --git a/README.md b/README.md index 73dc999c4..bc148c6e0 100644 --- a/README.md +++ b/README.md @@ -203,7 +203,7 @@ for example `graphify claude install --project` or `graphify codex install --pro > **Git hooks and uv tool / pipx:** `graphify hook install` embeds the current interpreter path directly into the hook scripts at install time, so the post-commit hook fires correctly even in GUI git clients and CI runners where `~/.local/bin` is not on PATH. If you reinstall or upgrade graphify, re-run `graphify hook install` to refresh the embedded path. -> **Strict mode (Claude Code):** `graphify install --project --strict` makes the assistant actually use the graph. The default install *nudges* it to use the MCP `query_graph` tool (or the recorded Python-interpreter fallback) before reading files; strict mode *blocks* the first raw source read of a session and redirects it to the graph, then reverts to the nudge (so it fires at most once per session and never gets stuck). Strict mode also *blocks* a recursive in-project search (`grep -r`, `rg`, `find`, `ack`, `ag`, or the Grep tool on a directory) until the session has run one graph traversal — a PostToolUse hook records the MCP graph tools and `graphify query|explain|path` per session and agent; the retry after a query is allowed, and searching a single named file, piping into grep, or paths outside the project are never blocked. Set `GRAPHIFY_HOOK_STRICT=1`/`0` in the Claude process environment to override the installed mode; the default install is unchanged (soft nudge). +> **Strict mode (Claude Code):** `graphify install --project --strict` makes the assistant actually use the graph. The default install *nudges* it to use the MCP `query_graph` tool (or the recorded Python-interpreter fallback) before reading files; strict mode *blocks* the first raw source read of a session and redirects it to the graph, then reverts to the nudge (so it fires at most once per session and never gets stuck). Strict mode also *blocks* a recursive in-project search (`grep -r`, `rg`, `find`, `ack`, `ag`, the Grep tool on a directory, or on Windows a PowerShell `Get-ChildItem -Recurse | Select-String` / `Select-String -Path `) until the session has run one graph traversal — a PostToolUse hook records the MCP graph tools and `graphify query|explain|path` per session and agent; the retry after a query is allowed, and searching a single named file, piping into grep, or paths outside the project are never blocked. Set `GRAPHIFY_HOOK_STRICT=1`/`0` in the Claude process environment to override the installed mode; the default install is unchanged (soft nudge). > **Project hook interpreter:** committed project hooks read `graphify-out/.graphify_python`, which each clone writes when Graphify resolves its environment. Until that sidecar exists, the hook fails open and emits no graph decision; run the installed Graphify skill once in a fresh clone before relying on strict enforcement. diff --git a/graphify/cli.py b/graphify/cli.py index 8a60ea139..b59a7d1d3 100644 --- a/graphify/cli.py +++ b/graphify/cli.py @@ -743,11 +743,27 @@ def _mark_session_queried(identity: str) -> bool: try: p.parent.mkdir(parents=True, exist_ok=True) p.touch() + _gc_session_markers(p.parent) return True except Exception: return False +def _gc_session_markers(d: "Path") -> None: + """Best-effort: drop session markers older than 24 hours so a reused + session id is neither pre-denied nor pre-authorized. Shared by both writers.""" + try: + cutoff = time.time() - 86400 + for entry in os.scandir(d): + try: + if entry.stat().st_mtime < cutoff: + os.unlink(entry.path) + except OSError: + pass + except OSError: + pass + + def _session_has_queried(identity: str) -> bool: p = _queried_marker_path(identity) try: @@ -772,16 +788,7 @@ def _mark_session_denied(identity: str) -> bool: d.mkdir(parents=True, exist_ok=True) fd = os.open(str(d / f"{sid}.denied"), os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o644) os.close(fd) - try: - cutoff = time.time() - 86400 - for entry in os.scandir(d): - try: - if entry.stat().st_mtime < cutoff: - os.unlink(entry.path) - except OSError: - pass - except OSError: - pass + _gc_session_markers(d) return True except FileExistsError: return False @@ -985,6 +992,65 @@ def _bash_invokes_graphify_query(cmd_str: str) -> bool: return False +_PS_LIST_CMDS = frozenset({"get-childitem", "gci", "ls", "dir"}) +_PS_SEARCH_CMDS = frozenset({"select-string", "sls"}) + + +def _ps_recursive_search_targets(cmd_str: str, root: "Path") -> "list[Path]": + """In-project DIRECTORIES a PowerShell corpus search would walk (Claude Code's + PowerShell tool: tool_name "PowerShell", tool_input.command). Two measured + shapes only: `Get-ChildItem|gci|ls|dir ... -Recurse|-r [path] | Select-String|sls` + and `Select-String ... -Path ` (with or without -Recurse). + Exact-file -Path, stdin (Get-Content | Select-String) and out-of-project + targets return nothing. Never executes anything.""" + found = [] + for tokens in _bash_command_segments(cmd_str): + name = tokens[0] + args = tokens[1:] + lowered = [a.lower() for a in args] + if name in _PS_LIST_CMDS: + if not any(a in ("-recurse", "-r") for a in lowered): + continue + if not any(t[0] in _PS_SEARCH_CMDS for t in _bash_command_segments(cmd_str)): + continue + paths, value_of = [], None + for a in args: + if value_of: # previous token was a flag that takes a value + if value_of == "-path": + paths.append(a) + value_of = None + continue + al = a.lower() + if al.startswith("-"): + if al in ("-path", "-filter", "-include", "-exclude"): + value_of = al + continue + paths.append(a) + elif name in _PS_SEARCH_CMDS: + paths = [args[i + 1] for i, a in enumerate(args) if a.lower() == "-path" and i + 1 < len(args)] + if not paths: + continue # stdin search or pattern-only + else: + continue + if not paths: + paths = ["."] + for p in paths: + p = p.rstrip("*").rstrip("\\/") or "." + try: + resolved = Path(p).resolve() + if not resolved.is_dir(): + continue + resolved.relative_to(root) + except (ValueError, OSError, RuntimeError): + continue + found.append(resolved) + return found + + +def _ps_invokes_search(cmd_str: str) -> bool: + return any(t[0] in _PS_SEARCH_CMDS for t in _bash_command_segments(cmd_str)) + + def _run_hook_guard(kind: str, strict: bool = False) -> None: """Shell-agnostic PreToolUse guard (#522). @@ -1039,7 +1105,10 @@ def _run_hook_guard(kind: str, strict: bool = False) -> None: # contains "ag ") and on heredoc bodies that merely mention # grep. Nudge-only, even in strict mode — see the docstring. is_grep_tool = not cmd_str and bool(t.get("pattern")) - is_bash_search = bool(cmd_str) and _bash_invokes_search(cmd_str) + is_powershell = d.get("tool_name") == "PowerShell" + is_bash_search = bool(cmd_str) and ( + _ps_invokes_search(cmd_str) if is_powershell else _bash_invokes_search(cmd_str) + ) if not (is_grep_tool or is_bash_search) or not out_path("graph.json").is_file(): return # Strict search gate: deny a recursive in-project corpus search until @@ -1059,6 +1128,8 @@ def _run_hook_guard(kind: str, strict: bool = False) -> None: recursive = target.is_dir() except (ValueError, OSError, RuntimeError): recursive = False + elif is_powershell: + recursive = bool(_ps_recursive_search_targets(cmd_str, root)) else: recursive = bool(_bash_recursive_search_targets(cmd_str, root)) if recursive: diff --git a/graphify/install.py b/graphify/install.py index 4548bc509..7bca7384f 100644 --- a/graphify/install.py +++ b/graphify/install.py @@ -320,7 +320,7 @@ def _claude_pretooluse_hooks(strict: bool = False, project: bool = False) -> "li exe = _resolve_graphify_exe(project=project) flag = " --strict" if strict else "" return [ - {"matcher": "Bash|Grep", + {"matcher": "Bash|Grep|PowerShell", "hooks": [{"type": "command", "command": f"{exe} hook-guard search{flag} || true", "commandWindows": _graphify_command_windows("hook-guard search" + flag, project), @@ -335,7 +335,7 @@ def _claude_pretooluse_hooks(strict: bool = False, project: bool = False) -> "li # Matchers graphify registers; the install/uninstall filters key on these plus # "graphify" in the entry so a user's unrelated hooks are never touched. -_CLAUDE_PRETOOLUSE_MATCHERS = ("Glob|Grep", "Bash", "Bash|Grep", "Read|Glob") +_CLAUDE_PRETOOLUSE_MATCHERS = ("Glob|Grep", "Bash", "Bash|Grep", "Bash|Grep|PowerShell", "Read|Glob") _CLAUDE_POSTTOOLUSE_MATCHER = "Bash|mcp__graphify__.*" _CLAUDE_POSTTOOLUSE_MATCHERS = (_CLAUDE_POSTTOOLUSE_MATCHER,) @@ -2047,7 +2047,7 @@ def _install_codebuddy_hook(project_dir: Path) -> None: if not isinstance(pre_tool, list): _refuse_to_modify(settings_path) - hooks["PreToolUse"] = [h for h in pre_tool if not (isinstance(h, dict) and h.get("matcher") in ("Glob|Grep", "Bash", "Bash|Grep", "Read|Glob") and "graphify" in str(h))] + hooks["PreToolUse"] = [h for h in pre_tool if not _is_graphify_hook(h, _CLAUDE_PRETOOLUSE_MATCHERS)] hooks["PreToolUse"].extend(_claude_pretooluse_hooks()) _write_settings_with_backup(settings_path, settings) print(f" .codebuddy/settings.json -> PreToolUse hooks registered") @@ -2061,7 +2061,7 @@ def _uninstall_codebuddy_hook(project_dir: Path) -> None: except json.JSONDecodeError: return pre_tool = settings.get("hooks", {}).get("PreToolUse", []) - filtered = [h for h in pre_tool if not (h.get("matcher") in ("Glob|Grep", "Bash", "Bash|Grep", "Read|Glob") and "graphify" in str(h))] + filtered = [h for h in pre_tool if not _is_graphify_hook(h, _CLAUDE_PRETOOLUSE_MATCHERS)] if len(filtered) == len(pre_tool): return settings["hooks"]["PreToolUse"] = filtered diff --git a/tests/test_codebuddy.py b/tests/test_codebuddy.py index ac1aa52cf..b77a472cc 100644 --- a/tests/test_codebuddy.py +++ b/tests/test_codebuddy.py @@ -98,7 +98,7 @@ def test_codebuddy_install_hook_has_bash_matcher(tmp_path): codebuddy_install(tmp_path) settings = json.loads(_settings_path(tmp_path).read_text()) hooks = settings["hooks"]["PreToolUse"] - bash_hooks = [h for h in hooks if h.get("matcher") == "Bash|Grep"] + bash_hooks = [h for h in hooks if h.get("matcher") == "Bash|Grep|PowerShell"] assert any("graphify" in str(h) for h in bash_hooks) diff --git a/tests/test_install.py b/tests/test_install.py index c350b1455..601dd1a9b 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -411,7 +411,7 @@ def test_claude_hook_is_shell_agnostic(tmp_path): _install_claude_hook(tmp_path) hooks = _json.loads((tmp_path / ".claude" / "settings.json").read_text())["hooks"]["PreToolUse"] matchers = {h["matcher"] for h in hooks} - assert {"Bash|Grep", "Read|Glob"} <= matchers # Grep in the search matcher: #1986 + assert {"Bash|Grep|PowerShell", "Read|Glob"} <= matchers # Grep in the search matcher: #1986 for h in hooks: cmd = h["hooks"][0]["command"] for token in ("$(", "case ", "[ -f", "&&", ";;", "echo '"): diff --git a/tests/test_search_hook.py b/tests/test_search_hook.py index d870c8a4d..248a777c8 100644 --- a/tests/test_search_hook.py +++ b/tests/test_search_hook.py @@ -15,7 +15,7 @@ def _search_matcher(): hooks = _claude_pretooluse_hooks() - return next(h for h in hooks if h["matcher"] == "Bash|Grep") + return next(h for h in hooks if h["matcher"] == "Bash|Grep|PowerShell") def _env(): @@ -49,8 +49,9 @@ def _run_grep_tool(tool_input, cwd, *, graph: bool): def test_matcher_targets_bash_and_grep(): # #1986: content search goes through Claude Code's dedicated Grep tool, so - # the matcher must cover it alongside Bash. - assert _search_matcher()["matcher"] == "Bash|Grep" + # the matcher must cover it alongside Bash. On Windows hosts Claude Code also + # exposes a PowerShell tool whose searches must reach the same guard. + assert _search_matcher()["matcher"] == "Bash|Grep|PowerShell" def test_hook_command_has_no_backslashes(monkeypatch): diff --git a/tests/test_search_strict.py b/tests/test_search_strict.py index 02a81c1f8..1d3688a95 100644 --- a/tests/test_search_strict.py +++ b/tests/test_search_strict.py @@ -143,6 +143,69 @@ def test_quoted_parens_do_not_fragment_the_segment(tmp_path, monkeypatch): assert _is_deny(_invoke("search", _search('grep -rn "foo(bar)" .'), tmp_path, monkeypatch)) +def test_queried_markers_are_garbage_collected(tmp_path, monkeypatch): + """Round 1 F1: a Bash-only session never reaches the .denied GC, so the + .queried writer must sweep too — an old marker goes, a fresh one stays.""" + _fixture(tmp_path) + d = tmp_path / "graphify-out" / "cache" / "hook_sessions" + d.mkdir(parents=True) + old = d / "stale-session.queried" + old.write_text("", encoding="utf-8") + os.utime(old, (time.time() - 90000, time.time() - 90000)) # 25h old + _mark({"session_id": "fresh", "tool_name": "mcp__graphify__query_graph", + "tool_input": {}}, tmp_path, monkeypatch) + assert not old.exists() + assert (d / "fresh.queried").exists() + # a stale marker no longer pre-authorizes a reused session id + assert _is_deny(_invoke("search", _search("grep -rn foo .", "stale-session"), tmp_path, monkeypatch)) + + +def _ps(command, sid="s1"): + return {"session_id": sid, "tool_name": "PowerShell", "tool_input": {"command": command}} + + +def test_powershell_recursive_search_denies(tmp_path, monkeypatch): + """Round 1 F2: Claude Code's PowerShell tool (tool_name "PowerShell", tool_input.command) + is matched by PreToolUse hooks; a recursive in-project search through it must gate too.""" + _fixture(tmp_path) + for command in ( + "Get-ChildItem -Recurse -Filter *.py | Select-String foo", + "gci -r . | sls foo", + "ls -Recurse src | Select-String -Pattern foo", + 'Select-String -Path "src\\*" -Pattern foo', + "Select-String -Pattern foo -Path src -Recurse", + ): + assert _is_deny(_invoke("search", _ps(command), tmp_path, monkeypatch)), command + + +def test_powershell_bounded_and_outside_only_nudge_or_stay_silent(tmp_path, monkeypatch): + f = _fixture(tmp_path) + for command in ( + f"Select-String -Path {f} -Pattern foo", # exact file + "Select-String -Path src/mod.py -Pattern foo", # exact file, relative + "Get-Content src/mod.py | Select-String foo", # stdin + "Get-ChildItem -Recurse C:/somewhere/else | Select-String foo", # outside project + ): + out = _invoke("search", _ps(command), tmp_path, monkeypatch) + assert not _is_deny(out), command + assert "MANDATORY" in out, command + assert _invoke("search", _ps("Get-Process | Select-Object Id"), tmp_path, monkeypatch).strip() == "" + + +def test_powershell_allowed_after_query(tmp_path, monkeypatch): + _fixture(tmp_path) + _mark({"session_id": "s1", "tool_name": "mcp__graphify__query_graph", "tool_input": {}}, + tmp_path, monkeypatch) + out = _invoke("search", _ps("gci -r . | sls foo"), tmp_path, monkeypatch) + assert not _is_deny(out) and "MANDATORY" in out + + +def test_search_matcher_covers_powershell(): + from graphify.install import _claude_pretooluse_hooks + m = next(h for h in _claude_pretooluse_hooks(strict=True) if "Bash" in h["matcher"])["matcher"] + assert m == "Bash|Grep|PowerShell" + + def test_mark_queried_ignores_non_query_calls(tmp_path, monkeypatch): _fixture(tmp_path) for payload in ( @@ -227,7 +290,7 @@ def test_installed_strict_search_hook_executes_and_denies(tmp_path): for project in (False, True): entry = next( h for h in _claude_pretooluse_hooks(strict=True, project=project) - if h["matcher"] == "Bash|Grep" + if h["matcher"] == "Bash|Grep|PowerShell" )["hooks"][0] command = entry["commandWindows"] if os.name == "nt" else entry["command"] result = subprocess.run( @@ -263,7 +326,7 @@ def test_install_and_uninstall_round_trip(tmp_path, monkeypatch): monkeypatch.chdir(tmp_path) _install_claude_hook(tmp_path, strict=True) settings = json.loads((tmp_path / ".claude" / "settings.json").read_text(encoding="utf-8")) - search = next(h for h in settings["hooks"]["PreToolUse"] if h["matcher"] == "Bash|Grep") + search = next(h for h in settings["hooks"]["PreToolUse"] if h["matcher"] == "Bash|Grep|PowerShell") assert "hook-guard search --strict" in search["hooks"][0]["command"] assert any("graphify" in str(h) for h in settings["hooks"]["PostToolUse"]) _uninstall_claude_hook(tmp_path) From 8d04a7230ddffdb40c0df9763d4a380f5dec1d4d Mon Sep 17 00:00:00 2001 From: Logan Date: Wed, 2 Sep 2026 09:40:49 -0500 Subject: [PATCH 11/14] test: match the Bash|Grep|PowerShell search hook in test_claude_md Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01HNTa1HY67hwopSPQtffAPy --- tests/test_claude_md.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_claude_md.py b/tests/test_claude_md.py index cac794083..fc410171b 100644 --- a/tests/test_claude_md.py +++ b/tests/test_claude_md.py @@ -109,7 +109,7 @@ def test_install_creates_settings_json(tmp_path): assert settings_path.exists() settings = json.loads(settings_path.read_text()) hooks = settings.get("hooks", {}).get("PreToolUse", []) - assert any(h.get("matcher") == "Bash|Grep" for h in hooks) + assert any(h.get("matcher") == "Bash|Grep|PowerShell" for h in hooks) def test_install_settings_json_idempotent(tmp_path): @@ -120,7 +120,7 @@ def test_install_settings_json_idempotent(tmp_path): settings_path = tmp_path / ".claude" / "settings.json" settings = json.loads(settings_path.read_text()) hooks = settings.get("hooks", {}).get("PreToolUse", []) - bash_hooks = [h for h in hooks if h.get("matcher") == "Bash|Grep" and "graphify" in str(h)] + bash_hooks = [h for h in hooks if h.get("matcher") == "Bash|Grep|PowerShell" and "graphify" in str(h)] assert len(bash_hooks) == 1 @@ -133,7 +133,7 @@ def test_uninstall_removes_settings_hook(tmp_path): if settings_path.exists(): settings = json.loads(settings_path.read_text()) hooks = settings.get("hooks", {}).get("PreToolUse", []) - assert not any(h.get("matcher") == "Bash|Grep" and "graphify" in str(h) for h in hooks) + assert not any(h.get("matcher") == "Bash|Grep|PowerShell" and "graphify" in str(h) for h in hooks) # --------------------------------------------------------------------------- From 968715a55fe4610ee2bfefa908edb2a0d091288b Mon Sep 17 00:00:00 2001 From: Logan Date: Wed, 2 Sep 2026 09:43:35 -0500 Subject: [PATCH 12/14] fix(hooks): enforce marker TTL in the reader; gate recursive PowerShell listing Round 3 review findings: - F1: _session_has_queried now enforces the 24-hour TTL itself and best-effort unlinks an expired marker, so a stale .queried never pre-authorizes a reused session id while no writer happens to sweep. One _SESSION_MARKER_TTL shared with the GC. - F2: a recursive PowerShell listing without a Select-String pipe (Get-ChildItem|gci|ls|dir ... -Recurse) is the same corpus-enumeration class as `find .` and now denies in-project, nudges out-of-project, and stays silent when not recursive. Tests: 2 RED on 8d04a72, 326 passed after; skillgen --check 134 OK; ruff clean. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01HNTa1HY67hwopSPQtffAPy --- graphify/cli.py | 41 +++++++++++++++++++++++++++---------- tests/test_search_strict.py | 26 +++++++++++++++++++++++ 2 files changed, 56 insertions(+), 11 deletions(-) diff --git a/graphify/cli.py b/graphify/cli.py index b59a7d1d3..ea1347937 100644 --- a/graphify/cli.py +++ b/graphify/cli.py @@ -753,7 +753,7 @@ def _gc_session_markers(d: "Path") -> None: """Best-effort: drop session markers older than 24 hours so a reused session id is neither pre-denied nor pre-authorized. Shared by both writers.""" try: - cutoff = time.time() - 86400 + cutoff = time.time() - _SESSION_MARKER_TTL for entry in os.scandir(d): try: if entry.stat().st_mtime < cutoff: @@ -764,10 +764,24 @@ def _gc_session_markers(d: "Path") -> None: pass +_SESSION_MARKER_TTL = 86400 # seconds; shared by the reader and the GC sweep + + def _session_has_queried(identity: str) -> bool: + """True only for a marker younger than the TTL. The reader enforces the TTL + itself — a stale marker must not pre-authorize a reused session id just + because no writer has swept yet — and best-effort unlinks an expired one.""" p = _queried_marker_path(identity) try: - return p is not None and p.is_file() + if p is None or not p.is_file(): + return False + if p.stat().st_mtime >= time.time() - _SESSION_MARKER_TTL: + return True + try: + p.unlink() + except OSError: + pass + return False except Exception: return False @@ -997,12 +1011,13 @@ def _bash_invokes_graphify_query(cmd_str: str) -> bool: def _ps_recursive_search_targets(cmd_str: str, root: "Path") -> "list[Path]": - """In-project DIRECTORIES a PowerShell corpus search would walk (Claude Code's - PowerShell tool: tool_name "PowerShell", tool_input.command). Two measured - shapes only: `Get-ChildItem|gci|ls|dir ... -Recurse|-r [path] | Select-String|sls` - and `Select-String ... -Path ` (with or without -Recurse). - Exact-file -Path, stdin (Get-Content | Select-String) and out-of-project - targets return nothing. Never executes anything.""" + """In-project DIRECTORIES a PowerShell corpus search or enumeration would walk + (Claude Code's PowerShell tool: tool_name "PowerShell", tool_input.command). + Two measured shapes: `Get-ChildItem|gci|ls|dir ... -Recurse|-r [path]` — with or + without a `| Select-String` pipe, because recursive enumeration is the same + bypass class as `find .` — and `Select-String ... -Path `. + Exact-file -Path, non-recursive listing, stdin (Get-Content | Select-String) + and out-of-project targets return nothing. Never executes anything.""" found = [] for tokens in _bash_command_segments(cmd_str): name = tokens[0] @@ -1011,8 +1026,6 @@ def _ps_recursive_search_targets(cmd_str: str, root: "Path") -> "list[Path]": if name in _PS_LIST_CMDS: if not any(a in ("-recurse", "-r") for a in lowered): continue - if not any(t[0] in _PS_SEARCH_CMDS for t in _bash_command_segments(cmd_str)): - continue paths, value_of = [], None for a in args: if value_of: # previous token was a flag that takes a value @@ -1048,7 +1061,13 @@ def _ps_recursive_search_targets(cmd_str: str, root: "Path") -> "list[Path]": def _ps_invokes_search(cmd_str: str) -> bool: - return any(t[0] in _PS_SEARCH_CMDS for t in _bash_command_segments(cmd_str)) + """A Select-String, or a recursive directory listing (enumeration counts).""" + for tokens in _bash_command_segments(cmd_str): + if tokens[0] in _PS_SEARCH_CMDS: + return True + if tokens[0] in _PS_LIST_CMDS and any(a.lower() in ("-recurse", "-r") for a in tokens[1:]): + return True + return False def _run_hook_guard(kind: str, strict: bool = False) -> None: diff --git a/tests/test_search_strict.py b/tests/test_search_strict.py index 1d3688a95..6ffa7cc2d 100644 --- a/tests/test_search_strict.py +++ b/tests/test_search_strict.py @@ -160,6 +160,20 @@ def test_queried_markers_are_garbage_collected(tmp_path, monkeypatch): assert _is_deny(_invoke("search", _search("grep -rn foo .", "stale-session"), tmp_path, monkeypatch)) +def test_stale_queried_marker_does_not_authorize_without_any_writer(tmp_path, monkeypatch): + """Round 3 F1: the reader enforces the TTL itself. No fresh mark call first — + the only marker is 25 hours old, and the search must still be denied.""" + _fixture(tmp_path) + d = tmp_path / "graphify-out" / "cache" / "hook_sessions" + d.mkdir(parents=True) + old = d / "reused.queried" + old.write_text("", encoding="utf-8") + os.utime(old, (time.time() - 90000, time.time() - 90000)) + out = _invoke("search", _search("grep -rn foo .", "reused"), tmp_path, monkeypatch) + assert _is_deny(out) + assert not old.exists() # best-effort unlink of the expired marker + + def _ps(command, sid="s1"): return {"session_id": sid, "tool_name": "PowerShell", "tool_input": {"command": command}} @@ -178,6 +192,18 @@ def test_powershell_recursive_search_denies(tmp_path, monkeypatch): assert _is_deny(_invoke("search", _ps(command), tmp_path, monkeypatch)), command +def test_powershell_standalone_recursive_listing_denies_like_find(tmp_path, monkeypatch): + """Round 3 F2: recursive corpus enumeration without a Select-String pipe is the + same bypass class as `find .`; it must deny in-project, nudge out-of-project, + and stay silent when not recursive.""" + _fixture(tmp_path) + for command in ("Get-ChildItem -Recurse .", "gci -r", "ls -Recurse src", "dir -Recurse -Filter *.md"): + assert _is_deny(_invoke("search", _ps(command), tmp_path, monkeypatch)), command + out = _invoke("search", _ps("Get-ChildItem -Recurse C:/somewhere/else"), tmp_path, monkeypatch) + assert not _is_deny(out) and "MANDATORY" in out + assert _invoke("search", _ps("Get-ChildItem src"), tmp_path, monkeypatch).strip() == "" + + def test_powershell_bounded_and_outside_only_nudge_or_stay_silent(tmp_path, monkeypatch): f = _fixture(tmp_path) for command in ( From 0529556842ffa43922c9cd6efa66d0cd979db55d Mon Sep 17 00:00:00 2001 From: Logan Date: Wed, 2 Sep 2026 10:39:29 -0500 Subject: [PATCH 13/14] feat(antigravity): strict hook-guard gate for the headless agy CLI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Measured on Google Antigravity CLI (agy -p): global rules load but are advice only — claude-sonnet-4-6 and gpt-oss-120b both grep the tree first with an always_on graph rule active — workspace .agents/rules never load headless, and hook payloads carry workspacePaths: []. `graphify antigravity install` therefore changed nothing for the CLI. - `hook-guard agy`: PreToolUse + PreInvocation handler for agy's camelCase payload. call_mcp_tool -> graphify writes a per-conversation marker (~/.graphify/agy_sessions, TTL shared with the Claude gate); grep_search, find_by_name, list_dir, or run_command running a recursive search (including `powershell -Command "..."` payloads) are denied until it exists. Workspace = workspacePaths[0] when sent, else the call's own target path walked up to graphify-out/graph.json; never cwd (agy runs hooks from the hooks.json directory). PreInvocation injects the same instruction while the workspace is known and unqueried. Fails open. - `graphify antigravity install --strict` merges a `graphify-graph-first` entry into ~/.gemini/config/hooks.json using the pinned interpreter in module form (a quoted script path after -File breaks under agy's cmd /c); `antigravity uninstall` removes only that entry. - `_ps_recursive_search_targets`: `-Path dir\*.md` names the directory. Tests: tests/test_hook_agy.py (11; RED on 968715a, GREEN after), 248 passed across the hook/install suites; skillgen --check 134 OK; ruff clean. Live: the PowerShell twin of this gate, installed on the author's machine, forced both models to query first (conversations 4a618532, 1df5adbc). Stacked on #3291. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01HNTa1HY67hwopSPQtffAPy --- CHANGELOG.md | 1 + README.md | 1 + graphify/cli.py | 136 +++++++++++++++++++++++++++++++- graphify/install.py | 72 +++++++++++++++-- tests/test_hook_agy.py | 175 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 379 insertions(+), 6 deletions(-) create mode 100644 tests/test_hook_agy.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f94dd803..bbb562978 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Full release notes with details on each version: [GitHub Releases](https://githu - Fix: generated agent guidance now prefers the MCP `query_graph` tool and uses the graph's recorded Python interpreter as the CLI fallback, so hardened Windows hosts do not route agents through an unsigned `graphify.exe` shim. - Fix: strict Claude hooks no longer use one graph-wide 30-minute query stamp that let any agent disable the first-read block for every other session; deny markers now key subagents by `session_id` plus `agent_id`, so a parent's first read cannot silently consume every subagent's guard. - Fix: project-scoped hooks now resolve Graphify through each clone's `graphify-out/.graphify_python` sidecar instead of a bare PATH launcher, preserving committed-config portability without producing an unrunnable hook on Application Control hosts (#3280, follow-up to #3129). +- Feature: `graphify antigravity install --strict` registers a `hook-guard agy` gate in `~/.gemini/config/hooks.json` (PreToolUse + PreInvocation). The headless Antigravity CLI loads `.agents/rules` as advice only and sends `workspacePaths: []`, so the gate derives the graph root from the call's own target path and denies `grep_search`, `find_by_name`, `list_dir` and recursive `run_command` searches until the conversation has called the graphify MCP server; `antigravity uninstall` removes it. Measured on claude-sonnet-4-6 and gpt-oss-120b: both grep first under the rule alone and query first under the gate. - Feature: strict Claude hooks now record query evidence per session and agent (a PostToolUse `hook-guard mark-queried` on the MCP graph tools and on `graphify query|explain|path`) and deny a recursive in-project search issued through Bash, the Grep tool, or Claude Code's PowerShell tool (`Get-ChildItem -Recurse | Select-String`, `Select-String -Path `) until that evidence exists; both marker writers garbage-collect markers older than 24 hours; the retry after one traversal is allowed, and exact-file grep, stdin grep, `git grep`, out-of-project targets, Glob, soft mode and malformed input never block. Closes the escape where an agent instructed to read and search through Bash never met the Read-only strict block. ## 0.9.53 (2026-08-30) diff --git a/README.md b/README.md index bc148c6e0..d7ceb37d5 100644 --- a/README.md +++ b/README.md @@ -752,6 +752,7 @@ graphify pi uninstall graphify devin install # skill file + .windsurf/rules/graphify.md (Devin CLI) graphify devin uninstall graphify antigravity install # .agents/rules + .agents/workflows (Google Antigravity) +graphify antigravity install --strict # + ~/.gemini/config/hooks.json gate: the headless agy CLI loads rules as advice only, so this denies recursive search until the conversation has queried the graph graphify antigravity uninstall graphify extract ./docs # headless LLM extraction for CI (no IDE needed) diff --git a/graphify/cli.py b/graphify/cli.py index ea1347937..a70225557 100644 --- a/graphify/cli.py +++ b/graphify/cli.py @@ -1048,7 +1048,9 @@ def _ps_recursive_search_targets(cmd_str: str, root: "Path") -> "list[Path]": if not paths: paths = ["."] for p in paths: - p = p.rstrip("*").rstrip("\\/") or "." + # `dir\*` or `dir\*.md` means the directory; drop a wildcard last component. + head, _, tail = p.replace("\\", "/").rpartition("/") + p = (head if ("*" in tail or "?" in tail) else p).rstrip("\\/") or "." try: resolved = Path(p).resolve() if not resolved.is_dir(): @@ -1070,6 +1072,135 @@ def _ps_invokes_search(cmd_str: str) -> bool: return False +_AGY_GATED_TOOLS = frozenset({"grep_search", "find_by_name", "list_dir"}) +_AGY_PATH_KEYS = ("SearchPath", "SearchDirectory", "DirectoryPath", "Cwd") +_AGY_DENY_REASON = ( + "graph-first: this workspace has graphify-out/graph.json and this conversation has not " + "queried it. Searching or listing the tree (grep_search, find_by_name, list_dir, or a " + "recursive search in run_command) stays denied until you call call_mcp_tool with " + "ServerName graphify, ToolName query_graph, Arguments {question, project_path = the " + "workspace}. After that one call every search is allowed. Reading a single named file is " + "allowed now." +) + + +def _agy_marker_path(identity: str) -> "Path | None": + sid = _session_marker_id(identity) + return Path.home() / ".graphify" / "agy_sessions" / f"{sid}.queried" if sid else None + + +def _agy_workspace(d: dict) -> "Path | None": + """The graph root this call touches. Antigravity's IDE sends workspacePaths; headless + `agy -p` sends [] (measured), so walk up from the call's own target paths to the nearest + directory holding graphify-out/graph.json. Never cwd: agy runs hooks from the hooks.json + directory.""" + paths = [str(p) for p in (d.get("workspacePaths") or []) if p] + args = (d.get("toolCall") or {}).get("args") or {} + paths += [str(args.get(k)) for k in _AGY_PATH_KEYS if args.get(k)] + cmd = str(args.get("CommandLine") or "") + if cmd: + paths += re.findall(r"[A-Za-z]:[\\/][^\"'\s|;&]*|/[^\"'\s|;&]+", cmd) + for p in paths: + try: + cur = Path(p).resolve() + except (OSError, RuntimeError): + continue + # A wildcard or not-yet-existing target (`ws\*.md`) still names its directory. + while not cur.exists() and cur.parent != cur: + cur = cur.parent + if not cur.exists(): + continue + if cur.is_file(): + cur = cur.parent + while True: + if (cur / _GRAPHIFY_OUT / "graph.json").is_file() or (cur / "graphify-out" / "graph.json").is_file(): + return cur + if cur.parent == cur: + break + cur = cur.parent + return None + + +def _run_hook_guard_agy() -> None: + """Google Antigravity CLI gate (`hook-guard agy`): PreToolUse + PreInvocation. + + Payload keys are camelCase. call_mcp_tool -> graphify writes a per-conversation marker; + grep_search / find_by_name / list_dir, or run_command running a recursive search, in a + tree that has a graph are denied until that marker exists (TTL _SESSION_MARKER_TTL). + PreInvocation injects the same instruction as an ephemeral message while the workspace + is known and unqueried. Everything else, and every error, allows. + """ + def out(obj: dict) -> None: + sys.stdout.write(json.dumps(obj, ensure_ascii=False, separators=(",", ":"))) + + try: + d = json.loads(sys.stdin.buffer.read().decode("utf-8", "replace")) + if not isinstance(d, dict): + out({"decision": "allow"}) + return + invocation = "invocationNum" in d and not d.get("toolCall") + conv = str(d.get("conversationId") or "") + marker = _agy_marker_path(conv) + if marker is None: + out({} if invocation else {"decision": "allow"}) + return + ws = _agy_workspace(d) + queried = False + try: + if marker.is_file(): + if marker.stat().st_mtime >= time.time() - _SESSION_MARKER_TTL: + queried = True + else: + marker.unlink() + except OSError: + pass + if invocation: + if ws is not None and not queried: + msg = ( + 'graph-first: before any search or listing, call call_mcp_tool with ServerName ' + '"graphify", ToolName "query_graph", Arguments {"question": "", ' + f'"project_path": "{ws.as_posix()}"}}. Searches and listings are denied until that ' + 'call happens; single-file reads are allowed.' + ) + out({"injectSteps": [{"ephemeralMessage": msg}]}) + else: + out({}) + return + call = d.get("toolCall") or {} + name = str(call.get("name") or "") + args = call.get("args") or {} + if name == "call_mcp_tool": + if str(args.get("ServerName") or "") == "graphify": + marker.parent.mkdir(parents=True, exist_ok=True) + marker.touch() + _gc_session_markers(marker.parent) + out({"decision": "allow"}) + return + if ws is None or queried: + out({"decision": "allow"}) + return + gated = name in _AGY_GATED_TOOLS + if name == "run_command": + cmd = str(args.get("CommandLine") or "") + # `powershell -Command ""` / `pwsh -c ''`: the search is the quoted + # payload, which the segment parser keeps as one token — inspect it as well. + inner = re.search(r"(?i)\b(?:powershell(?:\.exe)?|pwsh(?:\.exe)?)\b[^\"']*-c(?:ommand)?\s+([\"'])(.*)\1", cmd, re.S) + candidates = [cmd] + ([inner.group(2)] if inner else []) + # Relative targets in the command resolve against the call's Cwd (else the graph root). + prev = os.getcwd() + try: + os.chdir(str(args.get("Cwd") or ws)) + gated = any( + _bash_recursive_search_targets(c, ws) or _ps_recursive_search_targets(c, ws) + for c in candidates + ) + finally: + os.chdir(prev) + out({"decision": "deny", "reason": _AGY_DENY_REASON} if gated else {"decision": "allow"}) + except Exception: + out({"decision": "allow"}) + + def _run_hook_guard(kind: str, strict: bool = False) -> None: """Shell-agnostic PreToolUse guard (#522). @@ -1090,6 +1221,9 @@ def _run_hook_guard(kind: str, strict: bool = False) -> None: nudge instead of blocking or demanding. """ from graphify.paths import out_path, GRAPHIFY_OUT_NAME + if kind == "agy": + _run_hook_guard_agy() + return # Gemini's BeforeTool hook takes no stdin and must ALWAYS return a decision so # the tool is never blocked; the graph nudge is appended only when a graph # exists. Handled before the stdin read below (which the search/read guards need). diff --git a/graphify/install.py b/graphify/install.py index 7bca7384f..83312399b 100644 --- a/graphify/install.py +++ b/graphify/install.py @@ -1073,12 +1073,72 @@ def _antigravity_finalize(skill_dst: Path, project_dir: Path) -> None: else: wf_path.write_text(_ANTIGRAVITY_WORKFLOW, encoding="utf-8") print(f"graphify workflow written to {wf_path.resolve()}") -def _antigravity_install(project_dir: Path) -> None: - """Install graphify for Google Antigravity (global skill + .agents/rules + .agents/workflows).""" +_ANTIGRAVITY_HOOK_KEY = "graphify-graph-first" + + +def _antigravity_hooks_path() -> Path: + return Path.home() / ".gemini" / "config" / "hooks.json" + + +def _antigravity_hooks_json_entry() -> dict: + """The `hooks.json` entry for the strict gate: PreToolUse on the search tools and MCP + call, plus PreInvocation. The command is the pinned interpreter; agy runs it through + `cmd /c` on Windows, where a quoted script path after `-File` breaks — so this is the + module form, not a script.""" + command = f"{_resolve_graphify_exe()} hook-guard agy" + return { + "PreInvocation": [{"type": "command", "command": command, "timeout": 10}], + "PreToolUse": [{ + "matcher": "grep_search|find_by_name|list_dir|run_command|call_mcp_tool", + "hooks": [{"type": "command", "command": command, "timeout": 10}], + }], + } + + +def _antigravity_hooks_install() -> None: + path = _antigravity_hooks_path() + data = {} + if path.exists(): + try: + data = json.loads(path.read_text(encoding="utf-8")) + except json.JSONDecodeError: + _refuse_to_modify(path) + if not isinstance(data, dict): + _refuse_to_modify(path) + data[_ANTIGRAVITY_HOOK_KEY] = _antigravity_hooks_json_entry() + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(data, indent=2), encoding="utf-8") + print(f" {path} -> {_ANTIGRAVITY_HOOK_KEY} hook registered (strict)") + + +def _antigravity_hooks_uninstall() -> None: + path = _antigravity_hooks_path() + if not path.exists(): + return + try: + data = json.loads(path.read_text(encoding="utf-8")) + except json.JSONDecodeError: + return + if not isinstance(data, dict) or _ANTIGRAVITY_HOOK_KEY not in data: + return + del data[_ANTIGRAVITY_HOOK_KEY] + path.write_text(json.dumps(data, indent=2), encoding="utf-8") + print(f" {path} -> {_ANTIGRAVITY_HOOK_KEY} hook removed") + + +def _antigravity_install(project_dir: Path, strict: bool = False) -> None: + """Install graphify for Google Antigravity (global skill + .agents/rules + .agents/workflows). + + ``strict`` also registers the `hook-guard agy` gate in ~/.gemini/config/hooks.json: the + headless CLI loads rules as advice only and sends no workspacePaths, so without the gate + `.agents/rules/graphify.md` changes nothing there. + """ # Copy the skill to ~/.gemini/config/skills/graphify/SKILL.md (global), then # lay down the always-on rules/workflows under the project dir. install(platform="antigravity") _antigravity_finalize(_platform_skill_destination("antigravity"), project_dir) + if strict: + _antigravity_hooks_install() print() print("Antigravity will now check the knowledge graph before answering") @@ -1094,7 +1154,9 @@ def _antigravity_install(project_dir: Path) -> None: ) print(" }") def _antigravity_uninstall(project_dir: Path, *, project: bool = False) -> None: - """Remove graphify Antigravity rules, workflow, and skill files.""" + """Remove graphify Antigravity rules, workflow, skill files and the strict hook.""" + if not project: + _antigravity_hooks_uninstall() # Remove rules file rules_path = project_dir / _ANTIGRAVITY_RULES_PATH if rules_path.exists(): @@ -2399,13 +2461,13 @@ def dispatch_install_cli(cmd: str) -> bool: if "--project" in sys.argv[3:]: _project_install("antigravity", Path(".")) else: - _antigravity_install(Path(".")) + _antigravity_install(Path("."), strict="--strict" in sys.argv[3:]) elif subcmd == "uninstall": if "--project" in sys.argv[3:]: _project_uninstall("antigravity", Path(".")) else: _antigravity_uninstall(Path(".")) else: - print("Usage: graphify antigravity [install|uninstall]", file=sys.stderr) + print("Usage: graphify antigravity [install [--strict]|uninstall]", file=sys.stderr) sys.exit(1) return True diff --git a/tests/test_hook_agy.py b/tests/test_hook_agy.py new file mode 100644 index 000000000..72e811feb --- /dev/null +++ b/tests/test_hook_agy.py @@ -0,0 +1,175 @@ +"""`hook-guard agy`: Google Antigravity CLI PreToolUse/PreInvocation gate. + +Headless `agy -p` loads global rules but treats them as advice (measured: sonnet-4-6 and +gpt-oss-120b both grep first with an always-on graph rule active), and it sends +`workspacePaths: []`, so the gate derives the workspace from the tool call's own target +path. A recursive corpus search is denied until this conversation has called the +graphify MCP server through `call_mcp_tool`; the way out is the query. Payload keys are +camelCase (protojson). Fails open on anything unexpected. +""" +import io +import json +import os +import subprocess +import sys +import time + +import graphify.cli as cli +import graphify.install as install + + +def _ws(tmp_path): + ws = tmp_path / "ws" + (ws / "src").mkdir(parents=True) + (ws / "src" / "mod.py").write_text("x = 1\n", encoding="utf-8") + (ws / "graphify-out").mkdir() + (ws / "graphify-out" / "graph.json").write_text('{"nodes":[],"links":[]}', encoding="utf-8") + return ws + + +def _home(tmp_path, monkeypatch): + home = tmp_path / "home" + home.mkdir(exist_ok=True) + monkeypatch.setattr("pathlib.Path.home", lambda: home) + monkeypatch.setenv("HOME", str(home)) + monkeypatch.setenv("USERPROFILE", str(home)) + return home + + +def _invoke(payload, tmp_path, monkeypatch): + _home(tmp_path, monkeypatch) + monkeypatch.chdir(tmp_path) # never the workspace: agy runs hooks from the hooks.json dir + data = json.dumps(payload).encode() if not isinstance(payload, (bytes, bytearray)) else bytes(payload) + + class _Stdin: + buffer = io.BytesIO(data) + monkeypatch.setattr(sys, "stdin", _Stdin()) + buf = io.StringIO() + monkeypatch.setattr(sys, "stdout", buf) + cli._run_hook_guard("agy") + return json.loads(buf.getvalue() or "{}") + + +def _tool(name, args=None, conv="c1", ws=None): + return {"conversationId": conv, "workspacePaths": [str(ws)] if ws else [], + "toolCall": {"name": name, "args": args or {}}} + + +def test_search_in_graph_tree_denies_until_graphify_mcp_call(tmp_path, monkeypatch): + ws = _ws(tmp_path) + out = _invoke(_tool("grep_search", {"Query": "x", "SearchPath": str(ws / "src")}), tmp_path, monkeypatch) + assert out["decision"] == "deny" and "call_mcp_tool" in out["reason"] + assert _invoke(_tool("find_by_name", {"Pattern": "*", "SearchDirectory": str(ws)}), tmp_path, monkeypatch)["decision"] == "deny" + assert _invoke(_tool("list_dir", {"DirectoryPath": str(ws)}), tmp_path, monkeypatch)["decision"] == "deny" + # second attempt is denied again: not once-per-session + assert _invoke(_tool("grep_search", {"Query": "x", "SearchPath": str(ws)}), tmp_path, monkeypatch)["decision"] == "deny" + assert _invoke(_tool("call_mcp_tool", {"ServerName": "graphify", "ToolName": "query_graph"}), tmp_path, monkeypatch)["decision"] == "allow" + assert _invoke(_tool("grep_search", {"Query": "x", "SearchPath": str(ws)}), tmp_path, monkeypatch)["decision"] == "allow" + + +def test_run_command_recursive_search_denies_bounded_allows(tmp_path, monkeypatch): + ws = _ws(tmp_path) + w = str(ws) + for cmd in ( + f'powershell -NoProfile -Command "Get-ChildItem -Path \'{w}\' -Recurse -Include *.md | Select-String -Pattern x"', + f'rg -n "x" "{w}" -l', + f'Select-String -Path "{w}\\*.md" -Pattern x', + f'grep -rn x "{w}"', + ): + out = _invoke(_tool("run_command", {"CommandLine": cmd}), tmp_path, monkeypatch) + assert out["decision"] == "deny", cmd + # Cwd carries the workspace when the command uses a relative path + assert _invoke(_tool("run_command", {"CommandLine": "grep -rn x .", "Cwd": w}), tmp_path, monkeypatch)["decision"] == "deny" + for cmd in (f'grep -n x "{w}\\src\\mod.py"', f'Get-Content "{w}\\src\\mod.py" | Select-String x', "git status"): + assert _invoke(_tool("run_command", {"CommandLine": cmd, "Cwd": w}), tmp_path, monkeypatch)["decision"] == "allow", cmd + + +def test_workspace_paths_used_when_present(tmp_path, monkeypatch): + ws = _ws(tmp_path) + assert _invoke(_tool("grep_search", {"Query": "x"}, ws=ws), tmp_path, monkeypatch)["decision"] == "deny" + + +def test_conversations_are_isolated_and_other_servers_do_not_mark(tmp_path, monkeypatch): + ws = _ws(tmp_path) + _invoke(_tool("call_mcp_tool", {"ServerName": "graphify", "ToolName": "query_graph"}, conv="a"), tmp_path, monkeypatch) + assert _invoke(_tool("grep_search", {"Query": "x", "SearchPath": str(ws)}, conv="a"), tmp_path, monkeypatch)["decision"] == "allow" + assert _invoke(_tool("grep_search", {"Query": "x", "SearchPath": str(ws)}, conv="b"), tmp_path, monkeypatch)["decision"] == "deny" + _invoke(_tool("call_mcp_tool", {"ServerName": "code-review-graph", "ToolName": "x"}, conv="b"), tmp_path, monkeypatch) + assert _invoke(_tool("grep_search", {"Query": "x", "SearchPath": str(ws)}, conv="b"), tmp_path, monkeypatch)["decision"] == "deny" + + +def test_expired_marker_does_not_authorize(tmp_path, monkeypatch): + ws = _ws(tmp_path) + home = _home(tmp_path, monkeypatch) + d = home / ".graphify" / "agy_sessions" + d.mkdir(parents=True) + old = d / "stale.queried" + old.write_text("", encoding="utf-8") + os.utime(old, (time.time() - 90000, time.time() - 90000)) + assert _invoke(_tool("grep_search", {"Query": "x", "SearchPath": str(ws)}, conv="stale"), tmp_path, monkeypatch)["decision"] == "deny" + + +def test_safety_valves_allow(tmp_path, monkeypatch): + ws = _ws(tmp_path) + nograph = tmp_path / "plain" + nograph.mkdir() + assert _invoke(_tool("grep_search", {"Query": "x", "SearchPath": str(nograph)}), tmp_path, monkeypatch)["decision"] == "allow" + assert _invoke(_tool("grep_search", {"Query": "x"}), tmp_path, monkeypatch)["decision"] == "allow" # no path, no workspace + assert _invoke(_tool("view_file", {"AbsolutePath": str(ws / "src" / "mod.py")}), tmp_path, monkeypatch)["decision"] == "allow" + assert _invoke({"workspacePaths": [], "toolCall": {"name": "grep_search", "args": {"SearchPath": str(ws)}}}, tmp_path, monkeypatch)["decision"] == "allow" # no conversationId + assert _invoke(b"{not json", tmp_path, monkeypatch)["decision"] == "allow" + + +def test_preinvocation_injects_only_with_known_workspace_and_no_marker(tmp_path, monkeypatch): + ws = _ws(tmp_path) + out = _invoke({"conversationId": "p1", "workspacePaths": [str(ws)], "invocationNum": 1, "initialNumSteps": 0}, tmp_path, monkeypatch) + assert out["injectSteps"] and "call_mcp_tool" in out["injectSteps"][0]["ephemeralMessage"] and "decision" not in out + _invoke(_tool("call_mcp_tool", {"ServerName": "graphify", "ToolName": "query_graph"}, conv="p1"), tmp_path, monkeypatch) + assert _invoke({"conversationId": "p1", "workspacePaths": [str(ws)], "invocationNum": 2, "initialNumSteps": 3}, tmp_path, monkeypatch) == {} + assert _invoke({"conversationId": "p2", "workspacePaths": [], "invocationNum": 1, "initialNumSteps": 0}, tmp_path, monkeypatch) == {} + + +def test_reason_is_constant_never_echoes_command(tmp_path, monkeypatch): + ws = _ws(tmp_path) + out = _invoke(_tool("run_command", {"CommandLine": f'rg "$(rm -rf /)" "{ws}"'}), tmp_path, monkeypatch) + assert out["decision"] == "deny" and "rm -rf" not in json.dumps(out) + + +def test_installed_command_executes(tmp_path, monkeypatch): + """The generated command runs end to end through a shell, as agy runs it (cmd /c on Windows).""" + ws = _ws(tmp_path) + home = _home(tmp_path, monkeypatch) + hooks = install._antigravity_hooks_json_entry() + command = hooks["PreToolUse"][0]["hooks"][0]["command"] + env = dict(os.environ, HOME=str(home), USERPROFILE=str(home)) + payload = json.dumps(_tool("grep_search", {"Query": "x", "SearchPath": str(ws)}, conv="installed")) + result = subprocess.run(command, input=payload, text=True, capture_output=True, shell=True, cwd=tmp_path, timeout=30, env=env) + assert result.returncode == 0, result.stderr + assert json.loads(result.stdout)["decision"] == "deny", result.stdout or result.stderr + assert '"' not in command.split(" -File ")[-1] if " -File " in command else True + + +def test_antigravity_install_strict_merges_hooks_json_and_uninstall_removes(tmp_path, monkeypatch): + home = _home(tmp_path, monkeypatch) + cfg = home / ".gemini" / "config" + cfg.mkdir(parents=True) + hooks_path = cfg / "hooks.json" + hooks_path.write_text(json.dumps({"my-linter": {"PostToolUse": [{"matcher": "run_command", "hooks": [{"command": "lint"}]}]}}), encoding="utf-8") + install._antigravity_install(tmp_path, strict=True) + data = json.loads(hooks_path.read_text(encoding="utf-8")) + assert "my-linter" in data, "a user's own hook must survive" + entry = data["graphify-graph-first"] + assert entry["PreToolUse"][0]["matcher"] == "grep_search|find_by_name|list_dir|run_command|call_mcp_tool" + assert "hook-guard agy" in entry["PreToolUse"][0]["hooks"][0]["command"] + assert "hook-guard agy" in entry["PreInvocation"][0]["command"] + install._antigravity_install(tmp_path, strict=True) # idempotent + assert list(json.loads(hooks_path.read_text(encoding="utf-8")).keys()).count("graphify-graph-first") == 1 + install._antigravity_uninstall(tmp_path) + data = json.loads(hooks_path.read_text(encoding="utf-8")) + assert "graphify-graph-first" not in data and "my-linter" in data + + +def test_antigravity_install_without_strict_writes_no_hooks(tmp_path, monkeypatch): + home = _home(tmp_path, monkeypatch) + install._antigravity_install(tmp_path) + assert not (home / ".gemini" / "config" / "hooks.json").exists() From e4d408b68b85634962b31dd7298960f8cc1010ee Mon Sep 17 00:00:00 2001 From: Logan Date: Wed, 2 Sep 2026 10:44:29 -0500 Subject: [PATCH 14/14] test(antigravity): build the Select-String wildcard path with the OS separator On POSIX a literal backslash is part of a file name, so the fabricated "\*.md" climbed to the pytest dir and the gate correctly allowed; CI on Linux failed while Windows passed. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01HNTa1HY67hwopSPQtffAPy --- tests/test_hook_agy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_hook_agy.py b/tests/test_hook_agy.py index 72e811feb..361b9f2d4 100644 --- a/tests/test_hook_agy.py +++ b/tests/test_hook_agy.py @@ -73,7 +73,7 @@ def test_run_command_recursive_search_denies_bounded_allows(tmp_path, monkeypatc for cmd in ( f'powershell -NoProfile -Command "Get-ChildItem -Path \'{w}\' -Recurse -Include *.md | Select-String -Pattern x"', f'rg -n "x" "{w}" -l', - f'Select-String -Path "{w}\\*.md" -Pattern x', + f'Select-String -Path "{os.path.join(w, "*.md")}" -Pattern x', # OS separator: a backslash is a file name on POSIX f'grep -rn x "{w}"', ): out = _invoke(_tool("run_command", {"CommandLine": cmd}), tmp_path, monkeypatch)