Skip to content

Commit 1d7b470

Browse files
committed
fix: standalone+wenyan+inconsistency fixes from code review
πŸ”΄ Blocker fixes: - Move language rule loading AFTER if/else block (standalone gets rules) - Add resolveRulesDir() with multiple path tries for all install types - Add inline fallback rules when hangeul/wenyan .md files not found 🟑 Logic fixes: - mode-tracker writes canonical names (hangeul-full, wenyan-full) to flag file instead of short aliases - Add wenyan per-turn reminder (previously got English reminder) - SKILL.md Language Rules: 4 lines β†’ 2 lines (less noise for EN users) 🟑 UX fix: - Add πŸ‡°πŸ‡· Hangeul to README 'Pick your level' 5-column grid (was missing)
1 parent 66f1693 commit 1d7b470

4 files changed

Lines changed: 76 additions & 33 deletions

File tree

β€ŽREADME.mdβ€Ž

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,33 +79,40 @@ Based on the viral observation that caveman-speak dramatically reduces LLM token
7979

8080
<table>
8181
<tr>
82-
<td width="25%">
82+
<td width="20%">
8383

8484
#### πŸͺΆ Lite
8585

8686
> "Your component re-renders because you create a new object reference each render. Inline object props fail shallow comparison every time. Wrap it in `useMemo`."
8787
8888
</td>
89-
<td width="25%">
89+
<td width="20%">
9090

9191
#### πŸͺ¨ Full
9292

9393
> "New object ref each render. Inline object prop = new ref = re-render. Wrap in `useMemo`."
9494
9595
</td>
96-
<td width="25%">
96+
<td width="20%">
9797

9898
#### πŸ”₯ Ultra
9999

100100
> "Inline obj prop β†’ new ref β†’ re-render. `useMemo`."
101101
102102
</td>
103-
<td width="25%">
103+
<td width="20%">
104104

105105
#### πŸ“œ 文言文
106106

107107
> "η‰©ε‡Ίζ–°εƒη…§οΌŒθ‡΄ι‡ηΉͺ。useMemo Wrap之。"
108108
109+
</td>
110+
<td width="20%">
111+
112+
#### πŸ‡°πŸ‡· Hangeul
113+
114+
> "μƒˆ 객체 ref β†’ λ¦¬λ Œλ”λ§. useMemo."
115+
109116
</td>
110117
</tr>
111118
</table>

β€Žhooks/caveman-activate.jsβ€Ž

Lines changed: 56 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -44,20 +44,30 @@ if (INDEPENDENT_MODES.has(mode)) {
4444
process.exit(0);
4545
}
4646

47-
// Resolve the canonical label for aliases
47+
// Resolve canonical labels.
48+
// Flag file stores short names (wenyan, hangeul) for statusline readability.
49+
// We expand to canonical full names for the system prompt label.
4850
let modeLabel = mode;
4951
if (mode === 'wenyan') modeLabel = 'wenyan-full';
5052
if (mode === 'hangeul' || mode === 'korean' || mode === 'ko') modeLabel = 'hangeul-full';
5153

5254
// Read SKILL.md β€” the single source of truth for caveman behavior.
53-
// Plugin installs: __dirname = <plugin_root>/hooks/, SKILL.md at <plugin_root>/skills/caveman/SKILL.md
54-
// Standalone installs: __dirname = $CLAUDE_CONFIG_DIR/hooks/, SKILL.md won't exist β€” falls back to hardcoded rules.
55+
// Multiple lookup paths to handle plugin installs, cloned repos, and standalone installs.
5556
let skillContent = '';
5657
try {
5758
skillContent = fs.readFileSync(
5859
path.join(__dirname, '..', 'skills', 'caveman', 'SKILL.md'), 'utf8'
5960
);
60-
} catch (e) { /* standalone install β€” will use fallback below */ }
61+
} catch (e) { /* plugin install β€” will try other paths below */ }
62+
63+
// For standalone + cloned repo: also try repo-root-relative path
64+
if (!skillContent) {
65+
try {
66+
skillContent = fs.readFileSync(
67+
path.join(__dirname, '..', '..', 'skills', 'caveman', 'SKILL.md'), 'utf8'
68+
);
69+
} catch (e) { /* still not found β€” will use fallback below */ }
70+
}
6171

6272
let output;
6373

@@ -91,25 +101,6 @@ if (skillContent) {
91101
}, []);
92102

93103
output = 'CAVEMAN MODE ACTIVE β€” level: ' + modeLabel + '\n\n' + filtered.join('\n');
94-
95-
// Load language-specific compression rules (hook-based injection).
96-
// These are NOT in SKILL.md β€” English users never see them.
97-
// Only the active language mode's rules are loaded.
98-
if (modeLabel.startsWith('hangeul')) {
99-
const rulesDir = path.join(__dirname, '..', 'rules');
100-
const langRulesPath = path.join(rulesDir, 'hangeul-compression.md');
101-
try {
102-
const langRules = fs.readFileSync(langRulesPath, 'utf8');
103-
output += '\n\n' + langRules;
104-
} catch (e) { /* silent β€” rules file optional for standalone installs */ }
105-
} else if (modeLabel.startsWith('wenyan')) {
106-
const rulesDir = path.join(__dirname, '..', 'rules');
107-
const langRulesPath = path.join(rulesDir, 'wenyan-compression.md');
108-
try {
109-
const langRules = fs.readFileSync(langRulesPath, 'utf8');
110-
output += '\n\n' + langRules;
111-
} catch (e) { /* silent */ }
112-
}
113104
} else {
114105
// Fallback when SKILL.md is not found (standalone hook install without skills dir).
115106
// This is the minimum viable ruleset β€” better than nothing.
@@ -131,6 +122,48 @@ if (skillContent) {
131122
'Code/commits/PRs: write normal. "stop caveman" or "normal mode": revert. Level persist until changed or session end.';
132123
}
133124

125+
// Load language-specific compression rules (hook-based injection).
126+
// Runs AFTER the main output construction β€” applies to BOTH plugin install
127+
// and standalone fallback paths. English users: this block is skipped.
128+
// Multiple rulesDir tries: plugin (<plugin_root>/rules), cloned repo (<repo>/rules),
129+
// standalone hooks dir (<claude_config>/hooks/../rules).
130+
const resolveRulesDir = () => {
131+
for (const rel of ['..', '../..']) {
132+
const candidate = path.join(__dirname, rel, 'rules');
133+
try {
134+
if (fs.existsSync(candidate)) return candidate;
135+
} catch (e) { /* ignore */ }
136+
}
137+
return path.join(__dirname, '..', 'rules'); // default, will fail silently
138+
};
139+
140+
if (modeLabel.startsWith('hangeul')) {
141+
const rulesDir = resolveRulesDir();
142+
const langRulesPath = path.join(rulesDir, 'hangeul-compression.md');
143+
try {
144+
const langRules = fs.readFileSync(langRulesPath, 'utf8');
145+
output += '\n\n' + langRules;
146+
} catch (e) {
147+
// Fallback: inline minimal Korean rules for standalone installs
148+
output += '\n\n# Korean Compression Rules (minimal)\n' +
149+
'ACTIVE ONLY when `/caveman hangeul` (or `korean`, `ko`) is set.\n' +
150+
'Drop filler (사싀/κ·Έλƒ₯/μ§„μ§œ/기본적으둜), pleasantries (~λ“œλ¦¬κ² μŠ΅λ‹ˆλ‹€), hedging (~것 κ°™μŠ΅λ‹ˆλ‹€). ' +
151+
'Use 반말. Drop particles (은/λŠ”/이/κ°€). Use noun endings (~함/~됨). Fragments OK.\n';
152+
}
153+
} else if (modeLabel.startsWith('wenyan')) {
154+
const rulesDir = resolveRulesDir();
155+
const langRulesPath = path.join(rulesDir, 'wenyan-compression.md');
156+
try {
157+
const langRules = fs.readFileSync(langRulesPath, 'utf8');
158+
output += '\n\n' + langRules;
159+
} catch (e) {
160+
// Fallback: inline minimal wenyan rules for standalone installs
161+
output += '\n\n# Classical Chinese Rules (minimal)\n' +
162+
'Respond in Classical Chinese (文言文). Maximum terseness. ' +
163+
'Classical sentence patterns. Verbs precede objects. Subjects often omitted.\n';
164+
}
165+
}
166+
134167
// 3. Detect missing statusline config β€” nudge Claude to help set it up
135168
try {
136169
let hasStatusline = false;

β€Žhooks/caveman-mode-tracker.jsβ€Ž

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,10 @@ process.stdin.on('end', () => {
4848
if (arg === 'lite') mode = 'lite';
4949
else if (arg === 'ultra') mode = 'ultra';
5050
else if (arg === 'wenyan-lite') mode = 'wenyan-lite';
51-
else if (arg === 'wenyan' || arg === 'wenyan-full') mode = 'wenyan';
51+
else if (arg === 'wenyan' || arg === 'wenyan-full') mode = 'wenyan-full';
5252
else if (arg === 'wenyan-ultra') mode = 'wenyan-ultra';
5353
else if (arg === 'hangeul-lite') mode = 'hangeul-lite';
54-
else if (arg === 'hangeul' || arg === 'hangeul-full' || arg === 'korean' || arg === 'ko') mode = 'hangeul';
54+
else if (arg === 'hangeul' || arg === 'hangeul-full' || arg === 'korean' || arg === 'ko') mode = 'hangeul-full';
5555
else if (arg === 'hangeul-ultra') mode = 'hangeul-ultra';
5656
else mode = getDefaultMode();
5757
}
@@ -94,6 +94,11 @@ process.stdin.on('end', () => {
9494
"hedging (~것 κ°™μŠ΅λ‹ˆλ‹€). Fragments OK. " +
9595
"Use 반말. Drop particles (은/λŠ”/이/κ°€) when clear. " +
9696
"Code/commits/security: write normal.";
97+
} else if (activeMode.startsWith('wenyan')) {
98+
reminder = "CAVEMAN MODE ACTIVE (" + activeMode + "). " +
99+
"Use Classical Chinese (文言文). Maximum terseness. " +
100+
"Classical sentence patterns. Verbs before objects. " +
101+
"Code/commits/security: write normal.";
97102
}
98103

99104
process.stdout.write(JSON.stringify({

β€Žskills/caveman/SKILL.mdβ€Ž

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,8 @@ Example β€” "Explain database connection pooling."
6161

6262
## Language Rules
6363

64-
When a non-English intensity level is active (wenyan-*, hangeul-*),
65-
the hook loads language-specific compression rules from `rules/<lang>-compression.md`.
66-
These rules replace the English Rules section above for that mode.
67-
English users see no language rules in their system prompt.
64+
Non-English modes (wenyan-*, hangeul-*) load language-specific rules
65+
via the hook. English users never see them.
6866

6967
## Auto-Clarity
7068

0 commit comments

Comments
Β (0)