Commit Graph

44 Commits

Author SHA1 Message Date
krish rathi a2712bc830
fix: detect VS Code Copilot via CLAUDE_PLUGIN_ROOT fallback (#528) (#579)
VS Code Copilot never sets COPILOT_PLUGIN_DATA, only CLAUDE_PLUGIN_ROOT
(pointed at an .vscode/agent-plugins/... install path), so isCopilot was
false and ponytail assumed native Claude Code — emitting the statusline
setup nudge, which VS Code Copilot doesn't read. Also guard stateDir:
it was built from the same unset COPILOT_PLUGIN_DATA whenever isCopilot
resolved via this new fallback, so fall back to getClaudeDir() there too.
2026-08-08 00:37:09 +03:00
Prayag Raj cc37a5d581
fix: drop commandWindows from hooks.json for Claude.ai marketplace validation (#593) (#601)
commandWindows is not part of the supported hooks schema for the Claude.ai
plugin marketplace validator, which rejects it as an unknown field. The
shared `command` field already runs cross-platform: Claude Code expands
${CLAUDE_PLUGIN_ROOT} before the shell sees it, VS Code Copilot ignores
commandWindows and runs `command` through PowerShell on Windows, and `node`
is natively available in both bash and PowerShell.

Update the regression test to assert commandWindows is absent and remove
the now-unused CMD_VAR_SYNTAX guard. Fix comment placeholder syntax
({{CLAUDE_PLUGIN_ROOT}} → ${CLAUDE_PLUGIN_ROOT}) and narrow test name.
2026-08-08 00:34:41 +03:00
gglucass 14a0d79548
Emit statusline setup nudge at most once per user (#483)
Writes a flag file the first time the STATUSLINE SETUP NEEDED nudge fires, and skips it on later sessions so it stops nagging every start. Includes a regression test that a second session stays silent.
2026-07-10 04:44:32 +02:00
Dhruv Garg 0cdd11fe0c
fix: stop filterSkillBodyForMode from swallowing rule bullets that start with a mode word (#571)
filterSkillBodyForMode is the shared filter every host adapter (Claude
hooks, Pi, OpenCode, the MCP server, Hermes) uses to trim the ponytail
skill body down to the active intensity. Its worked-example detection
matched any bullet shaped "- label: text" and treated it as a
mode-specific example whenever the label happened to normalize to a
mode name (lite/full/ultra/off) -- silently dropping it in every other
mode.

The current SKILL.md doesn't trip this today (its only such bullets are
the three real worked examples), but it's a landmine: an ordinary rule
bullet added later that happens to start with "Full:" or "Lite:" would
vanish from every other mode's instructions with no warning, in the one
code path every adapter shares. Confirmed with a real bullet:

  filterSkillBodyForMode('- Full: real rule text.', 'ultra')

silently drops the line before this fix.

All three real worked examples are quoted (`- lite: "..."`); ordinary
rule bullets aren't. Required the quote so only genuine examples are
mode-filtered, and added a regression test plus fixed an existing test
whose synthetic fixture used unquoted examples that don't match the
real file's format.
2026-07-10 03:04:06 +02:00
DietrichGebert b6c04480c0
fix: narrow the ponytail: marker to real corner-cuts, keep the prefix (#120) (#577)
The ponytail: comment marker was being over-applied to trivial code (#120).
Narrow the rule so only deliberate simplifications that cut a real corner with a
known ceiling get a comment, and keep the `ponytail:` prefix that /ponytail-debt
greps for. Applied across all rule copies, SKILL.md, AGENTS.md, the fallback
instructions, both command files, and the Qoder/OpenCode copies that #491
missed; .openclaw regenerated. helpers.test.js now pins the prefix so it cannot
silently drop again.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 02:52:02 +02:00
DietrichGebert 65db9025a7
fix: reject review as a default mode in pi-extension and config (#576)
review is a session-only mode set by /ponytail-review, never a persistent
default. The Claude/Codex mode-tracker already enforces this (#377), but two
paths did not:

- pi-extension /ponytail default review was accepted, persisting review as the
  default, so the next session started in review with a blank status-bar icon.
- getDefaultMode accepted review from PONYTAIL_DEFAULT_MODE or a stale config.

Route default validation through RUNTIME_MODES (off/lite/full/ultra) in
getDefaultMode, writeDefaultMode, and the pi-extension default command. review
stays valid as a session mode. Adds regression tests for all three paths.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 01:48:24 +02:00
saime428 3465b1a3ca
Fix Codex hook output schema (#573) (#574)
Restore hookSpecificOutput so strict Codex schemas accept SessionStart, UserPromptSubmit, and SubagentStart context.

Co-authored-by: saime <2286263079@qq.com>
2026-07-10 01:16:30 +02:00
Sandeep pandey 2ba0262111
fix: drop bash-only `exec` from hooks so they run under PowerShell (#527, #569) (#572)
The three shared `command` fields in claude-codex-hooks.json used
`exec node "..."`. `exec` is a bash/zsh builtin with no PowerShell
equivalent. Some hosts run the `command` field through PowerShell on
Windows regardless of the PowerShell-native `commandWindows` field:
VS Code Copilot always does (it never reads `commandWindows`), and
native Claude Code launched from Git Bash was reported doing the same
(#569). PowerShell then fails on the first token with
CommandNotFoundException, so every SessionStart / SubagentStart /
UserPromptSubmit hook errors out on Windows.

Plain `node "..."` runs natively in both bash and PowerShell. Dropping
`exec` only gives up the POSIX optimization of replacing the wrapper
shell with node; the wrapper-process pileup that #461 added `exec` to
avoid is already handled by each hook's stdin self-exit guard
(#443/#477), so there is no regression on Codex/zsh.

Updated the hooks-windows regression test to assert the shell-agnostic
form instead of requiring `exec`.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 01:06:07 +02:00
Aly Dhedhi 33c00d3d7a
fix: Codex CLI SessionStart additionalContext at top level (#505) (#508)
* fix: Codex CLI SessionStart additionalContext at top level (#505)

Codex CLI expects additionalContext at the top level of the hook output,
not nested under hookSpecificOutput. Flattened the Codex branch of
writeHookOutput so the injected instructions actually reach the agent.

Fixes #505

* Merge main; move #99 Codex @ponytail report assertion to top-level additionalContext (#505)

---------

Co-authored-by: Emeriko <dietrich.gebert@gmail.com>
2026-07-09 18:03:43 +02:00
krish rathi 1715abccac
feat(hooks): opt-in agent-type scoping for SubagentStart injection (#… (#522)
* feat(hooks): opt-in agent-type scoping for SubagentStart injection (#506)

The SubagentStart hook injected the ~1,300-token persona into every
subagent, including read-only search agents where it is dead weight.
Consumers could not narrow a plugin-provided hook from their own
settings, so the scoping has to live here.

Add an opt-in allowlist: PONYTAIL_SUBAGENT_AGENTS (comma-separated env
var) or a subagentAgents array in config.json, matching getDefaultMode
precedence. The hook now reads agent_type from stdin and skips
injection only on a positive mismatch; unconfigured or missing
agent_type keeps the inject-everywhere behavior from issue #252. Stdin
reading follows the never-hang contract from issue #443.

Closes #506

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Address review: regex matcher, stdin-free default path, exit on skip

Rework the SubagentStart scoping per review on #522:

- Replace the comma-separated PONYTAIL_SUBAGENT_AGENTS allowlist with a
  regex PONYTAIL_SUBAGENT_MATCHER, matching the reviewed API on #507 and
  the PONYTAIL_DEFAULT_MODE naming convention.
- Default path (no matcher) injects synchronously and never reads stdin,
  fixing the #443-class Windows regression where the persona was delayed
  until a 1s timeout.
- On a definite agent_type mismatch the hook now process.exit(0)s
  immediately instead of lingering until the fallback timer.
- Drop the config-file (subagentAgents) source, so no config read on the
  subagent hot path; env-only keeps it lazy (YAGNI).
- Revert the unrelated tests/hooks-windows.test.js change; the scoped
  path mirrors the mode-tracker never-hang contract already tested there.

Fail open on unparseable input, missing agent_type, invalid regex, or
timeout so scoping never silently drops the persona (#252).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Emeriko <dietrich.gebert@gmail.com>
2026-07-09 17:23:07 +02:00
shixiaoding 83493b97bc
feat: add Qoder hooks support (UserPromptSubmit + PreToolUse) (#529)
* feat: add Qoder hooks support (UserPromptSubmit + PreToolUse)

- Add isQoder detection via QODER_SESSION_ID in ponytail-runtime.js
- Add UserPromptSubmit double-duty: auto-activate + ruleset injection
- Add PreToolUse hook with task|Task matcher for subagent injection
- Create hooks/qoder-hooks.json reference template
- Update .qoder-plugin/plugin.json with hooks field
- Add .qoder/rules/ponytail.md for per-project rules
- Add comprehensive tests for Qoder runtime behavior
- Update README.md and docs/agent-portability.md

* check-versions: enforce .qoder-plugin/plugin.json version too

---------

Co-authored-by: shiding <shiding@ruigushop.com>
Co-authored-by: Emeriko <dietrich.gebert@gmail.com>
2026-07-09 17:13:59 +02:00
Philipp Metzler d5f4ffdd65
pi-extension: make startup notification opt-out via quietStartup (#308)
The status bar already shows the active ponytail mode at all times, but the
startup toast is also a deliberate trust-heartbeat for a behavior-modifying
extension (it confirms ponytail loaded, in which mode, and survives session
resume where the persisted mode may differ from the default). Default
behavior is preserved.

Users who prefer a clean startup can now silence it via:

  - config:  ~/.config/ponytail/config.json -> { "quietStartup": true }
  - env var: PONYTAIL_QUIET_STARTUP=1

Resolution order mirrors the existing defaultMode setting: env var > config
file > default (false). Added readQuietStartup export and a test covering
all three layers.

Co-authored-by: blissini <blissini@users.noreply.github.com>
Co-authored-by: Emeriko <dietrich.gebert@gmail.com>
2026-07-09 12:29:12 +02:00
Jay shah 8e69b4a55f
feat: report active level on bare /ponytail instead of resetting to default (#99)
* fix: report active level on bare /ponytail instead of resetting to default

* remove . ponytail-active

---------

Co-authored-by: Emeriko <dietrich.gebert@gmail.com>
2026-07-09 04:15:43 +02:00
Lakshya Sharma 187acc2add
ux: make the ultra statusline badge stand out (#192)
Every mode rendered in the same green, so ultra (the high-intensity mode
that deletes and challenges requirements) looked identical to lite/full at
a glance. Give the ultra badge an amber color so it's obvious you're in it.
The level is still in the badge text, so color stays a redundant cue, not
the only one.
2026-07-09 03:01:14 +02:00
DietrichGebert 1b2760d384
feat: hide the pi status bar indicator while keeping ponytail active (#324) (#544)
Add getHideStatus() to the shared config resolver: PONYTAIL_HIDE_STATUS (any
truthy value; 0/false/empty mean don't hide) or config.hideStatus === true, with
BOM-stripped config reads. The pi extension caches it at init and re-reads on
session_start, and syncStatus() returns early when set, so the indicator is
suppressed while the ruleset still injects every turn.

Reconciles #511 and #328: takes #328's caching and ruleset-stays-active test,
#511's BOM-safe config read, a stricter env check (0/false do not hide), and
drops #328's writeDefaultMode change which already landed in #514.

Closes #324

Co-authored-by: dhedhialy <91044156+dhedhialy@users.noreply.github.com>
Co-authored-by: sanmaxdev <46221775+sanmaxdev@users.noreply.github.com>
2026-07-07 04:06:37 +02:00
DietrichGebert bed76c52fa
feat: add /ponytail default <mode> to persist the default (#329) (#541)
Claude Code had no way to change the persistent default from within a session:
plain /ponytail switches are session-scoped by design ('sticks until session
end'), and only env/config could set the default. So /ponytail off never
survived a restart, which is what #329 hit.

Add /ponytail default <mode> to the mode-tracker hook, matching pi's existing
command: it writes defaultMode to config (survives restart) and does NOT touch
the current session flag, keeping plain switches transient. review is rejected
as a default (#377); only off/lite/full/ultra are accepted. Test covers persist,
transient-switch-does-not-persist, and review-rejected.

Closes #329
2026-07-07 03:09:25 +02:00
Lakshya Sharma f790cebb57
fix: statusline setup nudge honors CLAUDE_CONFIG_DIR (#338)
The activation hook detects a missing statusline in getClaudeDir()/settings.json
(CLAUDE_CONFIG_DIR-aware) but the nudge text told the user and the agent to add
the statusLine config to ~/.claude/settings.json. With CLAUDE_CONFIG_DIR set,
that is the wrong file: Claude Code never reads it and the badge never turns on.
Point the nudge at the settingsPath the hook already computed, in both the
shell-safe and manual branches.

Fixes #250
2026-07-07 02:54:46 +02:00
Aly Dhedhi 988428d510
fix: writeDefaultMode merges config instead of overwriting (#490) (#514)
writeDefaultMode was writing a fresh { defaultMode } object, silently
dropping any other fields in config.json. Now it reads the existing config,
updates only defaultMode, and writes it back preserving all other fields.

Fixes #490
2026-07-07 02:44:56 +02:00
DietrichGebert 9ec4fb8b54
fix: strip UTF-8 BOM before parsing config.json (#478)
getDefaultMode() parsed the config with JSON.parse and no BOM strip, so a
config.json saved with a UTF-8 BOM (common on Windows) failed to parse and the
user's defaultMode was silently ignored. Strip the BOM first, matching the
existing /^\uFEFF/ pattern in ponytail-activate.js, check-versions.js, and
ponytail-mode-tracker.js.

Closes #375

Co-authored-by: nanaubusiness <195150264+nanaubusiness@users.noreply.github.com>
2026-07-02 00:25:27 +02:00
DietrichGebert 7e6eca6e29
fix: prevent Windows session freeze from stdin EOF deadlock (#477)
On Windows, Claude Code runs the UserPromptSubmit hook through a PowerShell
if {} wrapper that can swallow the piped prompt JSON, so stdin 'end' never
fires and ponytail-mode-tracker.js blocks forever, freezing the session.

Make the hook non-blocking: extract the handler into finish() guarded by a
done flag, and add a 1s unref()'d fallback timeout plus the existing stdin
error handler that both call finish() then exit 0. unref() means zero added
latency on the normal path where 'end' fires first. Adds a regression test
that spawns the hook with a never-closing stdin pipe and asserts self-exit.

Reconciles #453 onto main after #227 (stdin error handler) and #474 (exec
node hooks) landed.

Closes #443

Co-authored-by: Hasnan42 <154899774+Hasnan42@users.noreply.github.com>
2026-07-02 00:18:01 +02:00
Shubham Sharma b8f6fbe92a
fix: handle stdin error in ponytail-mode-tracker to avoid uncaught crash (#147) (#227)
Without a stdin 'error' listener, a broken pipe or parent-process crash
causes Node to emit an unhandled error event on the stream, which becomes
an uncaught exception and crashes the hook with a non-zero exit code.

Add process.stdin.on('error', () => process.exit(0)) before the data/end
listeners. Exiting 0 on error keeps the hook contract: always exit cleanly,
never block session start.

Fixes #147
2026-07-02 00:10:24 +02:00
Sangeeth Thilakarathna 7eda70d30f
fix: exec lifecycle hook commands (#474)
Co-authored-by: sanmaxdev <sanmaxdev@users.noreply.github.com>
2026-07-02 00:06:21 +02:00
Shane McCarron b9fa564429
feat: inject ponytail ruleset into subagents via SubagentStart hook (#254)
* feat: inject ponytail ruleset into subagents via SubagentStart hook

SessionStart additionalContext is parent-thread only, so every Task-spawned
agent ran ponytail-unaware. Add a SubagentStart hook that injects the active
ruleset into each subagent, reusing getPonytailInstructions. Native Claude
needs the hookSpecificOutput JSON form (not raw stdout), so writeHookOutput
grows a SubagentStart branch; readMode exposes the live flag.

Workflow- and team-spawned coverage is undocumented upstream; verify in a
fresh session once installed.

Closes #252

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1dMag33yz1Kf1jS24Aira

* fix: address QA round 1 — make hook tests hermetic + cover Codex SubagentStart

QA round 1 (panel + deepseek-v4-pro) found two real test issues:
- The new subagent block (and the pre-existing claudeEnv block) used a no-op
  `delete env.PLUGIN_DATA`; run() spreads process.env, so a PLUGIN_DATA /
  COPILOT_PLUGIN_DATA leaked from the shell would steer writeHookOutput into the
  codex/copilot branch and silently mis-fire the native-Claude assertions.
  Fixed at the source: neutralize both vars once at the top, like CLAUDE_CONFIG_DIR.
- The Codex SubagentStart branch (claude-codex-hooks.json is shared by both plugin
  manifests) had zero coverage. Added a codex-path assertion.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1dMag33yz1Kf1jS24Aira

* fix: use PowerShell-safe command form for SubagentStart hook

Match the post-#265 'node ...; exit 0' form used by the sibling hooks. The old 'command -v node ... || exit 0' form fails tests/hooks-windows.test.js (POSIX-guard and non-blocking asserts) once this branch merges onto current main.

---------

Co-authored-by: Shane McCarron <shane.mccarron@corvexconnect.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Emeriko <dietrich.gebert@gmail.com>
2026-06-24 04:19:14 +02:00
Haoqian 2b426c6ac9
fix: make shared hooks parse in PowerShell (#265) 2026-06-24 01:42:40 +02:00
DietrichGebert dedc97ca7c
fix: comprehension-first guard + reuse rung (#245, #217) (#253)
#245 "Dangerously lazy": add an operational "fix the root cause, not the
symptom" directive — grep every caller of the function you touch and fix the
shared function once (the smaller diff). Validated on the agentic benchmark: on
a shared-helper bug-fix trap, baseline fixes the root cause 1/6 while ponytail
does 6/6 on both Sonnet 4.6 (the model the issue was filed on) and Opus 4.8,
verified by reading the produced code. Plain prose ("trace the flow") did not
move it; the actionable, lazy-framed directive did.

#217 "Missing rung": add ladder rung 2 "Already in this codebase? Reuse it,
don't re-write it." Propagated across SKILL.md, AGENTS.md, all agent mirror
copies, the hook fallback, and both READMEs (check-rule-copies passes).

Benchmark: 4 new deterministic quality-tier tasks (reuse-slug, reuse-money,
trace-transfer, trace-amount) with selftest-proven good/bad refs; harness gains
multi-file seed support in --selftest, distinctive-behaviour reuse detection,
and counts in-file __main__/demo() self-checks as test LOC (not source bloat)
for surgical tasks. Full writeup in
benchmarks/results/2026-06-22-issue-245-217-comprehension.md.

Also carries the in-progress todo-null benchmark task already present in the
working tree.

Co-authored-by: Dietrich Gebert <dgebert@Dietrichs-MacBook-Pro.local>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 23:30:05 +02:00
DietrichGebert 215777d835
fix: don't embed shell-unsafe install paths in statusline setup nudge (#224)
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
2026-06-21 01:58:21 +02:00
Max Felker II 0403c4dd50
Fix for #168: Don't write output on SessionStart for Copilot (#181)
* Added isCopilot flag from runtime and check that in the hooks

* When going into off mode, check if codex or copilot
2026-06-19 10:50:55 +02:00
Sonai Biswas 4dad14fac5
Avoid Gemini loading Claude hook events (#139) 2026-06-19 00:24:43 +02:00
Lakshya77089 795ec0ee36
fix: statusline reads flag from CLAUDE_CONFIG_DIR, not just ~/.claude (#34 follow-up) (#154)
Issue #34 made the hooks honor CLAUDE_CONFIG_DIR when writing the mode flag
($CLAUDE_CONFIG_DIR/.ponytail-active), enforced by tests/hooks.test.js. But
both statusline scripts still hardcoded $HOME/.claude/.ponytail-active, so any
user with CLAUDE_CONFIG_DIR set gets no badge — or a stale mode from a
pre-migration ~/.claude flag that never updates again.

Make both scripts resolve the flag the same way getClaudeDir() does: prefer
CLAUDE_CONFIG_DIR, fall back to ~/.claude. The fallback branch is identical to
the previous behavior, so unset-env users are unaffected. Also corrects the
now-inaccurate path comment in the activation hook header.
2026-06-18 22:50:17 +02:00
Lakshya77089 c30854118e
fix: guard final writeHookOutput against stdout EPIPE in ponytail-activate (#149) (#152)
The final writeHookOutput('SessionStart', ...) call was the only operation
in the file outside a try/catch. writeHookOutput ends in a bare
process.stdout.write, so a closed stdout / broken pipe (EPIPE) at hook exit
throws uncaught and crashes the hook with a non-zero exit code. Wrap it to
match the file's existing never-block-session-start posture.
2026-06-18 22:50:13 +02:00
Lakshya77089 a3bc7db722
fix: strip UTF-8 BOM before parsing settings.json in ponytail-activate (#148) (#151)
settings.json written by Notepad or VS Code on Windows can carry a
UTF-8 BOM. JSON.parse then throws SyntaxError, the outer catch swallows
it, hasStatusline stays false, and the statusline setup nudge is never
emitted.

Strip the leading BOM before parsing, matching the existing handling in
ponytail-mode-tracker.js. (#96 added a null guard but not BOM stripping.)
2026-06-18 22:50:10 +02:00
Lakshya77089 53fd1e850e
fix: only deactivate on a standalone "stop ponytail" / "normal mode" (#162)
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
2026-06-18 22:50:03 +02:00
DietrichGebert d676635325
fix: hooks degrade gracefully when node is not on PATH (#57)
Claude Code runs hooks via a non-interactive /bin/sh. On setups where node
isn't on that shell's PATH (Nix/nix-darwin, nvm, fnm), every prompt errored
with "/bin/sh: node: command not found". Guard each hook command so it runs
node only when present and exits 0 otherwise, no more per-prompt noise. The
slash-command skills are unaffected; only the always-on activation needs node.
Document the requirement in the README install section.

Closes #51.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 11:23:59 +02:00
Max Felker II c1c80f3cc8
Adding support for Copilot Marketplace plugin (#47)
* 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>
2026-06-15 11:02:59 +02:00
DietrichGebert f3da910b4f
feat: refine ruleset from a full-project field review (#39)
* 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>
2026-06-15 02:02:50 +02:00
DietrichGebert 01578c0cd4
fix: honor CLAUDE_CONFIG_DIR in hooks (#37)
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>
2026-06-15 00:00:01 +02:00
Ben Younes 147bcfd621
fix: use PowerShell $env: syntax for Windows hook paths (#26)
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>
2026-06-14 17:39:59 +02:00
Abbas Pardawala 1556f10bc6
fix: use CLAUDE_PLUGIN_ROOT in hooks.json, drop duplicate manifest hooks
hooks/hooks.json used ${PLUGIN_ROOT}, which Claude Code never defines, so the literal resolved against the hook process cwd and SessionStart/UserPromptSubmit failed with 'Cannot find module'. Switch to ${CLAUDE_PLUGIN_ROOT} (and %CLAUDE_PLUGIN_ROOT% for the Codex commandWindows variant); Codex aliases CLAUDE_PLUGIN_ROOT so both hosts resolve. Drop the duplicate inline hooks block from .claude-plugin/plugin.json so hooks load from a single canonical source.
2026-06-13 01:53:53 +02:00
Paul Ogier c15db8d3c9
fix: stop mode filter stripping rule bullets with a colon
filterSkillBodyForMode only filters lines whose label is a real mode (lite/full/ultra). Rule bullets like 'No unrequested abstractions:' and the 'ponytail:' comment convention were being stripped from injected instructions in every mode. Adds regression test.
2026-06-13 01:43:26 +02:00
Hannes 8c279cbfb3
feat: add pi extension (#1) 2026-06-12 17:55:24 +02:00
Paul c16f967d37 feat: codex support 2026-06-12 08:49:33 -04:00
dgebert cbb8859f39 feat: skill v4 — test reflex, ceiling comments, robust-variant rule
Hardening per the A-F benchmark brief: non-trivial logic leaves one
runnable check; ponytail: comments on ceiling-bearing shortcuts name
the ceiling and upgrade path; prefer the edge-case-correct stdlib
option at equal size. Applied to SKILL.md, all cross-agent rule
copies, the hook fallback, and a guard line in ponytail-review.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 12:11:47 +02:00
Emeriko 3b4626a987 feat: skill v2 — output cap, reflex ladder, benchmarks
v1 lost to caveman on tokens/time despite minimal code: it wrote
essays defending each simplification. v2 caps explanation at three
lines and ships the lazy version instead of stalling on necessity
questions. Benchmark: 136.6k tok vs caveman 138.4k, code 47 vs 117
lines across 5 tasks.
2026-06-12 03:54:06 +02:00
Emeriko 7a3475c0f4 feat: full plugin integration + cross-agent rules 2026-06-12 03:25:15 +02:00