cc-hook-registry 2.1.0 → 3.0.0
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/index.mjs +95 -0
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -112,6 +112,7 @@ if (!command || command === '--help' || command === '-h') {
|
|
|
112
112
|
browse [category] Browse by category (safety, quality, approve, utility, monitoring, ux)
|
|
113
113
|
install <id> Install a hook
|
|
114
114
|
info <id> Show hook details
|
|
115
|
+
recommend Recommend hooks for current project
|
|
115
116
|
stats Registry statistics
|
|
116
117
|
|
|
117
118
|
Examples:
|
|
@@ -272,6 +273,100 @@ else if (command === 'info') {
|
|
|
272
273
|
console.log();
|
|
273
274
|
}
|
|
274
275
|
|
|
276
|
+
else if (command === 'recommend') {
|
|
277
|
+
console.log();
|
|
278
|
+
console.log(c.bold + ' Recommended hooks for this project' + c.reset);
|
|
279
|
+
console.log();
|
|
280
|
+
|
|
281
|
+
const cwd = process.cwd();
|
|
282
|
+
const recommendations = [];
|
|
283
|
+
|
|
284
|
+
// Always recommend safety essentials
|
|
285
|
+
recommendations.push({ id: 'destructive-guard', reason: 'Essential — prevents rm -rf disasters', priority: 1 });
|
|
286
|
+
recommendations.push({ id: 'branch-guard', reason: 'Essential — prevents push to main', priority: 1 });
|
|
287
|
+
recommendations.push({ id: 'secret-guard', reason: 'Essential — prevents .env commits', priority: 1 });
|
|
288
|
+
|
|
289
|
+
// Detect tech stack
|
|
290
|
+
if (existsSync(join(cwd, 'package.json'))) {
|
|
291
|
+
const pkg = JSON.parse(readFileSync(join(cwd, 'package.json'), 'utf-8'));
|
|
292
|
+
recommendations.push({ id: 'auto-approve-build', reason: 'Node.js project detected', priority: 2 });
|
|
293
|
+
if (pkg.dependencies?.prisma || pkg.devDependencies?.prisma) {
|
|
294
|
+
recommendations.push({ id: 'block-database-wipe', reason: 'Prisma detected — protect against migrate reset', priority: 1 });
|
|
295
|
+
}
|
|
296
|
+
if (pkg.scripts?.deploy || pkg.scripts?.['vercel-build']) {
|
|
297
|
+
recommendations.push({ id: 'deploy-guard', reason: 'Deploy script detected', priority: 2 });
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (existsSync(join(cwd, 'requirements.txt')) || existsSync(join(cwd, 'pyproject.toml'))) {
|
|
302
|
+
recommendations.push({ id: 'auto-approve-python', reason: 'Python project detected', priority: 2 });
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (existsSync(join(cwd, 'Dockerfile')) || existsSync(join(cwd, 'docker-compose.yml'))) {
|
|
306
|
+
recommendations.push({ id: 'auto-approve-docker', reason: 'Docker detected', priority: 2 });
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (existsSync(join(cwd, '.env')) || existsSync(join(cwd, '.env.local'))) {
|
|
310
|
+
recommendations.push({ id: 'env-source-guard', reason: '.env file present — prevent sourcing', priority: 1 });
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (existsSync(join(cwd, 'Gemfile'))) {
|
|
314
|
+
recommendations.push({ id: 'block-database-wipe', reason: 'Rails detected — protect against db:drop', priority: 1 });
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (existsSync(join(cwd, 'artisan'))) {
|
|
318
|
+
recommendations.push({ id: 'block-database-wipe', reason: 'Laravel detected — protect against migrate:fresh', priority: 1 });
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Always useful
|
|
322
|
+
recommendations.push({ id: 'compound-command-approver', reason: 'Fixes permission matching for cd && commands', priority: 2 });
|
|
323
|
+
recommendations.push({ id: 'loop-detector', reason: 'Prevents infinite command loops', priority: 3 });
|
|
324
|
+
recommendations.push({ id: 'session-handoff', reason: 'Saves state for next session', priority: 3 });
|
|
325
|
+
recommendations.push({ id: 'cost-tracker', reason: 'Track session costs', priority: 3 });
|
|
326
|
+
|
|
327
|
+
// Deduplicate and sort by priority
|
|
328
|
+
const seen = new Set();
|
|
329
|
+
const unique = recommendations.filter(r => { if (seen.has(r.id)) return false; seen.add(r.id); return true; });
|
|
330
|
+
unique.sort((a, b) => a.priority - b.priority);
|
|
331
|
+
|
|
332
|
+
// Check what's already installed
|
|
333
|
+
let installed = new Set();
|
|
334
|
+
if (existsSync(SETTINGS_PATH)) {
|
|
335
|
+
try {
|
|
336
|
+
const s = JSON.parse(readFileSync(SETTINGS_PATH, 'utf-8'));
|
|
337
|
+
for (const entries of Object.values(s.hooks || {})) {
|
|
338
|
+
for (const e of entries) {
|
|
339
|
+
for (const h of (e.hooks || [])) {
|
|
340
|
+
if (h.command) installed.add(h.command.split('/').pop().replace('.sh', ''));
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
} catch {}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
for (const rec of unique) {
|
|
348
|
+
const hook = REGISTRY.find(h => h.id === rec.id);
|
|
349
|
+
if (!hook) continue;
|
|
350
|
+
const isInstalled = installed.has(rec.id);
|
|
351
|
+
const icon = isInstalled ? c.green + '✓' + c.reset : c.yellow + '○' + c.reset;
|
|
352
|
+
const status = isInstalled ? c.dim + '(installed)' + c.reset : '';
|
|
353
|
+
console.log(' ' + icon + ' ' + c.bold + rec.id + c.reset + ' ' + status);
|
|
354
|
+
console.log(' ' + c.dim + rec.reason + c.reset);
|
|
355
|
+
if (!isInstalled) {
|
|
356
|
+
console.log(' ' + c.dim + 'Install: npx cc-hook-registry install ' + rec.id + c.reset);
|
|
357
|
+
}
|
|
358
|
+
console.log();
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const notInstalled = unique.filter(r => !installed.has(r.id));
|
|
362
|
+
if (notInstalled.length === 0) {
|
|
363
|
+
console.log(c.green + ' All recommended hooks are installed!' + c.reset);
|
|
364
|
+
} else {
|
|
365
|
+
console.log(c.dim + ' ' + notInstalled.length + ' recommended hook(s) not yet installed.' + c.reset);
|
|
366
|
+
}
|
|
367
|
+
console.log();
|
|
368
|
+
}
|
|
369
|
+
|
|
275
370
|
else if (command === 'stats') {
|
|
276
371
|
const categories = {};
|
|
277
372
|
const sources = {};
|