The quoted heredoc `<<'FEEDBACK'` should suppress all parameter
expansion and quote interpretation, but bash 3.2.57 (the system
bash shipped with every macOS) still scans the body for unmatched
single quotes. The apostrophe in `user's explicit choice` on
line 126 was therefore treated as the start of a quoted string,
producing:
line 150: unexpected EOF while looking for matching \`''
line 151: syntax error: unexpected end of file
The hook then exits non-zero on every Stop event for any macOS user
running the default bash. Every Claude Code session shows the parse
error in the Stop hook feedback channel.
Two changes:
1. Reword the body to remove the apostrophe ('user's' -> 'the user').
This is the actual fix for the EOF crash and is the minimum needed
to make bash 3.2 happy.
2. Rewrite the trailing parameter substitution to use intermediate
variables instead of a five-level-deep backslash-quote chain. Same
behaviour, but readable and safer against future quoting regressions.
Verified:
- bash -n hooks/stop-feedback.sh passes
- dry-run with a minimal Stop event exits 0
- tested on bash 3.2.57 (macOS) which previously crashed
Replace `$(cat <<'FEEDBACK' ... FEEDBACK)` command-substitution-wrapped
heredoc with `IFS= read -r -d '' ... <<'FEEDBACK' ... FEEDBACK`.
Root cause: bash 3.2's `$(...)` scanner tracks single-quote balance
inside the heredoc body even when the heredoc delimiter is quoted.
v3.4.6 added "user's explicit choice" to the body — that single
apostrophe flipped the body's `'` count from even to odd, leaving the
scanner waiting for a closing quote and reporting `unexpected EOF` at
the next statement boundary (line 150). bash 4+ parses both versions
fine; macOS ships only bash 3.2 due to the GPLv3 cutoff, so every
macOS user's stop-feedback hook fails silently with the regression.
`read -r -d ''` reads until NUL (never present in the body) and
populates the variable as a side effect, returning non-zero at EOF —
hence `|| true`. No `\$(...)` involved, so the parser never has to
scan the heredoc body for matching delimiters. Robust against future
copy-edits that change apostrophe parity.
Verified:
- `bash -n` clean on /bin/bash 3.2.57 (Darwin 25.5.0 arm64) and
Homebrew bash 5.x
- Runtime: feeds minimal HOOK_INPUT, exits 0, produces 72 lines of
feedback text on stdout, empty stderr
- Placeholder substitutions `__PUA_PLUGIN_ROOT__`,
`__PUA_SESSION_PATH__`, and `"flavor":"阿里" → "flavor":"\$_ACTUAL_FLAVOR"`
all still execute correctly
Fixes#166. Closes#165.
Credit to @ShunmeiCho who diagnosed the exact root cause and proposed
this exact fix in #166's comments.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Shell builtins like `eval`, `test`, and `spec` were treated as path
candidates because `path_candidates` yielded every token unconditionally,
which then matched `(^|/)(evals?|tests?|spec|...)(/|$)` and flagged
innocuous commands as scoring-adjacent.
Repro:
eval rg -n $opts '...' . 2>/dev/null
-> Grader gaming risk. Target: eval
Fix: require a candidate to contain a directory separator or end in a
file-extension suffix before yielding it. Embedded paths inside quoted
code (e.g. `tests/fixtures.json` in a python -c string) are still picked
up by the existing path-regex pass, and real path args like
`evals/runner.sh` are unchanged.
Adds four regression cases:
- bare `eval`, `test`, `spec` no longer fire as path candidates
- a mutating `sed -i ... evals/runner.sh` still asks approval (positive control)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary:
- Treat /contribute.html and /contribute as first-class SPA routes so
GitHub OAuth callback and documented upload links land on the uploader.
- Switch the browser uploader from multipart/base64 to raw JSONL POSTs
with metadata headers, while keeping JSON file_data and multipart
compatibility on the API.
- Add static upload-flow gates and Vitest coverage for authenticated
upload sanitization, R2 writes, and D1 metadata recording.
- Bump plugin and marketplace versions to 3.4.4.
Rationale:
- The upload path had two breakpoints: documented /contribute.html links
rendered the homepage because routing only checked hash, and multipart
browser uploads could be stripped by proxy/custom-domain behavior.
- Raw JSONL avoids multipart body loss and avoids base64 size/memory bloat
for large session files.
Tests:
- bash evals/test-upload-flow.sh
- bash evals/test-release-consistency.sh
- npm --prefix landing test -- --run src/test/upload-function.test.ts
- static eval suite: governance, feedback auth, heartbeat, integrity
guard, issue regressions, Microsoft flavor, platform compat, loop hook,
Windows Python hooks, YAML frontmatter
- npm --prefix landing run build
- ./landing/node_modules/.bin/tsc -p pi/pua/tsconfig.json
- git diff --check
Co-authored-by: Codex <codex@openai.com>
Summary:
- Make the initial uploads index migration use IF NOT EXISTS.
- Add a release consistency gate that rejects non-idempotent D1 CREATE
INDEX statements.
Rationale:
- Production D1 already had the old uploads index before Wrangler's
migration journal was used, so migrations apply could fail on 0001
before reaching newer migrations.
- Keeping migrations idempotent makes future Cloudflare release gates
mechanical instead of relying on manual SQL execution.
Tests:
- bash evals/test-release-consistency.sh
- git diff --check
Co-authored-by: Codex <codex@openai.com>
Summary:
- Add a silent SessionStart heartbeat hook that posts minimal anonymous
install activity to Cloudflare without emitting stdout, stderr, or
additionalContext.
- Add the Cloudflare Pages heartbeat endpoint, D1 migration, and
admin-only stats page at #/admin/heartbeats.
- Add heartbeat regression gates, release consistency checks, FAQ/design
notes, and bump marketplace/package versions to 3.4.3.
Rationale:
- Active-user measurement belongs in a hook-side mechanical gate, not in
the model prompt, so the worker context cannot narrate or leak the
telemetry behavior.
- Privacy gates disable the signal for offline mode, telemetry opt-out,
and feedback-off users before any local telemetry identity is created.
Tests:
- bash evals/test-heartbeat.sh
- bash evals/test-release-consistency.sh
- static eval suite: governance, feedback auth, integrity guard, issue
regressions, Microsoft flavor, platform compat, loop hook, Windows
Python hooks, YAML frontmatter
- bash evals/test-behavior.sh
- npm --prefix landing run build
- ./landing/node_modules/.bin/tsc -p pi/pua/tsconfig.json
- git diff --check
Co-authored-by: Codex <codex@openai.com>
Summary:
- Add shared Python launcher fallback, Git Bash path conversion, and JSON
config readers for hook configuration access.
- Route UserPromptSubmit, PostToolUse, SessionStart, Stop, and integrity
hooks through the shared config reader so /pua:off and feedback_frequency=0
work when only python is available.
- Add issue #159 Windows Git Bash regression coverage and bump all plugin
manifests to v3.2.8.
Rationale:
- Native Windows Python cannot open Git Bash /c/... paths directly, and some
Windows installations provide python without python3.
- The previous fallback treated config read failures as always_on=true, so
/pua:off could still inject PUA on frustration prompts.
Tests:
- for f in hooks/*.sh scripts/*.sh evals/*.sh; do bash -n "$f"; done
- bash evals/test-windows-python-hooks.sh
- bash evals/test-yaml-frontmatter.sh
- bash evals/test-release-consistency.sh
- bash evals/test-agent-governance.sh
- bash evals/test-pua-loop-hook.sh
- bash evals/test-integrity-guard.sh
- bash evals/test-behavior.sh
- bash evals/run-trigger-test.sh
- git diff --check
- python3 JSON parse check for plugin and hook manifests
Co-authored-by: Codex <codex@openai.com>
Summary:
- Add four context-isolated governance agents for action,
self-evaluation, verifier recommendation, and policy review.
- Bind each power to PUA cultural narratives: Alibaba/Musk for
execution, Huawei/Netflix/Jobs for review, ByteDance/JD for
verification, and Tencent/Amazon/Alibaba internal control for policy.
- Document the multi-agent topology in the PUA skill and harness
reference, inject it on SessionStart, add static agent governance evals,
and bump manifests to v3.2.7.
- Harden trigger evals to accept observable PUA behavior when Claude
applies pressure without emitting a Skill tool event before max-turns.
Rationale:
- v3.2.6 created mechanical hook gates; v3.2.7 separates the thinking
contexts too, so execution, review, verification, and environment
approval cannot collapse into one self-proving narrative.
- Cultural narratives provide role-specific pressure, but the agents still
remain advisory compartments under hook/external/human authority.
Tests:
- for f in hooks/*.sh scripts/*.sh evals/*.sh; do bash -n "$f"; done
- bash evals/test-yaml-frontmatter.sh
- bash evals/test-release-consistency.sh
- bash evals/test-agent-governance.sh
- bash evals/test-pua-loop-hook.sh
- bash evals/test-integrity-guard.sh
- bash evals/test-behavior.sh
- bash evals/run-trigger-test.sh
- git diff --check
- python3 JSON manifest validation
Co-authored-by: Codex <codex@openai.com>
Summary:
- Add harness governance reference for four-power separation,
task contracts, verifier-owned status, and memory boundaries.
- Add PreToolUse integrity guard for grader-gaming, solution
contamination, capability-abuse, status, CI, and memory risks.
- Add integrity-guard eval coverage and release gates, then bump
marketplace/plugin manifests to v3.2.6.
Rationale:
- PUA should not just pressure agents to try harder; it should make
it difficult to fake completion by modifying tests, verifier assets,
hidden answers, secrets, or persistent status/memory.
- The guard uses ask/deny decisions so ordinary code changes still
proceed while governance assets require verifier or human approval.
Tests:
- for f in hooks/*.sh scripts/*.sh evals/*.sh; do bash -n "$f"; done
- bash evals/test-yaml-frontmatter.sh
- bash evals/test-release-consistency.sh
- bash evals/test-pua-loop-hook.sh
- bash evals/test-integrity-guard.sh
- bash evals/test-behavior.sh
- bash evals/run-trigger-test.sh
- git diff --check
- python3 JSON manifest validation
Co-authored-by: Codex <codex@openai.com>
Summary:
- Replace the one-line confidence loop with an explicit Confidence Gate
that enumerates claims, finds risks, fixes or discloses them, and
reruns evidence before delivery.
- Make pua-loop Oracle verification portable on macOS/Linux and add
hook smoke tests for verified, rejected, and quoted verify commands.
- Fix trigger/behavior evals so they use portable timeouts, isolated
PUA config, a neutral workspace, and the Claude CLI stream-json
verbose requirement.
- Tighten PUA skill and slash-command descriptions to avoid triggering
on normal first-attempt coding or information requests.
- Bump marketplace/plugin manifests to v3.2.5 with an updated Claude
marketplace changelog summary.
Rationale:
- The previous strategy relied on vague "100% confidence" wording and
left the verification system itself vulnerable to false failures on
macOS, local user config leakage, and workspace-biased false triggers.
- Confidence is now defined as evidence-backed gates: all runnable
checks pass, known high-risk issues are fixed, and residual risks are
disclosed.
Tests:
- bash -n hooks/*.sh scripts/*.sh evals/*.sh
- bash evals/test-yaml-frontmatter.sh
- bash evals/test-release-consistency.sh
- bash evals/test-pua-loop-hook.sh
- bash evals/test-behavior.sh
- bash evals/run-trigger-test.sh
Co-authored-by: Codex <codex@openai.com>
- Add "信心闭环" self-verification loop before delivery in pua SKILL.md
- Fix puav2→pua path references in shot SKILL.md
- Fix flavor detection in stop-feedback hook (read from config)
- Bump version 3.2.3 → 3.2.4 across all plugin manifests
Agent files referenced "同目录下的 references/p{7,9,10}-protocol.md"
which resolves to agents/references/ — a directory that doesn't exist.
Protocol files live at skills/pua/references/. Now uses Glob pattern
(**//pua/skills/pua/references/*.md) consistent with SKILL.md discovery.
Reported-by: NLPM automated audit (#156)
On Windows, usernames with spaces (e.g. /c/Users/Lewis Dong/) cause
bash to split the path. All 7 hook commands now use escaped quotes
around the variable expansion.
Fixes#148
State file was written to a single global path (~/.claude/pua/loop-active.md),
causing two Claude Code instances in different directories to overwrite each
other's loop state. Now uses cwd hash in filename (loop-<hash>.md) so each
project directory has an independent loop instance.
Fixes: multiple users reporting task A executing task B's commands when
running pua-loop in two directories simultaneously.
Landing page was missing src/main.tsx (Vite entry) and
src/components/motion-primitives.tsx (13 animation primitives).
Build now succeeds and deploys to openpua.ai.
- skills/p7, p9, p10 SKILL.md referenced `references/xxx.md` but the
files live under `skills/pua/references/` — fix to `../pua/references/`
- Add evals/test-yaml-frontmatter.sh regression test for #147
- Bump version to 3.2.2
Ship the #144 fix (ccaa9c0) to all marketplace users via synchronized
version bump across all distribution channels.
Version bumps:
- .claude-plugin/plugin.json: 3.2.0 → 3.2.1
- .claude-plugin/marketplace.json: 3.2.0 → 3.2.1
- .codebuddy-plugin/plugin.json: 3.2.0 → 3.2.1
- .codebuddy-plugin/marketplace.json (top + plugins[0]): 3.2.0 → 3.2.1
- landing/dist/index.html: add v3.2.1 hotfix entry in zh/en/ja changelogs
Fix content (already in main @ ccaa9c0):
- hooks/flavor-helper.sh: initialize PUA_LANGUAGE="" before the config
file existence check so first-run users whose shells run with
set -euo pipefail don't trip 'PUA_LANGUAGE: unbound variable'.
No functional changes beyond #144 — hero brand (v3.2) intentionally
preserved; only manifest-level patch version increments so marketplace
clients will pick up the fix on next sync.
get_flavor() only assigned PUA_LANGUAGE inside the `if [ -f "$config" ]`
branch. Callers (frustration-trigger.sh / pua-loop-hook.sh /
failure-detector.sh) run under `set -euo pipefail`, so on first-run
users without ~/.pua/config.json, line 179's `[ "$PUA_LANGUAGE" = "zh" ]`
tripped `unbound variable` and exit 1 — throwing a red hook error on
every UserPromptSubmit.
Fix: declare `PUA_LANGUAGE=""` unconditionally at the top of get_flavor
so it always has a definition regardless of config file presence.
E2E verified: `set -euo pipefail; source flavor-helper.sh; get_flavor`
with no config → exits 0 instead of trapping.
Closes#144
Reported-by: @maj1999 (thorough repro + root-cause analysis)
.codebuddy-plugin/{plugin,marketplace}.json: bump versions
- plugin.json: 1.2.0 → 3.2.0
- marketplace.json top-level: 1.0.0 → 3.2.0
- marketplace.json plugin entry: 1.2.0 → 3.2.0
Keeper Test finding: codebuddy manifest was last touched 1 month ago
(cc50d9f). It's a pure manifest wrapper (no independent code), so the
version must track the main project; leaving it at 1.x made the
CodeBuddy marketplace card claim the plugin hadn't shipped since
January. Descriptions stay platform-neutral (no Claude-specific slash
commands) since CodeBuddy command mapping may differ.
- L0-L5 escalation: table → full paragraphs with authentic long-form nagging
- L2: 1 line → 5 sentences of genuine 正式唠叨
- L3: 1 line → 8 sentences of 翻旧账 with childhood references
- L4: 1 line → 6 sentences of 社会比较
- L5: full 情感核弹 with 假装放弃 4-step protocol
- 碎碎念话术库: all entries expanded 3-4x
- 抗借口表: all entries doubled in length
- 96 → 117 lines, flavor density significantly increased
Root cause: Cloudflare custom domain sometimes strips POST body (POST→GET
rewrite), causing request.json() to throw 'SyntaxError: Unexpected end of
JSON input' which was caught and shown to user verbatim.
Fix:
- Read body as text first, return 400 with clear message if empty
- Parse JSON manually to give specific parse error vs generic crash
- Bump compatibility_date 2024-12-01 → 2025-04-01 (fixes some CF edge cases)
- Add mama route to commands/pua.md router (was missing entirely)
- Create commands/mama.md for /pua:mama direct invocation
- Update argument-hint and trigger list to include mama
- Fixes: /pua mama and /pua:mama now both work correctly