Skip to content

Commit fa36f34

Browse files
Vaishnavi220506Dietrich Gebert
andauthored
fix(qoder): honor custom config dir for mode state (#1057)
Co-authored-by: Dietrich Gebert <dietrichgebert@gmail.com>
1 parent 4fc50ad commit fa36f34

5 files changed

Lines changed: 31 additions & 4 deletions

File tree

‎hooks/ponytail-config.js‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ function getClaudeDir() {
7373
return process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
7474
}
7575

76+
function getQoderDir() {
77+
return process.env.QODER_CONFIG_DIR || path.join(os.homedir(), '.qoder');
78+
}
79+
7680
function getDefaultMode() {
7781
// 1. Environment variable (highest priority)
7882
// ponytail: a default must be a runtime level (off/lite/full/ultra); review is
@@ -159,6 +163,7 @@ module.exports = {
159163
getConfigDir,
160164
getConfigPath,
161165
getClaudeDir,
166+
getQoderDir,
162167
getHideStatus,
163168
getQuietStartup,
164169
isShellSafe,

‎hooks/ponytail-runtime.js‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const fs = require('fs');
22
const path = require('path');
33
const os = require('os');
44
const { createHash } = require('crypto');
5-
const { getClaudeDir, getConfigDir } = require('./ponytail-config');
5+
const { getClaudeDir, getQoderDir } = require('./ponytail-config');
66

77
const STATE_FILE = '.ponytail-active';
88

@@ -37,7 +37,7 @@ if (isCodex) stateDir = process.env.PLUGIN_DATA;
3737
// COPILOT_PLUGIN_DATA is unset under VS Code Copilot, so fall back to
3838
// getClaudeDir() rather than building a path from undefined.
3939
if (isCopilot) stateDir = process.env.COPILOT_PLUGIN_DATA || getClaudeDir();
40-
if (isQoder) stateDir = path.join(os.homedir(), '.qoder');
40+
if (isQoder) stateDir = getQoderDir();
4141
if (isCodeBuddy) stateDir = process.env.CODEBUDDY_CONFIG_DIR || path.join(os.homedir(), '.codebuddy');
4242
if (isCursor) stateDir = path.join(os.homedir(), '.cursor');
4343

‎scripts/uninstall.js‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
const fs = require('fs');
1010
const os = require('os');
1111
const path = require('path');
12-
const { getConfigPath, getClaudeDir } = require('../hooks/ponytail-config');
12+
const { getConfigPath, getClaudeDir, getQoderDir } = require('../hooks/ponytail-config');
1313
const cursorHooks = require('./cursor-hooks');
1414

1515
const STATUSLINE_SCRIPT = /(?:^|[\s"'\\/])ponytail-statusline\.(?:sh|ps1)(?=$|[\s"'])/;
@@ -26,7 +26,7 @@ function removeIfExists(filePath, label) {
2626
removeIfExists(path.join(getClaudeDir(), '.ponytail-active'), 'mode flag');
2727
removeIfExists(path.join(getClaudeDir(), '.ponytail-statusline-nudged'), 'statusline nudge flag');
2828
removeIfExists(path.join(os.homedir(), '.cursor', '.ponytail-active'), 'Cursor mode flag');
29-
removeIfExists(path.join(os.homedir(), '.qoder', '.ponytail-active'), 'Qoder mode flag');
29+
removeIfExists(path.join(getQoderDir(), '.ponytail-active'), 'Qoder mode flag');
3030
for (const dir of [path.join(getClaudeDir(), 'ponytail-modes'), path.join(os.homedir(), '.cursor', 'ponytail-modes'), path.join(process.env.CODEBUDDY_CONFIG_DIR || path.join(os.homedir(), '.codebuddy'), 'ponytail-modes')]) {
3131
if (fs.existsSync(dir)) {
3232
fs.rmSync(dir, { recursive: true });

‎tests/hooks.test.js‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,19 @@ assert.match(
591591
/PONYTAIL MODE ACTIVE — level: full/,
592592
);
593593

594+
// Qoder's QODER_CONFIG_DIR relocates its user state. Mode switches and
595+
// subagent injection must use that location instead of creating ~/.qoder.
596+
const qoderCustomDir = path.join(temp, 'qoder-custom-config');
597+
const qoderCustomEnv = { ...qoderEnv, QODER_CONFIG_DIR: qoderCustomDir };
598+
const qoderCustomState = path.join(qoderCustomDir, '.ponytail-active');
599+
result = run('ponytail-mode-tracker.js', qoderCustomEnv, JSON.stringify({ prompt: '/ponytail ultra' }));
600+
assert.equal(result.status, 0, result.stderr);
601+
assert.equal(fs.readFileSync(qoderCustomState, 'utf8'), 'ultra');
602+
assert.equal(fs.readFileSync(qoderState, 'utf8'), 'full', 'custom Qoder mode must not change ~/.qoder');
603+
result = run('ponytail-subagent.js', qoderCustomEnv);
604+
assert.equal(result.status, 0, result.stderr);
605+
assert.match(JSON.parse(result.stdout).hookSpecificOutput.additionalContext, /PONYTAIL MODE ACTIVE — level: ultra/);
606+
594607
// Bare `/ponytail` on Qoder is report-only: there's no SessionStart, so the
595608
// double-duty block below emits the full ruleset as the report. A second
596609
// confirmation here would push two JSON objects to stdout. The point is that

‎tests/uninstall.test.js‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ fs.writeFileSync(flagPath, 'full');
3232
const qoderFlagPath = path.join(home, '.qoder', '.ponytail-active');
3333
fs.mkdirSync(path.dirname(qoderFlagPath), { recursive: true });
3434
fs.writeFileSync(qoderFlagPath, 'ultra');
35+
const qoderCustomDir = path.join(temp, 'qoder-custom-config');
36+
const qoderCustomFlagPath = path.join(qoderCustomDir, '.ponytail-active');
37+
fs.mkdirSync(qoderCustomDir, { recursive: true });
38+
fs.writeFileSync(qoderCustomFlagPath, 'lite');
3539

3640
const nudgeFlagPath = path.join(claudeDir, '.ponytail-statusline-nudged');
3741
fs.writeFileSync(nudgeFlagPath, '');
@@ -80,6 +84,7 @@ let result = runUninstall(env);
8084
assert.equal(result.status, 0, result.stderr);
8185
assert.equal(fs.existsSync(flagPath), false, 'mode flag must be removed');
8286
assert.equal(fs.existsSync(qoderFlagPath), false, 'Qoder mode flag must be removed');
87+
assert.equal(fs.existsSync(qoderCustomFlagPath), true, 'default Qoder uninstall must not touch a separate custom config');
8388
assert.equal(fs.existsSync(nudgeFlagPath), false, 'statusline nudge flag must be removed');
8489
assert.equal(fs.existsSync(configPath), false, 'config file must be removed');
8590
assert.equal(fs.existsSync(cursorFlagPath), false, 'Cursor mode flag must be removed');
@@ -141,6 +146,10 @@ assert.equal(
141146
'a combined statusLine must keep the non-ponytail command',
142147
);
143148

149+
result = runUninstall({ ...env, QODER_CONFIG_DIR: qoderCustomDir });
150+
assert.equal(result.status, 0, result.stderr);
151+
assert.equal(fs.existsSync(qoderCustomFlagPath), false, 'Qoder custom config mode flag must be removed');
152+
144153
// #434: a malformed settings.json must not crash the script mid-cleanup. It
145154
// can't be safely edited, so uninstall warns and leaves the file byte-for-byte
146155
// intact instead of throwing a SyntaxError after other state was already removed.

0 commit comments

Comments
 (0)