diff --git a/graphify/__main__.py b/graphify/__main__.py index 84c6901..038932a 100644 --- a/graphify/__main__.py +++ b/graphify/__main__.py @@ -1380,6 +1380,12 @@ def _uninstall_kilo_plugin(project_dir: Path) -> None: _OPENCODE_PLUGIN_JS = """\ // graphify OpenCode plugin // Injects a knowledge graph reminder before bash tool calls when the graph exists. +// +// IMPORTANT: keep the reminder string free of backticks and $(...) constructs. +// The hook prepends `echo "" && ` to the user's bash command; +// backticks inside the double-quoted echo trigger bash command substitution, +// which both corrupts tool output and silently executes the very graphify +// command we are only suggesting. Plain words render fine in opencode's TUI. import { existsSync } from "fs"; import { join } from "path"; @@ -1393,7 +1399,7 @@ export const GraphifyPlugin = async ({ directory }) => { if (input.tool === "bash") { output.args.command = - 'echo "[graphify] knowledge graph at graphify-out/. For focused questions, run \\`graphify query \\"\\"\\` (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, 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." && ' + output.args.command; reminded = true; } diff --git a/tests/test_install.py b/tests/test_install.py index a1cc939..7338809 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -599,6 +599,28 @@ def test_opencode_agents_install_writes_plugin(tmp_path): assert "tool.execute.before" in plugin.read_text() +def test_opencode_plugin_reminder_has_no_backticks(tmp_path): + """The bash reminder string must not contain backticks or $(...) (regression test for #1413). + + The plugin prepends `echo "" && ` to the user's bash command. + Backticks or $() inside the reminder trigger bash command substitution + when the echo runs, which both corrupts tool output and silently executes + the very graphify command we are only suggesting. + """ + _agents_install(tmp_path, "opencode") + plugin = tmp_path / ".opencode" / "plugins" / "graphify.js" + body = plugin.read_text() + # Extract the echoed reminder string literal between the double-quotes + # of the `output.args.command = 'echo "..." && ' +` line. + import re + + m = re.search(r'echo "([^"]*)"', body) + assert m, "echo reminder not found in plugin body" + reminder = m.group(1) + assert "`" not in reminder, f"backtick in reminder would trigger command substitution: {reminder!r}" + assert "$(" not in reminder, f"$() in reminder would trigger command substitution: {reminder!r}" + + def test_opencode_agents_install_registers_plugin_in_config(tmp_path): """opencode install registers the plugin in .opencode/opencode.json.""" _agents_install(tmp_path, "opencode")