Skip to content

Commit f571ffb

Browse files
committed
Merge remote-tracking branch 'origin/main' into garrytan/dublin-v1
# Conflicts: # CHANGELOG.md # VERSION # package.json
2 parents a2f09ca + 49cc4ff commit f571ffb

9 files changed

Lines changed: 131 additions & 62 deletions

File tree

CHANGELOG.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,73 @@ If you've been hitting the 35-minute hang on `/sync-gbrain`, it's gone. The arch
7171
- `TODOS.md` filed P2: investigate `gbrain import` perf on large staging dirs (5,131 files takes >10 minutes when 501 takes 10 seconds — gbrain-side N+1 SQL or auto-link reconciliation suspected). P3: cache "no changes since last import" at the prepare-batch level for true no-op fast paths.
7272
- `Plan completion audit` ran via subagent on this branch: 17/21 DONE, 1 CHANGED (D3 made opt-in), 2 deferred (F8 benchmark harness as separate work, 24-path unit coverage went integration-only).
7373

74+
## [1.32.0.0] - 2026-05-10
75+
76+
## **Seven contributor PRs land. Three are security or hardening.**
77+
## **Root-token comparison, IPv6 link-local, NUL transcripts, sidebar tabs, build resilience, model IDs, CJK escape — all fixed in one wave.**
78+
79+
Seven community PRs land together, hand-picked through `/plan-eng-review` plus a Codex outside-voice review that reshaped the wave mid-flight. The headline fixes are real: the root-token authentication path no longer throws on a multibyte input that matches JS character length but not UTF-8 byte length, direct `http://[fe80::N]/` URLs are now rejected the same way ULA addresses already were, `gbrain put` strips NUL bytes from pasted transcript content so Postgres doesn't reject the write, and the build script doesn't tear down when run on a fresh worktree with no git HEAD yet.
80+
81+
Two PRs in the original 9-PR plan got moved to follow-up reviews after Codex caught load-bearing problems: the SVG-XSS fix (#1153) needs a sanitizer integration rebuild, and the hook-command variable swap (#1141) needs runtime verification in plugin + dev-symlink modes. Both will land as their own PRs.
82+
83+
### The numbers that matter
84+
85+
Diff against `main` at v1.31.1.0, measured from the seven landed PRs after eng + Codex review reshaping. The wave is intentionally repo-local — no new dependencies, no risky integration changes.
86+
87+
| Metric | v1.31.1.0 | v1.32.0.0 | Δ |
88+
|---|---|---|---|
89+
| Community PRs landed | 3 | 7 | **+4** |
90+
| Security / hardening fixes | 0 | 3 | **+3** |
91+
| Behavior changes that ship to users | 1 | 7 | **+6** |
92+
| Free tests | 379 | 380 | +1 |
93+
| Memory-ingest tests | 18 | 19 | +1 |
94+
| LOC (excluding mechanical regen) | — | ~150 | — |
95+
| SKILL.md files regenerated (CJK preamble cascade) | — | 35 | — |
96+
| Preamble byte budget | 36,500 | 39,000 | +2,500 |
97+
98+
The seven shipped PRs cover three categories. **Security:** root-token UTF-8 compare hardened, IPv6 link-local blocked, sidebar tab awareness expanded. **Correctness:** gbrain ingestion tolerates pasted-NUL transcripts, build resilient to unborn HEAD. **Polish:** AskUserQuestion preamble forbids `\uXXXX` escaping of CJK characters, eval suite tracks the current Opus model ID.
99+
100+
### What this means for users
101+
102+
If you run `pair-agent` and someone hits your tunnel with a multibyte token guess that happens to match length, the auth path returns false instead of crashing. If a transcript you ingest into `gbrain` has a NUL byte in pasted output, the write succeeds instead of returning `invalid byte sequence`. If you bring up `bun run build` on a brand-new Conductor worktree before the first commit, the build runs to completion. If your sidebar agent watches a tab on a non-localhost site, it now actually sees the URL and title. If you ask Claude a long question in Chinese, you stop getting `\u`-escaped codepoints rendered as nonsense glyphs.
103+
104+
### Itemized changes
105+
106+
#### Added
107+
108+
- **#1257** Extension manifest gets the `tabs` permission. Sidebar tab awareness off-localhost now works — `chrome.tabs.query()` returns real `url`/`title` for sites outside `host_permissions` instead of undefined, so `snapshotTabs` writes real values into `tabs.json` and `active-tab.json` instead of silently skipping. Heads up: this widens the extension's permission scope; users will see the broader prompt on next install. Contributed by @fredchu.
109+
110+
#### Fixed
111+
112+
- **#1416** `isRootToken` constant-time compare hardened. Compares UTF-8 byte lengths via `Buffer.byteLength` before `crypto.timingSafeEqual`, which throws on length-mismatched buffers. A multibyte input whose JS string length matches but byte length differs now returns false instead of crashing on the auth path. Four regression tests cover multibyte byte-length mismatch, extra-prefix length mismatch, same-length last-byte flip, and empty-input-against-set-root. Contributed by @RagavRida.
113+
- **#1411** `gstack-memory-ingest` strips NUL bytes from the transcript body before piping to `gbrain put`. Postgres rejects 0x00 in UTF-8 text columns, and some Claude Code transcripts contain NUL inside pasted content or tool output. The fix uses `body.replace(/\x00/g, "")` so the regex literal stays reviewable in diffs and survives editors that strip control bytes. New regression test reuses the existing fake-gbrain writer harness at `test/gstack-memory-ingest.test.ts:376`. Contributed by @billy-armstrong.
114+
- **#1249** URL validation now blocks direct IPv6 link-local navigation. `fe80::/10` is centralised into `BLOCKED_IPV6_PREFIXES = ['fc', 'fd', 'fe8', 'fe9', 'fea', 'feb']` so `http://[fe80::N]/` is rejected by the same path that already blocked ULA addresses. Previously the link-local guard only fired during AAAA resolution; direct-literal URLs slipped through. Contributed by @hiSandog.
115+
- **#1207** `bun run build` resilient to missing git HEAD. The three chained `.version` writes (`browse/dist`, `design/dist`, `make-pdf/dist`) each now use `{ git rev-parse HEAD 2>/dev/null || true; } > ...`, so an unborn HEAD produces an empty file. `readVersionHash` already returns null on empty/trim, and the CLI's stale-binary check short-circuits on null — the "no version known" path flows through existing null handling without polluting `state.binaryVersion` with a sentinel string. Contributed by @topitopongsala.
116+
- **#1205** AskUserQuestion preamble forbids `\uXXXX` escaping of non-ASCII characters. Adds rule 12 plus a self-check item: models that hand-escape CJK strings get codepoints wrong, so `管理工具` ends up rendered as `㄃3用箱`. Long ≠ escape. Keep characters literal. The new rule cascades through the gen-skill-docs pipeline; 35 SKILL.md files regenerate to pick it up. Contributed by @joe51317-dotcom.
117+
- **#1392** Mechanical bump of remaining `claude-opus-4-6` → `4-7` references across the E2E eval suite. Covers `test/helpers/eval-store.ts` and five `test/skill-e2e-*.test.ts` files. Contributed by @johnnysoftware7.
118+
119+
#### For contributors
120+
121+
- The AskUserQuestion preamble byte budget ratchets from 36,500 → 39,000 to absorb the new CJK rule (rule 12 + self-check item). Generated SKILL.md files for all 35 tier-≥2 skills regenerate as a single mechanical commit.
122+
- Two PRs from the original 9-PR plan moved to follow-up reviews after Codex outside-voice caught load-bearing problems: #1153 (SVG sanitizer) needs the sanitizer integration rebuilt against the current `setTabContent` boundary in `browse/src/write-commands.ts:319` (the original PR removed `.svg` from the allowlist; the right fix is to keep it allowed and sanitize via DOMPurify before `setTabContent`). #1141 (CLAUDE_PLUGIN_ROOT) needs runtime verification in both plugin-installed and dev-symlink modes plus scope expansion to the non-frontmatter shell snippet at `investigate/SKILL.md.tmpl:107`.
123+
- Five gate-tier evals hardened against non-determinism / TTY rendering quirks after the wave's first `test:gate` run surfaced them as flakes (verified pre-existing on `main`, then fixed): `office-hours-builder-wildness` retiers `gate` → `periodic` because LLM-judge creativity scoring belongs in periodic per the tier-classification rules. `plan-design-with-ui` AUQ-detection tail expands 2.5KB → 5KB so the full Step 0 box-rendered AUQ fits inside the regex window. `ask-user-question-format-compliance` budget stretches 300s → 540s (poll), 360s → 600s (PTY session), 420s → 660s (bun wrapper) to accommodate `/plan-ceo-review`'s multi-bash-block preamble on substantive branches. `benchmark-providers` gemini smoke drops the brittle `toContain('ok')` assertion in favor of a shape check on the adapter result. `skillify` scrape-prototype-path accepts JSON shape variants (`results`, `data`, `hits`, bare arrays of `{title, score}` objects) instead of grepping for the literal `"items":[` key.
124+
- Housekeeping: the three source PRs absorbed into v1.31.1.0 (#1242, #1394, #1393) get closed with credit comments pointing at the merge SHA.
125+
126+
## [1.31.1.0] - 2026-05-10
127+
128+
## **Three small community fixes land cleanly.**
129+
## **`/careful` works on macOS again, Codex Step 0 stops colliding, `/make-pdf` setup runs in the right place.**
130+
131+
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.
132+
133+
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.
134+
135+
### Fixed
136+
137+
- **#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.
138+
- **#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.
139+
- **#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.
140+
74141
## [1.31.0.0] - 2026-05-09
75142

76143
## **AskUserQuestion stops getting silently buried in plan files.**

careful/bin/check-careful.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ CMD_LOWER=$(printf '%s' "$CMD" | tr '[:upper:]' '[:lower:]')
2828
# --- Check for safe exceptions (rm -rf of build artifacts) ---
2929
if printf '%s' "$CMD" | grep -qE 'rm\s+(-[a-zA-Z]*r[a-zA-Z]*\s+|--recursive\s+)' 2>/dev/null; then
3030
SAFE_ONLY=true
31-
RM_ARGS=$(printf '%s' "$CMD" | sed -E 's/.*rm\s+(-[a-zA-Z]+\s+)*//;s/--recursive\s*//')
31+
RM_ARGS=$(printf '%s' "$CMD" | sed -E 's/.*rm[[:space:]]+(-[a-zA-Z]+[[:space:]]+)*//;s/--recursive[[:space:]]*//')
3232
for target in $RM_ARGS; do
3333
case "$target" in
3434
*/node_modules|node_modules|*/\.next|\.next|*/dist|dist|*/__pycache__|__pycache__|*/\.cache|\.cache|*/build|build|*/\.turbo|\.turbo|*/coverage|coverage)

codex/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -792,7 +792,7 @@ assumptions, catches things you might miss. Present its output faithfully, not s
792792

793793
---
794794

795-
## Step 0: Check codex binary
795+
## Step 0.4: Check codex binary
796796

797797
```bash
798798
CODEX_BIN=$(which codex 2>/dev/null || echo "")

codex/SKILL.md.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ assumptions, catches things you might miss. Present its output faithfully, not s
3939

4040
---
4141

42-
## Step 0: Check codex binary
42+
## Step 0.4: Check codex binary
4343

4444
```bash
4545
CODEX_BIN=$(which codex 2>/dev/null || echo "")

make-pdf/SKILL.md

Lines changed: 36 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,42 @@ echo "CHECKPOINT_PUSH: $_CHECKPOINT_PUSH"
102102
[ -n "$OPENCLAW_SESSION" ] && echo "SPAWNED_SESSION: true" || true
103103
```
104104

105+
## MAKE-PDF SETUP (run this check BEFORE any make-pdf command)
106+
107+
```bash
108+
_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
109+
P=""
110+
[ -n "$MAKE_PDF_BIN" ] && [ -x "$MAKE_PDF_BIN" ] && P="$MAKE_PDF_BIN"
111+
[ -z "$P" ] && [ -n "$_ROOT" ] && [ -x "$_ROOT/.claude/skills/gstack/make-pdf/dist/pdf" ] && P="$_ROOT/.claude/skills/gstack/make-pdf/dist/pdf"
112+
[ -z "$P" ] && P="$HOME/.claude/skills/gstack/make-pdf/dist/pdf"
113+
if [ -x "$P" ]; then
114+
echo "MAKE_PDF_READY: $P"
115+
alias _p_="$P" # shellcheck alias helper (not exported)
116+
export P # available as $P in subsequent blocks within the same skill invocation
117+
else
118+
echo "MAKE_PDF_NOT_AVAILABLE (run './setup' in the gstack repo to build it)"
119+
fi
120+
```
121+
122+
If `MAKE_PDF_NOT_AVAILABLE` is printed: tell the user the binary is not
123+
built. Have them run `./setup` from the gstack repo, then retry.
124+
125+
If `MAKE_PDF_READY` is printed: `$P` is the binary path for the rest of
126+
the skill. Use `$P` (not an explicit path) so the skill body stays portable.
127+
128+
Core commands:
129+
- `$P generate <input.md> [output.pdf]` — render markdown to PDF (80% use case)
130+
- `$P generate --cover --toc essay.md out.pdf` — full publication layout
131+
- `$P generate --watermark DRAFT memo.md draft.pdf` — diagonal DRAFT watermark
132+
- `$P preview <input.md>` — render HTML and open in browser (fast iteration)
133+
- `$P setup` — verify browse + Chromium + pdftotext and run a smoke test
134+
- `$P --help` — full flag reference
135+
136+
Output contract:
137+
- `stdout`: ONLY the output path on success. One line.
138+
- `stderr`: progress (`Rendering HTML... Generating PDF...`) unless `--quiet`.
139+
- Exit 0 success / 1 bad args / 2 render error / 3 Paged.js timeout / 4 browse unavailable.
140+
105141
## Plan Mode Safe Operations
106142

107143
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
489525
aren't present by default, and Liberation Sans is the standard metric-compatible
490526
fallback. CI and Docker builds install it automatically via Dockerfile.ci.
491527

492-
## MAKE-PDF SETUP (run this check BEFORE any make-pdf command)
493-
494-
```bash
495-
_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
496-
P=""
497-
[ -n "$MAKE_PDF_BIN" ] && [ -x "$MAKE_PDF_BIN" ] && P="$MAKE_PDF_BIN"
498-
[ -z "$P" ] && [ -n "$_ROOT" ] && [ -x "$_ROOT/.claude/skills/gstack/make-pdf/dist/pdf" ] && P="$_ROOT/.claude/skills/gstack/make-pdf/dist/pdf"
499-
[ -z "$P" ] && P="$HOME/.claude/skills/gstack/make-pdf/dist/pdf"
500-
if [ -x "$P" ]; then
501-
echo "MAKE_PDF_READY: $P"
502-
alias _p_="$P" # shellcheck alias helper (not exported)
503-
export P # available as $P in subsequent blocks within the same skill invocation
504-
else
505-
echo "MAKE_PDF_NOT_AVAILABLE (run './setup' in the gstack repo to build it)"
506-
fi
507-
```
508-
509-
If `MAKE_PDF_NOT_AVAILABLE` is printed: tell the user the binary is not
510-
built. Have them run `./setup` from the gstack repo, then retry.
511-
512-
If `MAKE_PDF_READY` is printed: `$P` is the binary path for the rest of
513-
the skill. Use `$P` (not an explicit path) so the skill body stays portable.
514-
515-
Core commands:
516-
- `$P generate <input.md> [output.pdf]` — render markdown to PDF (80% use case)
517-
- `$P generate --cover --toc essay.md out.pdf` — full publication layout
518-
- `$P generate --watermark DRAFT memo.md draft.pdf` — diagonal DRAFT watermark
519-
- `$P preview <input.md>` — render HTML and open in browser (fast iteration)
520-
- `$P setup` — verify browse + Chromium + pdftotext and run a smoke test
521-
- `$P --help` — full flag reference
522-
523-
Output contract:
524-
- `stdout`: ONLY the output path on success. One line.
525-
- `stderr`: progress (`Rendering HTML... Generating PDF...`) unless `--quiet`.
526-
- Exit 0 success / 1 bad args / 2 render error / 3 Paged.js timeout / 4 browse unavailable.
527-
528528
## Core patterns
529529

530530
### 80% case — memo/letter

make-pdf/SKILL.md.tmpl

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,6 @@ On Linux, install `fonts-liberation` for correct rendering — Helvetica and Ari
4141
aren't present by default, and Liberation Sans is the standard metric-compatible
4242
fallback. CI and Docker builds install it automatically via Dockerfile.ci.
4343

44-
{{MAKE_PDF_SETUP}}
45-
4644
## Core patterns
4745

4846
### 80% case — memo/letter

scripts/resolvers/preamble.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ import { generateContextHealth } from './preamble/generate-context-health';
5858
// Tier 3+ repo mode + search
5959
import { generateRepoModeSection } from './preamble/generate-repo-mode-section';
6060
import { generateSearchBeforeBuildingSection } from './preamble/generate-search-before-building';
61+
import { generateMakePdfSetup } from './make-pdf';
6162

6263
// Standalone export used directly by the resolver registry
6364
export { generateTestFailureTriage } from './preamble/generate-test-failure-triage';
@@ -81,7 +82,8 @@ export function generatePreamble(ctx: TemplateContext): string {
8182
}
8283
const sections = [
8384
generatePreambleBash(ctx),
84-
// Plan-mode-skill semantics at position 1: after bash (so _SESSION_ID /
85+
...(ctx.skillName === 'make-pdf' ? [generateMakePdfSetup(ctx)] : []),
86+
// Plan-mode-skill semantics stays near the top: after bash (so _SESSION_ID /
8587
// _BRANCH / _TEL env vars are live) and before all onboarding gates so
8688
// models read the authoritative "AskUserQuestion satisfies plan mode's
8789
// end-of-turn" rule before any other instruction. Renders for all skills

test/gen-skill-docs.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1098,6 +1098,26 @@ describe('Plan status footer in preamble', () => {
10981098
});
10991099
});
11001100

1101+
// --- make-pdf setup ordering ---
1102+
1103+
describe('make-pdf setup ordering', () => {
1104+
test('MAKE-PDF SETUP appears before generic preamble footer sections', () => {
1105+
const content = fs.readFileSync(path.join(ROOT, 'make-pdf', 'SKILL.md'), 'utf-8');
1106+
const preambleIdx = content.indexOf('## Preamble (run first)');
1107+
const setupIdx = content.indexOf('## MAKE-PDF SETUP');
1108+
const planModeIdx = content.indexOf('## Plan Mode Safe Operations');
1109+
const telemetryIdx = content.indexOf('## Telemetry (run last)');
1110+
const workflowIdx = content.indexOf('# make-pdf: publication-quality PDFs from markdown');
1111+
1112+
expect(preambleIdx).toBeGreaterThanOrEqual(0);
1113+
expect(setupIdx).toBeGreaterThan(preambleIdx);
1114+
expect(setupIdx).toBeLessThan(planModeIdx);
1115+
expect(setupIdx).toBeLessThan(telemetryIdx);
1116+
expect(setupIdx).toBeLessThan(workflowIdx);
1117+
expect(content.match(/^## MAKE-PDF SETUP/gm)?.length ?? 0).toBe(1);
1118+
});
1119+
});
1120+
11011121
// --- Skill invocation during plan mode in preamble ---
11021122

11031123
describe('Skill invocation during plan mode in preamble', () => {

0 commit comments

Comments
 (0)