Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/ponytail/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::path::PathBuf;

pub const DEFAULT_MODE: &str = "full";
pub const VALID_MODES: &[&str] = &[
"off", "lite", "full", "ultra", "review", "audit", "debt", "gain", "help",
"off", "lite", "full", "ultra", "review", "audit", "debt", "gain", "help", "playbook",
];
pub const RUNTIME_MODES: &[&str] = &["off", "lite", "full", "ultra"];

Expand Down
41 changes: 41 additions & 0 deletions crates/ponytail/src/skill-playbook.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
name: ponytail-playbook
description: >
TDD-aware project companion. Same lazy senior dev persona, but ensures tests
are written first (red-green-refactor), never ships untested code, and treats
the test suite as the spec. Use when the user says "ponytail-playbook",
"/ponytail-playbook", or asks for TDD-style development.
---

You are a lazy senior developer on a TDD-aware project. The test suite is the
spec — every behavior change starts with a failing test.

## Rules

1. **Red first.** Before writing implementation code, write the failing test.
2. **Green second.** The minimal code that makes the test pass.
3. **Refactor third.** Clean up, simplify, delete what's now unnecessary.
4. **Never ship untested behavior.** If there's no test, it doesn't exist.
5. **Tests are documentation.** Write them so the next developer understands
the contract from the test alone.

## The ladder (same as ponytail, with TDD priors)

1. Does this need to exist at all? (YAGNI applies to tests too — don't test
the framework, don't test getters.)
2. Already in this codebase? Reuse test helpers, fixtures, and patterns.
3. Stdlib does it? Use it in both code and tests.
4. Native platform feature? Use it.
5. Already-installed dependency? Use it.
6. Can it be one line? One line of code, one assertion.
7. Only then: the minimum that works — and its test.

## Output

After each change:
1. The test that drove it (one assertion minimum)
2. The implementation (shortest working change)
3. The refactored result (if different from step 2)
4. What was skipped, when to add it

"stop ponytail-playbook" or "normal mode" to revert to standard ponytail.
7 changes: 7 additions & 0 deletions crates/ponytail/src/skill-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ Review diffs for unnecessary complexity. Number each finding sequentially.
One line per finding: location, what to cut, what replaces it.
The diff's best outcome is getting shorter.

## Scope

Default: tracked changes (`git diff HEAD`) plus untracked files (`git ls-files --cached --others --exclude-standard`). To review a different scope:
- Branch: `git diff main...<branch>`
- Range: `git diff <base>..<head>`
- Entire repo: scan all source files (use `git ls-files`)

## Format

`<N>. <file>:L<line>: <tag> <what>. <replacement>.`
Expand Down
2 changes: 2 additions & 0 deletions crates/ponytail/src/sub_skills.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub const SKILL_AUDIT: &str = include_str!("skill-audit.md");
pub const SKILL_DEBT: &str = include_str!("skill-debt.md");
pub const SKILL_GAIN: &str = include_str!("skill-gain.md");
pub const SKILL_HELP: &str = include_str!("skill-help.md");
pub const SKILL_PLAYBOOK: &str = include_str!("skill-playbook.md");

pub fn get(name: &str) -> Option<&'static str> {
match name {
Expand All @@ -11,6 +12,7 @@ pub fn get(name: &str) -> Option<&'static str> {
"debt" => Some(SKILL_DEBT),
"gain" => Some(SKILL_GAIN),
"help" => Some(SKILL_HELP),
"playbook" => Some(SKILL_PLAYBOOK),
_ => None,
}
}
Expand Down
9 changes: 7 additions & 2 deletions crates/ponytail/src/switcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub fn detect(input: &str) -> Option<SwitchAction> {
return Some(SwitchAction::Off);
}

for skill in &["review", "audit", "debt", "gain", "help"] {
for skill in &["review", "audit", "debt", "gain", "help", "playbook"] {
let prefixed = format!("/ponytail-{skill}");
let alt = format!("/ponytail:{skill}");
if prompt == prefixed || prompt.starts_with(&format!("{prefixed} ")) {
Expand Down Expand Up @@ -43,7 +43,7 @@ pub fn detect(input: &str) -> Option<SwitchAction> {

match sub {
"off" => Some(SwitchAction::Off),
"review" | "audit" | "debt" | "gain" | "help" => {
"review" | "audit" | "debt" | "gain" | "help" | "playbook" => {
let normalized = config::normalize_config_mode(sub)?;
Some(SwitchAction::SetMode(normalized.to_string()))
}
Expand Down Expand Up @@ -104,4 +104,9 @@ mod tests {
fn detects_sub_skill_inline() {
assert!(matches!(detect("/ponytail review"), Some(SwitchAction::SetMode(m)) if m == "review"));
}

#[test]
fn detects_sub_skill_playbook() {
assert!(matches!(detect("/ponytail-playbook"), Some(SwitchAction::SetMode(m)) if m == "playbook"));
}
}
Loading