From e719af0508219c8f9c78da1d1a46fda08a73bf08 Mon Sep 17 00:00:00 2001 From: ToraDady Date: Mon, 27 Apr 2026 20:31:08 +0900 Subject: [PATCH 1/5] fix(careful): BSD sed compatibility for safe exception detection on macOS The sed regex in check-careful.sh uses \s+, which is a GNU sed extension not supported by BSD sed (macOS default). On macOS, this causes the RM_ARGS strip to fail silently, making rm -rf of safe exceptions (node_modules, .next, dist, etc.) trigger the destructive warning instead of being permitted as designed. Fix: replace \s+ with POSIX [[:space:]]+, which works on both GNU sed (Linux) and BSD sed (macOS). The existing test/hook-scripts.test.ts already documented this limitation via a detectSafeRmWorks() helper and a platform-conditional assertion ("if GNU sed: expect undefined, else: expect ask"). Now that the regex works on both platforms, this dead path is removed and the safe-exception tests assert the same expectation on every OS. Note: the grep regex in the same file also uses \s+, but BSD grep -E on macOS does support \s (verified via bash -x trace), so only the sed expression needs the fix. Discovered while translating the careful skill for a Japanese derivative project (uzustack). Reference: https://github.com/uzumaki-inc/uzustack/commit/bc67c8d --- careful/bin/check-careful.sh | 2 +- test/hook-scripts.test.ts | 22 ++-------------------- 2 files changed, 3 insertions(+), 21 deletions(-) diff --git a/careful/bin/check-careful.sh b/careful/bin/check-careful.sh index c8bc2c7abe..d9c39e48c8 100755 --- a/careful/bin/check-careful.sh +++ b/careful/bin/check-careful.sh @@ -28,7 +28,7 @@ CMD_LOWER=$(printf '%s' "$CMD" | tr '[:upper:]' '[:lower:]') # --- Check for safe exceptions (rm -rf of build artifacts) --- if printf '%s' "$CMD" | grep -qE 'rm\s+(-[a-zA-Z]*r[a-zA-Z]*\s+|--recursive\s+)' 2>/dev/null; then SAFE_ONLY=true - RM_ARGS=$(printf '%s' "$CMD" | sed -E 's/.*rm\s+(-[a-zA-Z]+\s+)*//;s/--recursive\s*//') + RM_ARGS=$(printf '%s' "$CMD" | sed -E 's/.*rm[[:space:]]+(-[a-zA-Z]+[[:space:]]+)*//;s/--recursive[[:space:]]*//') for target in $RM_ARGS; do case "$target" in */node_modules|node_modules|*/\.next|\.next|*/dist|dist|*/__pycache__|__pycache__|*/\.cache|\.cache|*/build|build|*/\.turbo|\.turbo|*/coverage|coverage) diff --git a/test/hook-scripts.test.ts b/test/hook-scripts.test.ts index 850b5b983a..f1ffe12397 100644 --- a/test/hook-scripts.test.ts +++ b/test/hook-scripts.test.ts @@ -56,13 +56,6 @@ function withFreezeDir(freezePath: string, fn: (stateDir: string) => void) { } } -// Detect whether the safe-rm-targets regex works on this platform. -// macOS sed -E does not support \s, so the safe exception check fails there. -function detectSafeRmWorks(): boolean { - const { output } = runHook(CAREFUL_SCRIPT, carefulInput('rm -rf node_modules')); - return output.permissionDecision === undefined; -} - // ============================================================ // check-careful.sh tests // ============================================================ @@ -88,24 +81,13 @@ describe('check-careful.sh', () => { test('rm -rf node_modules allows (safe exception)', () => { const { exitCode, output } = runHook(CAREFUL_SCRIPT, carefulInput('rm -rf node_modules')); expect(exitCode).toBe(0); - if (detectSafeRmWorks()) { - // GNU sed: safe exception triggers, allows through - expect(output.permissionDecision).toBeUndefined(); - } else { - // macOS sed: safe exception regex uses \\s which is unsupported, - // so the safe-targets check fails and the command warns - expect(output.permissionDecision).toBe('ask'); - } + expect(output.permissionDecision).toBeUndefined(); }); test('rm -rf .next dist allows (multiple safe targets)', () => { const { exitCode, output } = runHook(CAREFUL_SCRIPT, carefulInput('rm -rf .next dist')); expect(exitCode).toBe(0); - if (detectSafeRmWorks()) { - expect(output.permissionDecision).toBeUndefined(); - } else { - expect(output.permissionDecision).toBe('ask'); - } + expect(output.permissionDecision).toBeUndefined(); }); test('rm -rf node_modules /var/data warns (mixed safe+unsafe)', () => { From 2cca6a1b38072063dc9631b4b001001dbb541db9 Mon Sep 17 00:00:00 2001 From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Date: Sat, 9 May 2026 09:36:28 -0700 Subject: [PATCH 2/5] docs(codex): rename Step 0 to avoid collision with platform-detect prelude The codex skill template had its own '## Step 0: Check codex binary' heading (line 42), which after gen-skill-docs collided with the platform-detection prelude '## Step 0: Detect platform and base branch' (injected by scripts/resolvers/utility.ts). The generated codex/SKILL.md ended up with two H2 headings labeled Step 0, which is ambiguous to an agent reading the skill in order. Renamed the local heading to Step 0.4, slotting it between the prelude (Step 0) and the existing Step 0.5 / Step 0.6 sections. No renumbering of downstream steps needed. Closes #1388 Co-Authored-By: Claude Opus 4.7 (1M context) --- codex/SKILL.md.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codex/SKILL.md.tmpl b/codex/SKILL.md.tmpl index 90dd1119ca..ed118a1193 100644 --- a/codex/SKILL.md.tmpl +++ b/codex/SKILL.md.tmpl @@ -39,7 +39,7 @@ assumptions, catches things you might miss. Present its output faithfully, not s --- -## Step 0: Check codex binary +## Step 0.4: Check codex binary ```bash CODEX_BIN=$(which codex 2>/dev/null || echo "") From 9d92f8e9c2a06902280313a1fe594b145f140d95 Mon Sep 17 00:00:00 2001 From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Date: Sat, 9 May 2026 09:36:28 -0700 Subject: [PATCH 3/5] docs(codex): regenerate SKILL.md after Step 0 rename Co-Authored-By: Claude Opus 4.7 (1M context) --- codex/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codex/SKILL.md b/codex/SKILL.md index 6be6ccdf93..464401fdf7 100644 --- a/codex/SKILL.md +++ b/codex/SKILL.md @@ -792,7 +792,7 @@ assumptions, catches things you might miss. Present its output faithfully, not s --- -## Step 0: Check codex binary +## Step 0.4: Check codex binary ```bash CODEX_BIN=$(which codex 2>/dev/null || echo "") From a6b32b71afde41036709a9124b11b69505c29fb0 Mon Sep 17 00:00:00 2001 From: Jayesh Betala Date: Sat, 9 May 2026 19:50:22 +0530 Subject: [PATCH 4/5] fix(make-pdf): move setup before preamble footer --- make-pdf/SKILL.md | 72 +++++++++++++++++------------------ make-pdf/SKILL.md.tmpl | 2 - scripts/resolvers/preamble.ts | 4 +- test/gen-skill-docs.test.ts | 20 ++++++++++ 4 files changed, 59 insertions(+), 39 deletions(-) diff --git a/make-pdf/SKILL.md b/make-pdf/SKILL.md index f116687da8..927b637d94 100644 --- a/make-pdf/SKILL.md +++ b/make-pdf/SKILL.md @@ -102,6 +102,42 @@ echo "CHECKPOINT_PUSH: $_CHECKPOINT_PUSH" [ -n "$OPENCLAW_SESSION" ] && echo "SPAWNED_SESSION: true" || true ``` +## MAKE-PDF SETUP (run this check BEFORE any make-pdf command) + +```bash +_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) +P="" +[ -n "$MAKE_PDF_BIN" ] && [ -x "$MAKE_PDF_BIN" ] && P="$MAKE_PDF_BIN" +[ -z "$P" ] && [ -n "$_ROOT" ] && [ -x "$_ROOT/.claude/skills/gstack/make-pdf/dist/pdf" ] && P="$_ROOT/.claude/skills/gstack/make-pdf/dist/pdf" +[ -z "$P" ] && P="$HOME/.claude/skills/gstack/make-pdf/dist/pdf" +if [ -x "$P" ]; then + echo "MAKE_PDF_READY: $P" + alias _p_="$P" # shellcheck alias helper (not exported) + export P # available as $P in subsequent blocks within the same skill invocation +else + echo "MAKE_PDF_NOT_AVAILABLE (run './setup' in the gstack repo to build it)" +fi +``` + +If `MAKE_PDF_NOT_AVAILABLE` is printed: tell the user the binary is not +built. Have them run `./setup` from the gstack repo, then retry. + +If `MAKE_PDF_READY` is printed: `$P` is the binary path for the rest of +the skill. Use `$P` (not an explicit path) so the skill body stays portable. + +Core commands: +- `$P generate [output.pdf]` — render markdown to PDF (80% use case) +- `$P generate --cover --toc essay.md out.pdf` — full publication layout +- `$P generate --watermark DRAFT memo.md draft.pdf` — diagonal DRAFT watermark +- `$P preview ` — render HTML and open in browser (fast iteration) +- `$P setup` — verify browse + Chromium + pdftotext and run a smoke test +- `$P --help` — full flag reference + +Output contract: +- `stdout`: ONLY the output path on success. One line. +- `stderr`: progress (`Rendering HTML... Generating PDF...`) unless `--quiet`. +- Exit 0 success / 1 bad args / 2 render error / 3 Paged.js timeout / 4 browse unavailable. + ## Plan Mode Safe Operations In plan mode, allowed because they inform the plan: `$B`, `$D`, `codex exec`/`codex review`, writes to `~/.gstack/`, writes to the plan file, and `open` for generated artifacts. @@ -489,42 +525,6 @@ On Linux, install `fonts-liberation` for correct rendering — Helvetica and Ari aren't present by default, and Liberation Sans is the standard metric-compatible fallback. CI and Docker builds install it automatically via Dockerfile.ci. -## MAKE-PDF SETUP (run this check BEFORE any make-pdf command) - -```bash -_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) -P="" -[ -n "$MAKE_PDF_BIN" ] && [ -x "$MAKE_PDF_BIN" ] && P="$MAKE_PDF_BIN" -[ -z "$P" ] && [ -n "$_ROOT" ] && [ -x "$_ROOT/.claude/skills/gstack/make-pdf/dist/pdf" ] && P="$_ROOT/.claude/skills/gstack/make-pdf/dist/pdf" -[ -z "$P" ] && P="$HOME/.claude/skills/gstack/make-pdf/dist/pdf" -if [ -x "$P" ]; then - echo "MAKE_PDF_READY: $P" - alias _p_="$P" # shellcheck alias helper (not exported) - export P # available as $P in subsequent blocks within the same skill invocation -else - echo "MAKE_PDF_NOT_AVAILABLE (run './setup' in the gstack repo to build it)" -fi -``` - -If `MAKE_PDF_NOT_AVAILABLE` is printed: tell the user the binary is not -built. Have them run `./setup` from the gstack repo, then retry. - -If `MAKE_PDF_READY` is printed: `$P` is the binary path for the rest of -the skill. Use `$P` (not an explicit path) so the skill body stays portable. - -Core commands: -- `$P generate [output.pdf]` — render markdown to PDF (80% use case) -- `$P generate --cover --toc essay.md out.pdf` — full publication layout -- `$P generate --watermark DRAFT memo.md draft.pdf` — diagonal DRAFT watermark -- `$P preview ` — render HTML and open in browser (fast iteration) -- `$P setup` — verify browse + Chromium + pdftotext and run a smoke test -- `$P --help` — full flag reference - -Output contract: -- `stdout`: ONLY the output path on success. One line. -- `stderr`: progress (`Rendering HTML... Generating PDF...`) unless `--quiet`. -- Exit 0 success / 1 bad args / 2 render error / 3 Paged.js timeout / 4 browse unavailable. - ## Core patterns ### 80% case — memo/letter diff --git a/make-pdf/SKILL.md.tmpl b/make-pdf/SKILL.md.tmpl index 0827492a85..d134ee62a3 100644 --- a/make-pdf/SKILL.md.tmpl +++ b/make-pdf/SKILL.md.tmpl @@ -41,8 +41,6 @@ On Linux, install `fonts-liberation` for correct rendering — Helvetica and Ari aren't present by default, and Liberation Sans is the standard metric-compatible fallback. CI and Docker builds install it automatically via Dockerfile.ci. -{{MAKE_PDF_SETUP}} - ## Core patterns ### 80% case — memo/letter diff --git a/scripts/resolvers/preamble.ts b/scripts/resolvers/preamble.ts index b866e90b1d..97698bfcfe 100644 --- a/scripts/resolvers/preamble.ts +++ b/scripts/resolvers/preamble.ts @@ -58,6 +58,7 @@ import { generateContextHealth } from './preamble/generate-context-health'; // Tier 3+ repo mode + search import { generateRepoModeSection } from './preamble/generate-repo-mode-section'; import { generateSearchBeforeBuildingSection } from './preamble/generate-search-before-building'; +import { generateMakePdfSetup } from './make-pdf'; // Standalone export used directly by the resolver registry export { generateTestFailureTriage } from './preamble/generate-test-failure-triage'; @@ -81,7 +82,8 @@ export function generatePreamble(ctx: TemplateContext): string { } const sections = [ generatePreambleBash(ctx), - // Plan-mode-skill semantics at position 1: after bash (so _SESSION_ID / + ...(ctx.skillName === 'make-pdf' ? [generateMakePdfSetup(ctx)] : []), + // Plan-mode-skill semantics stays near the top: after bash (so _SESSION_ID / // _BRANCH / _TEL env vars are live) and before all onboarding gates so // models read the authoritative "AskUserQuestion satisfies plan mode's // end-of-turn" rule before any other instruction. Renders for all skills diff --git a/test/gen-skill-docs.test.ts b/test/gen-skill-docs.test.ts index 86cdac953b..23a4965e7c 100644 --- a/test/gen-skill-docs.test.ts +++ b/test/gen-skill-docs.test.ts @@ -1098,6 +1098,26 @@ describe('Plan status footer in preamble', () => { }); }); +// --- make-pdf setup ordering --- + +describe('make-pdf setup ordering', () => { + test('MAKE-PDF SETUP appears before generic preamble footer sections', () => { + const content = fs.readFileSync(path.join(ROOT, 'make-pdf', 'SKILL.md'), 'utf-8'); + const preambleIdx = content.indexOf('## Preamble (run first)'); + const setupIdx = content.indexOf('## MAKE-PDF SETUP'); + const planModeIdx = content.indexOf('## Plan Mode Safe Operations'); + const telemetryIdx = content.indexOf('## Telemetry (run last)'); + const workflowIdx = content.indexOf('# make-pdf: publication-quality PDFs from markdown'); + + expect(preambleIdx).toBeGreaterThanOrEqual(0); + expect(setupIdx).toBeGreaterThan(preambleIdx); + expect(setupIdx).toBeLessThan(planModeIdx); + expect(setupIdx).toBeLessThan(telemetryIdx); + expect(setupIdx).toBeLessThan(workflowIdx); + expect(content.match(/^## MAKE-PDF SETUP/gm)?.length ?? 0).toBe(1); + }); +}); + // --- Skill invocation during plan mode in preamble --- describe('Skill invocation during plan mode in preamble', () => { From ec5cabbbfe216b191e3f9d37d6a611346a4162d6 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Sun, 10 May 2026 02:15:19 -0700 Subject: [PATCH 5/5] chore: bump version and changelog (v1.31.1.0) Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 15 +++++++++++++++ VERSION | 2 +- package.json | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 674d8b2a65..bca6f8fb02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## [1.31.1.0] - 2026-05-10 + +## **Three small community fixes land cleanly.** +## **`/careful` works on macOS again, Codex Step 0 stops colliding, `/make-pdf` setup runs in the right place.** + +A short patch wave from three contributors. macOS users who ran `/careful` with `rm -rf node_modules` were silently hitting the warning gate instead of the safe exception path because BSD sed doesn't understand `\s`. The Codex skill's `## Step 0: Check codex binary` header was colliding with the platform-detect prelude that also runs first. `/make-pdf`'s SETUP block was rendered after the Telemetry footer instead of immediately after the Preamble Bash, so `$P` could be referenced before it was set. Each fix is tightly scoped and ships with a regression test (or template ordering invariant) that catches the original failure shape. + +This release came out of a contributor-wave triage pass that closed ~75 stale PRs, dropped 11 candidates that needed focused review with specific feedback to each contributor, and lined the survivors through `/plan-eng-review` + Codex outside-voice review before merge. One additional security PR (token-registry timing-safe comparison) was rejected at the codex-review gate after Codex caught a subtle multi-byte UTF-8 buffer-mismatch bug that would have thrown on the auth path instead of returning false; that finding now lives as feedback on the original PR. + +### Fixed + +- **#1242** `careful/bin/check-careful.sh` uses `[[:space:]]` instead of `\s` in the safe-rm exception regex. macOS sed -E does not support `\s`, which silently broke the exception detection — `rm -rf node_modules` now correctly skips the warning gate on macOS, matching Linux behavior. Removes the `detectSafeRmWorks()` platform-conditional from `test/hook-scripts.test.ts` so both platforms are tested at the same bar. Contributed by @ToraDady. +- **#1394** Codex skill `## Step 0: Check codex binary` renamed to `## Step 0.4: Check codex binary` so the header no longer collides with the new platform-detect prelude (also numbered Step 0). Affects both `codex/SKILL.md.tmpl` and the regenerated `codex/SKILL.md`. Contributed by @mvanhorn. +- **#1393** `/make-pdf` MAKE-PDF SETUP block moves from after the Telemetry footer to right after the Preamble Bash, so `$P` is set before any subsequent step references it. The implementation switches from the `{{MAKE_PDF_SETUP}}` placeholder pattern to programmatic insertion via `generateMakePdfSetup` in `scripts/resolvers/preamble.ts`, gated on `ctx.skillName === 'make-pdf'`. New `make-pdf setup ordering` test in `test/gen-skill-docs.test.ts` asserts the SETUP block sits after the Preamble heading and before Plan Mode / Telemetry / workflow headings. Contributed by @jbetala7. + ## [1.31.0.0] - 2026-05-09 ## **AskUserQuestion stops getting silently buried in plan files.** diff --git a/VERSION b/VERSION index 52c3b4a50e..7a251efb7b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.31.0.0 +1.31.1.0 diff --git a/package.json b/package.json index 679bb5036d..ec6ad15995 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gstack", - "version": "1.31.0.0", + "version": "1.31.1.0", "description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.", "license": "MIT", "type": "module",