@esoteric-logic/praxis-harness 2.0.2 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/bin/praxis.js +94 -45
  2. package/package.json +1 -1
package/bin/praxis.js CHANGED
@@ -37,7 +37,7 @@ async function install() {
37
37
  header('Praxis Harness v' + VERSION);
38
38
 
39
39
  // Ensure ~/.claude/ structure
40
- for (const sub of ['rules', 'commands', 'skills']) {
40
+ for (const sub of ['rules', 'skills', 'hooks']) {
41
41
  fs.mkdirSync(path.join(CLAUDE_DIR, sub), { recursive: true });
42
42
  }
43
43
 
@@ -59,17 +59,6 @@ async function install() {
59
59
  ok(count + ' rules installed');
60
60
  }
61
61
 
62
- // Copy base/commands/* → ~/.claude/commands/
63
- const cmdsDir = path.join(PKG_DIR, 'base', 'commands');
64
- if (fs.existsSync(cmdsDir)) {
65
- let count = 0;
66
- for (const f of fs.readdirSync(cmdsDir)) {
67
- copyFile(path.join(cmdsDir, f), path.join(CLAUDE_DIR, 'commands', f));
68
- count++;
69
- }
70
- ok(count + ' commands installed');
71
- }
72
-
73
62
  // Copy base/skills/* → ~/.claude/skills/
74
63
  const skillsDir = path.join(PKG_DIR, 'base', 'skills');
75
64
  if (fs.existsSync(skillsDir)) {
@@ -87,6 +76,35 @@ async function install() {
87
76
  ok(count + ' skills installed');
88
77
  }
89
78
 
79
+ // Copy base/hooks/*.sh → ~/.claude/hooks/
80
+ const hooksDir = path.join(PKG_DIR, 'base', 'hooks');
81
+ if (fs.existsSync(hooksDir)) {
82
+ let count = 0;
83
+ for (const f of fs.readdirSync(hooksDir)) {
84
+ if (f.endsWith('.sh')) {
85
+ copyFile(path.join(hooksDir, f), path.join(CLAUDE_DIR, 'hooks', f));
86
+ // Make executable
87
+ fs.chmodSync(path.join(CLAUDE_DIR, 'hooks', f), 0o755);
88
+ count++;
89
+ }
90
+ }
91
+ ok(count + ' hooks installed');
92
+
93
+ // Merge hooks configuration into settings.json
94
+ const hooksConfig = path.join(hooksDir, 'settings-hooks.json');
95
+ const settingsFile = path.join(CLAUDE_DIR, 'settings.json');
96
+ if (fs.existsSync(hooksConfig)) {
97
+ const hooksCfg = JSON.parse(fs.readFileSync(hooksConfig, 'utf8'));
98
+ let settings = {};
99
+ if (fs.existsSync(settingsFile)) {
100
+ try { settings = JSON.parse(fs.readFileSync(settingsFile, 'utf8')); } catch {}
101
+ }
102
+ Object.assign(settings, hooksCfg);
103
+ fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2) + '\n');
104
+ ok('hooks configuration merged into settings.json');
105
+ }
106
+ }
107
+
90
108
  // Copy kits/ → ~/.claude/kits/
91
109
  const kitsDir = path.join(PKG_DIR, 'kits');
92
110
  if (fs.existsSync(kitsDir)) {
@@ -94,11 +112,29 @@ async function install() {
94
112
  ok('kits installed');
95
113
  }
96
114
 
97
- // Orphan cleanup: obsidian.md renamed to vault.md
98
- const legacyObsidian = path.join(CLAUDE_DIR, 'rules', 'obsidian.md');
99
- if (fs.existsSync(legacyObsidian)) {
100
- fs.unlinkSync(legacyObsidian);
101
- ok('Removed legacy obsidian.md (renamed to vault.md)');
115
+ // Orphan cleanup: deleted rules and legacy files
116
+ const orphans = [
117
+ 'obsidian.md', 'code-quality.md', 'security.md',
118
+ 'communication.md', 'architecture.md'
119
+ ];
120
+ for (const f of orphans) {
121
+ const target = path.join(CLAUDE_DIR, 'rules', f);
122
+ if (fs.existsSync(target)) {
123
+ fs.unlinkSync(target);
124
+ dim('removed legacy ' + f);
125
+ }
126
+ }
127
+
128
+ // Orphan cleanup: old commands directory (v1.x)
129
+ const oldCmdsDir = path.join(CLAUDE_DIR, 'commands');
130
+ if (fs.existsSync(oldCmdsDir)) {
131
+ const files = fs.readdirSync(oldCmdsDir);
132
+ if (files.length > 0) {
133
+ for (const f of files) {
134
+ fs.unlinkSync(path.join(oldCmdsDir, f));
135
+ }
136
+ dim('cleaned up ' + files.length + ' legacy command files');
137
+ }
102
138
  }
103
139
 
104
140
  // Vault configuration
@@ -115,7 +151,7 @@ async function install() {
115
151
  }
116
152
 
117
153
  const config = {
118
- version: '1.1.0',
154
+ version: VERSION,
119
155
  vault_path: vaultPath || '',
120
156
  vault_backend: 'obsidian',
121
157
  repo_path: PKG_DIR
@@ -140,7 +176,7 @@ async function install() {
140
176
  // Summary
141
177
  header('Install complete');
142
178
  console.log(' Files copied to ' + CLAUDE_DIR);
143
- console.log(' Run: npx praxis-harness health');
179
+ console.log(' Run: npx @esoteric-logic/praxis-harness health');
144
180
  console.log('');
145
181
  }
146
182
 
@@ -177,15 +213,6 @@ function health() {
177
213
  }
178
214
  }
179
215
 
180
- // Commands
181
- console.log('\nCommands:');
182
- const cmdsDir = path.join(PKG_DIR, 'base', 'commands');
183
- if (fs.existsSync(cmdsDir)) {
184
- for (const f of fs.readdirSync(cmdsDir)) {
185
- check(fs.existsSync(path.join(CLAUDE_DIR, 'commands', f)), 'commands/' + f + ' installed');
186
- }
187
- }
188
-
189
216
  // Skills
190
217
  console.log('\nSkills:');
191
218
  const skillsDir = path.join(PKG_DIR, 'base', 'skills');
@@ -195,6 +222,18 @@ function health() {
195
222
  }
196
223
  }
197
224
 
225
+ // Hooks
226
+ console.log('\nHooks:');
227
+ const hooksDir = path.join(PKG_DIR, 'base', 'hooks');
228
+ if (fs.existsSync(hooksDir)) {
229
+ for (const f of fs.readdirSync(hooksDir)) {
230
+ if (f.endsWith('.sh')) {
231
+ check(fs.existsSync(path.join(CLAUDE_DIR, 'hooks', f)), 'hooks/' + f + ' installed');
232
+ }
233
+ }
234
+ }
235
+ check(fs.existsSync(path.join(CLAUDE_DIR, 'settings.json')), 'settings.json with hooks configured');
236
+
198
237
  // Kits
199
238
  console.log('\nKits:');
200
239
  check(fs.existsSync(path.join(CLAUDE_DIR, 'kits')), 'kits directory installed');
@@ -236,34 +275,26 @@ function health() {
236
275
  function uninstall() {
237
276
  header('Uninstalling Praxis Harness');
238
277
 
239
- // Remove files that came from base/
278
+ // Remove CLAUDE.md
240
279
  const claudeMd = path.join(CLAUDE_DIR, 'CLAUDE.md');
241
280
  if (fs.existsSync(claudeMd)) { fs.unlinkSync(claudeMd); ok('CLAUDE.md removed'); }
242
281
 
243
- // Remove rules from base/rules/
282
+ // Remove rules
244
283
  const rulesDir = path.join(PKG_DIR, 'base', 'rules');
245
284
  if (fs.existsSync(rulesDir)) {
246
285
  for (const f of fs.readdirSync(rulesDir)) {
247
286
  const target = path.join(CLAUDE_DIR, 'rules', f);
248
287
  if (fs.existsSync(target)) fs.unlinkSync(target);
249
288
  }
250
- // Also remove legacy obsidian.md if present
251
- const legacyRule = path.join(CLAUDE_DIR, 'rules', 'obsidian.md');
252
- if (fs.existsSync(legacyRule)) fs.unlinkSync(legacyRule);
253
- ok('rules removed');
254
- }
255
-
256
- // Remove commands from base/commands/
257
- const cmdsDir = path.join(PKG_DIR, 'base', 'commands');
258
- if (fs.existsSync(cmdsDir)) {
259
- for (const f of fs.readdirSync(cmdsDir)) {
260
- const target = path.join(CLAUDE_DIR, 'commands', f);
289
+ // Also remove legacy files
290
+ for (const f of ['obsidian.md', 'code-quality.md', 'security.md', 'communication.md', 'architecture.md']) {
291
+ const target = path.join(CLAUDE_DIR, 'rules', f);
261
292
  if (fs.existsSync(target)) fs.unlinkSync(target);
262
293
  }
263
- ok('commands removed');
294
+ ok('rules removed');
264
295
  }
265
296
 
266
- // Remove skills from base/skills/
297
+ // Remove skills
267
298
  const skillsDir = path.join(PKG_DIR, 'base', 'skills');
268
299
  if (fs.existsSync(skillsDir)) {
269
300
  for (const entry of fs.readdirSync(skillsDir)) {
@@ -275,6 +306,17 @@ function uninstall() {
275
306
  ok('skills removed');
276
307
  }
277
308
 
309
+ // Remove hooks
310
+ const hooksDir = path.join(CLAUDE_DIR, 'hooks');
311
+ if (fs.existsSync(hooksDir)) {
312
+ for (const f of fs.readdirSync(hooksDir)) {
313
+ if (f.endsWith('.sh')) {
314
+ fs.unlinkSync(path.join(hooksDir, f));
315
+ }
316
+ }
317
+ ok('hooks removed');
318
+ }
319
+
278
320
  // Remove kits
279
321
  const kitsTarget = path.join(CLAUDE_DIR, 'kits');
280
322
  if (fs.existsSync(kitsTarget)) {
@@ -282,6 +324,13 @@ function uninstall() {
282
324
  ok('kits removed');
283
325
  }
284
326
 
327
+ // Remove legacy commands directory
328
+ const oldCmdsDir = path.join(CLAUDE_DIR, 'commands');
329
+ if (fs.existsSync(oldCmdsDir)) {
330
+ fs.rmSync(oldCmdsDir, { recursive: true, force: true });
331
+ ok('legacy commands directory removed');
332
+ }
333
+
285
334
  header('Uninstall complete');
286
335
  dim('Config file preserved: ' + CONFIG_FILE);
287
336
  dim('Run: rm ' + CONFIG_FILE + ' to remove config');
@@ -294,10 +343,10 @@ function printHelp() {
294
343
  console.log(`
295
344
  praxis-harness v${VERSION}
296
345
 
297
- Usage: npx praxis-harness [command]
346
+ Usage: npx @esoteric-logic/praxis-harness [command]
298
347
 
299
348
  Commands:
300
- install Copy rules, commands, skills, and kits to ~/.claude/ (default)
349
+ install Copy rules, skills, hooks, and kits to ~/.claude/ (default)
301
350
  update Re-copy from latest npm package version
302
351
  health Verify install integrity
303
352
  uninstall Remove Praxis-owned files from ~/.claude/
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@esoteric-logic/praxis-harness",
3
- "version": "2.0.2",
3
+ "version": "2.0.3",
4
4
  "description": "Layered Claude Code harness — workflow discipline, AI-Kits, persistent vault integration",
5
5
  "bin": {
6
6
  "praxis-harness": "./bin/praxis.js"