merge: integrate origin/main (v1.1.0.0) — V1 + Puppeteer parity + /plan-tune

Big merge. Main shipped three releases while this branch was in flight:
- v0.19.0.0 /plan-tune skill (observational layer; dual-track dev profile)
- v1.0.0.0 V1 prompts (simpler, outcome-framed, jargon-glossed) + LOC receipts
- v1.1.0.0 browse Puppeteer parity (load-html, file://, --selector, --scale)

This branch bumps to v1.2.0.0 (above main's v1.1.0.0) per the
branch-scoped-version rule in CLAUDE.md. My "0.19.0.0" CHANGELOG entry
is renamed to "1.2.0.0" and dated 2026-04-18 to land above main's trail.

Conflicts resolved:
- VERSION / package.json: 1.2.0.0
- CHANGELOG.md: preserved my entry at top (renamed), kept main's 1.1.0.0
  / 1.0.0.0 / 0.19.0.0 / 0.18.4.0 trail below in correct order
- .github/docker/Dockerfile.ci: kept my xz-utils + nodejs.org tarball
  fix (real CI bug fix main didn't have); absorbed main's retry loop
  structure for both apt and the tarball curl
- bin/gstack-config: kept both my checkpoint_mode/push section and
  main's explain_level writing-style section
- scripts/resolvers/preamble.ts: kept my submodule refactor as the
  file shape; extracted main's new generateWritingStyle and
  generateWritingStyleMigration into scripts/resolvers/preamble/
  submodules; absorbed main's generateQuestionTuning import
- All generated SKILL.md files: resolved by regen via
  bun run gen:skill-docs --host all (per CLAUDE.md: never hand-merge
  generated files — resolve templates and regen)
- Ship golden fixtures (claude/codex/factory): refreshed

Tier 2 preamble composition now includes all 8 sections: context
recovery, ask-user-format, writing-style, completeness, confusion,
continuous checkpoint, context health, question tuning.

Main also brought new test files from /plan-tune: skill-e2e-plan-tune,
upgrade-migration-v1, v0-dormancy, writing-style-resolver. All absorbed.
468 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-04-18 23:35:36 +08:00
98 changed files with 14458 additions and 258 deletions

View File

@@ -148,6 +148,29 @@ prompts from sub-sessions.
After handling JUST_UPGRADED (prompts done or skipped), continue with the skill
workflow.
If `WRITING_STYLE_PENDING` is `yes`: You're on the first skill run after upgrading
to gstack v1. Ask the user once about the new default writing style. Use AskUserQuestion:
> v1 prompts = simpler. Technical terms get a one-sentence gloss on first use,
> questions are framed in outcome terms, sentences are shorter.
>
> Keep the new default, or prefer the older tighter prose?
Options:
- A) Keep the new default (recommended — good writing helps everyone)
- B) Restore V0 prose — set `explain_level: terse`
If A: leave `explain_level` unset (defaults to `default`).
If B: run `~/.claude/skills/gstack/bin/gstack-config set explain_level terse`.
Always run (regardless of choice):
```bash
rm -f ~/.gstack/.writing-style-prompt-pending
touch ~/.gstack/.writing-style-prompted
```
This only happens once. If `WRITING_STYLE_PENDING` is `no`, skip this entirely.
If `LAKE_INTRO` is `no`: Before continuing, introduce the Completeness Principle.
Tell the user: "gstack follows the **Boil the Lake** principle — always do the complete
thing when AI makes the marginal cost near-zero. Read more: https://garryslist.org/posts/boil-the-ocean"
@@ -553,6 +576,57 @@ $B diff https://staging.app.com https://prod.app.com
### 11. Show screenshots to the user
After `$B screenshot`, `$B snapshot -a -o`, or `$B responsive`, always use the Read tool on the output PNG(s) so the user can see them. Without this, screenshots are invisible.
### 12. Render local HTML (no HTTP server needed)
Two paths, pick the cleaner one:
```bash
# HTML file on disk → goto file:// (absolute, or cwd-relative)
$B goto file:///tmp/report.html
$B goto file://./docs/page.html # cwd-relative
$B goto file://~/Documents/page.html # home-relative
# HTML generated in memory → load-html reads the file into setContent
echo '<div class="tweet">hello</div>' > /tmp/tweet.html
$B load-html /tmp/tweet.html
```
`goto file://...` is usually cleaner (URL is saved in state, relative asset URLs resolve against the file's dir, scale changes replay naturally). `load-html` uses `page.setContent()` — URL stays `about:blank`, but the content survives `viewport --scale` via in-memory replay. Both are scoped to files under cwd or `$TMPDIR`.
### 13. Retina screenshots (deviceScaleFactor)
```bash
$B viewport 480x600 --scale 2 # 2x deviceScaleFactor
$B load-html /tmp/tweet.html # or: $B goto file://./tweet.html
$B screenshot /tmp/out.png --selector .tweet-card
# → /tmp/out.png is 2x the pixel dimensions of the element
```
Scale must be 1-3 (gstack policy cap). Changing `--scale` recreates the browser context; refs from `snapshot` are invalidated (rerun `snapshot`), but `load-html` content is replayed automatically. Not supported in headed mode.
## Puppeteer → browse cheatsheet
Migrating from Puppeteer? Here's the 1:1 mapping for the core workflow:
| Puppeteer | browse |
|---|---|
| `await page.goto(url)` | `$B goto <url>` |
| `await page.setContent(html)` | `$B load-html <file>` (or `$B goto file://<abs>`) |
| `await page.setViewport({width, height})` | `$B viewport WxH` |
| `await page.setViewport({width, height, deviceScaleFactor: 2})` | `$B viewport WxH --scale 2` |
| `await (await page.$('.x')).screenshot({path})` | `$B screenshot <path> --selector .x` |
| `await page.screenshot({fullPage: true, path})` | `$B screenshot <path>` (full page default) |
| `await page.screenshot({clip: {x, y, w, h}, path})` | `$B screenshot <path> --clip x,y,w,h` |
Worked example (the tweet-renderer flow — Puppeteer → browse):
```bash
# Generate HTML in memory, render at 2x scale, screenshot the tweet card.
echo '<div class="tweet-card" style="width:400px;height:200px;background:#1da1f2;color:white;padding:20px">hello</div>' > /tmp/tweet.html
$B viewport 480x600 --scale 2
$B load-html /tmp/tweet.html
$B screenshot /tmp/out.png --selector .tweet-card
# /tmp/out.png is 800x400 px, crisp (2x deviceScaleFactor).
```
Aliases: typing `setcontent` or `set-content` routes to `load-html` automatically. Typing a typo (`load-htm`) returns `Did you mean 'load-html'?`.
## User Handoff
When you hit something you can't handle in headless mode (CAPTCHA, complex auth, multi-factor
@@ -657,7 +731,8 @@ $B prettyscreenshot --cleanup --scroll-to ".pricing" --width 1440 ~/Desktop/hero
|---------|-------------|
| `back` | History back |
| `forward` | History forward |
| `goto <url>` | Navigate to URL |
| `goto <url>` | Navigate to URL (http://, https://, or file:// scoped to cwd/TEMP_DIR) |
| `load-html <file> [--wait-until load|domcontentloaded|networkidle]` | Load a local HTML file via setContent (no HTTP server needed). For self-contained HTML (inline CSS/JS, data URIs). For HTML on disk, goto file://... is often cleaner. |
| `reload` | Reload page |
| `url` | Print current URL |
@@ -708,7 +783,7 @@ $B prettyscreenshot --cleanup --scroll-to ".pricing" --width 1440 ~/Desktop/hero
| `type <text>` | Type into focused element |
| `upload <sel> <file> [file2...]` | Upload file(s) |
| `useragent <string>` | Set user agent |
| `viewport <WxH>` | Set viewport size |
| `viewport [<WxH>] [--scale <n>]` | Set viewport size and optional deviceScaleFactor (1-3, for retina screenshots). --scale requires a context rebuild. |
| `wait <sel|--networkidle|--load>` | Wait for element, network idle, or page load (timeout: 15s) |
### Inspection
@@ -735,7 +810,7 @@ $B prettyscreenshot --cleanup --scroll-to ".pricing" --width 1440 ~/Desktop/hero
| `pdf [path]` | Save as PDF |
| `prettyscreenshot [--scroll-to sel|text] [--cleanup] [--hide sel...] [--width px] [path]` | Clean screenshot with optional cleanup, scroll positioning, and element hiding |
| `responsive [prefix]` | Screenshots at mobile (375x812), tablet (768x1024), desktop (1280x720). Saves as {prefix}-mobile.png etc. |
| `screenshot [--viewport] [--clip x,y,w,h] [selector|@ref] [path]` | Save screenshot (supports element crop via CSS/@ref, --clip region, --viewport) |
| `screenshot [--selector <css>] [--viewport] [--clip x,y,w,h] [--base64] [selector|@ref] [path]` | Save screenshot. --selector targets a specific element (explicit flag form). Positional selectors starting with ./#/@/[ still work. |
### Snapshot
| Command | Description |