@@ -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.
4850let modeLabel = mode ;
4951if ( mode === 'wenyan' ) modeLabel = 'wenyan-full' ;
5052if ( 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.
5556let skillContent = '' ;
5657try {
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
6272let 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
135168try {
136169 let hasStatusline = false ;
0 commit comments