diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d818561b3..bbb5629781 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ Full release notes with details on each version: [GitHub Releases](https://github.com/safishamsi/graphify/releases) +## 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). +- 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) - 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). diff --git a/README.md b/README.md index d83ea76c0c..d7ceb37d5e 100644 --- a/README.md +++ b/README.md @@ -203,7 +203,9 @@ 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). 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.
Pick your platform (20+ assistants, click to expand) @@ -750,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/always_on/agents-md.md b/graphify/always_on/agents-md.md index 6511cd1dd5..6fb9419549 100644 --- a/graphify/always_on/agents-md.md +++ b/graphify/always_on/agents-md.md @@ -5,8 +5,8 @@ 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. 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. -- 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 0fc7864147..28f540dd77 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. 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 417efeb27e..53d52fe75c 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. 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 417efeb27e..53d52fe75c 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). +- 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/kiro-steering.md b/graphify/always_on/kiro-steering.md index cb6f4543db..87a8e8deaf 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 9cb983c950..5f9181c1b6 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/cli.py b/graphify/cli.py index 6642f57d52..a702255574 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 use the recorded-interpreter query ' + 'command in the installed Graphify skill. Only grep ' 'after graphify has oriented you, or to modify/debug specific lines.' ), } @@ -30,9 +31,9 @@ "hookEventName": "PreToolUse", "additionalContext": ( 'MANDATORY: graphify-out/graph.json exists. You MUST run graphify ' - 'before reading source files. Use: `graphify query ""` ' - '(scoped subgraph), `graphify explain ""`, or ' - '`graphify path "" ""`. Only read raw files after graphify has ' + 'before reading source files. Prefer the MCP `query_graph` tool; ' + '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.' @@ -44,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.' ), } @@ -60,22 +62,43 @@ "permissionDecision": "deny", "permissionDecisionReason": ( 'graphify strict mode: this project has a fresh knowledge graph that covers ' - 'this file. 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.' ), } }, 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', '.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 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.' ) @@ -684,36 +707,94 @@ 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.""" +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: - 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())) + 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() - _SESSION_MARKER_TTL + for entry in os.scandir(d): + try: + if entry.stat().st_mtime < cutoff: + os.unlink(entry.path) + except OSError: + pass + except OSError: pass -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 +_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: - ttl = float(os.environ.get("GRAPHIFY_HOOK_STRICT_TTL", "1800")) - return (time.time() - out_path("cache", "last_query_stamp").stat().st_mtime) < ttl + 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 -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.""" +def _mark_session_denied(identity: str) -> bool: + """Atomically claim one strict block per session/agent identity. + + 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] + sid = _session_marker_id(identity) if not sid: return False try: @@ -721,16 +802,7 @@ def _mark_session_denied(session_id: 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 @@ -764,22 +836,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 + + +_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 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] + 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 + 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: + # `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(): + continue + resolved.relative_to(root) + except (ValueError, OSError, RuntimeError): + continue + found.append(resolved) + return found + + +def _ps_invokes_search(cmd_str: str) -> bool: + """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 + + +_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). @@ -831,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). @@ -865,9 +1258,45 @@ 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) - if (is_grep_tool or is_bash_search) and out_path("graph.json").is_file(): - sys.stdout.write(_SEARCH_NUDGE) + 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 + # 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 + elif is_powershell: + recursive = bool(_ps_recursive_search_targets(cmd_str, root)) + 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("\\", "/") @@ -928,13 +1357,15 @@ 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 = _hook_session_key(d) 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) @@ -1201,7 +1632,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 @@ -1318,7 +1749,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: @@ -1534,7 +1964,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, ) @@ -1696,11 +2126,10 @@ def dispatch_command(cmd: str) -> None: corpus=str(gp), nodes_returned=hops, ) - _touch_query_stamp(gp) 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 @@ -1830,7 +2259,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 0485894769..83312399b2 100644 --- a/graphify/install.py +++ b/graphify/install.py @@ -301,31 +301,61 @@ 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 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) - if " " in exe and not exe.startswith('"'): - exe = f'"{exe}"' - read_cmd = f"{exe} 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"}]}, + {"matcher": "Bash|Grep|PowerShell", + "hooks": [{"type": "command", + "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": read_cmd}]}, + "hooks": [{"type": "command", + "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", "Bash|Grep|PowerShell", "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" @@ -698,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" @@ -1042,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") @@ -1063,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(): @@ -1107,10 +1200,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. @@ -1120,7 +1210,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.""" @@ -1154,10 +1244,10 @@ 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) +- 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.""" @@ -1360,7 +1450,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; } @@ -1419,13 +1509,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 +1528,18 @@ 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 + # 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 @@ -1451,12 +1549,32 @@ 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"' + ) + # 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. - 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) @@ -1469,7 +1587,17 @@ 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": _graphify_command_windows("hook-check", project), + "timeout": 10, + } + ], } ] } @@ -1767,8 +1895,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. @@ -1787,11 +1916,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. @@ -1810,13 +1945,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(".") @@ -1969,7 +2109,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") @@ -1983,7 +2123,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 @@ -2321,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/graphify/skill-agents.md b/graphify/skill-agents.md index 190827d9ac..48689345a6 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. @@ -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 190827d9ac..48689345a6 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. @@ -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 abd2811d23..238d3ccff0 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. @@ -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 af3f723c78..9f9cbc652f 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. @@ -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 abd2811d23..238d3ccff0 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. @@ -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 fd148d485d..b0879f6c8d 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. @@ -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 3e70b050a4..0c1ffe0829 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. @@ -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`. --- @@ -709,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/graphify/skill-kiro.md b/graphify/skill-kiro.md index abd2811d23..238d3ccff0 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. @@ -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 91ced60675..7efbd23a9e 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. @@ -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 abd2811d23..238d3ccff0 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. @@ -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 050667bc20..098cb33aa2 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. @@ -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 20c7c0835c..d98332a2b0 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. @@ -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 b09ecca3c4..7f8d9d6e23 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. @@ -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 abd2811d23..238d3ccff0 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. @@ -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 56565eb782..5ce5354336 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: @@ -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')) @@ -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: @@ -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 56565eb782..5ce5354336 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: @@ -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')) @@ -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: @@ -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 56565eb782..5ce5354336 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: @@ -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')) @@ -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: @@ -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 56565eb782..5ce5354336 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: @@ -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')) @@ -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: @@ -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 56565eb782..5ce5354336 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: @@ -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')) @@ -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: @@ -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 56565eb782..5ce5354336 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: @@ -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')) @@ -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: @@ -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 56565eb782..5ce5354336 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: @@ -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')) @@ -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: @@ -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 56565eb782..5ce5354336 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: @@ -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')) @@ -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: @@ -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 56565eb782..5ce5354336 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: @@ -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')) @@ -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: @@ -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 56565eb782..5ce5354336 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: @@ -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')) @@ -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: @@ -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 56565eb782..5ce5354336 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: @@ -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')) @@ -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: @@ -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 56565eb782..5ce5354336 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: @@ -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')) @@ -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: @@ -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 56565eb782..5ce5354336 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: @@ -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')) @@ -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: @@ -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 56565eb782..5ce5354336 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: @@ -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')) @@ -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: @@ -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_claude_md.py b/tests/test_claude_md.py index cac7940834..fc410171be 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) # --------------------------------------------------------------------------- diff --git a/tests/test_codebuddy.py b/tests/test_codebuddy.py index 62a7e6c023..b77a472ccb 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 # --------------------------------------------------------------------------- @@ -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_devin.py b/tests/test_devin.py index a3bca5d05b..88c400ef89 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 aa84117ddf..017fa23089 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_agy.py b/tests/test_hook_agy.py new file mode 100644 index 0000000000..361b9f2d4c --- /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 "{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) + 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() diff --git a/tests/test_hook_guard.py b/tests/test_hook_guard.py index 869f7ccb88..32b9688828 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 # --------------------------------------------------------------------------- # @@ -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 1b34d13f98..61809a4f87 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 @@ -65,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 @@ -80,24 +81,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): @@ -147,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): @@ -189,14 +197,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_install.py b/tests/test_install.py index 7637714e5a..601dd1a9ba 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): @@ -406,19 +404,19 @@ 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) 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 '"): + 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): @@ -1211,9 +1209,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)}" @@ -1270,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) @@ -1277,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}" @@ -1291,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 @@ -1302,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)) @@ -1312,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] @@ -1322,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") @@ -1341,3 +1351,61 @@ 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 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}" + ) diff --git a/tests/test_install_strings.py b/tests/test_install_strings.py index 7e65b768e9..5e8fa1f2a4 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 e85add452f..8e66d7ad88 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 82a514d927..baa3cbf822 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): @@ -58,14 +60,14 @@ 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): 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): @@ -82,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): @@ -109,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 93c4923758..248a777c87 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): @@ -59,18 +60,24 @@ 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"] + 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(): - # #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): @@ -84,7 +91,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): @@ -102,7 +109,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): @@ -116,7 +123,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 @@ -134,7 +141,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 # --------------------------------------------------------------------------- @@ -150,7 +157,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): @@ -162,10 +169,10 @@ 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): +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 0000000000..6ffa7cc2df --- /dev/null +++ b/tests/test_search_strict.py @@ -0,0 +1,361 @@ +"""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_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 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}} + + +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_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 ( + 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 ( + {"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|PowerShell" + )["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|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) + 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 ab4cd562b4..ec4120a340 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") 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 diff --git a/tests/test_skillgen.py b/tests/test_skillgen.py index 63a6f7c173..04a117f120 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 @@ -58,6 +59,40 @@ 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 "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|update) (?=[\"<.])|^(?:# or: )?graphify (?:query|path|explain|update) (?=[\"<.])", + guidance, + re.MULTILINE, + ), 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 "query_graph" in antigravity + assert not re.search(r"`graphify (?:query|path|explain|update) (?=[\"<.])", antigravity) + + forbidden = re.compile( + r"(?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 6511cd1dd5..6fb9419549 100644 --- a/tools/skillgen/expected/graphify__always_on__agents-md.md +++ b/tools/skillgen/expected/graphify__always_on__agents-md.md @@ -5,8 +5,8 @@ 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. 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. -- 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__antigravity-rules.md b/tools/skillgen/expected/graphify__always_on__antigravity-rules.md index 0fc7864147..28f540dd77 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. 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/expected/graphify__always_on__claude-md.md b/tools/skillgen/expected/graphify__always_on__claude-md.md index 417efeb27e..53d52fe75c 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. 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 417efeb27e..53d52fe75c 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). +- 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__kiro-steering.md b/tools/skillgen/expected/graphify__always_on__kiro-steering.md index cb6f4543db..87a8e8deaf 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 9cb983c950..5f9181c1b6 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 190827d9ac..48689345a6 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. @@ -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 190827d9ac..48689345a6 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. @@ -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 abd2811d23..238d3ccff0 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. @@ -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 af3f723c78..9f9cbc652f 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. @@ -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 abd2811d23..238d3ccff0 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. @@ -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 fd148d485d..b0879f6c8d 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. @@ -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 3e70b050a4..0c1ffe0829 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. @@ -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`. --- @@ -709,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/expected/graphify__skill-kiro.md b/tools/skillgen/expected/graphify__skill-kiro.md index abd2811d23..238d3ccff0 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. @@ -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 91ced60675..7efbd23a9e 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. @@ -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 abd2811d23..238d3ccff0 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. @@ -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 050667bc20..098cb33aa2 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. @@ -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 20c7c0835c..d98332a2b0 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. @@ -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 b09ecca3c4..7f8d9d6e23 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. @@ -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 abd2811d23..238d3ccff0 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. @@ -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 56565eb782..5ce5354336 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: @@ -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')) @@ -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: @@ -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 56565eb782..5ce5354336 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: @@ -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')) @@ -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: @@ -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 56565eb782..5ce5354336 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: @@ -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')) @@ -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: @@ -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 56565eb782..5ce5354336 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: @@ -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')) @@ -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: @@ -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 56565eb782..5ce5354336 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: @@ -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')) @@ -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: @@ -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 56565eb782..5ce5354336 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: @@ -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')) @@ -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: @@ -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 56565eb782..5ce5354336 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: @@ -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')) @@ -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: @@ -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 56565eb782..5ce5354336 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: @@ -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')) @@ -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: @@ -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 56565eb782..5ce5354336 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: @@ -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')) @@ -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: @@ -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 56565eb782..5ce5354336 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: @@ -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')) @@ -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: @@ -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 56565eb782..5ce5354336 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: @@ -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')) @@ -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: @@ -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 56565eb782..5ce5354336 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: @@ -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')) @@ -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: @@ -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 56565eb782..5ce5354336 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: @@ -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')) @@ -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: @@ -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 56565eb782..5ce5354336 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: @@ -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')) @@ -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: @@ -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 6511cd1dd5..6fb9419549 100644 --- a/tools/skillgen/fragments/always-on/agents-md.md +++ b/tools/skillgen/fragments/always-on/agents-md.md @@ -5,8 +5,8 @@ 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. 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. -- 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 0fc7864147..28f540dd77 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. 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 417efeb27e..53d52fe75c 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. 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 417efeb27e..53d52fe75c 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). +- 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/kiro-steering.md b/tools/skillgen/fragments/always-on/kiro-steering.md index cb6f4543db..87a8e8deaf 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 9cb983c950..5f9181c1b6 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 c527a12563..b1f111a8d0 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/extra/kilo-rules.md b/tools/skillgen/fragments/extra/kilo-rules.md index c42881efe0..e31a8b7861 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/fragments/query-stub/default.md b/tools/skillgen/fragments/query-stub/default.md index 696796ec5c..07ee97a90e 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 56565eb782..5ce5354336 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: @@ -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')) @@ -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: @@ -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 09e19ede00..1e5b240a46 100644 --- a/tools/skillgen/gen.py +++ b/tools/skillgen/gen.py @@ -110,6 +110,63 @@ 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.", + ), + ( + "- 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": ( + ( + "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.", + ), ), }