diff --git a/CHANGELOG.md b/CHANGELOG.md index 4566392..2196548 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Full release notes with details on each version: [GitHub Releases](https://githu ## Unreleased +- Fix: the skill's Python-interpreter detection now accepts Homebrew `python@3.x` paths (#1586, thanks @SUDARSHANCHAUDHARI). The shebang allowlist rejected any path with a character outside `[a-zA-Z0-9/_.-]`, but Homebrew installs versioned Python under `python@3.13`, so a valid interpreter containing `@` was skipped and detection fell through to a bare `python3` that lacked graphify (every step then failed with `ModuleNotFoundError`). `@` is now allowed across all skill variants (matching the #473 hooks.py fix); injection characters are still rejected. +- Fix: `graphify merge-graphs` no longer crashes on inputs that disagree on graph type (#1606, thanks @AdrianRusan). Per-repo `graph.json` files don't always share the same `directed` / `multigraph` flags, and `compose` requires one uniform type, so a mixed set raised an unhandled `NetworkXError`. All inputs are now normalized to a plain undirected graph (which the cross-repo merged view already is) before composing. - Fix: type-reference / inheritance edge gaps closed across seven languages (all thanks @Synvoya): - Scala: `var` field declarations now emit type `references` like `val` (#1587). - PowerShell: class base types after `:` now emit `inherits` (first) / `implements` (rest), matching the C# convention (#1588). diff --git a/graphify/__main__.py b/graphify/__main__.py index 736d7be..318f983 100644 --- a/graphify/__main__.py +++ b/graphify/__main__.py @@ -3912,7 +3912,14 @@ def main() -> None: # may be a MultiGraph and another a Graph. Normalise everything to Graph # (the graphify default) by converting MultiGraphs with nx.Graph(). def _to_simple(g: "_nx.Graph") -> "_nx.Graph": - if isinstance(g, _nx.MultiGraph): + # nx.compose requires every graph to be the same type. Inputs may + # disagree on BOTH axes — directed vs undirected, and multi vs simple + # — because per-repo graph.json files are written by different extract + # paths at different times. Normalise everything to a plain undirected + # Graph (the merged cross-repo view is undirected anyway), which covers + # DiGraph / MultiGraph / MultiDiGraph. Without this a directed input + # crashed compose with "All graphs must be directed or undirected" (#1606). + if type(g) is not _nx.Graph: return _nx.Graph(g) return g merged = _nx.Graph() diff --git a/graphify/skill-agents.md b/graphify/skill-agents.md index 3735e8a..2e19931 100644 --- a/graphify/skill-agents.md +++ b/graphify/skill-agents.md @@ -77,7 +77,7 @@ fi if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') case "$_SHEBANG" in - *[!a-zA-Z0-9/_.-]*) ;; + *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; esac fi @@ -626,7 +626,7 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') - case "$PYTHON" in *[!a-zA-Z0-9/_.-]*) PYTHON="python3" ;; esac + case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" fi diff --git a/graphify/skill-aider.md b/graphify/skill-aider.md index c79b7b8..2ccf532 100644 --- a/graphify/skill-aider.md +++ b/graphify/skill-aider.md @@ -71,7 +71,7 @@ fi if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') case "$_SHEBANG" in - *[!a-zA-Z0-9/_.-]*) ;; + *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; esac fi diff --git a/graphify/skill-amp.md b/graphify/skill-amp.md index 3735e8a..2e19931 100644 --- a/graphify/skill-amp.md +++ b/graphify/skill-amp.md @@ -77,7 +77,7 @@ fi if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') case "$_SHEBANG" in - *[!a-zA-Z0-9/_.-]*) ;; + *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; esac fi @@ -626,7 +626,7 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') - case "$PYTHON" in *[!a-zA-Z0-9/_.-]*) PYTHON="python3" ;; esac + case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" fi diff --git a/graphify/skill-claw.md b/graphify/skill-claw.md index 8ec9ad2..b354243 100644 --- a/graphify/skill-claw.md +++ b/graphify/skill-claw.md @@ -77,7 +77,7 @@ fi if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') case "$_SHEBANG" in - *[!a-zA-Z0-9/_.-]*) ;; + *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; esac fi @@ -629,7 +629,7 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') - case "$PYTHON" in *[!a-zA-Z0-9/_.-]*) PYTHON="python3" ;; esac + case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" fi diff --git a/graphify/skill-codex.md b/graphify/skill-codex.md index 4a956e7..e6e411e 100644 --- a/graphify/skill-codex.md +++ b/graphify/skill-codex.md @@ -77,7 +77,7 @@ fi if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') case "$_SHEBANG" in - *[!a-zA-Z0-9/_.-]*) ;; + *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; esac fi @@ -626,7 +626,7 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') - case "$PYTHON" in *[!a-zA-Z0-9/_.-]*) PYTHON="python3" ;; esac + case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" fi diff --git a/graphify/skill-copilot.md b/graphify/skill-copilot.md index 8ec9ad2..b354243 100644 --- a/graphify/skill-copilot.md +++ b/graphify/skill-copilot.md @@ -77,7 +77,7 @@ fi if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') case "$_SHEBANG" in - *[!a-zA-Z0-9/_.-]*) ;; + *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; esac fi @@ -629,7 +629,7 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') - case "$PYTHON" in *[!a-zA-Z0-9/_.-]*) PYTHON="python3" ;; esac + case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" fi diff --git a/graphify/skill-devin.md b/graphify/skill-devin.md index 6bbbeaf..13fb37c 100644 --- a/graphify/skill-devin.md +++ b/graphify/skill-devin.md @@ -82,7 +82,7 @@ fi if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') case "$_SHEBANG" in - *[!a-zA-Z0-9/_.-]*) ;; + *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; esac fi @@ -865,7 +865,7 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') - case "$PYTHON" in *[!a-zA-Z0-9/_.-]*) PYTHON="python3" ;; esac + case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" fi diff --git a/graphify/skill-droid.md b/graphify/skill-droid.md index 480ef92..d1147b9 100644 --- a/graphify/skill-droid.md +++ b/graphify/skill-droid.md @@ -77,7 +77,7 @@ fi if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') case "$_SHEBANG" in - *[!a-zA-Z0-9/_.-]*) ;; + *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; esac fi @@ -626,7 +626,7 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') - case "$PYTHON" in *[!a-zA-Z0-9/_.-]*) PYTHON="python3" ;; esac + case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" fi diff --git a/graphify/skill-kilo.md b/graphify/skill-kilo.md index df53e14..c455781 100644 --- a/graphify/skill-kilo.md +++ b/graphify/skill-kilo.md @@ -77,7 +77,7 @@ fi if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') case "$_SHEBANG" in - *[!a-zA-Z0-9/_.-]*) ;; + *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; esac fi @@ -629,7 +629,7 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') - case "$PYTHON" in *[!a-zA-Z0-9/_.-]*) PYTHON="python3" ;; esac + case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" fi diff --git a/graphify/skill-kiro.md b/graphify/skill-kiro.md index 8ec9ad2..b354243 100644 --- a/graphify/skill-kiro.md +++ b/graphify/skill-kiro.md @@ -77,7 +77,7 @@ fi if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') case "$_SHEBANG" in - *[!a-zA-Z0-9/_.-]*) ;; + *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; esac fi @@ -629,7 +629,7 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') - case "$PYTHON" in *[!a-zA-Z0-9/_.-]*) PYTHON="python3" ;; esac + case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" fi diff --git a/graphify/skill-opencode.md b/graphify/skill-opencode.md index b023ff8..684e0e2 100644 --- a/graphify/skill-opencode.md +++ b/graphify/skill-opencode.md @@ -77,7 +77,7 @@ fi if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') case "$_SHEBANG" in - *[!a-zA-Z0-9/_.-]*) ;; + *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; esac fi @@ -621,7 +621,7 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') - case "$PYTHON" in *[!a-zA-Z0-9/_.-]*) PYTHON="python3" ;; esac + case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" fi diff --git a/graphify/skill-pi.md b/graphify/skill-pi.md index 8ec9ad2..b354243 100644 --- a/graphify/skill-pi.md +++ b/graphify/skill-pi.md @@ -77,7 +77,7 @@ fi if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') case "$_SHEBANG" in - *[!a-zA-Z0-9/_.-]*) ;; + *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; esac fi @@ -629,7 +629,7 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') - case "$PYTHON" in *[!a-zA-Z0-9/_.-]*) PYTHON="python3" ;; esac + case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" fi diff --git a/graphify/skill-trae.md b/graphify/skill-trae.md index a643daa..ac16b92 100644 --- a/graphify/skill-trae.md +++ b/graphify/skill-trae.md @@ -77,7 +77,7 @@ fi if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') case "$_SHEBANG" in - *[!a-zA-Z0-9/_.-]*) ;; + *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; esac fi @@ -627,7 +627,7 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') - case "$PYTHON" in *[!a-zA-Z0-9/_.-]*) PYTHON="python3" ;; esac + case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" fi diff --git a/graphify/skill-vscode.md b/graphify/skill-vscode.md index f0719bf..31a3352 100644 --- a/graphify/skill-vscode.md +++ b/graphify/skill-vscode.md @@ -77,7 +77,7 @@ fi if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') case "$_SHEBANG" in - *[!a-zA-Z0-9/_.-]*) ;; + *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; esac fi @@ -625,7 +625,7 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') - case "$PYTHON" in *[!a-zA-Z0-9/_.-]*) PYTHON="python3" ;; esac + case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" fi diff --git a/graphify/skill-windows.md b/graphify/skill-windows.md index f6c83fb..6ab0e02 100644 --- a/graphify/skill-windows.md +++ b/graphify/skill-windows.md @@ -651,7 +651,7 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') - case "$PYTHON" in *[!a-zA-Z0-9/_.-]*) PYTHON="python3" ;; esac + case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" fi diff --git a/graphify/skill.md b/graphify/skill.md index 8ec9ad2..b354243 100644 --- a/graphify/skill.md +++ b/graphify/skill.md @@ -77,7 +77,7 @@ fi if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') case "$_SHEBANG" in - *[!a-zA-Z0-9/_.-]*) ;; + *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; esac fi @@ -629,7 +629,7 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') - case "$PYTHON" in *[!a-zA-Z0-9/_.-]*) PYTHON="python3" ;; esac + case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" fi diff --git a/tests/test_merge_graphs_cli.py b/tests/test_merge_graphs_cli.py new file mode 100644 index 0000000..f3e0e05 --- /dev/null +++ b/tests/test_merge_graphs_cli.py @@ -0,0 +1,48 @@ +"""`graphify merge-graphs` tolerates inputs that disagree on graph type (#1606). + +Per-repo graph.json files written by different extract paths at different times +don't always agree on the `directed` / `multigraph` flags. compose requires one +uniform type, so a mixed set used to crash with an unhandled NetworkXError. The +handler now normalizes every input to a plain undirected Graph before composing. +""" +from __future__ import annotations + +import json +import subprocess +import sys +from pathlib import Path + +PYTHON = sys.executable + + +def _run(args, cwd): + return subprocess.run([PYTHON, "-m", "graphify"] + args, cwd=cwd, + capture_output=True, text=True) + + +def _write(p: Path, directed: bool, multigraph: bool, node_id: str): + p.parent.mkdir(parents=True, exist_ok=True) + p.write_text(json.dumps({ + "directed": directed, "multigraph": multigraph, "graph": {}, + "nodes": [{"id": node_id}], "links": [], + })) + + +def test_merge_graphs_mixed_directed_and_multigraph(tmp_path): + a = tmp_path / "r1" / "graphify-out" / "graph.json" + b = tmp_path / "r2" / "graphify-out" / "graph.json" + c = tmp_path / "r3" / "graphify-out" / "graph.json" + _write(a, directed=True, multigraph=False, node_id="x") # DiGraph + _write(b, directed=False, multigraph=False, node_id="y") # Graph + _write(c, directed=False, multigraph=True, node_id="z") # MultiGraph + out = tmp_path / "merged.json" + + r = _run(["merge-graphs", str(a), str(b), str(c), "--out", str(out)], tmp_path) + assert r.returncode == 0, f"merge crashed: {r.stderr}" + assert out.exists() + data = json.loads(out.read_text()) + ids = {n["id"] for n in data["nodes"]} + # every input's node survives, normalized into one undirected simple graph + assert {"r1::x", "r2::y", "r3::z"} <= ids or len(ids) == 3 + assert data.get("directed") is False + assert data.get("multigraph") is False diff --git a/tools/skillgen/expected/graphify__skill-agents.md b/tools/skillgen/expected/graphify__skill-agents.md index 3735e8a..2e19931 100644 --- a/tools/skillgen/expected/graphify__skill-agents.md +++ b/tools/skillgen/expected/graphify__skill-agents.md @@ -77,7 +77,7 @@ fi if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') case "$_SHEBANG" in - *[!a-zA-Z0-9/_.-]*) ;; + *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; esac fi @@ -626,7 +626,7 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') - case "$PYTHON" in *[!a-zA-Z0-9/_.-]*) PYTHON="python3" ;; esac + case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" fi diff --git a/tools/skillgen/expected/graphify__skill-aider.md b/tools/skillgen/expected/graphify__skill-aider.md index c79b7b8..2ccf532 100644 --- a/tools/skillgen/expected/graphify__skill-aider.md +++ b/tools/skillgen/expected/graphify__skill-aider.md @@ -71,7 +71,7 @@ fi if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') case "$_SHEBANG" in - *[!a-zA-Z0-9/_.-]*) ;; + *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; esac fi diff --git a/tools/skillgen/expected/graphify__skill-amp.md b/tools/skillgen/expected/graphify__skill-amp.md index 3735e8a..2e19931 100644 --- a/tools/skillgen/expected/graphify__skill-amp.md +++ b/tools/skillgen/expected/graphify__skill-amp.md @@ -77,7 +77,7 @@ fi if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') case "$_SHEBANG" in - *[!a-zA-Z0-9/_.-]*) ;; + *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; esac fi @@ -626,7 +626,7 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') - case "$PYTHON" in *[!a-zA-Z0-9/_.-]*) PYTHON="python3" ;; esac + case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" fi diff --git a/tools/skillgen/expected/graphify__skill-claw.md b/tools/skillgen/expected/graphify__skill-claw.md index 8ec9ad2..b354243 100644 --- a/tools/skillgen/expected/graphify__skill-claw.md +++ b/tools/skillgen/expected/graphify__skill-claw.md @@ -77,7 +77,7 @@ fi if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') case "$_SHEBANG" in - *[!a-zA-Z0-9/_.-]*) ;; + *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; esac fi @@ -629,7 +629,7 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') - case "$PYTHON" in *[!a-zA-Z0-9/_.-]*) PYTHON="python3" ;; esac + case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" fi diff --git a/tools/skillgen/expected/graphify__skill-codex.md b/tools/skillgen/expected/graphify__skill-codex.md index 4a956e7..e6e411e 100644 --- a/tools/skillgen/expected/graphify__skill-codex.md +++ b/tools/skillgen/expected/graphify__skill-codex.md @@ -77,7 +77,7 @@ fi if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') case "$_SHEBANG" in - *[!a-zA-Z0-9/_.-]*) ;; + *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; esac fi @@ -626,7 +626,7 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') - case "$PYTHON" in *[!a-zA-Z0-9/_.-]*) PYTHON="python3" ;; esac + case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" fi diff --git a/tools/skillgen/expected/graphify__skill-copilot.md b/tools/skillgen/expected/graphify__skill-copilot.md index 8ec9ad2..b354243 100644 --- a/tools/skillgen/expected/graphify__skill-copilot.md +++ b/tools/skillgen/expected/graphify__skill-copilot.md @@ -77,7 +77,7 @@ fi if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') case "$_SHEBANG" in - *[!a-zA-Z0-9/_.-]*) ;; + *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; esac fi @@ -629,7 +629,7 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') - case "$PYTHON" in *[!a-zA-Z0-9/_.-]*) PYTHON="python3" ;; esac + case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" fi diff --git a/tools/skillgen/expected/graphify__skill-devin.md b/tools/skillgen/expected/graphify__skill-devin.md index 6bbbeaf..13fb37c 100644 --- a/tools/skillgen/expected/graphify__skill-devin.md +++ b/tools/skillgen/expected/graphify__skill-devin.md @@ -82,7 +82,7 @@ fi if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') case "$_SHEBANG" in - *[!a-zA-Z0-9/_.-]*) ;; + *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; esac fi @@ -865,7 +865,7 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') - case "$PYTHON" in *[!a-zA-Z0-9/_.-]*) PYTHON="python3" ;; esac + case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" fi diff --git a/tools/skillgen/expected/graphify__skill-droid.md b/tools/skillgen/expected/graphify__skill-droid.md index 480ef92..d1147b9 100644 --- a/tools/skillgen/expected/graphify__skill-droid.md +++ b/tools/skillgen/expected/graphify__skill-droid.md @@ -77,7 +77,7 @@ fi if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') case "$_SHEBANG" in - *[!a-zA-Z0-9/_.-]*) ;; + *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; esac fi @@ -626,7 +626,7 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') - case "$PYTHON" in *[!a-zA-Z0-9/_.-]*) PYTHON="python3" ;; esac + case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" fi diff --git a/tools/skillgen/expected/graphify__skill-kilo.md b/tools/skillgen/expected/graphify__skill-kilo.md index df53e14..c455781 100644 --- a/tools/skillgen/expected/graphify__skill-kilo.md +++ b/tools/skillgen/expected/graphify__skill-kilo.md @@ -77,7 +77,7 @@ fi if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') case "$_SHEBANG" in - *[!a-zA-Z0-9/_.-]*) ;; + *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; esac fi @@ -629,7 +629,7 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') - case "$PYTHON" in *[!a-zA-Z0-9/_.-]*) PYTHON="python3" ;; esac + case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" fi diff --git a/tools/skillgen/expected/graphify__skill-kiro.md b/tools/skillgen/expected/graphify__skill-kiro.md index 8ec9ad2..b354243 100644 --- a/tools/skillgen/expected/graphify__skill-kiro.md +++ b/tools/skillgen/expected/graphify__skill-kiro.md @@ -77,7 +77,7 @@ fi if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') case "$_SHEBANG" in - *[!a-zA-Z0-9/_.-]*) ;; + *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; esac fi @@ -629,7 +629,7 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') - case "$PYTHON" in *[!a-zA-Z0-9/_.-]*) PYTHON="python3" ;; esac + case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" fi diff --git a/tools/skillgen/expected/graphify__skill-opencode.md b/tools/skillgen/expected/graphify__skill-opencode.md index b023ff8..684e0e2 100644 --- a/tools/skillgen/expected/graphify__skill-opencode.md +++ b/tools/skillgen/expected/graphify__skill-opencode.md @@ -77,7 +77,7 @@ fi if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') case "$_SHEBANG" in - *[!a-zA-Z0-9/_.-]*) ;; + *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; esac fi @@ -621,7 +621,7 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') - case "$PYTHON" in *[!a-zA-Z0-9/_.-]*) PYTHON="python3" ;; esac + case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" fi diff --git a/tools/skillgen/expected/graphify__skill-pi.md b/tools/skillgen/expected/graphify__skill-pi.md index 8ec9ad2..b354243 100644 --- a/tools/skillgen/expected/graphify__skill-pi.md +++ b/tools/skillgen/expected/graphify__skill-pi.md @@ -77,7 +77,7 @@ fi if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') case "$_SHEBANG" in - *[!a-zA-Z0-9/_.-]*) ;; + *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; esac fi @@ -629,7 +629,7 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') - case "$PYTHON" in *[!a-zA-Z0-9/_.-]*) PYTHON="python3" ;; esac + case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" fi diff --git a/tools/skillgen/expected/graphify__skill-trae.md b/tools/skillgen/expected/graphify__skill-trae.md index a643daa..ac16b92 100644 --- a/tools/skillgen/expected/graphify__skill-trae.md +++ b/tools/skillgen/expected/graphify__skill-trae.md @@ -77,7 +77,7 @@ fi if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') case "$_SHEBANG" in - *[!a-zA-Z0-9/_.-]*) ;; + *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; esac fi @@ -627,7 +627,7 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') - case "$PYTHON" in *[!a-zA-Z0-9/_.-]*) PYTHON="python3" ;; esac + case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" fi diff --git a/tools/skillgen/expected/graphify__skill-vscode.md b/tools/skillgen/expected/graphify__skill-vscode.md index f0719bf..31a3352 100644 --- a/tools/skillgen/expected/graphify__skill-vscode.md +++ b/tools/skillgen/expected/graphify__skill-vscode.md @@ -77,7 +77,7 @@ fi if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') case "$_SHEBANG" in - *[!a-zA-Z0-9/_.-]*) ;; + *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; esac fi @@ -625,7 +625,7 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') - case "$PYTHON" in *[!a-zA-Z0-9/_.-]*) PYTHON="python3" ;; esac + case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" fi diff --git a/tools/skillgen/expected/graphify__skill-windows.md b/tools/skillgen/expected/graphify__skill-windows.md index f6c83fb..6ab0e02 100644 --- a/tools/skillgen/expected/graphify__skill-windows.md +++ b/tools/skillgen/expected/graphify__skill-windows.md @@ -651,7 +651,7 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') - case "$PYTHON" in *[!a-zA-Z0-9/_.-]*) PYTHON="python3" ;; esac + case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" fi diff --git a/tools/skillgen/expected/graphify__skill.md b/tools/skillgen/expected/graphify__skill.md index 8ec9ad2..b354243 100644 --- a/tools/skillgen/expected/graphify__skill.md +++ b/tools/skillgen/expected/graphify__skill.md @@ -77,7 +77,7 @@ fi if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') case "$_SHEBANG" in - *[!a-zA-Z0-9/_.-]*) ;; + *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; esac fi @@ -629,7 +629,7 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') - case "$PYTHON" in *[!a-zA-Z0-9/_.-]*) PYTHON="python3" ;; esac + case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" fi diff --git a/tools/skillgen/fragments/core/aider.md b/tools/skillgen/fragments/core/aider.md index c79b7b8..2ccf532 100644 --- a/tools/skillgen/fragments/core/aider.md +++ b/tools/skillgen/fragments/core/aider.md @@ -71,7 +71,7 @@ fi if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') case "$_SHEBANG" in - *[!a-zA-Z0-9/_.-]*) ;; + *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; esac fi diff --git a/tools/skillgen/fragments/core/core.md b/tools/skillgen/fragments/core/core.md index a118947..0516b3a 100644 --- a/tools/skillgen/fragments/core/core.md +++ b/tools/skillgen/fragments/core/core.md @@ -564,7 +564,7 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') - case "$PYTHON" in *[!a-zA-Z0-9/_.-]*) PYTHON="python3" ;; esac + case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" fi diff --git a/tools/skillgen/fragments/core/devin.md b/tools/skillgen/fragments/core/devin.md index 6bbbeaf..13fb37c 100644 --- a/tools/skillgen/fragments/core/devin.md +++ b/tools/skillgen/fragments/core/devin.md @@ -82,7 +82,7 @@ fi if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') case "$_SHEBANG" in - *[!a-zA-Z0-9/_.-]*) ;; + *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; esac fi @@ -865,7 +865,7 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') - case "$PYTHON" in *[!a-zA-Z0-9/_.-]*) PYTHON="python3" ;; esac + case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" fi diff --git a/tools/skillgen/fragments/shell/posix.md b/tools/skillgen/fragments/shell/posix.md index be3b8ab..717bd80 100644 --- a/tools/skillgen/fragments/shell/posix.md +++ b/tools/skillgen/fragments/shell/posix.md @@ -11,7 +11,7 @@ fi if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') case "$_SHEBANG" in - *[!a-zA-Z0-9/_.-]*) ;; + *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; esac fi diff --git a/tools/skillgen/gen.py b/tools/skillgen/gen.py index 7b198d1..0082e9e 100644 --- a/tools/skillgen/gen.py +++ b/tools/skillgen/gen.py @@ -864,6 +864,19 @@ def _is_no_api_key_fix_line(line: str) -> bool: return "graphify needs no API key" in line +def _is_shebang_allowlist_fix_line(line: str) -> bool: + """Whether a line is part of the Homebrew ``python@`` shebang allowlist fix (#1586). + + The interpreter-detection guard rejected any shebang containing a character + outside ``[!a-zA-Z0-9/_.-]``, but Homebrew installs versioned Python under + ``python@3.13``, so a valid interpreter path legitimately contains ``@`` and + detection fell through to a bare ``python3`` that lacked graphify. ``@`` is now + allowed, matching the #473 hooks.py fix; injection chars are still rejected. + Both the old (removed) and new (added) allowlist forms match here. + """ + return "[!a-zA-Z0-9/_." in line + + # Every line that may differ between a rendered monolith and its pristine v8 # baseline. Each predicate documents one sanctioned change-class; a blank line is # allowed because the multi-line fix blocks insert spacing. Anything else failing @@ -878,6 +891,7 @@ _SANCTIONED_MONOLITH_DIFFS = ( _is_zero_node_guard_fix_line, _is_manifest_root_fix_line, _is_no_api_key_fix_line, + _is_shebang_allowlist_fix_line, )