mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-11 03:00:26 +01:00
* G1: gstack-plan-reality check opens every file:line receipt (#973) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * G1: office-hours spec reviewer checks paths and flags through a bin-enforced probe (#973) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * G2: plan-review reruns reconcile the last task list (gstack-tasks prior/supersede) (#1791) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * G2: plan reviews write task lists through gstack-tasks start/add/close The emit block shrinks to three calls: prior (now defaulting --dir and --branch, and printing how to reconcile), start (creates the empty branch-named list) and add (one validated row per task). close records a done or dropped prior task. The plan-review result codes move to lib/result-codes-plan.ts so lib/result-codes.ts stays under the module-size ratchet; generated plan-review sections shrink, so their size pins go down. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * F2: office-hours saves the sketch beside the design doc; /plan-design-review reads it Step 5 copies sketch.png/html to <PROJECT_DIR>/{user}-{branch}-sketch-{datetime}.* and writes a Wireframe: line (state-root relative) or Wireframe: none (<reason>). The design review section finds the design doc before Pass 1 and opens the wireframe, text-only when the file is missing. G4's signals line rides along in the same generated office-hours files; tests in planning-wave-prose. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * G3: plan reviews tie storage to queries, not file count (#1048) One bullet in each of plan-eng-review and plan-ceo-review: fields queried, constrained or updated on their own get explicit columns or tables; JSON suits a document read and written whole. Written fresh; the reporter's patch is not ported. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * evals: GSTA-228 planning-wave cases with branch, main and no-gstack arms planted facts (G1, behavior), data model (G3, behavior), task-list rerun (G2, rule) and closing signals (G4, behavior). GSTACK_WAVE_ARM picks the arm; only the branch arm asserts the preregistered bar, every arm writes a score row. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * changelog: GSTA-228 planning wave fragment Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * G1: the probe takes subcommand words for fixed-subcommand CLIs The planted-facts eval's one branch miss: the reviewer asked to probe "git log", the probe refused it, and git's top-level help cannot show a log flag, so the fabricated --since-commit stayed "unverified". probe --help <command> [<subcommand> [<subcommand>]] now runs <cli> <sub> --help for git, gh, docker, cargo, go, npm and uv (never node, bun, python3, make, yarn, pnpm or a gstack bin, where the word could name a script), in an empty temporary directory with pagers set to cat, falling back to -h when --help has no usage line (a minimized system's missing man page). Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * G2: task text goes through a rows file, never a command line The free suite's generated-bash lint (INV-3) refused the add command's <title>/<finding> placeholders: free text reaches a command only as a file the agent writes with its file-write tool. start now also creates an empty rows file in the repository's git-excluded .gstack/tmp and records the run; the agent writes task and disposition rows there; add validates every row, stamps phase, run, branch and commit, appends tasks to the list and dispositions to task-dispositions-<phase>-<branch>.jsonl, then supersedes the branch's earlier lists. close is gone (dispositions are rows). The new planning-wave cases get measured periodic walls in scripts/paid-test-durations.json so the periodic census still packs into 40 slices, the schema case declares the preamble sources its eng-template owners need, and the paid file count is 114. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * G2: fold start into add so each plan review emits one bash fence The rows file lives at .gstack/tmp/tasks-<phase>-rows.jsonl; add writes this run's list (empty when there are no rows), appends dispositions, deletes the rows file and supersedes the branch's earlier lists. Lowers 27 size pins. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * G2 tests: give gstack-tasks a private HOME and state root CI shard 13 home containment caught gstack-slug writing ~/.gstack/slug-cache entries from the tasks-prior fixtures. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * changelog: gstack-tasks has no start command Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * G1/G2: tighten the probe and the rows file after ship review The probe now accepts only each CLI's listed built-in subcommands (no uv run, docker run, go test, npm exec or gh extensions), refuses gstack bins that do not handle --help and never retries them with -h, and runs with a throwaway HOME and an environment without keys. gstack-tasks resolves the slug through runScriptSync (Windows) and prior deletes a rows file an unfinished run left. Adds tests for the dropped disposition and the stale rows file. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * G2: add refuses while an earlier task is neither carried nor closed Ship review (state matrix): a rerun that skipped RECONCILE silently retired open tasks. add now names each undecided <run_id>/<id> and writes nothing. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * G1/G2: fixes from the ship fix-check and Step 11 voices Probe: find the repository root without running git, drop relative and in-repository PATH entries for lookup and for the child, keep HOME and toolchain locations (rustup, mise, asdf) but no keys, drop cargo fmt/clippy (external cargo-* programs), restore planted env in the test. gstack-tasks: resolve the exclude path against the cwd (subdirectories), resolve --dir, stamp an empty branch on a detached HEAD like the aggregator, name the file from --branch, and refuse a same-second second list instead of appending. Receipts: line 0, reversed ranges, dangling links and paths through files are unresolved instead of crashing. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * changelog: probe limits and the reconcile refusal Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs: probe subcommand counts per CLI (ship docs audit) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --------- Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
283 lines
19 KiB
Markdown
283 lines
19 KiB
Markdown
<!-- AUTO-GENERATED from visual-design.md.tmpl — do not edit directly -->
|
||
<!-- Regenerate: bun run gen:skill-docs -->
|
||
## Visual Design Exploration
|
||
|
||
```bash
|
||
_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
|
||
D=""
|
||
[ -n "$_ROOT" ] && [ -x "$_ROOT/.claude/skills/gstack/design/dist/design" ] && D="$_ROOT/.claude/skills/gstack/design/dist/design"
|
||
[ -z "$D" ] && D="$HOME/.claude/skills/gstack/design/dist/design"
|
||
_DS=$("$HOME/.claude/skills/gstack/bin/gstack-paths" --get GSTACK_STATE_ROOT 2>/dev/null); _DC=${_DS:+$_DS/design-ready}; _DK=$(ls -diL "$D" 2>/dev/null)
|
||
_dt() { if command -v gtimeout >/dev/null; then gtimeout 10 "$@"; elif command -v timeout >/dev/null; then timeout 10 "$@"
|
||
elif command -v perl >/dev/null; then perl -e 'alarm(shift);exec(@ARGV)' 10 "$@"; else return 125; fi; }
|
||
_RC=0; _F="Fix: cd ${D%/design/dist/design} && ./setup"
|
||
if [ ! -x "$D" ]; then _RC=missing
|
||
elif [ ! "$_DC" -nt "$D" ] || [ "$(cat "$_DC")" != "$_DK" ]; then _dt "$D" --version >/dev/null 2>&1 </dev/null || _RC=$?
|
||
fi
|
||
case "$_RC" in
|
||
0) echo "DESIGN_READY: $D"; [ -n "$_DC" ] && echo "$_DK" > "$_DC" 2>/dev/null ;;
|
||
missing) echo "DESIGN_NOT_AVAILABLE: $D is not installed. $_F" ;;
|
||
124|142) echo "DESIGN_NOT_AVAILABLE: $D --version timed out after 10s" ;;
|
||
125) echo "DESIGN_NOT_AVAILABLE: no timeout/gtimeout/perl to bound $D" ;;
|
||
137) echo "DESIGN_NOT_AVAILABLE: $D --version exited 137 (killed at launch; on macOS usually an invalid code signature). $_F" ;;
|
||
*) echo "DESIGN_NOT_AVAILABLE: $D --version exited $_RC" ;;
|
||
esac
|
||
```
|
||
|
||
**If `DESIGN_NOT_AVAILABLE`:** Fall back to the HTML wireframe approach below
|
||
(the existing DESIGN_SKETCH section). Visual mockups require the design binary.
|
||
|
||
**If `DESIGN_READY`:** Generate visual mockup explorations for the user.
|
||
|
||
Generating visual mockups of the proposed design... (say "skip" if you don't need visuals)
|
||
|
||
**Step 1: Set up the design directory**
|
||
|
||
```bash
|
||
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/mockup-$(date +%Y%m%d)"
|
||
mkdir -p "$_DESIGN_DIR"
|
||
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. The brief, and the user's feedback in Step 6, go in private files:
|
||
|
||
```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 "Not sent: mktemp failed in $_GT." >&2; exit 1; }; echo "BRIEF_FILE: $BRIEF_FILE (name: ${BRIEF_FILE##*/})"
|
||
FEEDBACK_FILE=$(mktemp "${_GT:?}/feedback.XXXXXX") || { echo "Not sent: mktemp failed in $_GT." >&2; exit 1; }; 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"
|
||
```
|
||
|
||
This generates 3 style variations of the same brief (~40 seconds total).
|
||
|
||
<!-- 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`.
|
||
|
||
**Step 4: Show variants inline, then open comparison board**
|
||
|
||
Show each saved image to the user inline first (read the printed paths with Read tool), then
|
||
create and serve the comparison board:
|
||
|
||
<!-- design:board -->
|
||
Write this round's board images (printed paths that passed checks, in order) as a JSON array to `$_DESIGN_DIR/board-images.json` with the Write tool; board letters A, B, C follow that order. Then archive any earlier Submit so it cannot approve these images, and build the board:
|
||
|
||
```bash
|
||
[ -f "${_DESIGN_DIR:?set _DESIGN_DIR to the design dir printed above}/feedback.json" ] && mv "${_DESIGN_DIR:?}/feedback.json" "${_DESIGN_DIR:?}/feedback-$(date -u +%Y%m%dT%H%M%SZ).json"
|
||
$D compare --images-file "$_DESIGN_DIR/board-images.json" --output "$_DESIGN_DIR/design-board.html" --serve
|
||
```
|
||
|
||
This publishes the board to the design daemon, opens it in the user's default
|
||
browser, and exits; it does not wait for feedback. Read captured stderr for the
|
||
`BOARD_URL: http://127.0.0.1:N/boards/<id>/` line, then wait with AskUserQuestion:
|
||
"Review <BOARD_URL>, click Submit or Regenerate, then tell me (or paste your
|
||
preferences here)." After the answer, read `feedback.json` or
|
||
`feedback-pending.json` next to the board HTML.
|
||
|
||
If the command exits nonzero or prints no `BOARD_URL`, show each variant inline
|
||
with Read and fall back to AskUserQuestion: "Which variant do you prefer? Any feedback?"
|
||
|
||
**Step 5: Handle feedback**
|
||
|
||
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 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
|
||
back to the legacy port (`SERVE_STARTED: port=N` — only emitted under
|
||
`--no-daemon`, hits `/api/reload` root). Daemon path:
|
||
`jq -nc --arg html "$_DESIGN_DIR/design-board.html" '{html: $html}' | curl -sS -X POST "${BOARD_URL}api/reload" -H 'Content-Type: application/json' --data-binary @-`
|
||
5. Board auto-refreshes in the same tab
|
||
|
||
If `"regenerated": false`: proceed with the approved variant.
|
||
|
||
**Step 6: Save approved choice**
|
||
|
||
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
|
||
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"
|
||
fi
|
||
```
|
||
|
||
Reference the printed `APPROVED_IMAGE` in the design doc or plan.
|
||
|
||
## Visual Sketch (UI ideas only)
|
||
|
||
If the chosen approach involves user-facing UI (screens, pages, forms, dashboards,
|
||
or interactive elements), generate a rough wireframe to help the user visualize it.
|
||
If the idea is backend-only, infrastructure, or has no UI component — skip this
|
||
section silently.
|
||
|
||
**Step 1: Gather design context**
|
||
|
||
1. Check if `DESIGN.md` exists in the repo root. If it does, read it for design
|
||
system constraints (colors, typography, spacing, component patterns). Use these
|
||
constraints in the wireframe.
|
||
2. Apply core design principles:
|
||
- **Information hierarchy** — what does the user see first, second, third?
|
||
- **Interaction states** — loading, empty, error, success, partial
|
||
- **Edge case paranoia** — what if the name is 47 chars? Zero results? Network fails?
|
||
- **Subtraction default** — "as little design as possible" (Rams). Every element earns its pixels.
|
||
- **Design for trust** — every interface element builds or erodes user trust.
|
||
|
||
**Step 2: Generate wireframe HTML**
|
||
|
||
Generate a single-page HTML file with these constraints:
|
||
- **Intentionally rough aesthetic** — use system fonts, thin gray borders, no color,
|
||
hand-drawn-style elements. This is a sketch, not a polished mockup.
|
||
- Self-contained — no external dependencies, no CDN links, inline CSS only
|
||
- Show the core interaction flow (1-3 screens/states max)
|
||
- Include realistic placeholder content (not "Lorem ipsum" — use content that
|
||
matches the actual use case)
|
||
- Add HTML comments explaining design decisions
|
||
|
||
Create a private directory for it first — the renderer serves that whole directory
|
||
over loopback, so it must be yours alone and hold nothing else (never a fixed,
|
||
shared /tmp name another user could pre-create):
|
||
```bash
|
||
mktemp -d "${TMPDIR:-/tmp}/gstack-sketch.XXXXXX"
|
||
```
|
||
Write the sketch to `<that directory>/sketch.html` (Write tool).
|
||
|
||
**Step 3: Render and capture**
|
||
|
||
`gstack-render` opens the sketch in the Aside browser when it is running — otherwise
|
||
in gstack's own headless browser (its first line says which: `ENGINE=aside` or
|
||
`ENGINE=browse`) — and screenshots it:
|
||
|
||
```bash
|
||
bun --no-env-file --config=/dev/null run $HOME/.claude/skills/gstack/bin/gstack-render.ts <sketch-dir>/sketch.html --screenshot <sketch-dir>/sketch.png --width 1280
|
||
```
|
||
|
||
Only if it prints `NEEDS_ASIDE` or `ASIDE_NOT_RUNNING` followed by `ERROR: no browser
|
||
available` (Aside is not open AND gstack's own browser is not built), skip the render
|
||
step. Tell the user: "The visual sketch renders through the Aside browser (macOS 15+,
|
||
aside.com) or gstack's own browser. Open Aside, or run ./setup in the gstack repo, and
|
||
I'll render the wireframe." Never install either for them.
|
||
|
||
**Step 4: Present and iterate**
|
||
|
||
Show the screenshot to the user. Ask: "Does this feel right? Want to iterate on the layout?"
|
||
|
||
If they want changes, regenerate the HTML with their feedback and re-render.
|
||
If they approve or say "good enough," proceed.
|
||
|
||
**Step 5: Save the sketch**
|
||
|
||
Copy the temporary `sketch.png` and `sketch.html` to `<PROJECT_DIR>/{user}-{branch}-sketch-{datetime}.*`.
|
||
In "Recommended Approach" add `Wireframe: projects/<slug>/<png name>` (state-root relative), or `Wireframe: none (<reason>)`.
|
||
|
||
**Step 6: Outside design voices** (optional)
|
||
|
||
After the wireframe is approved, offer outside design perspectives:
|
||
|
||
```bash
|
||
|
||
if ! _OUTSIDE_CFG=$("$HOME/.claude/skills/gstack/bin/gstack-config" get --stored codex_reviews) || [ "$_OUTSIDE_CFG" = disabled ]; then
|
||
echo 'CODEX_MODE: disabled'
|
||
elif ( # GSTACK_ACTIVE_HOST names the harness, never the model.
|
||
if { [ -n "${CODEX_THREAD_ID:-}" ] || [ -n "${CODEX_SANDBOX:-}" ] || [ "${GSTACK_ACTIVE_HOST:-}" = codex ]; }; then
|
||
echo 'Codex outside review unavailable: harness mismatch; no outside process started. Missing coverage.' >&2
|
||
if { [ -n "${CLAUDECODE:-}" ] || [ "${GSTACK_ACTIVE_HOST:-}" = claude ]; } && { [ -n "${CODEX_THREAD_ID:-}" ] || [ -n "${CODEX_SANDBOX:-}" ] || [ "${GSTACK_ACTIVE_HOST:-}" = codex ]; }; then
|
||
echo 'Inherited harness markers conflict. Run setup --host <actual-harness> (claude or codex); do not guess a replacement provider.' >&2
|
||
else
|
||
echo 'Repair installed skills: run setup --host codex from your gstack checkout.' >&2
|
||
fi
|
||
exit 78
|
||
fi
|
||
); then
|
||
if command -v codex >/dev/null 2>&1; then echo 'CODEX_MODE: ready'; else echo 'CODEX_MODE: not_installed'; fi
|
||
else
|
||
echo 'CODEX_MODE: under_current_harness'
|
||
fi
|
||
```
|
||
|
||
The historical `CODEX_MODE` variable describes **Codex** availability here. `CODEX_MODE: disabled` or a send's `consent_missing` line: `codex_reviews` is off or unreadable; do not ask; record `outside_status: disabled`. Authentication and configured model validity are checked by the actual invocation, without overriding either. Missing/broken CLI: install or repair Codex; authentication failure: run `codex login`. Otherwise honor this caller’s opt-in/skip choice. Any non-ready outcome is missing outside coverage; follow the caller’s existing fallback. Never substitute another external provider.
|
||
|
||
If Codex is available, use AskUserQuestion:
|
||
> "Want outside design perspectives on the chosen approach? Codex proposes a visual thesis, content plan, and interaction ideas. A Claude subagent proposes an alternative aesthetic direction."
|
||
>
|
||
> A) Yes — get outside design voices
|
||
> B) No — proceed without
|
||
|
||
If user chooses A, run both independent voices below and wait for both results before synthesis. They may overlap when the host supports parallel tool calls; the native subagent call remains blocking.
|
||
|
||
1. **Codex** (via Bash, `model_reasoning_effort="medium"`):
|
||
Prompt: "For this product approach, provide: a visual thesis (one sentence — mood, material, energy), a content plan (hero → support → detail → CTA), and 2 interaction ideas that change page feel. Apply beautiful defaults: composition-first, brand-first, cardless, poster not document. Be opinionated." Include the approved product approach and wireframe source in the prepared prompt.
|
||
|
||
Write the **complete prompt and context**, including actual plan/spec/source, to a private file. Substitute its shell-quoted path for `<prepared-prompt-file>`; never interpolate user text into shell source. Request a complete design proposal ending with Recommendation: <direction> because <product-specific reason>.
|
||
|
||
```bash
|
||
# GSTACK_ACTIVE_HOST names the harness, never the model.
|
||
if { [ -n "${CODEX_THREAD_ID:-}" ] || [ -n "${CODEX_SANDBOX:-}" ] || [ "${GSTACK_ACTIVE_HOST:-}" = codex ]; }; then
|
||
echo 'Codex outside review unavailable: harness mismatch; no outside process started. Missing coverage.' >&2
|
||
if { [ -n "${CLAUDECODE:-}" ] || [ "${GSTACK_ACTIVE_HOST:-}" = claude ]; } && { [ -n "${CODEX_THREAD_ID:-}" ] || [ -n "${CODEX_SANDBOX:-}" ] || [ "${GSTACK_ACTIVE_HOST:-}" = codex ]; }; then
|
||
echo 'Inherited harness markers conflict. Run setup --host <actual-harness> (claude or codex); do not guess a replacement provider.' >&2
|
||
else
|
||
echo 'Repair installed skills: run setup --host codex from your gstack checkout.' >&2
|
||
fi
|
||
exit 78
|
||
fi
|
||
|
||
_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo 'ERROR: not in a git repo' >&2; exit 1; }
|
||
_OUTSIDE_TMP=$(mktemp -d "${TMPDIR:-/tmp}/gstack-outside.XXXXXXXX") || exit 1
|
||
trap 'rm -rf "$_OUTSIDE_TMP"' EXIT
|
||
_OUTSIDE_INPUT="$_OUTSIDE_TMP/prompt"
|
||
cat -- '<prepared-prompt-file>' >"$_OUTSIDE_INPUT" || exit 1
|
||
|
||
_CODEX_PROBE="$HOME/.claude/skills/gstack/bin/gstack-codex-probe"
|
||
_CODEX_OUT=$("$_CODEX_PROBE" select-model exec) || exit 1
|
||
_CODEX_SEL=$(echo "$_CODEX_OUT" | sed -n 's/^CODEX_SEL: //p')
|
||
_CODEX_SANDBOX_MODE=$(echo "$_CODEX_OUT" | sed -n 's/^CODEX_SANDBOX: //p')
|
||
"$_CODEX_PROBE" check-sandbox || exit 1
|
||
"$_CODEX_PROBE" show-first-use-notice
|
||
_OUTSIDE_EXIT=0
|
||
"$_CODEX_PROBE" run-with-timeout --effort medium 300 codex exec - -C "$_REPO_ROOT" -s "${_CODEX_SANDBOX_MODE:?}" -c "model=\"${_CODEX_SEL:?}\"" -c skills.include_instructions=false -c 'web_search="cached"' --json -o "$_OUTSIDE_TMP/text" <"$_OUTSIDE_INPUT" >"$_OUTSIDE_TMP/events" 2>"$_OUTSIDE_TMP/stderr" || _OUTSIDE_EXIT=$?
|
||
cat "$_OUTSIDE_TMP/text" 2>/dev/null || tail -n 20 "$_OUTSIDE_TMP/events"
|
||
|
||
cat "$_OUTSIDE_TMP/stderr" >&2 || { [ "$_OUTSIDE_EXIT" -ne 0 ] || _OUTSIDE_EXIT=1; }
|
||
_OUTSIDE_RC=0
|
||
bun --no-env-file --config=/dev/null "$HOME/.claude/skills/gstack/lib/outside-review-result.ts" --label 'Codex outside review' --exit "$_OUTSIDE_EXIT" --stderr "$_OUTSIDE_TMP/stderr" --events "$_OUTSIDE_TMP/events" proposal "$_OUTSIDE_TMP/text" || _OUTSIDE_RC=$?
|
||
case "$_OUTSIDE_RC" in
|
||
0|3) ;;
|
||
4) echo 'OUTSIDE_STATUS: unverified provider=codex host=claude'; exit 4 ;;
|
||
*) [ "$_OUTSIDE_EXIT" -ne 0 ] && exit "$_OUTSIDE_EXIT"; exit 1 ;;
|
||
esac
|
||
echo 'OUTSIDE_STATUS: completed provider=codex host=claude'
|
||
```
|
||
|
||
Use Bash `timeout: 360000`; show the full response in a `tool-output` fence. Require successful execution and valid markers. Refusal, empty/malformed output, missing Recommendation markers, timeout or CLI failure means `outside_status: unavailable`. P0/P1 findings block like native ones; `OUTSIDE_STATUS: unverified` is missing coverage. Continue completed proposals; native completion does not count as outside coverage. After either outcome, delete only your private prompt; scratch cleanup is automatic.
|
||
|
||
Retain the historical review-log skill ID; add `"host":"claude","outside_provider":"codex","outside_status":"completed|unavailable|disabled|skipped","phase":"design-sketch"`. Record differing attempt outcomes separately. `source:"codex"` requires completed CLI output; native uses `source:"in-host"` (historical `source:"claude"`: native Claude). Availability/native fallback is not outside completion. Preserve all reported modelUsage; unknown model identity stays unknown. Under `GSTACK_CODEX_NO_SANDBOX=1` add `"codex_sandbox":"danger-full-access"`.
|
||
|
||
2. **Claude subagent** (via Agent tool, `run_in_background: false` when available — subagents default to background since Claude Code v2.1.198; A launch receipt means it went background: await its completion notice.):
|
||
"For this product approach, what design direction would you recommend? What aesthetic, typography, and interaction patterns fit? What would make this approach feel inevitable to the user? Be specific — font names, hex colors, spacing values. Do not fall back on these defaults: a cream ground with a high-contrast serif and terracotta accent; near-black with one neon accent and glowing edges; italic accent words inside headlines; numbered 01 / 02 / 03 section labels; tiny tracked monospace labels; pill-shaped buttons. If your first idea is one of these, name it and choose again."
|
||
|
||
Present Codex output under `CODEX SAYS (design sketch):` and subagent output under `CLAUDE SUBAGENT (design direction):`.
|
||
Error handling: all non-blocking. On failure, skip and continue.
|