The SessionStart nudge built a statusLine command by interpolating the
plugin's __dirname path into a double-quoted shell string. A clone path
containing shell metacharacters (quotes, &, $, backtick, ;) could break
out when the suggested command later runs via the statusline shell.
Low severity in practice: the path is the install location, so triggering
it requires installing into a maliciously-named directory, i.e. the
attacker already controls the filesystem. Hardening it anyway.
Gate the snippet behind isShellSafe() (allowlist of ordinary path chars,
allowing : \ / for normal Windows and POSIX paths). Unsafe paths fall
back to a manual-setup instruction instead of an embeddable command. An
allowlist beats a per-shell escaper, which is its own edge-case bug farm.
Refs #200
#213 guarded cleanup with a flag + named function + process.once. But
fs.rmSync with force:true already no-ops on a missing path, so the guard
and the explicit end-of-file call are unnecessary. Collapse to a single
process.on('exit') handler.
Refs #204
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
The deactivation check matched the phrase anywhere in the prompt, so an
ordinary request like "add a normal mode toggle" silently turned ponytail
off for the rest of the session. Match the whole message instead (trimmed,
case-insensitive, trailing punctuation ignored) through a shared helper used
by both the Claude/Codex hook and the pi extension.
Fixes#161
Adds .openclaw/skills/ (ponytail + review/audit/debt/help) generated from the canonical skills/ (verbatim body, no drift), a generator script, and a drift test. Verified live: loads as Ready in OpenClaw 2026.6.6.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ships the previously-missing /ponytail-help adapter files (commands/ponytail-help.toml, .opencode/command/ponytail-help.md) and adds tests/commands.test.js, a parity guard asserting every pi-registered command has both adapter files. Thanks @hooni0918.
* Add GitHub Copilot plugin and marketplace manifests for Ponytail
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add Copilot hook adapters and plugin data runtime precedence
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Document Copilot plugin install flow and instruction fallback mode
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix Copilot hooks for native output context and state-only mode tracking
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs: add Copilot CLI namespaced command examples
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Collapse Copilot hooks into shared activate/mode-tracker
The Copilot hook files duplicated ponytail-activate.js and
ponytail-mode-tracker.js, differing only in output shape. Move that
difference into writeHookOutput (isCopilot branch) and point
copilot-hooks.json at the shared hooks. Deletes both forks (-73 lines).
Refs #1
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Align Copilot manifest version to 4.4.0 with cross-manifest parity test
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Make Copilot and Codex host detection exclusive in runtime output routing
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add Copilot debt command validation with a pull request acceptance checklist
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Removed PR template
* Drop tautological copilot command-form test
The namespaced-form assertion built '/ponytail:ponytail-debt' from two
constants and compared it to itself — it tests string concatenation, not
wiring. The file-exists check above already catches a renamed manifest.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat: refine ruleset from a full-project field review
A reviewer ran ponytail across a 9-phase rewrite (protocol, PC app, simulator,
RPi daemon, ESP32 firmware) and flagged three gaps. All three land in SKILL.md
and propagate to AGENTS.md + the rule copies:
- Promote the one-runnable-check rule to a headline ("Lazy code without its
check is unfinished"), enforced as a check-rule-copies invariant.
- Hardware carve-out in "When NOT to be lazy": a real device is never the spec
ideal (clock drift, sensor offset), leave the calibration knob.
- Clarify the Output rule: explanation the user explicitly asked for is not
debt, only unrequested prose is.
Fallback instructions kept in sync. Rule-copy check + tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test: add a behavior gate proving the refinements actually fire
The refinements were verified as injected text, but injected != behavioral.
This adds a behavior eval that probes each refined rule on a task that should
trigger it:
- hardware -> does the output leave a calibration knob?
- explanation -> when a write-up is explicitly requested, is it given in full?
- onecheck -> is a runnable check left behind?
benchmarks/behavior.yaml runs the probes (baseline vs ponytail arm); the
grader benchmarks/behavior.js is proven by tests/behavior.test.js (8 cases,
RED/GREEN, no API key, runs in CI). Live-confirmed: the model under the
current ruleset passes all three gates, graded by the same grader.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ponytail-activate.js and ponytail-runtime.js hardcoded ~/.claude for the
flag file and settings lookup, ignoring CLAUDE_CONFIG_DIR. Add a shared
getClaudeDir() to ponytail-config.js and use it in both. Regression test
added to hooks.test.js.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(benchmarks): add correctness assertion - proves less code is not broken code
The existing benchmark measures lines-of-code (loc.js) but never checks
whether the generated code actually works. This adds a functional
correctness gate (correctness.js) that extracts code from fenced blocks
and runs per-task checks:
- email validator: spawns Python, asserts accept/reject on 5 inputs
- debounce: spawns Node, asserts delayed execution + reset on re-call
- csv sum: spawns Python with a test CSV, asserts correct total (351)
- countdown (React): structural check (useState + useEffect + decrement)
- rate limiter (FastAPI): structural check (limit logic + framework usage)
12 unit tests (node:test) cover good/bad outputs for every task plus the
unknown-task edge case. Existing tests and rule-copy checks unaffected.
* fix: address review feedback
- csv check: use regex lookaround instead of substring match to prevent
false positives (e.g. 13510 containing '351')
- ratelimit: fix operator precedence in block finder by adding parens
around the || inside the !b.lang guard
- README: note that React/FastAPI checks are structural only, add
prerequisites section (Python 3, pandas, Node.js 18+)
- test: add regression test for csv substring false positive
* feat: add Gemini CLI support
Add a thin Gemini CLI extension adapter (closes#22). The manifest points
contextFileName at the existing AGENTS.md for always-on rules and reuses the
repo's commands/*.toml (/ponytail, /ponytail-review) and skills/, which Gemini
CLI auto-discovers — no rule text is duplicated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test: read manifest inside cases for a clean RED
The manifest was parsed at module scope, so reverting the adapter crashed the
whole test file at load (ENOENT) and collapsed all four cases into one
unreadable failure. Read it inside each case via a helper that asserts existence
first, so a missing or malformed manifest surfaces as clear per-case assertion
failures instead of a stack trace.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On Windows the lifecycle hooks run via PowerShell, which does not expand
cmd.exe-style %CLAUDE_PLUGIN_ROOT%. The path was passed literally, so the hook
launcher could not find the script and both SessionStart and UserPromptSubmit
failed with exit code 1 (issue #19). Switch the two commandWindows entries to
$env:CLAUDE_PLUGIN_ROOT, keeping the working node + .js invocation.
Add a regression test that rejects cmd.exe %VAR% syntax in commandWindows and
asserts every hook command points at a script that actually ships in hooks/.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>