cc-safe-setup 10.0.0 → 10.2.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/README.md +1 -1
- package/examples/stale-env-guard.sh +33 -0
- package/examples/test-coverage-guard.sh +27 -0
- package/index.mjs +93 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
**One command to make Claude Code safe for autonomous operation.** [日本語](docs/README.ja.md)
|
|
8
8
|
|
|
9
|
-
8 built-in + 104 examples = **
|
|
9
|
+
8 built-in + 104 examples = **116 hooks**. 36 CLI commands. 531 tests. 5 languages. [**Hub**](https://yurukusa.github.io/cc-safe-setup/hub.html) · [Cheat Sheet](https://yurukusa.github.io/cc-safe-setup/hooks-cheatsheet.html) · [Builder](https://yurukusa.github.io/cc-safe-setup/builder.html) · [FAQ](https://yurukusa.github.io/cc-safe-setup/faq.html) · [Examples](https://yurukusa.github.io/cc-safe-setup/by-example.html) · [Matrix](https://yurukusa.github.io/cc-safe-setup/matrix.html) · [Playground](https://yurukusa.github.io/cc-hook-registry/playground.html)
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
12
|
npx cc-safe-setup
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ================================================================
|
|
3
|
+
# stale-env-guard.sh — Warn when .env files are very old
|
|
4
|
+
# ================================================================
|
|
5
|
+
# PURPOSE:
|
|
6
|
+
# .env files with API keys should be rotated periodically.
|
|
7
|
+
# This hook warns when .env hasn't been modified in 90+ days,
|
|
8
|
+
# suggesting credential rotation.
|
|
9
|
+
#
|
|
10
|
+
# TRIGGER: PreToolUse MATCHER: "Bash"
|
|
11
|
+
#
|
|
12
|
+
# CONFIG:
|
|
13
|
+
# CC_ENV_MAX_AGE_DAYS=90
|
|
14
|
+
# ================================================================
|
|
15
|
+
|
|
16
|
+
COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
17
|
+
[ -z "$COMMAND" ] && exit 0
|
|
18
|
+
|
|
19
|
+
# Only check on deploy-related or env-reading commands
|
|
20
|
+
echo "$COMMAND" | grep -qE '(deploy|source\s+\.env|cat\s+\.env|docker.*\.env)' || exit 0
|
|
21
|
+
|
|
22
|
+
MAX_DAYS="${CC_ENV_MAX_AGE_DAYS:-90}"
|
|
23
|
+
|
|
24
|
+
for envfile in .env .env.local .env.production; do
|
|
25
|
+
[ -f "$envfile" ] || continue
|
|
26
|
+
AGE_DAYS=$(( ($(date +%s) - $(stat -c %Y "$envfile" 2>/dev/null || echo 0)) / 86400 ))
|
|
27
|
+
if [ "$AGE_DAYS" -gt "$MAX_DAYS" ]; then
|
|
28
|
+
echo "WARNING: $envfile is $AGE_DAYS days old (threshold: $MAX_DAYS)." >&2
|
|
29
|
+
echo "Consider rotating API keys and credentials." >&2
|
|
30
|
+
fi
|
|
31
|
+
done
|
|
32
|
+
|
|
33
|
+
exit 0
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ================================================================
|
|
3
|
+
# test-coverage-guard.sh — Warn when code grows without tests
|
|
4
|
+
# ================================================================
|
|
5
|
+
# PURPOSE:
|
|
6
|
+
# Claude adds features without writing tests. This hook checks
|
|
7
|
+
# if source files changed more than test files, suggesting tests
|
|
8
|
+
# are needed before committing.
|
|
9
|
+
#
|
|
10
|
+
# TRIGGER: PreToolUse MATCHER: "Bash"
|
|
11
|
+
# ================================================================
|
|
12
|
+
|
|
13
|
+
COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
14
|
+
[ -z "$COMMAND" ] && exit 0
|
|
15
|
+
echo "$COMMAND" | grep -qE '^\s*git\s+commit' || exit 0
|
|
16
|
+
|
|
17
|
+
# Count staged source vs test file changes
|
|
18
|
+
SRC_CHANGES=$(git diff --cached --name-only 2>/dev/null | grep -cvE '(test|spec|__tests__|_test\.|\.test\.)' || echo 0)
|
|
19
|
+
TEST_CHANGES=$(git diff --cached --name-only 2>/dev/null | grep -cE '(test|spec|__tests__|_test\.|\.test\.)' || echo 0)
|
|
20
|
+
|
|
21
|
+
# If source changed significantly but no tests
|
|
22
|
+
if [ "$SRC_CHANGES" -gt 3 ] && [ "$TEST_CHANGES" -eq 0 ]; then
|
|
23
|
+
echo "WARNING: $SRC_CHANGES source files changed but 0 test files." >&2
|
|
24
|
+
echo "Consider adding tests for the new code." >&2
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
exit 0
|
package/index.mjs
CHANGED
|
@@ -97,6 +97,8 @@ const MIGRATE_FROM_IDX = process.argv.findIndex(a => a === '--migrate-from');
|
|
|
97
97
|
const MIGRATE_FROM = MIGRATE_FROM_IDX !== -1 ? process.argv[MIGRATE_FROM_IDX + 1] : null;
|
|
98
98
|
const HEALTH = process.argv.includes('--health');
|
|
99
99
|
const FROM_CLAUDEMD = process.argv.includes('--from-claudemd');
|
|
100
|
+
const DIFF_HOOKS_IDX = process.argv.findIndex(a => a === '--diff-hooks');
|
|
101
|
+
const DIFF_HOOKS = DIFF_HOOKS_IDX !== -1 ? process.argv[DIFF_HOOKS_IDX + 1] : null;
|
|
100
102
|
const PROFILE_IDX = process.argv.findIndex(a => a === '--profile');
|
|
101
103
|
const PROFILE = PROFILE_IDX !== -1 ? process.argv[PROFILE_IDX + 1] : null;
|
|
102
104
|
const COMPARE_IDX = process.argv.findIndex(a => a === '--compare');
|
|
@@ -134,6 +136,7 @@ if (HELP) {
|
|
|
134
136
|
npx cc-safe-setup --doctor Diagnose why hooks aren't working
|
|
135
137
|
npx cc-safe-setup --watch Live dashboard of blocked commands
|
|
136
138
|
npx cc-safe-setup --create "<desc>" Generate a custom hook from description
|
|
139
|
+
npx cc-safe-setup --diff-hooks <path> Compare hooks between two settings files
|
|
137
140
|
npx cc-safe-setup --from-claudemd Convert CLAUDE.md rules into hooks
|
|
138
141
|
npx cc-safe-setup --health Hook health dashboard (size, permissions, age)
|
|
139
142
|
npx cc-safe-setup --migrate-from <tool> Migrate from safety-net/hooks-mastery/etc.
|
|
@@ -847,6 +850,95 @@ async function fullSetup() {
|
|
|
847
850
|
console.log();
|
|
848
851
|
}
|
|
849
852
|
|
|
853
|
+
async function diffHooks(otherPath) {
|
|
854
|
+
console.log();
|
|
855
|
+
console.log(c.bold + ' cc-safe-setup --diff-hooks' + c.reset);
|
|
856
|
+
console.log(c.dim + ' Compare hook configurations' + c.reset);
|
|
857
|
+
console.log();
|
|
858
|
+
|
|
859
|
+
// Parse hooks from a settings file
|
|
860
|
+
function getHooks(path) {
|
|
861
|
+
if (!existsSync(path)) return new Set();
|
|
862
|
+
try {
|
|
863
|
+
const s = JSON.parse(readFileSync(path, 'utf-8'));
|
|
864
|
+
const hooks = new Set();
|
|
865
|
+
for (const [trigger, groups] of Object.entries(s.hooks || {})) {
|
|
866
|
+
for (const group of groups) {
|
|
867
|
+
for (const h of (group.hooks || [])) {
|
|
868
|
+
const cmd = h.command || '';
|
|
869
|
+
const name = cmd.split('/').pop().replace('.sh', '').replace('.py', '');
|
|
870
|
+
if (name) hooks.add(name);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
return hooks;
|
|
875
|
+
} catch { return new Set(); }
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// Get current settings
|
|
879
|
+
const myHooks = getHooks(SETTINGS_PATH);
|
|
880
|
+
|
|
881
|
+
if (!otherPath) {
|
|
882
|
+
// Compare global vs project settings
|
|
883
|
+
const projectSettings = join(process.cwd(), '.claude', 'settings.json');
|
|
884
|
+
const projectLocal = join(process.cwd(), '.claude', 'settings.local.json');
|
|
885
|
+
|
|
886
|
+
if (existsSync(projectSettings) || existsSync(projectLocal)) {
|
|
887
|
+
const projectHooks = new Set([
|
|
888
|
+
...getHooks(projectSettings),
|
|
889
|
+
...getHooks(projectLocal)
|
|
890
|
+
]);
|
|
891
|
+
|
|
892
|
+
console.log(c.bold + ' Global vs Project comparison' + c.reset);
|
|
893
|
+
console.log(` Global: ${myHooks.size} hooks (${SETTINGS_PATH})`);
|
|
894
|
+
console.log(` Project: ${projectHooks.size} hooks (.claude/settings*.json)`);
|
|
895
|
+
console.log();
|
|
896
|
+
|
|
897
|
+
const onlyGlobal = [...myHooks].filter(h => !projectHooks.has(h));
|
|
898
|
+
const onlyProject = [...projectHooks].filter(h => !myHooks.has(h));
|
|
899
|
+
const both = [...myHooks].filter(h => projectHooks.has(h));
|
|
900
|
+
|
|
901
|
+
if (both.length > 0) {
|
|
902
|
+
console.log(c.green + ` ✓ ${both.length} hooks in both:` + c.reset);
|
|
903
|
+
both.slice(0, 10).forEach(h => console.log(c.dim + ` ${h}` + c.reset));
|
|
904
|
+
if (both.length > 10) console.log(c.dim + ` ... and ${both.length - 10} more` + c.reset);
|
|
905
|
+
console.log();
|
|
906
|
+
}
|
|
907
|
+
if (onlyGlobal.length > 0) {
|
|
908
|
+
console.log(c.yellow + ` △ ${onlyGlobal.length} only in global:` + c.reset);
|
|
909
|
+
onlyGlobal.forEach(h => console.log(` ${h}`));
|
|
910
|
+
console.log();
|
|
911
|
+
}
|
|
912
|
+
if (onlyProject.length > 0) {
|
|
913
|
+
console.log(c.blue + ` ○ ${onlyProject.length} only in project:` + c.reset);
|
|
914
|
+
onlyProject.forEach(h => console.log(` ${h}`));
|
|
915
|
+
console.log();
|
|
916
|
+
}
|
|
917
|
+
} else {
|
|
918
|
+
console.log(c.dim + ' No project-level settings found.' + c.reset);
|
|
919
|
+
console.log(c.dim + ' Create with: npx cc-safe-setup --team' + c.reset);
|
|
920
|
+
console.log();
|
|
921
|
+
console.log(c.bold + ' Global hooks (' + myHooks.size + '):' + c.reset);
|
|
922
|
+
[...myHooks].sort().forEach(h => console.log(` ${h}`));
|
|
923
|
+
}
|
|
924
|
+
} else {
|
|
925
|
+
// Compare with specified file
|
|
926
|
+
const otherHooks = getHooks(otherPath);
|
|
927
|
+
console.log(` File A: ${SETTINGS_PATH} (${myHooks.size} hooks)`);
|
|
928
|
+
console.log(` File B: ${otherPath} (${otherHooks.size} hooks)`);
|
|
929
|
+
console.log();
|
|
930
|
+
|
|
931
|
+
const onlyA = [...myHooks].filter(h => !otherHooks.has(h));
|
|
932
|
+
const onlyB = [...otherHooks].filter(h => !myHooks.has(h));
|
|
933
|
+
const both = [...myHooks].filter(h => otherHooks.has(h));
|
|
934
|
+
|
|
935
|
+
console.log(c.green + ` ${both.length} shared` + c.reset);
|
|
936
|
+
if (onlyA.length > 0) console.log(c.yellow + ` ${onlyA.length} only in A: ${onlyA.join(', ')}` + c.reset);
|
|
937
|
+
if (onlyB.length > 0) console.log(c.blue + ` ${onlyB.length} only in B: ${onlyB.join(', ')}` + c.reset);
|
|
938
|
+
}
|
|
939
|
+
console.log();
|
|
940
|
+
}
|
|
941
|
+
|
|
850
942
|
async function fromClaudeMd() {
|
|
851
943
|
console.log();
|
|
852
944
|
console.log(c.bold + ' cc-safe-setup --from-claudemd' + c.reset);
|
|
@@ -3723,6 +3815,7 @@ async function main() {
|
|
|
3723
3815
|
if (FULL) return fullSetup();
|
|
3724
3816
|
if (DOCTOR) return doctor();
|
|
3725
3817
|
if (WATCH) return watch();
|
|
3818
|
+
if (DIFF_HOOKS_IDX !== -1) return diffHooks(DIFF_HOOKS);
|
|
3726
3819
|
if (FROM_CLAUDEMD) return fromClaudeMd();
|
|
3727
3820
|
if (HEALTH) return health();
|
|
3728
3821
|
if (MIGRATE_FROM_IDX !== -1) return migrateFrom(MIGRATE_FROM);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cc-safe-setup",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.2.0",
|
|
4
4
|
"description": "One command to make Claude Code safe. 59 hooks (8 built-in + 51 examples). 26 CLI commands: dashboard, create, audit, lint, diff, migrate, compare, generate-ci. 284 tests.",
|
|
5
5
|
"main": "index.mjs",
|
|
6
6
|
"bin": {
|