fix: design briefs and approval feedback travel as agent-written files

Design briefs went into $D variants/generate/check as a double-quoted
argument, and approval feedback was spliced into a single-quoted echo of JSON,
so a quote, backtick or $(...) in either broke the command or ran. The agent
now writes each brief into a private .gstack/tmp file and the block passes
--brief "$(cat "$BRIEF_FILE")" (the CLI's --brief-file expects a JSON
brief, so plain text uses --brief). variants and the later check reuse the
same brief file. approved.json is built with jq -n from --arg values and
--rawfile feedback, so the feedback is data. The office-hours mockup flow
creates its brief and feedback files in one block.

design-approval-flow now writes the feedback file (with quotes, backticks and
$(...) in it) instead of substituting <FEEDBACK>, and checks the file is
removed after the save.

Co-authored-by: dgrant <dgrant@users.noreply.github.com>
This commit is contained in:
garrytananddgrant committed 2026-10-06 20:57:02 +00:00
1 parent e399cf69e2
commit 7637f604a4
13 files changed
+228 -77

No files matched your search

+4 -4
View File
@@ -561,14 +561,14 @@ For interactive feedback, use `compare --serve` and its printed HTTP URL; openin
If `DESIGN_READY`: the design binary is available for visual mockup generation.
Commands:
- `$D generate --brief "..." --output /path.png` — generate a single mockup (prints `outputPath`)
- `$D variants --brief "..." --count 3 --output-dir /path/` — generate N style variants (prints `paths`)
- `$D generate --brief "$(cat "$BRIEF_FILE")" --output /path.png` — generate a single mockup (prints `outputPath`)
- `$D variants --brief "$(cat "$BRIEF_FILE")" --count 3 --output-dir /path/` — generate N style variants (prints `paths`)
- `$D compare --images-file /path/board-images.json --output /path/board.html --serve` — comparison board + HTTP server
- `$D serve --html /path/board.html` — serve comparison board and collect feedback via HTTP
- `$D check --image /path.png --brief "..."` — vision quality gate
- `$D check --image /path.png --brief "$(cat "$BRIEF_FILE")"` — vision quality gate
- `$D iterate --session /path/session.json --feedback "..." --output /path.png` — iterate
Image commands never overwrite (a taken name gets `-2`) and always print JSON (`requested`, `saved`, `failures`); exit 0 ready, 2 nothing saved, 3 stopped after saving some. Capture without `set -e`: `_OUT=$($D ...); _RC=$?`.
Image commands never overwrite (a taken name gets `-2`) and always print JSON (`requested`, `saved`, `failures`); exit 0 ready, 2 nothing saved, 3 stopped after saving some. Capture without `set -e`: `_OUT=$($D ...); _RC=$?`. Briefs travel as files, never inline.
- `$D extract --image /absolute/path.png` — print tokens and automatically update DESIGN.md in the current Git repository; no read-only flag
`generate` returns `sessionFile`; `iterate` requires that existing session. `variants` returns `paths` but creates no session: regenerate with an updated brief instead.
@@ -303,20 +303,35 @@ mkdir -p "$_DESIGN_DIR"
echo "DESIGN_DIR: $_DESIGN_DIR"
```
Brief: Phase 3 aesthetic/colors/type/spacing/layout plus Phase 1 product context:
Brief: Phase 3 aesthetic/colors/type/spacing/layout plus Phase 1 product context, written into a private file:
```bash
_OUT=$($D variants --brief "<product name: [name]. Product type: [type]. Aesthetic: [direction]. Colors: primary [hex], secondary [hex], neutrals [range]. Typography: display [font], body [font]. Layout: [approach]. Show a realistic [page type] screen with [specific content for this product].>" --count 3 --output-dir "$_DESIGN_DIR/"); _RC=$?
_GT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.gstack/tmp"
mkdir -p "$_GT" && chmod 700 "$_GT" || { echo "Not sent: cannot create $_GT for the text file." >&2; exit 1; }
_EX=$(git rev-parse --git-path info/exclude 2>/dev/null) && mkdir -p "$(dirname "$_EX")" && { grep -qxF '/.gstack/tmp/' "$_EX" 2>/dev/null || echo '/.gstack/tmp/' >> "$_EX"; }
BRIEF_FILE=$(mktemp "$_GT/brief.XXXXXX") && echo "BRIEF_FILE: $BRIEF_FILE (name: ${BRIEF_FILE##*/})"
```
Write the text into each printed file with your file-write tool (Claude Code's Write tool needs a Read of the empty file first), exactly as it should appear. The text never goes into a shell command, heredoc or quoted argument. If a write fails or is refused, do not send: print the cause, the file path and the command below for sending by hand.
Brief shape: `Product name: [name]. Product type: [type]. Aesthetic: [direction]. Colors: primary [hex], secondary [hex], neutrals [range]. Typography: display [font], body [font]. Layout: [approach]. Show a realistic [page type] screen with [specific content for this product].` Then substitute the printed name for `<brief-file-name>`:
```bash
BRIEF_FILE="$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.gstack/tmp/<brief-file-name>"
[ -s "$BRIEF_FILE" ] || { echo "Not run: $BRIEF_FILE is missing or empty. Write the brief, then rerun this block." >&2; exit 1; }
_OUT=$($D variants --brief "$(cat "$BRIEF_FILE")" --count 3 --output-dir "$_DESIGN_DIR/"); _RC=$?
printf '%s\n' "$_OUT"; echo "EXIT: $_RC"
```
<!-- design:round-accounting -->
**Round accounting:** names are never overwritten (a taken one is bumped), so use only the printed `saved` paths. Tell the user how many of `requested` paid images were saved and name each `failures` entry. Exit 2 means nothing was saved: report `failures` and stop here (offer Path B or skip); run no `$D check` or board.
Run quality check on each saved path, starting with the first; never include failed variants:
Run quality check on each saved path, starting with the first, against the same brief file; never include failed variants:
```bash
$D check --image "<first path from the printed saved list>" --brief "<the original brief>"
BRIEF_FILE="$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.gstack/tmp/<brief-file-name>"
[ -s "$BRIEF_FILE" ] || { echo "Not run: $BRIEF_FILE is missing or empty. Write the brief, then rerun this block." >&2; exit 1; }
$D check --image "<first path from the printed saved list>" --brief "$(cat "$BRIEF_FILE")"
```
Read JSON, not exit code: `pass: false` means regenerate addressing `issues`, then recheck. `pass: true` with an unavailable/skipped warning is missing automated coverage; disclose it and inspect visually.
@@ -354,7 +369,7 @@ After the response, read current feedback next to the board HTML:
**Board or chat:** revisions regenerate; a final choice needs summary confirmation; skip goes to Phase 6 without a mockup. Ask if no choice/detail; never infer approval from a missing file. Submit with revision notes is a revision.
**Regenerate:**
1. Revise the brief, preserving unrelated constraints. Archive this round's feedback files so old Submit cannot approve new images (the board block does this on rebuild).
1. Revise the brief in the brief file, preserving unrelated constraints. Archive this round's feedback files so old Submit cannot approve new images (the board block does this on rebuild).
2. Run `$D variants` with the new brief (no session), with the same capture and round accounting. Re-run the quality check and visual self-gate on every new image (its printed path).
3. Rebuild with the board block above (it rewrites board-images.json), without `--serve`.
4. Reload at the saved URL (keep its per-board path; legacy uses root):
@@ -108,20 +108,28 @@ mkdir -p "$_DESIGN_DIR"
echo "DESIGN_DIR: $_DESIGN_DIR"
```
Brief: Phase 3 aesthetic/colors/type/spacing/layout plus Phase 1 product context:
Brief: Phase 3 aesthetic/colors/type/spacing/layout plus Phase 1 product context, written into a private file:
{{FREE_TEXT_FILE:BRIEF_FILE=brief}}
Brief shape: `Product name: [name]. Product type: [type]. Aesthetic: [direction]. Colors: primary [hex], secondary [hex], neutrals [range]. Typography: display [font], body [font]. Layout: [approach]. Show a realistic [page type] screen with [specific content for this product].` Then substitute the printed name for `<brief-file-name>`:
```bash
_OUT=$($D variants --brief "<product name: [name]. Product type: [type]. Aesthetic: [direction]. Colors: primary [hex], secondary [hex], neutrals [range]. Typography: display [font], body [font]. Layout: [approach]. Show a realistic [page type] screen with [specific content for this product].>" --count 3 --output-dir "$_DESIGN_DIR/"); _RC=$?
BRIEF_FILE="$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.gstack/tmp/<brief-file-name>"
[ -s "$BRIEF_FILE" ] || { echo "Not run: $BRIEF_FILE is missing or empty. Write the brief, then rerun this block." >&2; exit 1; }
_OUT=$($D variants --brief "$(cat "$BRIEF_FILE")" --count 3 --output-dir "$_DESIGN_DIR/"); _RC=$?
printf '%s\n' "$_OUT"; echo "EXIT: $_RC"
```
<!-- design:round-accounting -->
**Round accounting:** names are never overwritten (a taken one is bumped), so use only the printed `saved` paths. Tell the user how many of `requested` paid images were saved and name each `failures` entry. Exit 2 means nothing was saved: report `failures` and stop here (offer Path B or skip); run no `$D check` or board.
Run quality check on each saved path, starting with the first; never include failed variants:
Run quality check on each saved path, starting with the first, against the same brief file; never include failed variants:
```bash
$D check --image "<first path from the printed saved list>" --brief "<the original brief>"
BRIEF_FILE="$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.gstack/tmp/<brief-file-name>"
[ -s "$BRIEF_FILE" ] || { echo "Not run: $BRIEF_FILE is missing or empty. Write the brief, then rerun this block." >&2; exit 1; }
$D check --image "<first path from the printed saved list>" --brief "$(cat "$BRIEF_FILE")"
```
Read JSON, not exit code: `pass: false` means regenerate addressing `issues`, then recheck. `pass: true` with an unavailable/skipped warning is missing automated coverage; disclose it and inspect visually.
+4 -4
View File
@@ -409,14 +409,14 @@ Comparison boards are local HTML files: open them with `open file://...` on macO
If `DESIGN_READY`: the design binary is available for visual mockup generation.
Commands:
- `$D generate --brief "..." --output /path.png` — generate a single mockup (prints `outputPath`)
- `$D variants --brief "..." --count 3 --output-dir /path/` — generate N style variants (prints `paths`)
- `$D generate --brief "$(cat "$BRIEF_FILE")" --output /path.png` — generate a single mockup (prints `outputPath`)
- `$D variants --brief "$(cat "$BRIEF_FILE")" --count 3 --output-dir /path/` — generate N style variants (prints `paths`)
- `$D compare --images-file /path/board-images.json --output /path/board.html --serve` — comparison board + HTTP server
- `$D serve --html /path/board.html` — serve comparison board and collect feedback via HTTP
- `$D check --image /path.png --brief "..."` — vision quality gate
- `$D check --image /path.png --brief "$(cat "$BRIEF_FILE")"` — vision quality gate
- `$D iterate --session /path/session.json --feedback "..." --output /path.png` — iterate
Image commands never overwrite (a taken name gets `-2`) and always print JSON (`requested`, `saved`, `failures`); exit 0 ready, 2 nothing saved, 3 stopped after saving some. Capture without `set -e`: `_OUT=$($D ...); _RC=$?`.
Image commands never overwrite (a taken name gets `-2`) and always print JSON (`requested`, `saved`, `failures`); exit 0 ready, 2 nothing saved, 3 stopped after saving some. Capture without `set -e`: `_OUT=$($D ...); _RC=$?`. Briefs travel as files, never inline.
**Path rule:** Design artifacts belong in `$GSTACK_STATE_ROOT/projects/$SLUG/designs/`.
Use `bin/gstack-paths` (docs/state-root.md). Keep it even if temporary; never substitute
+20 -7
View File
@@ -747,14 +747,14 @@ Comparison boards are local HTML files: open them with `open file://...` on macO
If `DESIGN_READY`: the design binary is available for visual mockup generation.
Commands:
- `$D generate --brief "..." --output /path.png` — generate a single mockup (prints `outputPath`)
- `$D variants --brief "..." --count 3 --output-dir /path/` — generate N style variants (prints `paths`)
- `$D generate --brief "$(cat "$BRIEF_FILE")" --output /path.png` — generate a single mockup (prints `outputPath`)
- `$D variants --brief "$(cat "$BRIEF_FILE")" --count 3 --output-dir /path/` — generate N style variants (prints `paths`)
- `$D compare --images-file /path/board-images.json --output /path/board.html --serve` — comparison board + HTTP server
- `$D serve --html /path/board.html` — serve comparison board and collect feedback via HTTP
- `$D check --image /path.png --brief "..."` — vision quality gate
- `$D check --image /path.png --brief "$(cat "$BRIEF_FILE")"` — vision quality gate
- `$D iterate --session /path/session.json --feedback "..." --output /path.png` — iterate
Image commands never overwrite (a taken name gets `-2`) and always print JSON (`requested`, `saved`, `failures`); exit 0 ready, 2 nothing saved, 3 stopped after saving some. Capture without `set -e`: `_OUT=$($D ...); _RC=$?`.
Image commands never overwrite (a taken name gets `-2`) and always print JSON (`requested`, `saved`, `failures`); exit 0 ready, 2 nothing saved, 3 stopped after saving some. Capture without `set -e`: `_OUT=$($D ...); _RC=$?`. Briefs travel as files, never inline.
**Path rule:** Design artifacts belong in `$GSTACK_STATE_ROOT/projects/$SLUG/designs/`.
Use `bin/gstack-paths` (docs/state-root.md). Keep it even if temporary; never substitute
@@ -1749,11 +1749,24 @@ For each fixable finding, in impact order:
### 8a.5. Target Mockup (if DESIGN_READY)
If the gstack designer is available and the finding involves visual layout, hierarchy, or spacing (not just a CSS value fix like wrong color or font-size), generate a target mockup showing what the corrected version should look like:
If the gstack designer is available and the finding involves visual layout, hierarchy, or spacing (not just a CSS value fix like wrong color or font-size), generate a target mockup showing what the corrected version should look like. Write the description of the page or component with the finding fixed, referencing DESIGN.md constraints, into a private file:
```bash
_OUT=$($D generate --brief "<description of the page/component with the finding fixed, referencing DESIGN.md constraints>" --output "$REPORT_DIR/screenshots/finding-NNN-target.png"); _RC=$?
printf '%s\n' "$_OUT"; echo "EXIT: $_RC"
_GT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.gstack/tmp"
mkdir -p "$_GT" && chmod 700 "$_GT" || { echo "Not sent: cannot create $_GT for the text file." >&2; exit 1; }
_EX=$(git rev-parse --git-path info/exclude 2>/dev/null) && mkdir -p "$(dirname "$_EX")" && { grep -qxF '/.gstack/tmp/' "$_EX" 2>/dev/null || echo '/.gstack/tmp/' >> "$_EX"; }
BRIEF_FILE=$(mktemp "$_GT/brief.XXXXXX") && echo "BRIEF_FILE: $BRIEF_FILE (name: ${BRIEF_FILE##*/})"
```
Write the text into each printed file with your file-write tool (Claude Code's Write tool needs a Read of the empty file first), exactly as it should appear. The text never goes into a shell command, heredoc or quoted argument. If a write fails or is refused, do not send: print the cause, the file path and the command below for sending by hand.
Then substitute the printed name for `<brief-file-name>`:
```bash
BRIEF_FILE="$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.gstack/tmp/<brief-file-name>"
[ -s "$BRIEF_FILE" ] || { echo "Not run: $BRIEF_FILE is missing or empty. Write the brief, then rerun this block." >&2; exit 1; }
_OUT=$($D generate --brief "$(cat "$BRIEF_FILE")" --output "$REPORT_DIR/screenshots/finding-NNN-target.png"); _RC=$?
printf '%s\n' "$_OUT"; echo "EXIT: $_RC"; [ "$_RC" != 0 ] || rm -f "$BRIEF_FILE"
```
Record the printed `outputPath` for this finding (images are never overwritten, so it may be bumped); later steps use that path. Exit 2: no mockup was saved; skip it for this finding.
+9 -3
View File
@@ -176,11 +176,17 @@ For each fixable finding, in impact order:
### 8a.5. Target Mockup (if DESIGN_READY)
If the gstack designer is available and the finding involves visual layout, hierarchy, or spacing (not just a CSS value fix like wrong color or font-size), generate a target mockup showing what the corrected version should look like:
If the gstack designer is available and the finding involves visual layout, hierarchy, or spacing (not just a CSS value fix like wrong color or font-size), generate a target mockup showing what the corrected version should look like. Write the description of the page or component with the finding fixed, referencing DESIGN.md constraints, into a private file:
{{FREE_TEXT_FILE:BRIEF_FILE=brief}}
Then substitute the printed name for `<brief-file-name>`:
```bash
_OUT=$($D generate --brief "<description of the page/component with the finding fixed, referencing DESIGN.md constraints>" --output "$REPORT_DIR/screenshots/finding-NNN-target.png"); _RC=$?
printf '%s\n' "$_OUT"; echo "EXIT: $_RC"
BRIEF_FILE="$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.gstack/tmp/<brief-file-name>"
[ -s "$BRIEF_FILE" ] || { echo "Not run: $BRIEF_FILE is missing or empty. Write the brief, then rerun this block." >&2; exit 1; }
_OUT=$($D generate --brief "$(cat "$BRIEF_FILE")" --output "$REPORT_DIR/screenshots/finding-NNN-target.png"); _RC=$?
printf '%s\n' "$_OUT"; echo "EXIT: $_RC"; [ "$_RC" != 0 ] || rm -f "$BRIEF_FILE"
```
Record the printed `outputPath` for this finding (images are never overwritten, so it may be bumped); later steps use that path. Exit 2: no mockup was saved; skip it for this finding.
+17 -6
View File
@@ -419,14 +419,14 @@ Comparison boards are local HTML files: open them with `open file://...` on macO
If `DESIGN_READY`: the design binary is available for visual mockup generation.
Commands:
- `$D generate --brief "..." --output /path.png` — generate a single mockup (prints `outputPath`)
- `$D variants --brief "..." --count 3 --output-dir /path/` — generate N style variants (prints `paths`)
- `$D generate --brief "$(cat "$BRIEF_FILE")" --output /path.png` — generate a single mockup (prints `outputPath`)
- `$D variants --brief "$(cat "$BRIEF_FILE")" --count 3 --output-dir /path/` — generate N style variants (prints `paths`)
- `$D compare --images-file /path/board-images.json --output /path/board.html --serve` — comparison board + HTTP server
- `$D serve --html /path/board.html` — serve comparison board and collect feedback via HTTP
- `$D check --image /path.png --brief "..."` — vision quality gate
- `$D check --image /path.png --brief "$(cat "$BRIEF_FILE")"` — vision quality gate
- `$D iterate --session /path/session.json --feedback "..." --output /path.png` — iterate
Image commands never overwrite (a taken name gets `-2`) and always print JSON (`requested`, `saved`, `failures`); exit 0 ready, 2 nothing saved, 3 stopped after saving some. Capture without `set -e`: `_OUT=$($D ...); _RC=$?`.
Image commands never overwrite (a taken name gets `-2`) and always print JSON (`requested`, `saved`, `failures`); exit 0 ready, 2 nothing saved, 3 stopped after saving some. Capture without `set -e`: `_OUT=$($D ...); _RC=$?`. Briefs travel as files, never inline.
**Path rule:** Design artifacts belong in `$GSTACK_STATE_ROOT/projects/$SLUG/designs/`.
Use `bin/gstack-paths` (docs/state-root.md). Keep it even if temporary; never substitute
@@ -836,12 +836,23 @@ Is this right?"
Use AskUserQuestion to verify before proceeding.
**Save the approved choice.** Map the confirmed letter through this board's `board-images.json` (never the directory listing) and save it:
**Save the approved choice.** Write the user's feedback (`none` when they gave none) into a private file:
```bash
_GT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.gstack/tmp"
mkdir -p "$_GT" && chmod 700 "$_GT" || { echo "Not sent: cannot create $_GT for the text file." >&2; exit 1; }
_EX=$(git rev-parse --git-path info/exclude 2>/dev/null) && mkdir -p "$(dirname "$_EX")" && { grep -qxF '/.gstack/tmp/' "$_EX" 2>/dev/null || echo '/.gstack/tmp/' >> "$_EX"; }
FEEDBACK_FILE=$(mktemp "$_GT/feedback.XXXXXX") && echo "FEEDBACK_FILE: $FEEDBACK_FILE (name: ${FEEDBACK_FILE##*/})"
```
Write the text into each printed file with your file-write tool (Claude Code's Write tool needs a Read of the empty file first), exactly as it should appear. The text never goes into a shell command, heredoc or quoted argument. If a write fails or is refused, do not send: print the cause, the file path and the command below for sending by hand. Then map the confirmed letter through this board's `board-images.json` (never the directory listing) and save it, substituting the printed name for `<feedback-file-name>`:
```bash
_IMG=$(jq -r --arg v "<VARIANT>" '.[($v | explode[0]) - 65] // empty' "$_DESIGN_DIR/board-images.json")
if [ -n "$_IMG" ]; then
echo '{"approved_variant":"<VARIANT>","approved_path":"'"$(basename "$_IMG")"'","feedback":"<FEEDBACK>","date":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","screen":"<SCREEN>","branch":"'$(git branch --show-current 2>/dev/null)'"}' > "$_DESIGN_DIR/approved.json"
FEEDBACK_FILE="$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.gstack/tmp/<feedback-file-name>"
[ -s "$FEEDBACK_FILE" ] || { echo "Not saved: $FEEDBACK_FILE is missing or empty. Write the feedback, then rerun this block." >&2; exit 1; }
jq -n --arg approved_variant "<VARIANT>" --arg approved_path "$(basename "$_IMG")" --rawfile feedback "$FEEDBACK_FILE" --arg date "$(date -u +%Y-%m-%dT%H:%M:%SZ)" --arg screen "<SCREEN>" --arg branch "$(git branch --show-current 2>/dev/null)" '$ARGS.named | .feedback |= rtrimstr("\n")' > "$_DESIGN_DIR/approved.json" && rm -f "$FEEDBACK_FILE"
echo "APPROVED_IMAGE: $_IMG"
else
echo "NO_BOARD_IMAGE: <VARIANT> is not on this board; reselect from the board"
+21 -7
View File
@@ -995,12 +995,24 @@ echo "DESIGN_DIR: $_DESIGN_DIR"
**Step 2: Construct the design brief**
Read DESIGN.md if it exists — use it to constrain the visual style. If no DESIGN.md,
explore wide across diverse directions.
**Step 3: Generate 3 variants**
explore wide across diverse directions. The brief, and the user's feedback in Step 6, go in private files:
```bash
_OUT=$($D variants --brief "<assembled brief>" --count 3 --output-dir "$_DESIGN_DIR/"); _RC=$?
_GT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.gstack/tmp"
mkdir -p "$_GT" && chmod 700 "$_GT" || { echo "Not sent: cannot create $_GT for the text file." >&2; exit 1; }
_EX=$(git rev-parse --git-path info/exclude 2>/dev/null) && mkdir -p "$(dirname "$_EX")" && { grep -qxF '/.gstack/tmp/' "$_EX" 2>/dev/null || echo '/.gstack/tmp/' >> "$_EX"; }
BRIEF_FILE=$(mktemp "$_GT/brief.XXXXXX") && echo "BRIEF_FILE: $BRIEF_FILE (name: ${BRIEF_FILE##*/})"
FEEDBACK_FILE=$(mktemp "$_GT/feedback.XXXXXX") && echo "FEEDBACK_FILE: $FEEDBACK_FILE (name: ${FEEDBACK_FILE##*/})"
```
Write the text into each printed file with your file-write tool (Claude Code's Write tool needs a Read of the empty file first), exactly as it should appear. The text never goes into a shell command, heredoc or quoted argument. If a write fails or is refused, do not send: print the cause, the file path and the command below for sending by hand.
**Step 3: Generate 3 variants** (substitute the printed name for `<brief-file-name>`)
```bash
BRIEF_FILE="$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.gstack/tmp/<brief-file-name>"
[ -s "$BRIEF_FILE" ] || { echo "Not run: $BRIEF_FILE is missing or empty. Write the brief, then rerun this block." >&2; exit 1; }
_OUT=$($D variants --brief "$(cat "$BRIEF_FILE")" --count 3 --output-dir "$_DESIGN_DIR/"); _RC=$?
printf '%s\n' "$_OUT"; echo "EXIT: $_RC"
```
@@ -1036,7 +1048,7 @@ with Read and fall back to AskUserQuestion: "Which variant do you prefer? Any fe
If the JSON contains `"regenerated": true`:
1. Read `regenerateAction` (or `remixSpec` for remix requests)
2. Generate new variants with `$D iterate` or `$D variants` using updated brief (capture and round accounting as in Step 3)
2. Generate new variants with `$D iterate` or `$D variants` using the updated brief, rewritten into the brief file (capture and round accounting as in Step 3)
3. Rebuild the board with Step 4's board block, without `--serve`
4. POST the new HTML to the running board. Parse the board URL from stderr
(`BOARD_URL: http://127.0.0.1:N/boards/<id>/` — the daemon path) or fall
@@ -1049,12 +1061,14 @@ If `"regenerated": false`: proceed with the approved variant.
**Step 6: Save approved choice**
Map the confirmed letter through this board's `board-images.json` (never the directory listing) and save it:
Write the user's feedback (`none` when they gave none) into the feedback file from Step 2, then map the confirmed letter through this board's `board-images.json` (never the directory listing) and save it, substituting the printed name for `<feedback-file-name>`:
```bash
_IMG=$(jq -r --arg v "<VARIANT>" '.[($v | explode[0]) - 65] // empty' "$_DESIGN_DIR/board-images.json")
if [ -n "$_IMG" ]; then
echo '{"approved_variant":"<VARIANT>","approved_path":"'"$(basename "$_IMG")"'","feedback":"<FEEDBACK>","date":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","screen":"mockup","branch":"'$(git branch --show-current 2>/dev/null)'"}' > "$_DESIGN_DIR/approved.json"
FEEDBACK_FILE="$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.gstack/tmp/<feedback-file-name>"
[ -s "$FEEDBACK_FILE" ] || { echo "Not saved: $FEEDBACK_FILE is missing or empty. Write the feedback, then rerun this block." >&2; exit 1; }
jq -n --arg approved_variant "<VARIANT>" --arg approved_path "$(basename "$_IMG")" --rawfile feedback "$FEEDBACK_FILE" --arg date "$(date -u +%Y-%m-%dT%H:%M:%SZ)" --arg screen "mockup" --arg branch "$(git branch --show-current 2>/dev/null)" '$ARGS.named | .feedback |= rtrimstr("\n")' > "$_DESIGN_DIR/approved.json" && rm -f "$FEEDBACK_FILE"
echo "APPROVED_IMAGE: $_IMG"
else
echo "NO_BOARD_IMAGE: <VARIANT> is not on this board; reselect from the board"
+53 -13
View File
@@ -664,14 +664,14 @@ Comparison boards are local HTML files: open them with `open file://...` on macO
If `DESIGN_READY`: the design binary is available for visual mockup generation.
Commands:
- `$D generate --brief "..." --output /path.png` — generate a single mockup (prints `outputPath`)
- `$D variants --brief "..." --count 3 --output-dir /path/` — generate N style variants (prints `paths`)
- `$D generate --brief "$(cat "$BRIEF_FILE")" --output /path.png` — generate a single mockup (prints `outputPath`)
- `$D variants --brief "$(cat "$BRIEF_FILE")" --count 3 --output-dir /path/` — generate N style variants (prints `paths`)
- `$D compare --images-file /path/board-images.json --output /path/board.html --serve` — comparison board + HTTP server
- `$D serve --html /path/board.html` — serve comparison board and collect feedback via HTTP
- `$D check --image /path.png --brief "..."` — vision quality gate
- `$D check --image /path.png --brief "$(cat "$BRIEF_FILE")"` — vision quality gate
- `$D iterate --session /path/session.json --feedback "..." --output /path.png` — iterate
Image commands never overwrite (a taken name gets `-2`) and always print JSON (`requested`, `saved`, `failures`); exit 0 ready, 2 nothing saved, 3 stopped after saving some. Capture without `set -e`: `_OUT=$($D ...); _RC=$?`.
Image commands never overwrite (a taken name gets `-2`) and always print JSON (`requested`, `saved`, `failures`); exit 0 ready, 2 nothing saved, 3 stopped after saving some. Capture without `set -e`: `_OUT=$($D ...); _RC=$?`. Briefs travel as files, never inline.
**Path rule:** Design artifacts belong in `$GSTACK_STATE_ROOT/projects/$SLUG/designs/`.
Use `bin/gstack-paths` (docs/state-root.md). Keep it even if temporary; never substitute
@@ -788,20 +788,35 @@ Replace `<screen-name>` with a descriptive kebab-case name (e.g., `homepage-vari
**Generate mockups one screen at a time in this skill.** The inline review flow generates
fewer variants and benefits from sequential control; /design-shotgun is the parallel flow.
For each UI screen/section in scope, construct a design brief from the plan's description (and DESIGN.md if present) and generate variants:
For each UI screen/section in scope, construct a design brief from the plan's description (and DESIGN.md if present) and write it into a private file:
```bash
_OUT=$($D variants --brief "<description assembled from plan + DESIGN.md constraints>" --count 3 --output-dir "$_DESIGN_DIR/"); _RC=$?
_GT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.gstack/tmp"
mkdir -p "$_GT" && chmod 700 "$_GT" || { echo "Not sent: cannot create $_GT for the text file." >&2; exit 1; }
_EX=$(git rev-parse --git-path info/exclude 2>/dev/null) && mkdir -p "$(dirname "$_EX")" && { grep -qxF '/.gstack/tmp/' "$_EX" 2>/dev/null || echo '/.gstack/tmp/' >> "$_EX"; }
BRIEF_FILE=$(mktemp "$_GT/brief.XXXXXX") && echo "BRIEF_FILE: $BRIEF_FILE (name: ${BRIEF_FILE##*/})"
```
Write the text into each printed file with your file-write tool (Claude Code's Write tool needs a Read of the empty file first), exactly as it should appear. The text never goes into a shell command, heredoc or quoted argument. If a write fails or is refused, do not send: print the cause, the file path and the command below for sending by hand.
Then generate variants, substituting the printed name for `<brief-file-name>`:
```bash
BRIEF_FILE="$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.gstack/tmp/<brief-file-name>"
[ -s "$BRIEF_FILE" ] || { echo "Not run: $BRIEF_FILE is missing or empty. Write the brief, then rerun this block." >&2; exit 1; }
_OUT=$($D variants --brief "$(cat "$BRIEF_FILE")" --count 3 --output-dir "$_DESIGN_DIR/"); _RC=$?
printf '%s\n' "$_OUT"; echo "EXIT: $_RC"
```
<!-- design:round-accounting -->
**Round accounting (before any check or board):** `$D` never overwrites, so a taken name is bumped (for example `-2`); use only the printed `saved` paths. Tell the user how many of `requested` paid images were saved and name each `failures` entry. Exit 0: continue with `saved`. Exit 2: nothing was saved; report `failures` and stop: no `$D check`, no board.
Then run a cross-model quality check on each saved path, starting with the first:
Then run a cross-model quality check on each saved path, starting with the first, against the same brief file:
```bash
$D check --image "<first path from the printed saved list>" --brief "<the original brief>"
BRIEF_FILE="$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.gstack/tmp/<brief-file-name>"
[ -s "$BRIEF_FILE" ] || { echo "Not run: $BRIEF_FILE is missing or empty. Write the brief, then rerun this block." >&2; exit 1; }
$D check --image "<first path from the printed saved list>" --brief "$(cat "$BRIEF_FILE")"
```
Flag any variants that fail the quality check. Offer to regenerate failures.
@@ -914,12 +929,23 @@ Is this right?"
Use AskUserQuestion to verify before proceeding.
**Save the approved choice.** Map the confirmed letter through this board's `board-images.json` (never the directory listing) and save it:
**Save the approved choice.** Write the user's feedback (`none` when they gave none) into a private file:
```bash
_GT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.gstack/tmp"
mkdir -p "$_GT" && chmod 700 "$_GT" || { echo "Not sent: cannot create $_GT for the text file." >&2; exit 1; }
_EX=$(git rev-parse --git-path info/exclude 2>/dev/null) && mkdir -p "$(dirname "$_EX")" && { grep -qxF '/.gstack/tmp/' "$_EX" 2>/dev/null || echo '/.gstack/tmp/' >> "$_EX"; }
FEEDBACK_FILE=$(mktemp "$_GT/feedback.XXXXXX") && echo "FEEDBACK_FILE: $FEEDBACK_FILE (name: ${FEEDBACK_FILE##*/})"
```
Write the text into each printed file with your file-write tool (Claude Code's Write tool needs a Read of the empty file first), exactly as it should appear. The text never goes into a shell command, heredoc or quoted argument. If a write fails or is refused, do not send: print the cause, the file path and the command below for sending by hand. Then map the confirmed letter through this board's `board-images.json` (never the directory listing) and save it, substituting the printed name for `<feedback-file-name>`:
```bash
_IMG=$(jq -r --arg v "<VARIANT>" '.[($v | explode[0]) - 65] // empty' "$_DESIGN_DIR/board-images.json")
if [ -n "$_IMG" ]; then
echo '{"approved_variant":"<VARIANT>","approved_path":"'"$(basename "$_IMG")"'","feedback":"<FEEDBACK>","date":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","screen":"<SCREEN>","branch":"'$(git branch --show-current 2>/dev/null)'"}' > "$_DESIGN_DIR/approved.json"
FEEDBACK_FILE="$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.gstack/tmp/<feedback-file-name>"
[ -s "$FEEDBACK_FILE" ] || { echo "Not saved: $FEEDBACK_FILE is missing or empty. Write the feedback, then rerun this block." >&2; exit 1; }
jq -n --arg approved_variant "<VARIANT>" --arg approved_path "$(basename "$_IMG")" --rawfile feedback "$FEEDBACK_FILE" --arg date "$(date -u +%Y-%m-%dT%H:%M:%SZ)" --arg screen "<SCREEN>" --arg branch "$(git branch --show-current 2>/dev/null)" '$ARGS.named | .feedback |= rtrimstr("\n")' > "$_DESIGN_DIR/approved.json" && rm -f "$FEEDBACK_FILE"
echo "APPROVED_IMAGE: $_IMG"
else
echo "NO_BOARD_IMAGE: <VARIANT> is not on this board; reselect from the board"
@@ -1125,9 +1151,23 @@ Re-run loop: invoke /plan-design-review again → re-rate → sections at 8+ get
### "Show me what 10/10 looks like" (requires design binary)
If `DESIGN_READY` was printed during setup AND a dimension rates below 7/10,
offer to generate a visual mockup showing what the improved version would look like:
offer to generate a visual mockup showing what the improved version would look like.
Write the description of what 10/10 looks like for this dimension into a private file:
```bash
_GT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.gstack/tmp"
mkdir -p "$_GT" && chmod 700 "$_GT" || { echo "Not sent: cannot create $_GT for the text file." >&2; exit 1; }
_EX=$(git rev-parse --git-path info/exclude 2>/dev/null) && mkdir -p "$(dirname "$_EX")" && { grep -qxF '/.gstack/tmp/' "$_EX" 2>/dev/null || echo '/.gstack/tmp/' >> "$_EX"; }
BRIEF_FILE=$(mktemp "$_GT/brief.XXXXXX") && echo "BRIEF_FILE: $BRIEF_FILE (name: ${BRIEF_FILE##*/})"
```
Write the text into each printed file with your file-write tool (Claude Code's Write tool needs a Read of the empty file first), exactly as it should appear. The text never goes into a shell command, heredoc or quoted argument. If a write fails or is refused, do not send: print the cause, the file path and the command below for sending by hand.
Then substitute the printed name for `<brief-file-name>`:
```bash
BRIEF_FILE="$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.gstack/tmp/<brief-file-name>"
[ -s "$BRIEF_FILE" ] || { echo "Not run: $BRIEF_FILE is missing or empty. Write the brief, then rerun this block." >&2; exit 1; }
SLUG=$(~/.claude/skills/gstack/bin/gstack-slug --get SLUG 2>/dev/null)
GSTACK_STATE_ROOT=$(~/.claude/skills/gstack/bin/gstack-paths --get GSTACK_STATE_ROOT); : "${GSTACK_STATE_ROOT:?gstack-paths failed; reinstall with ./setup or /gstack-upgrade}"
_DESIGN_DIR="$GSTACK_STATE_ROOT/projects/$SLUG/designs/ideal-$(date +%Y%m%d)"
@@ -1135,8 +1175,8 @@ mkdir -p "$_DESIGN_DIR"
_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
D="$_ROOT/.claude/skills/gstack/design/dist/design"
[ -x "$D" ] || D=~/.claude/skills/gstack/design/dist/design
_OUT=$("$D" generate --brief "<description of what 10/10 looks like for this dimension>" --output "$_DESIGN_DIR/ideal-<dimension>.png"); _RC=$?
printf '%s\n' "$_OUT"; echo "EXIT: $_RC"
_OUT=$("$D" generate --brief "$(cat "$BRIEF_FILE")" --output "$_DESIGN_DIR/ideal-<dimension>.png"); _RC=$?
printf '%s\n' "$_OUT"; echo "EXIT: $_RC"; [ "$_RC" != 0 ] || rm -f "$BRIEF_FILE"
```
Exit 0: show the image at the printed `outputPath` (it may be bumped, never overwritten) to the user via the Read tool. Exit 2 or 3: report `failures` and continue text-only. This makes the gap between
+22 -7
View File
@@ -237,20 +237,28 @@ Replace `<screen-name>` with a descriptive kebab-case name (e.g., `homepage-vari
**Generate mockups one screen at a time in this skill.** The inline review flow generates
fewer variants and benefits from sequential control; /design-shotgun is the parallel flow.
For each UI screen/section in scope, construct a design brief from the plan's description (and DESIGN.md if present) and generate variants:
For each UI screen/section in scope, construct a design brief from the plan's description (and DESIGN.md if present) and write it into a private file:
{{FREE_TEXT_FILE:BRIEF_FILE=brief}}
Then generate variants, substituting the printed name for `<brief-file-name>`:
```bash
_OUT=$($D variants --brief "<description assembled from plan + DESIGN.md constraints>" --count 3 --output-dir "$_DESIGN_DIR/"); _RC=$?
BRIEF_FILE="$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.gstack/tmp/<brief-file-name>"
[ -s "$BRIEF_FILE" ] || { echo "Not run: $BRIEF_FILE is missing or empty. Write the brief, then rerun this block." >&2; exit 1; }
_OUT=$($D variants --brief "$(cat "$BRIEF_FILE")" --count 3 --output-dir "$_DESIGN_DIR/"); _RC=$?
printf '%s\n' "$_OUT"; echo "EXIT: $_RC"
```
<!-- design:round-accounting -->
**Round accounting (before any check or board):** `$D` never overwrites, so a taken name is bumped (for example `-2`); use only the printed `saved` paths. Tell the user how many of `requested` paid images were saved and name each `failures` entry. Exit 0: continue with `saved`. Exit 2: nothing was saved; report `failures` and stop: no `$D check`, no board.
Then run a cross-model quality check on each saved path, starting with the first:
Then run a cross-model quality check on each saved path, starting with the first, against the same brief file:
```bash
$D check --image "<first path from the printed saved list>" --brief "<the original brief>"
BRIEF_FILE="$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.gstack/tmp/<brief-file-name>"
[ -s "$BRIEF_FILE" ] || { echo "Not run: $BRIEF_FILE is missing or empty. Write the brief, then rerun this block." >&2; exit 1; }
$D check --image "<first path from the printed saved list>" --brief "$(cat "$BRIEF_FILE")"
```
Flag any variants that fail the quality check. Offer to regenerate failures.
@@ -298,9 +306,16 @@ Re-run loop: invoke /plan-design-review again → re-rate → sections at 8+ get
### "Show me what 10/10 looks like" (requires design binary)
If `DESIGN_READY` was printed during setup AND a dimension rates below 7/10,
offer to generate a visual mockup showing what the improved version would look like:
offer to generate a visual mockup showing what the improved version would look like.
Write the description of what 10/10 looks like for this dimension into a private file:
{{FREE_TEXT_FILE:BRIEF_FILE=brief}}
Then substitute the printed name for `<brief-file-name>`:
```bash
BRIEF_FILE="$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.gstack/tmp/<brief-file-name>"
[ -s "$BRIEF_FILE" ] || { echo "Not run: $BRIEF_FILE is missing or empty. Write the brief, then rerun this block." >&2; exit 1; }
SLUG=$(~/.claude/skills/gstack/bin/gstack-slug --get SLUG 2>/dev/null)
GSTACK_STATE_ROOT=$(~/.claude/skills/gstack/bin/gstack-paths --get GSTACK_STATE_ROOT); : "${GSTACK_STATE_ROOT:?gstack-paths failed; reinstall with ./setup or /gstack-upgrade}"
_DESIGN_DIR="$GSTACK_STATE_ROOT/projects/$SLUG/designs/ideal-$(date +%Y%m%d)"
@@ -308,8 +323,8 @@ mkdir -p "$_DESIGN_DIR"
_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
D="$_ROOT/.claude/skills/gstack/design/dist/design"
[ -x "$D" ] || D=~/.claude/skills/gstack/design/dist/design
_OUT=$("$D" generate --brief "<description of what 10/10 looks like for this dimension>" --output "$_DESIGN_DIR/ideal-<dimension>.png"); _RC=$?
printf '%s\n' "$_OUT"; echo "EXIT: $_RC"
_OUT=$("$D" generate --brief "$(cat "$BRIEF_FILE")" --output "$_DESIGN_DIR/ideal-<dimension>.png"); _RC=$?
printf '%s\n' "$_OUT"; echo "EXIT: $_RC"; [ "$_RC" != 0 ] || rm -f "$BRIEF_FILE"
```
Exit 0: show the image at the printed `outputPath` (it may be bumped, never overwritten) to the user via the Read tool. Exit 2 or 3: report `failures` and continue text-only. This makes the gap between
+34 -14
View File
@@ -1,5 +1,6 @@
import { outsideVoiceFailurePolicy, outsideVoiceFor, outsideVoiceInvocation, outsideVoicePreflight, outsideVoiceProvenance } from './outside-voice';
import { type TemplateContext, quoteSafePath, toShellPath } from './types';
import { FREE_TEXT_WRITE_RULE, freeTextFileBash } from './free-text-file';
import { binaryAssignment } from './runtime-root';
import { usesLazySections } from './sections';
import { AI_SLOP_BLACKLIST, OPENAI_HARD_REJECTIONS, OPENAI_LITMUS_CHECKS, CC_BACKGROUND_DEFAULT_SINCE } from './constants';
@@ -1144,6 +1145,9 @@ case "$_RC" in
esac`;
}
const DESIGN_BRIEF_FILE_CHECK = `BRIEF_FILE="$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.gstack/tmp/<brief-file-name>"
[ -s "$BRIEF_FILE" ] || { echo "Not run: $BRIEF_FILE is missing or empty. Write the brief, then rerun this block." >&2; exit 1; }`;
const DESIGN_ROUND_ACCOUNTING = `<!-- design:round-accounting -->
**Round accounting (before any board, check or inline view):** \`$D\` never overwrites; a taken name is bumped (for example \`-2\`), so use only the paths its JSON printed and carry them as literal paths into later blocks (bash blocks do not share variables). Tell the user how many of \`requested\` paid images were saved (\`saved\`) and name each \`failures\` entry. Route on the exit code: 0 continue with \`saved\`; 3 continue with \`saved\` and say the run stopped early; 2 nothing was saved, so report \`failures\` and stop: do not run \`$D compare\` or \`$D check\`.`;
@@ -1157,13 +1161,22 @@ $D compare --images-file "$_DESIGN_DIR/board-images.json" --output "$_DESIGN_DIR
\`\`\``;
}
function designApprovalBlock(screen: string): string {
return `Map the confirmed letter through this board's \`board-images.json\` (never the directory listing) and save it:
/** Feedback goes into FEEDBACK_FILE; the mockup flow creates that file with its brief file in Step 2. */
function designApprovalBlock(screen: string, createFeedbackFile = true): string {
return `${createFeedbackFile ? `Write the user's feedback (\`none\` when they gave none) into a private file:
\`\`\`bash
${freeTextFileBash([{ variable: 'FEEDBACK_FILE', stem: 'feedback' }])}
\`\`\`
${FREE_TEXT_WRITE_RULE} Then map` : `Write the user's feedback (\`none\` when they gave none) into the feedback file from Step 2, then map`} the confirmed letter through this board's \`board-images.json\` (never the directory listing) and save it, substituting the printed name for \`<feedback-file-name>\`:
\`\`\`bash
_IMG=$(jq -r --arg v "<VARIANT>" '.[($v | explode[0]) - 65] // empty' "$_DESIGN_DIR/board-images.json")
if [ -n "$_IMG" ]; then
echo '{"approved_variant":"<VARIANT>","approved_path":"'"$(basename "$_IMG")"'","feedback":"<FEEDBACK>","date":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","screen":"${screen}","branch":"'$(git branch --show-current 2>/dev/null)'"}' > "$_DESIGN_DIR/approved.json"
FEEDBACK_FILE="$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.gstack/tmp/<feedback-file-name>"
[ -s "$FEEDBACK_FILE" ] || { echo "Not saved: $FEEDBACK_FILE is missing or empty. Write the feedback, then rerun this block." >&2; exit 1; }
jq -n --arg approved_variant "<VARIANT>" --arg approved_path "$(basename "$_IMG")" --rawfile feedback "$FEEDBACK_FILE" --arg date "$(date -u +%Y-%m-%dT%H:%M:%SZ)" --arg screen "${screen}" --arg branch "$(git branch --show-current 2>/dev/null)" '$ARGS.named | .feedback |= rtrimstr("\\n")' > "$_DESIGN_DIR/approved.json" && rm -f "$FEEDBACK_FILE"
echo "APPROVED_IMAGE: $_IMG"
else
echo "NO_BOARD_IMAGE: <VARIANT> is not on this board; reselect from the board"
@@ -1190,14 +1203,14 @@ Comparison boards are local HTML files: open them with \`open file://...\` on ma
If \`DESIGN_READY\`: the design binary is available for visual mockup generation.
Commands:
- \`$D generate --brief "..." --output /path.png\` — generate a single mockup (prints \`outputPath\`)
- \`$D variants --brief "..." --count 3 --output-dir /path/\` — generate N style variants (prints \`paths\`)
- \`$D generate --brief "$(cat "$BRIEF_FILE")" --output /path.png\` — generate a single mockup (prints \`outputPath\`)
- \`$D variants --brief "$(cat "$BRIEF_FILE")" --count 3 --output-dir /path/\` — generate N style variants (prints \`paths\`)
- \`$D compare --images-file /path/board-images.json --output /path/board.html --serve\` — comparison board + HTTP server
- \`$D serve --html /path/board.html\` — serve comparison board and collect feedback via HTTP
- \`$D check --image /path.png --brief "..."\` — vision quality gate
- \`$D check --image /path.png --brief "$(cat "$BRIEF_FILE")"\` — vision quality gate
- \`$D iterate --session /path/session.json --feedback "..." --output /path.png\` — iterate
Image commands never overwrite (a taken name gets \`-2\`) and always print JSON (\`requested\`, \`saved\`, \`failures\`); exit 0 ready, 2 nothing saved, 3 stopped after saving some. Capture without \`set -e\`: \`_OUT=$($D ...); _RC=$?\`.${ctx.skillName === 'design-consultation' ? `
Image commands never overwrite (a taken name gets \`-2\`) and always print JSON (\`requested\`, \`saved\`, \`failures\`); exit 0 ready, 2 nothing saved, 3 stopped after saving some. Capture without \`set -e\`: \`_OUT=$($D ...); _RC=$?\`. Briefs travel as files, never inline.${ctx.skillName === 'design-consultation' ? `
- \`$D extract --image /absolute/path.png\` — print tokens and automatically update DESIGN.md in the current Git repository; no read-only flag
\`generate\` returns \`sessionFile\`; \`iterate\` requires that existing session. \`variants\` returns \`paths\` but creates no session: regenerate with an updated brief instead.` : ''}
@@ -1236,12 +1249,19 @@ echo "DESIGN_DIR: $_DESIGN_DIR"
**Step 2: Construct the design brief**
Read DESIGN.md if it exists — use it to constrain the visual style. If no DESIGN.md,
explore wide across diverse directions.
**Step 3: Generate 3 variants**
explore wide across diverse directions. The brief, and the user's feedback in Step 6, go in private files:
\`\`\`bash
_OUT=$($D variants --brief "<assembled brief>" --count 3 --output-dir "$_DESIGN_DIR/"); _RC=$?
${freeTextFileBash([{ variable: 'BRIEF_FILE', stem: 'brief' }, { variable: 'FEEDBACK_FILE', stem: 'feedback' }])}
\`\`\`
${FREE_TEXT_WRITE_RULE}
**Step 3: Generate 3 variants** (substitute the printed name for \`<brief-file-name>\`)
\`\`\`bash
${DESIGN_BRIEF_FILE_CHECK}
_OUT=$($D variants --brief "$(cat "$BRIEF_FILE")" --count 3 --output-dir "$_DESIGN_DIR/"); _RC=$?
printf '%s\\n' "$_OUT"; echo "EXIT: $_RC"
\`\`\`
@@ -1270,7 +1290,7 @@ with Read and fall back to AskUserQuestion: "Which variant do you prefer? Any fe
If the JSON contains \`"regenerated": true\`:
1. Read \`regenerateAction\` (or \`remixSpec\` for remix requests)
2. Generate new variants with \`$D iterate\` or \`$D variants\` using updated brief (capture and round accounting as in Step 3)
2. Generate new variants with \`$D iterate\` or \`$D variants\` using the updated brief, rewritten into the brief file (capture and round accounting as in Step 3)
3. Rebuild the board with Step 4's board block, without \`--serve\`
4. POST the new HTML to the running board. Parse the board URL from stderr
(\`BOARD_URL: http://127.0.0.1:N/boards/<id>/\` — the daemon path) or fall
@@ -1283,7 +1303,7 @@ If \`"regenerated": false\`: proceed with the approved variant.
**Step 6: Save approved choice**
${designApprovalBlock('mockup')}
${designApprovalBlock('mockup', false)}
Reference the printed \`APPROVED_IMAGE\` in the design doc or plan.`;
}
@@ -1312,7 +1332,7 @@ After the response, read current feedback next to the board HTML:
**Board or chat:** revisions regenerate; a final choice needs summary confirmation; skip goes to Phase 6 without a mockup. Ask if no choice/detail; never infer approval from a missing file. Submit with revision notes is a revision.
**Regenerate:**
1. Revise the brief, preserving unrelated constraints. Archive this round's feedback files so old Submit cannot approve new images (the board block does this on rebuild).
1. Revise the brief in the brief file, preserving unrelated constraints. Archive this round's feedback files so old Submit cannot approve new images (the board block does this on rebuild).
2. Run \`$D variants\` with the new brief (no session), with the same capture and round accounting. Re-run the quality check and visual self-gate on every new image (its printed path).
3. Rebuild with the board block above (it rewrites board-images.json), without \`--serve\`.
4. Reload at the saved URL (keep its per-board path; legacy uses root):
+10 -3
View File
@@ -22,7 +22,8 @@ function bashBlockAfter(marker: string): string {
}
const BOARD = bashBlockAfter('<!-- design:board -->');
const APPROVAL = SKILL.slice(SKILL.indexOf('**Save the approved choice.**')).match(/```bash\n([\s\S]*?)```/)![1];
// The save block follows the feedback-file block.
const APPROVAL = SKILL.slice(SKILL.indexOf('**Save the approved choice.**')).match(/```bash\n(_IMG=[\s\S]*?)```/)![1];
let dir: string;
let designDir: string;
@@ -50,7 +51,12 @@ afterEach(() => { fs.rmSync(dir, { recursive: true, force: true }); });
const sh = (script: string) => spawnSync('bash', ['-c', script], { cwd: dir, env, encoding: 'utf8', timeout: 60_000 });
const variants = () => JSON.parse(sh('"$D" variants --brief "home page" --count 2 --output-dir "$_DESIGN_DIR/"').stdout);
const approve = (letter: string) => sh(APPROVAL.replaceAll('<VARIANT>', letter).replaceAll('<FEEDBACK>', 'calmer').replaceAll('<SCREEN>', 'home'));
// CEO-12: the feedback travels in an agent-written file under the project's .gstack/tmp, never in the command.
const approve = (letter: string) => {
fs.mkdirSync(path.join(dir, '.gstack', 'tmp'), { recursive: true });
fs.writeFileSync(path.join(dir, '.gstack', 'tmp', 'feedback.fixture'), 'calmer "quoted" `not run` $(not run)\n');
return sh(APPROVAL.replaceAll('<VARIANT>', letter).replaceAll('<feedback-file-name>', 'feedback.fixture').replaceAll('<SCREEN>', 'home'));
};
// Value: protects=round two's approval comes from round two's board, never round one's stale feedback.json or the newest-file listing;
// fails_when=the board block stops archiving feedback.json or approval maps letters through the directory; why_new=eng feedback-binding; seam=none
@@ -73,7 +79,8 @@ test('round two board archives round one Submit and approval maps to the bumped
expect(approved.status, approved.stderr).toBe(0);
expect(approved.stdout).toContain(`APPROVED_IMAGE: ${path.join(designDir, 'variant-A-2.png')}`);
const record = JSON.parse(fs.readFileSync(path.join(designDir, 'approved.json'), 'utf8'));
expect(record).toMatchObject({ approved_variant: 'A', approved_path: 'variant-A-2.png', screen: 'home', feedback: 'calmer' });
expect(record).toMatchObject({ approved_variant: 'A', approved_path: 'variant-A-2.png', screen: 'home', feedback: 'calmer "quoted" `not run` $(not run)' });
expect(fs.existsSync(path.join(dir, '.gstack', 'tmp', 'feedback.fixture'))).toBe(false);
const missing = approve('C');
expect(missing.stdout).toContain('NO_BOARD_IMAGE');
+2
View File
@@ -161,4 +161,6 @@ export const IDENTIFIER_PLACEHOLDERS: Record<string, IdentifierGrammar> = {
// A3 group 1 (Aside prompts, design briefs, design approval feedback): mktemp basenames.
'<prompt-file-name>': { grammar: ID, what: 'basename of the mktemp Aside prompt file printed by the previous block' },
'<brief-file-name>': { grammar: ID, what: 'basename of the mktemp design brief file printed by the free-text block' },
'<feedback-file-name>': { grammar: ID, what: 'basename of the mktemp design feedback file printed by the previous block' },
};