Files
gstack/diagram/SKILL.md.tmpl
Garry Tanandcapy-ai[bot] 4c6fa89cdc v1.91.159.0 security: gstack's Bun entry points ignore the audited repo's .env and bunfig.toml (S1) (#3312)
* security: isolate every gstack Bun entry point from the audited checkout's .env and bunfig.toml

Bun auto-loads .env, .env.local and .env.<NODE_ENV> from its cwd and runs a
cwd bunfig.toml preload before the program starts. gstack runs inside the repo
it audits, so a committed .env could steer state roots, the claude binary or
spend caps, and a bunfig preload ran arbitrary code in every bin and hook.

Every bun shebang, shell and skill caller, TypeScript spawn and compiled build
now disables both (--no-env-file --config=/dev/null, bunIsolationArgs(),
--no-compile-autoload-dotenv/bunfig), with a ratchet and hostile-fixture
regressions.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: name the directly executed scripts in the isolation ratchet

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: follow the isolation flags in touchfiles, copy lists, recipe assertions and the harness pin

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: keep browse/src/bun-child-config.ts unchanged so the native cookie qualification still binds

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(windows): the empty bunfig outlives its process so the timeline hook's detached repair child can read it

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* v1.91.159.0 security: gstack's Bun entry points ignore the audited repo's .env and bunfig.toml (S1)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: plan-mode render pattern accepts the isolation flags

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-20 00:24:28 +00:00

176 lines
8.2 KiB
Cheetah

---
name: diagram
preamble-tier: 1
version: 1.0.0
description: |
Turn an English description (or mermaid source) into a diagram triplet:
the source, an editable .excalidraw file you can open on excalidraw.com,
and rendered SVG + PNG. The SVG/PNG use clean mermaid style; the
.excalidraw carries the hand-drawn aesthetic. Fully offline.
Use when asked to "make a diagram", "draw the architecture", "create a
flowchart", "diagram this", or "visualize this flow". (gstack)
allowed-tools:
- Bash
- Read
- Write
- AskUserQuestion
triggers:
- make a diagram
- draw a diagram
- create a flowchart
- diagram this
- visualize this flow
- architecture diagram
---
{{PREAMBLE}}
# /diagram — English in, editable diagram out
Every run emits a **triplet**, never a dead pixel dump:
| Artifact | What it's for |
|---|---|
| `<slug>.mmd` | the mermaid source — the LLM-friendly interchange format |
| `<slug>.excalidraw` | editable scene — open it at excalidraw.com, move a box, keep working |
| `<slug>.svg` + `<slug>.png` | crisp vector for docs + raster for chat/issues/READMEs |
Rendering is fully offline: the diagram-render bundle
(`lib/diagram-render/dist/diagram-render.html`) is one self-contained page, and
`gstack-render` opens it from a loopback server on this machine — in the Aside
browser when Aside is running, otherwise in gstack's own headless browser. Its
first output line says which (`ENGINE=aside` or `ENGINE=browse`); the triplet
is identical either way. No CDN, no network.
## Step 1 — Author the diagram
Write mermaid for the user's request. Rules:
- **Flowcharts (`graph LR`/`graph TD`) and sequence diagrams** convert to a
fully editable excalidraw scene (real boxes, arrows, and text). Prefer
`graph LR` for pipelines/flows, `graph TD` for hierarchies.
- State, class, gantt, and the other mermaid types render to SVG/PNG fine and
still get an `.excalidraw`, but the converter exports them as ONE image
element: it opens at excalidraw.com and can be moved and annotated, not
edited box by box. Tell the user that when you deliver one.
- Keep node labels short; put detail in edge labels. 5-15 nodes is the
readable range. If the user's ask needs more, split into multiple diagrams
and say why.
Decide the output directory: `./diagrams/` when the cwd is a git repo
(artifacts the user can commit), else `/tmp/gstack-diagrams/`. Derive
`<slug>` from the diagram's subject (kebab-case, ≤40 chars).
## Step 2 — Stage the render bundle (once per session)
`gstack-render` serves the bundle's directory on 127.0.0.1 for each render
(Aside refuses `file://`, and both engines get the same origin). Stage the bundle under
gstack's own render staging directory, `${TMPDIR:-/tmp}/gstack-render` (yours
alone: if that name is a symlink or another user's directory, a private
`mktemp -d` is used instead), content-addressed by bundle sha: the served
directory holds nothing but gstack bundles, and concurrent sessions or mixed
gstack versions never clobber each other.
```bash
BUNDLE=""
for c in "{{RUNTIME_ROOT}}/lib/diagram-render/dist/diagram-render.html" \
"$(git rev-parse --show-toplevel 2>/dev/null)/lib/diagram-render/dist/diagram-render.html"; do
[ -f "$c" ] && BUNDLE="$c" && break
done
[ -z "$BUNDLE" ] && echo "BUNDLE_MISSING — run: cd ~/.claude/skills/gstack && bun run build:diagram-render" && exit 1
RD="${TMPDIR:-/tmp}/gstack-render"
if [ -e "$RD" ] && { [ -L "$RD" ] || [ ! -O "$RD" ]; }; then RD=$(mktemp -d "${TMPDIR:-/tmp}/gstack-render.XXXXXX"); else mkdir -p -m 700 "$RD"; fi
SHA=$(shasum -a 256 "$BUNDLE" | cut -c1-16)
STAGED="$RD/gstack-diagram-render-$SHA.html"
[ -f "$STAGED" ] && shasum -a 256 "$STAGED" | grep -q "^$SHA" || { cp "$BUNDLE" "$STAGED.$$" && mv "$STAGED.$$" "$STAGED"; }
echo "STAGED: $STAGED"
```
Remember the `STAGED:` path — every render below opens it (it stands in for
`<staged>`). If `BUNDLE_MISSING`: stop and show the user the build command.
Do not improvise a CDN fallback — offline is the contract.
## Step 3 — Render the triplet
Write the mermaid source to `<outdir>/<slug>.mmd` first (Write tool). ONE
`gstack-render` call renders the whole triplet: it opens the staged bundle in
the browser, waits for the page to finish loading (`#done`), runs the `--eval`
expressions in order inside that page, and writes each result to the `--out`
path that follows it. The page cannot read files itself, so ship the source in
via **base64** — never splice file contents into a JS template literal
(backticks, `${`, and backslashes in the source would be interpreted and
corrupt it):
```bash
SRC=$(base64 < <outdir>/<slug>.mmd | tr -d '\n')
bun --no-env-file --config=/dev/null run ~/.claude/skills/gstack/bin/gstack-render.ts "<staged>" --wait-selector '#done' \
--eval "window.__renderMermaid('diagram-1', atob('$SRC')).then(s => (window.__svg = s))" --out <outdir>/<slug>.svg \
--eval "window.__rasterize(window.__svg, 1950)" --out <outdir>/<slug>.png \
--eval "window.__mermaidToExcalidraw(atob('$SRC')).then(j => (window.__scene = j))" --out <outdir>/<slug>.excalidraw
```
Always run all three `--eval`/`--out` pairs, whatever the diagram type. The PNG
is 1950px wide (300dpi of a 6.5in placement). Success prints one `OK <path>`
line per artifact. Read the output for two other lines:
- A hard `ERROR:` line (e.g. `ERROR: render script did not finish: Error: Parse
error on line 4: ...`) is a mermaid parse error. Nothing was copied out. Show
the error to the user, fix the `.mmd`, and retry — do not hand the user a
broken source file.
- A `PAGE_ERRORS=[...]` entry containing `Error processing Mermaid diagram`
means the excalidraw converter fell back to a single image element (Step 1's
state/class/gantt case). The triplet is complete and correct; deliver the
`.excalidraw` anyway with the note that it is not element-editable. Any OTHER
`PAGE_ERRORS` text: read it before trusting the output.
Note: `atob()` yields Latin-1; for sources with non-ASCII labels use
`decodeURIComponent(escape(atob('…')))` to recover UTF-8 exactly.
`gstack-render` picks the browser itself: Aside when it is running, otherwise
gstack's own headless browser. Only when it prints `NEEDS_ASIDE` or
`ASIDE_NOT_RUNNING` followed by `ERROR: no browser available` is there nothing
to render with — Aside (macOS 15+, aside.com) is not open and gstack's browser
is not built. Tell the user to open Aside, or to run `./setup` in the gstack
repo to build the fallback, and stop. Never install Aside for them, and never
substitute a CDN or another renderer.
## Step 4 — Show and deliver
1. Read the PNG with the Read tool so the user sees the diagram inline.
2. List the triplet paths.
3. One-line editability note: "The `.excalidraw` file opens at excalidraw.com
(File → Open) — edit it there and I can re-render from the edited scene."
4. If the user wants changes, edit the `.mmd` source and re-run Step 3 — the
source is the single source of truth.
Re-rendering an EDITED `.excalidraw` (user round-trip): load the scene file
and export without touching the mermaid — base64 transport again, since scene
JSON is full of quotes and backslashes:
```bash
SCENE=$(base64 < <outdir>/<slug>.excalidraw | tr -d '\n')
bun --no-env-file --config=/dev/null run ~/.claude/skills/gstack/bin/gstack-render.ts "<staged>" --wait-selector '#done' \
--eval "window.__excalidrawToSvg(atob('$SCENE')).then(s => (window.__svg = s))" --out <outdir>/<slug>.svg \
--eval "window.__rasterize(window.__svg, 1950)" --out <outdir>/<slug>.png
```
This path prints one benign `PAGE_ERRORS` entry — excalidraw's font subsetter
falls back from a worker to the main thread inside the single-file bundle
(`WorkerInTheMainChunkError`). The SVG/PNG are correct; ignore that one.
## Rules
- **Never ship the triplet without rendering it.** A `.mmd` file alone is not
a diagram. If rendering is impossible (bundle missing, no browser available),
say so and stop.
- For diagrams destined for a PDF: remind the user that `make-pdf` renders
` ```mermaid ` fences natively — embedding the `.mmd` in their markdown is
better than embedding the PNG.
## Completion status
- DONE — triplet delivered and shown (with the image-only note for
non-flowchart, non-sequence types).
- BLOCKED — bundle missing or no browser available; the build command, "open
Aside", or "run ./setup" surfaced.