claudex-setup 1.15.0 → 1.15.1
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 +1 -1
- package/src/governance.js +16 -6
- package/src/plans.js +3 -3
- package/src/setup.js +56 -58
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claudex-setup",
|
|
3
|
-
"version": "1.15.
|
|
3
|
+
"version": "1.15.1",
|
|
4
4
|
"description": "Score your repo's Claude Code setup against 84 checks. See gaps, apply fixes selectively with rollback, govern hooks and permissions, and benchmark impact — without breaking existing config.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
package/src/governance.js
CHANGED
|
@@ -254,36 +254,46 @@ function buildHookConfig(hookFiles, profileKey) {
|
|
|
254
254
|
return {};
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
+
// Detect hook runtime: .js files use node, .sh files use bash
|
|
258
|
+
const hookCommand = (file) => {
|
|
259
|
+
if (file.endsWith('.js')) return `node .claude/hooks/${file}`;
|
|
260
|
+
return `bash .claude/hooks/${file}`;
|
|
261
|
+
};
|
|
262
|
+
const isSecrets = (f) => f === 'protect-secrets.sh' || f === 'protect-secrets.js';
|
|
263
|
+
const isSession = (f) => f === 'session-start.sh' || f === 'session-start.js';
|
|
264
|
+
|
|
257
265
|
const hookConfig = {
|
|
258
266
|
PostToolUse: [{
|
|
259
267
|
matcher: 'Write|Edit',
|
|
260
268
|
hooks: uniqueFiles
|
|
261
|
-
.filter(file => file
|
|
269
|
+
.filter(file => !isSecrets(file) && !isSession(file))
|
|
262
270
|
.map(file => ({
|
|
263
271
|
type: 'command',
|
|
264
|
-
command:
|
|
272
|
+
command: hookCommand(file),
|
|
265
273
|
timeout: 10,
|
|
266
274
|
})),
|
|
267
275
|
}],
|
|
268
276
|
};
|
|
269
277
|
|
|
270
|
-
|
|
278
|
+
const secretsFile = uniqueFiles.find(isSecrets);
|
|
279
|
+
if (secretsFile) {
|
|
271
280
|
hookConfig.PreToolUse = [{
|
|
272
281
|
matcher: 'Read|Write|Edit',
|
|
273
282
|
hooks: [{
|
|
274
283
|
type: 'command',
|
|
275
|
-
command:
|
|
284
|
+
command: hookCommand(secretsFile),
|
|
276
285
|
timeout: 5,
|
|
277
286
|
}],
|
|
278
287
|
}];
|
|
279
288
|
}
|
|
280
289
|
|
|
281
|
-
|
|
290
|
+
const sessionFile = uniqueFiles.find(isSession);
|
|
291
|
+
if (sessionFile) {
|
|
282
292
|
hookConfig.SessionStart = [{
|
|
283
293
|
matcher: '*',
|
|
284
294
|
hooks: [{
|
|
285
295
|
type: 'command',
|
|
286
|
-
command:
|
|
296
|
+
command: hookCommand(sessionFile),
|
|
287
297
|
timeout: 5,
|
|
288
298
|
}],
|
|
289
299
|
}];
|
package/src/plans.js
CHANGED
|
@@ -260,7 +260,7 @@ function buildAgentPatchFiles(ctx) {
|
|
|
260
260
|
|
|
261
261
|
function buildHookSettings(ctx, plannedHookFiles, options = {}) {
|
|
262
262
|
const existing = ctx.hasDir('.claude/hooks')
|
|
263
|
-
? ctx.dirFiles('.claude/hooks').filter(file => file.endsWith('.sh'))
|
|
263
|
+
? ctx.dirFiles('.claude/hooks').filter(file => file.endsWith('.sh') || file.endsWith('.js'))
|
|
264
264
|
: [];
|
|
265
265
|
const hookFiles = [...new Set([...existing, ...plannedHookFiles])].sort();
|
|
266
266
|
if (hookFiles.length === 0) {
|
|
@@ -385,7 +385,7 @@ async function buildProposalBundle(options) {
|
|
|
385
385
|
if (templateKey === 'hooks') {
|
|
386
386
|
const plannedHookFiles = templateFiles
|
|
387
387
|
.map(file => path.basename(file.path))
|
|
388
|
-
.filter(file => file.endsWith('.sh'));
|
|
388
|
+
.filter(file => file.endsWith('.sh') || file.endsWith('.js'));
|
|
389
389
|
const settingsFile = buildHookSettings(ctx, plannedHookFiles, options);
|
|
390
390
|
if (settingsFile) {
|
|
391
391
|
templateFiles.push(settingsFile);
|
|
@@ -476,7 +476,7 @@ function applyRuntimeSettingsOverlays(bundle, options) {
|
|
|
476
476
|
|
|
477
477
|
const ctx = new ProjectContext(options.dir);
|
|
478
478
|
const existingHooks = ctx.hasDir('.claude/hooks')
|
|
479
|
-
? ctx.dirFiles('.claude/hooks').filter(file => file.endsWith('.sh'))
|
|
479
|
+
? ctx.dirFiles('.claude/hooks').filter(file => file.endsWith('.sh') || file.endsWith('.js'))
|
|
480
480
|
: [];
|
|
481
481
|
|
|
482
482
|
const proposals = bundle.proposals.map((proposal) => {
|
package/src/setup.js
CHANGED
|
@@ -782,66 +782,64 @@ ${verificationSteps.join('\n')}
|
|
|
782
782
|
},
|
|
783
783
|
|
|
784
784
|
'hooks': () => ({
|
|
785
|
-
'on-edit-lint.
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
if
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
fi
|
|
785
|
+
'on-edit-lint.js': `#!/usr/bin/env node
|
|
786
|
+
// PostToolUse hook - runs linter after file edits
|
|
787
|
+
const { execSync } = require('child_process');
|
|
788
|
+
const fs = require('fs');
|
|
789
|
+
try {
|
|
790
|
+
if (fs.existsSync('package.json')) {
|
|
791
|
+
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
|
792
|
+
if (pkg.scripts && pkg.scripts.lint) {
|
|
793
|
+
execSync('npm run lint --silent', { stdio: 'ignore', timeout: 30000 });
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
} catch (e) { /* linter not available or failed - non-blocking */ }
|
|
798
797
|
`,
|
|
799
|
-
'protect-secrets.
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
798
|
+
'protect-secrets.js': `#!/usr/bin/env node
|
|
799
|
+
// PreToolUse hook - blocks reads of secret files
|
|
800
|
+
let input = '';
|
|
801
|
+
process.stdin.on('data', d => input += d);
|
|
802
|
+
process.stdin.on('end', () => {
|
|
803
|
+
try {
|
|
804
|
+
const data = JSON.parse(input);
|
|
805
|
+
const fp = (data.tool_input && data.tool_input.file_path) || '';
|
|
806
|
+
if (/\\.env$|\\.env\\.|secrets[\\/\\\\]|credentials|\\.pem$|\\.key$/i.test(fp)) {
|
|
807
|
+
console.log(JSON.stringify({ decision: 'block', reason: 'Blocked: accessing secret/credential files is not allowed.' }));
|
|
808
|
+
} else {
|
|
809
|
+
console.log(JSON.stringify({ decision: 'allow' }));
|
|
810
|
+
}
|
|
811
|
+
} catch (e) {
|
|
812
|
+
console.log(JSON.stringify({ decision: 'allow' }));
|
|
813
|
+
}
|
|
814
|
+
});
|
|
809
815
|
`,
|
|
810
|
-
'log-changes.
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
TIMESTAMP=$(date +"%Y-%m-%d %H:%M:%S")
|
|
829
|
-
echo "[$TIMESTAMP] $TOOL_NAME: $FILE_PATH" >> "$LOG_FILE"
|
|
830
|
-
|
|
831
|
-
exit 0
|
|
816
|
+
'log-changes.js': `#!/usr/bin/env node
|
|
817
|
+
// PostToolUse hook - logs all file changes with timestamps
|
|
818
|
+
const fs = require('fs');
|
|
819
|
+
const path = require('path');
|
|
820
|
+
let input = '';
|
|
821
|
+
process.stdin.on('data', d => input += d);
|
|
822
|
+
process.stdin.on('end', () => {
|
|
823
|
+
try {
|
|
824
|
+
const data = JSON.parse(input);
|
|
825
|
+
const fp = (data.tool_input && data.tool_input.file_path) || '';
|
|
826
|
+
if (!fp) process.exit(0);
|
|
827
|
+
const toolName = data.tool_name || 'unknown';
|
|
828
|
+
const logDir = path.join('.claude', 'logs');
|
|
829
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
830
|
+
const ts = new Date().toISOString().replace('T', ' ').split('.')[0];
|
|
831
|
+
fs.appendFileSync(path.join(logDir, 'file-changes.log'), \`[\${ts}] \${toolName}: \${fp}\\n\`);
|
|
832
|
+
} catch (e) { /* non-blocking */ }
|
|
833
|
+
});
|
|
832
834
|
`,
|
|
833
|
-
'session-start.
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
TIMESTAMP=$(date +"%Y-%m-%d %H:%M:%S")
|
|
842
|
-
echo "[$TIMESTAMP] session started" >> "$LOG_FILE"
|
|
843
|
-
|
|
844
|
-
exit 0
|
|
835
|
+
'session-start.js': `#!/usr/bin/env node
|
|
836
|
+
// SessionStart hook - prepares logs and records session entry
|
|
837
|
+
const fs = require('fs');
|
|
838
|
+
const path = require('path');
|
|
839
|
+
const logDir = path.join('.claude', 'logs');
|
|
840
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
841
|
+
const ts = new Date().toISOString().replace('T', ' ').split('.')[0];
|
|
842
|
+
fs.appendFileSync(path.join(logDir, 'sessions.log'), \`[\${ts}] session started\\n\`);
|
|
845
843
|
`,
|
|
846
844
|
}),
|
|
847
845
|
|
|
@@ -1219,7 +1217,7 @@ async function setup(options) {
|
|
|
1219
1217
|
const hooksDir = path.join(options.dir, '.claude/hooks');
|
|
1220
1218
|
const settingsPath = path.join(options.dir, '.claude/settings.json');
|
|
1221
1219
|
if (fs.existsSync(hooksDir) && !fs.existsSync(settingsPath)) {
|
|
1222
|
-
const hookFiles = fs.readdirSync(hooksDir).filter(f => f.endsWith('.sh'));
|
|
1220
|
+
const hookFiles = fs.readdirSync(hooksDir).filter(f => f.endsWith('.sh') || f.endsWith('.js'));
|
|
1223
1221
|
if (hookFiles.length > 0) {
|
|
1224
1222
|
const settings = buildSettingsForProfile({
|
|
1225
1223
|
profileKey: options.profile || 'safe-write',
|