mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-07 17:28:23 +01:00
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:
1 parent
e399cf69e2
commit
7637f604a4
13 files changed
+228
-77
No files matched your search
@@ -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.
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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):
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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' },
|
||||
};
|
||||
Reference in new issue
Block a user