* 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>
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
* 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>
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>