From 07259dd396a6384405f63ce4d8848f8c0c7a05f6 Mon Sep 17 00:00:00 2001 From: Jason Irish Date: Tue, 1 Sep 2026 14:24:04 -0500 Subject: [PATCH 1/2] feat(#72): add CI typography gate for outward-facing docs Mechanizes the existing no-em-dash writing-style rule so it can't regress silently: a git grep step in the shell CI job (mirrored in .local-ci.json) fails the build on em/en-dashes or curly quotes in README.md, docs/*.md, docs/index.html, and the plugin marketplace description. Fixed the one live violation, in .claude-plugin/marketplace.json's description. The .local-ci.json entry uses literal Unicode characters in the bracket class rather than \x{...} hex escapes: local-ci's custom-check extraction pipes each check's "run" field through jq's @tsv, which escapes embedded backslashes for safe TSV framing, and the downstream `read -r` + `bash -c` preserves that doubling literally inside the command's own single-quoted argument - turning \x{2014} into \\x{2014} and silently breaking the PCRE hex-escape into a bogus literal character class. Caught by actually running the check through local-ci.sh (not just testing the command by hand) and comparing the output against a byte-count sanity check. ci.yml's own step is unaffected (GitHub Actions runs it directly, no jq round-trip) and keeps the \x{...} form specifically to avoid pasting a literal em-dash into the workflow file itself. Closes #72 Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_019VuUKvqH3q511w8xVd4VFY --- .claude-plugin/marketplace.json | 2 +- .github/workflows/ci.yml | 16 ++++++++++++++++ .local-ci.json | 4 ++++ CHANGELOG.md | 6 ++++++ 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index d960e55..cabd7ae 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -8,7 +8,7 @@ { "name": "throughline", "source": "./", - "description": "Continuous, state-aware session memory for Claude Code — capture what you did and what is, hand it off with judgment.", + "description": "Continuous, state-aware session memory for Claude Code - capture what you did and what is, hand it off with judgment.", "author": { "name": "Dynamic Agency", "email": "support@dynamicagency.com" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 157e36d..35f32ef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,6 +53,22 @@ jobs: echo "version drift: .claude-plugin/plugin.json=$c .codex-plugin/plugin.json=$x .opencode-plugin/package.json=$o" >&2 exit 1 fi + - name: Typography (Linux) + if: runner.os == 'Linux' + # Mechanizes Jason's writing-style rule (no em/en-dashes or curly + # quotes in outward-facing content) so it can't regress silently - + # scoped to files a reader/user actually opens (README, promo site, + # per-harness docs, the plugin marketplace description), not the + # whole tree: internal docs (CHANGELOG, HANDOFF, this project's own + # memory files, code comments) are explicitly exempt per the style + # guide. The pattern is written as escape sequences on purpose - a + # literal em-dash pasted into this step would make it match itself. + run: | + if git grep -I -n -P '[\x{2013}\x{2014}\x{2018}\x{2019}\x{201C}\x{201D}]' -- \ + README.md docs/*.md docs/index.html .claude-plugin/marketplace.json; then + echo "non-ASCII em/en-dash or curly quote in outward-facing files (see above) - use ASCII punctuation instead ( - , straight quotes)" >&2 + exit 1 + fi - name: Run hook tests run: sh tests/run.sh diff --git a/.local-ci.json b/.local-ci.json index 2e38837..5dd14c9 100644 --- a/.local-ci.json +++ b/.local-ci.json @@ -12,6 +12,10 @@ "label": "hook tests", "run": "sh tests/run.sh" }, + { + "label": "typography", + "run": "! git grep -I -n -P '[—–‘’“”]' -- README.md docs/*.md docs/index.html .claude-plugin/marketplace.json" + }, { "label": "opencode plugin", "run": "cd .opencode-plugin && npm ci && npm run typecheck && npm test" diff --git a/CHANGELOG.md b/CHANGELOG.md index 36c30a8..8a1c9cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,12 @@ All notable changes to throughline are documented here. Format loosely follows availability, live/archived buffer counts, and the three env vars' current values - so diagnosing "why isn't capture firing" no longer means reading `_lib.sh` and reasoning through the precedence rules by hand. +- **CI typography gate** (issue #72): a `git grep` step fails the build on + em/en-dashes or curly quotes in outward-facing files (`README.md`, + `docs/*.md`, `docs/index.html`, the plugin marketplace description) - + mechanizes the existing no-em-dash writing-style rule instead of relying + on review to catch a regression. Mirrored in `.local-ci.json`. Fixed one + live violation in `.claude-plugin/marketplace.json`'s description. ## [0.15.0] From 950443f1f621c944bb3650c4d5c1c983983c3122 Mon Sep 17 00:00:00 2001 From: Jason Irish Date: Tue, 1 Sep 2026 14:33:06 -0500 Subject: [PATCH 2/2] fix: review finding on PR #78 (typography gate silent-pass under non-UTF-8 locale) git grep -P only enables PCRE2's UTF mode under a UTF-8 locale codeset; \x{2013} exceeds PCRE2's 8-bit max outside one, so under e.g. LC_ALL=C the pattern fails to COMPILE (rc=128) rather than finding zero matches (rc=1). The prior `if git grep ...; then FAIL; fi` treated that compile failure identically to "no match found" and passed clean - a gate whose entire point is "can't regress silently" had a silent-pass failure mode of its own. ci.yml: pins LC_ALL=C.UTF-8 on the invocation and checks the exit code explicitly (0 = matches found = fail; 1 = clean; anything else = the check itself broke = fail loud, never silent-pass). .local-ci.json: same fix, and closes the mirror-image bug the reviewer also found - the literal-Unicode-character bracket class (needed there to route around the @tsv double-escaping bug from the prior commit) degrades to a raw BYTE class outside a UTF-8 locale under LC_ALL=C, over-matching unrelated multi-byte sequences (verified: 40 false positives on docs/REFERENCE.md's box-drawing tree chars). Verified live through the actual local-ci.sh custom-check driver, not just by hand: normal pass, a staged regression still fails correctly, and the explicit rc-check was exercised directly under a broken locale. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_019VuUKvqH3q511w8xVd4VFY --- .github/workflows/ci.yml | 21 +++++++++++++++++++-- .local-ci.json | 2 +- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 35f32ef..0e64fe2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,11 +63,28 @@ jobs: # memory files, code comments) are explicitly exempt per the style # guide. The pattern is written as escape sequences on purpose - a # literal em-dash pasted into this step would make it match itself. + # + # LC_ALL pinned to a UTF-8 locale: git grep -P only enables PCRE2's + # UTF mode under a UTF-8 locale codeset, and \x{2013} exceeds PCRE2's + # 8-bit max outside it - under e.g. LC_ALL=C the pattern fails to + # COMPILE (rc=128), and a bare `if git grep ...; then FAIL; fi` reads + # that failure as "no match found" and passes clean. A gate whose + # whole point is "can't regress silently" must not itself have a + # silent-pass failure mode, so the exit code is checked explicitly + # instead of trusting the if/then branch alone. run: | - if git grep -I -n -P '[\x{2013}\x{2014}\x{2018}\x{2019}\x{201C}\x{201D}]' -- \ - README.md docs/*.md docs/index.html .claude-plugin/marketplace.json; then + set +e + hits=$(LC_ALL=C.UTF-8 git grep -I -n -P '[\x{2013}\x{2014}\x{2018}\x{2019}\x{201C}\x{201D}]' -- \ + README.md docs/*.md docs/index.html .claude-plugin/marketplace.json) + rc=$? + set -e + if [ "$rc" -eq 0 ]; then + printf '%s\n' "$hits" echo "non-ASCII em/en-dash or curly quote in outward-facing files (see above) - use ASCII punctuation instead ( - , straight quotes)" >&2 exit 1 + elif [ "$rc" -ne 1 ]; then + echo "typography check failed to run (git grep rc=$rc) - this must not read as a clean pass" >&2 + exit 1 fi - name: Run hook tests run: sh tests/run.sh diff --git a/.local-ci.json b/.local-ci.json index 5dd14c9..a03f5bd 100644 --- a/.local-ci.json +++ b/.local-ci.json @@ -14,7 +14,7 @@ }, { "label": "typography", - "run": "! git grep -I -n -P '[—–‘’“”]' -- README.md docs/*.md docs/index.html .claude-plugin/marketplace.json" + "run": "export LC_ALL=C.UTF-8; out=$(git grep -I -n -P '[—–‘’“”]' -- README.md docs/*.md docs/index.html .claude-plugin/marketplace.json); rc=$?; if [ \"$rc\" -eq 0 ]; then echo \"$out\" >&2; exit 1; elif [ \"$rc\" -ne 1 ]; then echo \"typography check failed to run (git grep rc=$rc)\" >&2; exit 1; fi" }, { "label": "opencode plugin",