Skip to content
Open
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
11 changes: 7 additions & 4 deletions hooks/ponytail-mode-tracker.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,17 @@ function finish() {
} else if (mode && mode !== 'off') {
setMode(mode);
modeSwitched = true;
// ponytail: Qoder needs the full ruleset every turn, so when a mode
// switch happens we fold the confirmation into the ruleset output
// below (one JSON on stdout) instead of emitting two separate writes.
// ponytail: a mid-session switch must deliver the ruleset immediately,
// not just the confirmation (#663) — otherwise the main thread stays
// rule-less until the next SessionStart, even though subagents spawned
// meanwhile already see it (ponytail-subagent.js reads the flag
// independently). Qoder folds this same way below since it has no
// SessionStart to fall back on.
if (!isQoder) {
writeHookOutput(
'UserPromptSubmit',
mode,
'PONYTAIL MODE CHANGED — level: ' + mode,
'PONYTAIL MODE CHANGED — level: ' + mode + '\n\n' + getPonytailInstructions(mode),
);
}
} else if (mode === 'off') {
Expand Down
28 changes: 28 additions & 0 deletions tests/hooks.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,19 @@ assert.equal(result.status, 0, result.stderr);
assert.equal(fs.readFileSync(codexState, 'utf8'), 'lite');
output = JSON.parse(result.stdout);
assert.equal(output.systemMessage, 'PONYTAIL:LITE');
// #663: Codex shares the same !isQoder mode-switch write path as native
// Claude, so a mid-session switch must also deliver the ruleset here, not
// just the systemMessage badge.
assert.equal(output.hookSpecificOutput.hookEventName, 'UserPromptSubmit');
assert.match(
output.hookSpecificOutput.additionalContext,
/PONYTAIL MODE CHANGED — level: lite/,
);
assert.match(
output.hookSpecificOutput.additionalContext,
/You are a lazy senior developer/,
'Codex mid-session mode switch must deliver the ruleset immediately, not just the badge (#663)',
);

// Querying bare @ponytail should report the active level ('lite') without resetting it to default ('ultra')
result = run(
Expand Down Expand Up @@ -428,6 +441,21 @@ result = run('ponytail-mode-tracker.js', defEnv, JSON.stringify({ prompt: '/pony
assert.equal(result.status, 0, result.stderr);
assert.equal(fs.readFileSync(defFlag, 'utf8'), 'ultra', 'plain switch must set the session mode');
assert.equal(JSON.parse(fs.readFileSync(defConfig, 'utf8')).defaultMode, 'lite', 'plain switch must not persist the default');
// #663: a mid-session switch on native Claude/Codex (non-Qoder) must deliver
// the ruleset immediately, not just the confirmation — otherwise the main
// thread stays rule-less until the next SessionStart, while subagents
// spawned meanwhile (ponytail-subagent.js reads the flag independently)
// already see it.
assert.match(
result.stdout,
/PONYTAIL MODE CHANGED — level: ultra/,
'plain switch must still emit the confirmation',
);
assert.match(
result.stdout,
/You are a lazy senior developer/,
'mid-session mode switch must deliver the ruleset immediately, not just the confirmation (#663)',
);

// review is not a valid default (#377) — the command is ignored, config unchanged.
result = run('ponytail-mode-tracker.js', defEnv, JSON.stringify({ prompt: '/ponytail default review' }));
Expand Down