@codihaus/claude-skills 1.6.1 → 1.6.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codihaus/claude-skills",
3
- "version": "1.6.1",
3
+ "version": "1.6.3",
4
4
  "description": "Claude Code skills for software development workflow",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -79,16 +79,22 @@ export async function init(options) {
79
79
 
80
80
  // Offer to install missing Python packages (only if Python is installed)
81
81
  const missingPython = globalDeps.python?.filter(p => !p.installed) || [];
82
- if (hasPython && hasPip && missingPython.length > 0 && !options.yes) {
83
- const { installPython } = await inquirer.prompt([{
84
- type: 'confirm',
85
- name: 'installPython',
86
- message: `Install missing Python packages (${missingPython.map(p => p.name).join(', ')})? (will try --user first, then sudo if needed)`,
87
- default: true
88
- }]);
89
-
90
- if (installPython) {
82
+ if (hasPython && hasPip && missingPython.length > 0) {
83
+ if (options.yes) {
84
+ // Auto-install when --yes flag is used
85
+ console.log(chalk.cyan(`\nAuto-installing Python packages: ${missingPython.map(p => p.name).join(', ')}`));
91
86
  await installPythonDeps(missingPython);
87
+ } else {
88
+ const { installPython } = await inquirer.prompt([{
89
+ type: 'confirm',
90
+ name: 'installPython',
91
+ message: `Install missing Python packages (${missingPython.map(p => p.name).join(', ')})? (will try --user first, then sudo if needed)`,
92
+ default: true
93
+ }]);
94
+
95
+ if (installPython) {
96
+ await installPythonDeps(missingPython);
97
+ }
92
98
  }
93
99
  } else if (missingPython.length > 0 && (!hasPython || !hasPip)) {
94
100
  console.log(chalk.yellow('\n⚠️ Python packages are needed but Python/pip is not available.'));
@@ -41,70 +41,86 @@ const DEFAULT_SETTINGS = {
41
41
  "Edit(*)"
42
42
  ],
43
43
  deny: []
44
+ },
45
+ hooks: {
46
+ PostToolUse: [
47
+ {
48
+ matcher: "Write|Edit",
49
+ hooks: [
50
+ {
51
+ type: "command",
52
+ command: "jq -r '.tool_input.file_path // empty' | xargs -I {} bash .claude/scripts/safe-graph-update.sh '{}'"
53
+ }
54
+ ]
55
+ }
56
+ ]
44
57
  }
45
58
  };
46
59
 
47
- /**
48
- * Hooks for automatic graph updates
49
- *
50
- * IMPORTANT: Hooks should be resilient and fail gracefully.
51
- * - Use `|| true` to prevent hook failures from blocking Claude
52
- * - Add retry limits to prevent infinite loops
53
- * - Keep hooks fast (< 2 seconds) to avoid slowing down workflow
54
- */
55
- const DEFAULT_HOOKS = {
56
- postToolUse: [
57
- {
58
- matcher: {
59
- toolName: "Write|Edit",
60
- path: "plans/**/*.md" // Only trigger on .md files in plans/
61
- },
62
- command: "bash .claude/scripts/safe-graph-update.sh $PATH",
63
- // Retry configuration (Claude Code built-in support)
64
- retries: {
65
- maxAttempts: 3, // Max 3 attempts total
66
- backoff: "exponential", // Wait longer between retries
67
- failureAction: "warn" // Show warning but don't block
68
- },
69
- timeout: 5000 // 5 second timeout per attempt
70
- }
71
- ]
72
- };
60
+ // Hooks are now configured in settings.json (see DEFAULT_SETTINGS)
73
61
 
74
62
  /**
75
63
  * Set up Claude Code settings
64
+ * Creates TWO files:
65
+ * - settings.json (with hooks, checked into git)
66
+ * - settings.local.json (with permissions, gitignored)
76
67
  */
77
68
  export async function setupSettings(projectPath, options = {}) {
78
69
  const claudePath = path.join(projectPath, '.claude');
79
- const settingsPath = path.join(claudePath, 'settings.local.json');
70
+ const settingsPath = path.join(claudePath, 'settings.json');
71
+ const settingsLocalPath = path.join(claudePath, 'settings.local.json');
80
72
 
81
73
  await fs.ensureDir(claudePath);
82
74
 
83
- let settings = DEFAULT_SETTINGS;
75
+ // Create settings.json with hooks (for team, checked in)
76
+ const settingsJsonContent = {
77
+ hooks: DEFAULT_SETTINGS.hooks
78
+ };
79
+
80
+ await fs.writeJson(settingsPath, settingsJsonContent, { spaces: 2 });
81
+
82
+ // Create/update settings.local.json with permissions (personal, gitignored)
83
+ let localSettings = {
84
+ permissions: DEFAULT_SETTINGS.permissions
85
+ };
84
86
 
85
87
  // Merge with existing if present
86
- if (await fs.pathExists(settingsPath)) {
88
+ if (await fs.pathExists(settingsLocalPath)) {
87
89
  try {
88
- const existing = await fs.readJson(settingsPath);
89
- settings = mergeSettings(existing, settings);
90
+ const existing = await fs.readJson(settingsLocalPath);
91
+ localSettings = {
92
+ permissions: {
93
+ allow: [...new Set([
94
+ ...(existing.permissions?.allow || []),
95
+ ...(DEFAULT_SETTINGS.permissions?.allow || [])
96
+ ])],
97
+ deny: [...new Set([
98
+ ...(existing.permissions?.deny || []),
99
+ ...(DEFAULT_SETTINGS.permissions?.deny || [])
100
+ ])]
101
+ }
102
+ };
90
103
  } catch (e) {
91
104
  // Invalid JSON, use defaults
92
105
  }
93
106
  }
94
107
 
95
- await fs.writeJson(settingsPath, settings, { spaces: 2 });
108
+ await fs.writeJson(settingsLocalPath, localSettings, { spaces: 2 });
96
109
 
97
- return { path: settingsPath, settings };
110
+ return {
111
+ settingsPath,
112
+ settingsLocalPath,
113
+ settings: { ...settingsJsonContent, ...localSettings }
114
+ };
98
115
  }
99
116
 
100
117
  /**
101
- * Set up hooks
118
+ * Set up hook scripts (hooks config is in settings.json)
102
119
  */
103
120
  export async function setupHooks(projectPath, options = {}) {
104
121
  if (options.noHooks) return null;
105
122
 
106
123
  const claudePath = path.join(projectPath, '.claude');
107
- const hooksPath = path.join(claudePath, 'hooks.json');
108
124
  const claudeScriptsPath = path.join(claudePath, 'scripts');
109
125
 
110
126
  await fs.ensureDir(claudePath);
@@ -124,27 +140,14 @@ export async function setupHooks(projectPath, options = {}) {
124
140
  }
125
141
  }
126
142
 
127
- let hooks = DEFAULT_HOOKS;
128
-
129
- // Merge with existing if present
130
- if (await fs.pathExists(hooksPath)) {
131
- try {
132
- const existing = await fs.readJson(hooksPath);
133
- hooks = mergeHooks(existing, hooks);
134
- } catch (e) {
135
- // Invalid JSON, use defaults
136
- }
137
- }
143
+ // Copy graph.py to .claude/scripts/ (for docs-graph hook)
144
+ const graphSource = path.join(__dirname, '../../project-scripts/graph.py');
145
+ const graphDest = path.join(claudeScriptsPath, 'graph.py');
138
146
 
139
- // Update command to use safe wrapper if it exists
140
- if (hooks.postToolUse && hooks.postToolUse[0]) {
141
- if (await fs.pathExists(wrapperDest)) {
142
- hooks.postToolUse[0].command = "bash .claude/scripts/safe-graph-update.sh $PATH";
143
- }
147
+ if (await fs.pathExists(graphSource)) {
148
+ await fs.copy(graphSource, graphDest);
144
149
  }
145
150
 
146
- await fs.writeJson(hooksPath, hooks, { spaces: 2 });
147
-
148
151
  // Copy hooks guide documentation
149
152
  const hooksGuideSource = path.join(TEMPLATES_PATH, 'hooks-guide.md');
150
153
  const hooksGuideDest = path.join(claudePath, 'hooks-guide.md');
@@ -153,7 +156,7 @@ export async function setupHooks(projectPath, options = {}) {
153
156
  await fs.copy(hooksGuideSource, hooksGuideDest);
154
157
  }
155
158
 
156
- return { path: hooksPath, hooks };
159
+ return { scriptsPath: claudeScriptsPath };
157
160
  }
158
161
 
159
162
  /**
@@ -292,32 +295,4 @@ export async function updateGitignore(projectPath) {
292
295
  return { action: 'created' };
293
296
  }
294
297
 
295
- /**
296
- * Merge settings objects
297
- */
298
- function mergeSettings(existing, defaults) {
299
- return {
300
- permissions: {
301
- allow: [...new Set([
302
- ...(existing.permissions?.allow || []),
303
- ...(defaults.permissions?.allow || [])
304
- ])],
305
- deny: [...new Set([
306
- ...(existing.permissions?.deny || []),
307
- ...(defaults.permissions?.deny || [])
308
- ])]
309
- }
310
- };
311
- }
312
-
313
- /**
314
- * Merge hooks objects
315
- */
316
- function mergeHooks(existing, defaults) {
317
- // For now, just use defaults if hooks exist
318
- // More sophisticated merging could be added
319
- return {
320
- ...defaults,
321
- ...existing
322
- };
323
- }
298
+ // mergeSettings and mergeHooks removed - settings split into settings.json and settings.local.json
@@ -52,10 +52,10 @@ Always append `|| true` to prevent hook failures from blocking:
52
52
 
53
53
  ```bash
54
54
  # Good - fails gracefully
55
- python3 scripts/graph.py --check-path $PATH || true
55
+ python3 .claude/scripts/graph.py --check-path $PATH || true
56
56
 
57
57
  # Bad - failure blocks Claude
58
- python3 scripts/graph.py --check-path $PATH
58
+ python3 .claude/scripts/graph.py --check-path $PATH
59
59
  ```
60
60
 
61
61
  ### 3. Add Timeout Guards
@@ -64,7 +64,7 @@ Protect against hanging commands:
64
64
 
65
65
  ```json
66
66
  {
67
- "command": "timeout 10s python3 scripts/graph.py --check-path $PATH || true",
67
+ "command": "timeout 10s python3 .claude/scripts/graph.py --check-path $PATH || true",
68
68
  "timeout": 15000
69
69
  }
70
70
  ```
@@ -138,7 +138,7 @@ Check if required tools exist before running:
138
138
 
139
139
  ```json
140
140
  {
141
- "command": "command -v python3 >/dev/null 2>&1 && python3 scripts/graph.py --check-path $PATH || true"
141
+ "command": "command -v python3 >/dev/null 2>&1 && python3 .claude/scripts/graph.py --check-path $PATH || true"
142
142
  }
143
143
  ```
144
144
 
@@ -153,12 +153,12 @@ if ! command -v python3 &> /dev/null; then
153
153
  exit 0
154
154
  fi
155
155
 
156
- if [ ! -f "scripts/graph.py" ]; then
156
+ if [ ! -f ".claude/scripts/graph.py" ]; then
157
157
  echo "graph.py not found, skipping"
158
158
  exit 0
159
159
  fi
160
160
 
161
- timeout 10s python3 scripts/graph.py --check-path "$1" || true
161
+ timeout 10s python3 .claude/scripts/graph.py --check-path "$1" || true
162
162
  ```
163
163
 
164
164
  Then in hooks.json:
@@ -182,7 +182,7 @@ Choose the right `failureAction` based on hook criticality:
182
182
  **Example - Non-critical hook:**
183
183
  ```json
184
184
  {
185
- "command": "python3 scripts/graph.py --check-path $PATH || true",
185
+ "command": "python3 .claude/scripts/graph.py --check-path $PATH || true",
186
186
  "retries": {
187
187
  "maxAttempts": 3,
188
188
  "failureAction": "warn"
@@ -209,7 +209,7 @@ If a hook keeps failing:
209
209
 
210
210
  Claude Code logs hook output. Look for:
211
211
  ```
212
- [Hook Failed] postToolUse: python3 scripts/graph.py
212
+ [Hook Failed] postToolUse: python3 .claude/scripts/graph.py
213
213
  Exit code: 1
214
214
  Stderr: ModuleNotFoundError: No module named 'networkx'
215
215
  ```
@@ -219,7 +219,7 @@ Stderr: ModuleNotFoundError: No module named 'networkx'
219
219
  Run the hook command directly:
220
220
  ```bash
221
221
  cd /path/to/project
222
- python3 scripts/graph.py --check-path plans/brd/README.md
222
+ python3 .claude/scripts/graph.py --check-path plans/brd/README.md
223
223
  ```
224
224
 
225
225
  ### 3. Common Issues
@@ -244,7 +244,7 @@ To temporarily disable a problematic hook:
244
244
  "toolName": "Write|Edit",
245
245
  "path": "plans/**/*"
246
246
  },
247
- "command": "python3 scripts/graph.py --check-path $PATH || true",
247
+ "command": "python3 .claude/scripts/graph.py --check-path $PATH || true",
248
248
  "enabled": false
249
249
  }
250
250
  ]
@@ -287,10 +287,10 @@ set -e
287
287
  command -v python3 >/dev/null 2>&1 || exit 0
288
288
 
289
289
  # Exit early if script missing
290
- [ -f "scripts/graph.py" ] || exit 0
290
+ [ -f ".claude/scripts/graph.py" ] || exit 0
291
291
 
292
292
  # Run with timeout and graceful failure
293
- timeout 10s python3 scripts/graph.py --check-path "$1" || {
293
+ timeout 10s python3 .claude/scripts/graph.py --check-path "$1" || {
294
294
  echo "Graph update failed (non-critical), continuing..."
295
295
  exit 0
296
296
  }
@@ -304,7 +304,7 @@ timeout 10s python3 scripts/graph.py --check-path "$1" || {
304
304
  {
305
305
  "postToolUse": [{
306
306
  "matcher": { "toolName": "Write|Edit", "path": "plans/**/*" },
307
- "command": "python3 scripts/graph.py --check-path $PATH"
307
+ "command": "python3 .claude/scripts/graph.py --check-path $PATH"
308
308
  }]
309
309
  }
310
310
  ```
@@ -13,7 +13,7 @@ set -e
13
13
 
14
14
  # Configuration
15
15
  TIMEOUT_SECONDS=10
16
- GRAPH_SCRIPT="scripts/graph.py"
16
+ GRAPH_SCRIPT=".claude/scripts/graph.py"
17
17
  MAX_RETRIES=0 # Let Claude Code handle retries
18
18
 
19
19
  # Colors for output (optional)