diff --git a/CHANGELOG.md b/CHANGELOG.md index 674d8b2a..bca6f8fb 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 52c3b4a5..7a251efb 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.31.0.0 +1.31.1.0 diff --git a/careful/bin/check-careful.sh b/careful/bin/check-careful.sh index c8bc2c7a..d9c39e48 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/codex/SKILL.md b/codex/SKILL.md index 6be6ccdf..464401fd 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 "") diff --git a/codex/SKILL.md.tmpl b/codex/SKILL.md.tmpl index 90dd1119..ed118a11 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 "") diff --git a/make-pdf/SKILL.md b/make-pdf/SKILL.md index f116687d..927b637d 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 0827492a..d134ee62 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/package.json b/package.json index 679bb503..ec6ad159 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", diff --git a/scripts/resolvers/preamble.ts b/scripts/resolvers/preamble.ts index b866e90b..97698bfc 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 86cdac95..23a4965e 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', () => { diff --git a/test/hook-scripts.test.ts b/test/hook-scripts.test.ts index 850b5b98..f1ffe123 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)', () => {