From dcd3661b7c0835dde6d7484dd77cace209083d5b Mon Sep 17 00:00:00 2001 From: Jesse Vincent Date: Thu, 18 Jun 2026 15:15:54 -0700 Subject: [PATCH 1/2] fix(writing-skills): run graphviz without a shell in render-graphs.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `dot` availability check shelled out to `which dot`, which is not a command on Windows, so render-graphs.js reported graphviz as missing on Windows even when it was installed. Replace it with a direct `dot -V` probe via execFileSync. Also switch the SVG render call from execSync to execFileSync('dot', ['-Tsvg']). Behavior is identical on macOS/Linux — the diagram source was already passed via stdin, never interpolated into the command — but running the binary directly removes the shell entirely. --- skills/writing-skills/render-graphs.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/skills/writing-skills/render-graphs.js b/skills/writing-skills/render-graphs.js index 1d670fbb..14988965 100755 --- a/skills/writing-skills/render-graphs.js +++ b/skills/writing-skills/render-graphs.js @@ -15,7 +15,7 @@ const fs = require('fs'); const path = require('path'); -const { execSync } = require('child_process'); +const { execFileSync } = require('child_process'); function extractDotBlocks(markdown) { const blocks = []; @@ -69,7 +69,7 @@ ${bodies.join('\n\n')} function renderToSvg(dotContent) { try { - return execSync('dot -Tsvg', { + return execFileSync('dot', ['-Tsvg'], { input: dotContent, encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 @@ -107,9 +107,10 @@ function main() { process.exit(1); } - // Check if dot is available + // Check if dot is available. Run the binary directly rather than probing + // with `which`, which is not a command on Windows. try { - execSync('which dot', { encoding: 'utf-8' }); + execFileSync('dot', ['-V'], { stdio: 'ignore' }); } catch { console.error('Error: graphviz (dot) not found. Install with:'); console.error(' brew install graphviz # macOS'); From 02654f93bf723eb1e3c8c4c6e74bd338f990b1c2 Mon Sep 17 00:00:00 2001 From: Drew Ritter Date: Thu, 2 Jul 2026 14:09:14 -0700 Subject: [PATCH 2/2] test(writing-skills): cover render-graphs execution --- skills/writing-skills/render-graphs.js | 6 +- tests/writing-skills/test-render-graphs.sh | 113 +++++++++++++++++++++ 2 files changed, 116 insertions(+), 3 deletions(-) create mode 100755 tests/writing-skills/test-render-graphs.sh diff --git a/skills/writing-skills/render-graphs.js b/skills/writing-skills/render-graphs.js index 14988965..59e74b54 100755 --- a/skills/writing-skills/render-graphs.js +++ b/skills/writing-skills/render-graphs.js @@ -13,9 +13,9 @@ * Requires: graphviz (dot) installed on system */ -const fs = require('fs'); -const path = require('path'); -const { execFileSync } = require('child_process'); +import * as fs from 'fs'; +import * as path from 'path'; +import { execFileSync } from 'child_process'; function extractDotBlocks(markdown) { const blocks = []; diff --git a/tests/writing-skills/test-render-graphs.sh b/tests/writing-skills/test-render-graphs.sh new file mode 100755 index 00000000..349cd47e --- /dev/null +++ b/tests/writing-skills/test-render-graphs.sh @@ -0,0 +1,113 @@ +#!/usr/bin/env bash +set -u + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +SCRIPT_UNDER_TEST="$REPO_ROOT/skills/writing-skills/render-graphs.js" +NODE_BIN="$(command -v node)" + +PASSES=0 +FAILURES=0 +TEST_ROOT="$(mktemp -d)" + +cleanup() { + rm -rf "$TEST_ROOT" +} +trap cleanup EXIT + +pass() { + echo " [PASS] $1" + PASSES=$((PASSES + 1)) +} + +fail() { + echo " [FAIL] $1" + FAILURES=$((FAILURES + 1)) +} + +assert_contains() { + local haystack="$1" + local needle="$2" + local description="$3" + + if printf '%s' "$haystack" | grep -Fq -- "$needle"; then + pass "$description" + else + fail "$description" + echo " expected to find: $needle" + fi +} + +assert_not_contains() { + local haystack="$1" + local needle="$2" + local description="$3" + + if printf '%s' "$haystack" | grep -Fq -- "$needle"; then + fail "$description" + echo " did not expect to find: $needle" + else + pass "$description" + fi +} + +fixture="$TEST_ROOT/fixture-skill" +mkdir -p "$fixture" "$TEST_ROOT/empty-path" +cat >"$fixture/SKILL.md" <<'EOF' +--- +name: fixture-skill +--- + +# Fixture Skill + +```dot +digraph fixture_graph { + start -> end; +} +``` +EOF + +echo "Writing-skills render-graphs tests" + +missing_dot_output="$(PATH="$TEST_ROOT/empty-path" "$NODE_BIN" "$SCRIPT_UNDER_TEST" "$fixture" 2>&1)" +missing_dot_status=$? + +if [[ "$missing_dot_status" -ne 0 ]]; then + pass "missing Graphviz exits non-zero" +else + fail "missing Graphviz exits non-zero" +fi +assert_contains "$missing_dot_output" "Error: graphviz (dot) not found." "missing Graphviz reports install guidance" +assert_not_contains "$missing_dot_output" "ReferenceError: require is not defined" "script runs as an ES module" + +render_output="$("$NODE_BIN" "$SCRIPT_UNDER_TEST" "$fixture" 2>&1)" +render_status=$? + +if [[ "$render_status" -eq 0 ]]; then + pass "fixture diagram renders" +else + fail "fixture diagram renders" + printf '%s\n' "$render_output" +fi + +assert_contains "$render_output" "Found 1 diagram(s)" "reports discovered diagram" +assert_contains "$render_output" "Rendered: fixture_graph.svg" "reports rendered SVG" + +if [[ -f "$fixture/diagrams/fixture_graph.svg" ]]; then + pass "writes SVG output" +else + fail "writes SVG output" +fi + +if [[ -f "$fixture/diagrams/fixture_graph.svg" ]] && grep -Fq "