kastell 2.2.4 → 2.2.5
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/.claude-plugin/marketplace.json +18 -18
- package/.claude-plugin/plugin.json +45 -38
- package/CHANGELOG.md +1294 -1266
- package/LICENSE +201 -201
- package/NOTICE +5 -5
- package/README.md +1 -1
- package/README.tr.md +1 -1
- package/bin/kastell +2 -2
- package/bin/kastell-mcp +5 -5
- package/dist/adapters/coolify.js +92 -92
- package/dist/adapters/dokploy.js +99 -99
- package/dist/core/audit/formatters/badge.js +20 -20
- package/dist/core/completions.js +631 -631
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +25 -31
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools/serverExplain.d.ts.map +1 -1
- package/dist/mcp/tools/serverExplain.js.map +1 -1
- package/dist/mcp/tools/serverFleet.d.ts.map +1 -1
- package/dist/mcp/tools/serverFleet.js.map +1 -1
- package/dist/mcp/tools/serverInfo.d.ts +1 -1
- package/dist/mcp/tools/serverInfo.js +1 -1
- package/dist/mcp/tools/serverPlugin.d.ts.map +1 -1
- package/dist/mcp/tools/serverPlugin.js.map +1 -1
- package/dist/mcp-bundle.mjs +101015 -0
- package/dist/utils/cloudInit.js +58 -58
- package/dist/utils/version.d.ts.map +1 -1
- package/dist/utils/version.js +19 -4
- package/dist/utils/version.js.map +1 -1
- package/kastell-plugin/.claude-plugin/plugin.json +20 -20
- package/kastell-plugin/.mcp.json +15 -8
- package/kastell-plugin/README.md +113 -113
- package/kastell-plugin/agents/kastell-auditor.md +77 -77
- package/kastell-plugin/agents/scripts/bucket_mapper.sh +101 -101
- package/kastell-plugin/agents/scripts/trend_report.sh +91 -91
- package/kastell-plugin/hooks/destroy-block.cjs +31 -31
- package/kastell-plugin/hooks/hooks.json +57 -57
- package/kastell-plugin/hooks/pre-commit-audit-guard.cjs +75 -75
- package/kastell-plugin/hooks/session-audit.cjs +86 -86
- package/kastell-plugin/hooks/session-log.cjs +56 -56
- package/kastell-plugin/hooks/stop-quality-check.cjs +72 -72
- package/kastell-plugin/skills/kastell-careful/SKILL.md +64 -64
- package/kastell-plugin/skills/kastell-ops/SKILL.md +139 -139
- package/kastell-plugin/skills/kastell-ops/references/commands.md +45 -45
- package/kastell-plugin/skills/kastell-ops/references/mcp-tools.md +50 -50
- package/kastell-plugin/skills/kastell-ops/references/patterns.md +145 -145
- package/kastell-plugin/skills/kastell-ops/references/pitfalls.md +136 -136
- package/kastell-plugin/skills/kastell-ops/scripts/check_coverage.sh +101 -101
- package/kastell-plugin/skills/kastell-ops/scripts/fleet_report.sh +73 -73
- package/kastell-plugin/skills/kastell-ops/scripts/parse_audit.sh +76 -76
- package/kastell-plugin/skills/kastell-research/SKILL.md +90 -90
- package/kastell-plugin/skills/kastell-scaffold/SKILL.md +104 -104
- package/kastell-plugin/skills/kastell-scaffold/references/template-audit-check.md +150 -150
- package/kastell-plugin/skills/kastell-scaffold/references/template-command.md +80 -80
- package/kastell-plugin/skills/kastell-scaffold/references/template-mcp-tool.md +72 -72
- package/kastell-plugin/skills/kastell-scaffold/references/template-provider.md +67 -67
- package/kastell-plugin/skills/kastell-scaffold/scripts/scaffold.sh +180 -180
- package/kastell-plugin/skills/kastell-scaffold/templates/check-test.ts.tpl +27 -27
- package/kastell-plugin/skills/kastell-scaffold/templates/check.ts.tpl +50 -50
- package/kastell-plugin/skills/kastell-scaffold/templates/command-core.ts.tpl +18 -18
- package/kastell-plugin/skills/kastell-scaffold/templates/command-test.ts.tpl +17 -17
- package/kastell-plugin/skills/kastell-scaffold/templates/command.ts.tpl +25 -25
- package/kastell-plugin/skills/kastell-scaffold/templates/mcp-tool-test.ts.tpl +30 -30
- package/kastell-plugin/skills/kastell-scaffold/templates/mcp-tool.ts.tpl +29 -29
- package/kastell-plugin/skills/kastell-scaffold/templates/provider-test.ts.tpl +34 -34
- package/kastell-plugin/skills/kastell-scaffold/templates/provider.ts.tpl +32 -32
- package/package.json +125 -122
- package/dist/commands/interactive.d.ts +0 -11
- package/dist/commands/interactive.d.ts.map +0 -1
- package/dist/commands/interactive.js +0 -1079
- package/dist/commands/interactive.js.map +0 -1
- package/dist/core/lock.d.ts +0 -66
- package/dist/core/lock.d.ts.map +0 -1
- package/dist/core/lock.js +0 -556
- package/dist/core/lock.js.map +0 -1
|
@@ -1,101 +1,101 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# bucket_mapper.sh — Map kastell audit JSON checks to 5 security buckets.
|
|
3
|
-
# Usage: kastell audit --server <name> --json | bash bucket_mapper.sh
|
|
4
|
-
# OR: bash bucket_mapper.sh < audit-output.json
|
|
5
|
-
# OR: bash bucket_mapper.sh audit-output.json
|
|
6
|
-
#
|
|
7
|
-
# Output: Per-bucket check list with pass/fail status and severity.
|
|
8
|
-
# Used by kastell-auditor agent for structured analysis.
|
|
9
|
-
#
|
|
10
|
-
# Requires: node
|
|
11
|
-
|
|
12
|
-
set -euo pipefail
|
|
13
|
-
|
|
14
|
-
if [[ -n "${1:-}" && -f "$1" ]]; then
|
|
15
|
-
INPUT=$(cat "$1")
|
|
16
|
-
elif [[ ! -t 0 ]]; then
|
|
17
|
-
INPUT=$(cat)
|
|
18
|
-
else
|
|
19
|
-
echo "Usage: kastell audit --server <name> --json | bash bucket_mapper.sh" >&2
|
|
20
|
-
exit 1
|
|
21
|
-
fi
|
|
22
|
-
|
|
23
|
-
node -e "
|
|
24
|
-
const data = JSON.parse(process.argv[1]);
|
|
25
|
-
const checks = data.checks || data.results || [];
|
|
26
|
-
|
|
27
|
-
const BUCKETS = {
|
|
28
|
-
'1_Perimeter': {
|
|
29
|
-
match: ['network', 'firewall', 'dns'],
|
|
30
|
-
checks: []
|
|
31
|
-
},
|
|
32
|
-
'2_Authentication': {
|
|
33
|
-
match: ['ssh', 'auth', 'crypto', 'accounts'],
|
|
34
|
-
checks: []
|
|
35
|
-
},
|
|
36
|
-
'3_Runtime': {
|
|
37
|
-
match: ['docker', 'services', 'boot', 'scheduling'],
|
|
38
|
-
checks: []
|
|
39
|
-
},
|
|
40
|
-
'4_Internals': {
|
|
41
|
-
match: ['filesystem', 'logging', 'kernel', 'memory'],
|
|
42
|
-
checks: []
|
|
43
|
-
},
|
|
44
|
-
'5_Compliance': {
|
|
45
|
-
match: [], // catchall
|
|
46
|
-
checks: []
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
function getBucket(category) {
|
|
51
|
-
const cat = (category || '').toLowerCase();
|
|
52
|
-
for (const [name, bucket] of Object.entries(BUCKETS)) {
|
|
53
|
-
if (name === '5_Compliance') continue;
|
|
54
|
-
if (bucket.match.some(m => cat.includes(m))) return name;
|
|
55
|
-
}
|
|
56
|
-
return '5_Compliance';
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Map checks to buckets
|
|
60
|
-
for (const c of checks) {
|
|
61
|
-
const bucket = getBucket(c.category);
|
|
62
|
-
BUCKETS[bucket].checks.push({
|
|
63
|
-
id: c.id || 'unknown',
|
|
64
|
-
name: c.name || '',
|
|
65
|
-
severity: c.severity || 'info',
|
|
66
|
-
passed: !!c.passed,
|
|
67
|
-
category: c.category || ''
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Output
|
|
72
|
-
const score = data.score ?? data.overallScore ?? 'N/A';
|
|
73
|
-
console.log('Score: ' + score + '/100');
|
|
74
|
-
console.log('Total checks: ' + checks.length);
|
|
75
|
-
console.log('');
|
|
76
|
-
|
|
77
|
-
for (const [name, bucket] of Object.entries(BUCKETS)) {
|
|
78
|
-
const label = name.replace(/^[0-9]_/, '');
|
|
79
|
-
const passed = bucket.checks.filter(c => c.passed).length;
|
|
80
|
-
const total = bucket.checks.length;
|
|
81
|
-
const critFail = bucket.checks.filter(c => !c.passed && c.severity === 'critical');
|
|
82
|
-
|
|
83
|
-
console.log('--- ' + label + ' (' + passed + '/' + total + ') ---');
|
|
84
|
-
|
|
85
|
-
// Show failed checks (critical first, then warning)
|
|
86
|
-
const failed = bucket.checks
|
|
87
|
-
.filter(c => !c.passed)
|
|
88
|
-
.sort((a, b) => {
|
|
89
|
-
const sev = { critical: 0, warning: 1, info: 2 };
|
|
90
|
-
return (sev[a.severity] ?? 3) - (sev[b.severity] ?? 3);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
for (const c of failed.slice(0, 5)) {
|
|
94
|
-
const icon = c.severity === 'critical' ? '!!' : c.severity === 'warning' ? '! ' : ' ';
|
|
95
|
-
console.log(' [FAIL] ' + icon + c.id + ' — ' + c.name);
|
|
96
|
-
}
|
|
97
|
-
if (failed.length > 5) console.log(' ... and ' + (failed.length - 5) + ' more');
|
|
98
|
-
if (failed.length === 0) console.log(' All checks passed');
|
|
99
|
-
console.log('');
|
|
100
|
-
}
|
|
101
|
-
" "$INPUT"
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# bucket_mapper.sh — Map kastell audit JSON checks to 5 security buckets.
|
|
3
|
+
# Usage: kastell audit --server <name> --json | bash bucket_mapper.sh
|
|
4
|
+
# OR: bash bucket_mapper.sh < audit-output.json
|
|
5
|
+
# OR: bash bucket_mapper.sh audit-output.json
|
|
6
|
+
#
|
|
7
|
+
# Output: Per-bucket check list with pass/fail status and severity.
|
|
8
|
+
# Used by kastell-auditor agent for structured analysis.
|
|
9
|
+
#
|
|
10
|
+
# Requires: node
|
|
11
|
+
|
|
12
|
+
set -euo pipefail
|
|
13
|
+
|
|
14
|
+
if [[ -n "${1:-}" && -f "$1" ]]; then
|
|
15
|
+
INPUT=$(cat "$1")
|
|
16
|
+
elif [[ ! -t 0 ]]; then
|
|
17
|
+
INPUT=$(cat)
|
|
18
|
+
else
|
|
19
|
+
echo "Usage: kastell audit --server <name> --json | bash bucket_mapper.sh" >&2
|
|
20
|
+
exit 1
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
node -e "
|
|
24
|
+
const data = JSON.parse(process.argv[1]);
|
|
25
|
+
const checks = data.checks || data.results || [];
|
|
26
|
+
|
|
27
|
+
const BUCKETS = {
|
|
28
|
+
'1_Perimeter': {
|
|
29
|
+
match: ['network', 'firewall', 'dns'],
|
|
30
|
+
checks: []
|
|
31
|
+
},
|
|
32
|
+
'2_Authentication': {
|
|
33
|
+
match: ['ssh', 'auth', 'crypto', 'accounts'],
|
|
34
|
+
checks: []
|
|
35
|
+
},
|
|
36
|
+
'3_Runtime': {
|
|
37
|
+
match: ['docker', 'services', 'boot', 'scheduling'],
|
|
38
|
+
checks: []
|
|
39
|
+
},
|
|
40
|
+
'4_Internals': {
|
|
41
|
+
match: ['filesystem', 'logging', 'kernel', 'memory'],
|
|
42
|
+
checks: []
|
|
43
|
+
},
|
|
44
|
+
'5_Compliance': {
|
|
45
|
+
match: [], // catchall
|
|
46
|
+
checks: []
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
function getBucket(category) {
|
|
51
|
+
const cat = (category || '').toLowerCase();
|
|
52
|
+
for (const [name, bucket] of Object.entries(BUCKETS)) {
|
|
53
|
+
if (name === '5_Compliance') continue;
|
|
54
|
+
if (bucket.match.some(m => cat.includes(m))) return name;
|
|
55
|
+
}
|
|
56
|
+
return '5_Compliance';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Map checks to buckets
|
|
60
|
+
for (const c of checks) {
|
|
61
|
+
const bucket = getBucket(c.category);
|
|
62
|
+
BUCKETS[bucket].checks.push({
|
|
63
|
+
id: c.id || 'unknown',
|
|
64
|
+
name: c.name || '',
|
|
65
|
+
severity: c.severity || 'info',
|
|
66
|
+
passed: !!c.passed,
|
|
67
|
+
category: c.category || ''
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Output
|
|
72
|
+
const score = data.score ?? data.overallScore ?? 'N/A';
|
|
73
|
+
console.log('Score: ' + score + '/100');
|
|
74
|
+
console.log('Total checks: ' + checks.length);
|
|
75
|
+
console.log('');
|
|
76
|
+
|
|
77
|
+
for (const [name, bucket] of Object.entries(BUCKETS)) {
|
|
78
|
+
const label = name.replace(/^[0-9]_/, '');
|
|
79
|
+
const passed = bucket.checks.filter(c => c.passed).length;
|
|
80
|
+
const total = bucket.checks.length;
|
|
81
|
+
const critFail = bucket.checks.filter(c => !c.passed && c.severity === 'critical');
|
|
82
|
+
|
|
83
|
+
console.log('--- ' + label + ' (' + passed + '/' + total + ') ---');
|
|
84
|
+
|
|
85
|
+
// Show failed checks (critical first, then warning)
|
|
86
|
+
const failed = bucket.checks
|
|
87
|
+
.filter(c => !c.passed)
|
|
88
|
+
.sort((a, b) => {
|
|
89
|
+
const sev = { critical: 0, warning: 1, info: 2 };
|
|
90
|
+
return (sev[a.severity] ?? 3) - (sev[b.severity] ?? 3);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
for (const c of failed.slice(0, 5)) {
|
|
94
|
+
const icon = c.severity === 'critical' ? '!!' : c.severity === 'warning' ? '! ' : ' ';
|
|
95
|
+
console.log(' [FAIL] ' + icon + c.id + ' — ' + c.name);
|
|
96
|
+
}
|
|
97
|
+
if (failed.length > 5) console.log(' ... and ' + (failed.length - 5) + ' more');
|
|
98
|
+
if (failed.length === 0) console.log(' All checks passed');
|
|
99
|
+
console.log('');
|
|
100
|
+
}
|
|
101
|
+
" "$INPUT"
|
|
@@ -1,91 +1,91 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# trend_report.sh — Generate audit score trend from audit-history.json.
|
|
3
|
-
# Usage: bash trend_report.sh [server-name]
|
|
4
|
-
# OR: bash trend_report.sh --all
|
|
5
|
-
#
|
|
6
|
-
# Reads ~/.kastell/audit-history.json (maintained by kastell-auditor agent).
|
|
7
|
-
# Shows score over time with delta indicators.
|
|
8
|
-
#
|
|
9
|
-
# Requires: node
|
|
10
|
-
|
|
11
|
-
set -euo pipefail
|
|
12
|
-
|
|
13
|
-
HISTORY_FILE="${KASTELL_HOME:-$HOME/.kastell}/audit-history.json"
|
|
14
|
-
SERVER="${1:-}"
|
|
15
|
-
|
|
16
|
-
if [[ ! -f "$HISTORY_FILE" ]]; then
|
|
17
|
-
echo "No audit history found at: $HISTORY_FILE"
|
|
18
|
-
echo "Run 'kastell audit --server <name>' to generate data."
|
|
19
|
-
exit 0
|
|
20
|
-
fi
|
|
21
|
-
|
|
22
|
-
node -e "
|
|
23
|
-
const fs = require('fs');
|
|
24
|
-
const data = JSON.parse(fs.readFileSync(process.argv[1], 'utf8'));
|
|
25
|
-
const server = process.argv[2] || '';
|
|
26
|
-
const showAll = server === '--all';
|
|
27
|
-
|
|
28
|
-
if (!Array.isArray(data) || data.length === 0) {
|
|
29
|
-
console.log('No audit history entries found.');
|
|
30
|
-
process.exit(0);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Filter by server if specified
|
|
34
|
-
let entries = showAll ? data : server
|
|
35
|
-
? data.filter(e => e.serverName === server || e.server === server)
|
|
36
|
-
: data;
|
|
37
|
-
|
|
38
|
-
if (entries.length === 0) {
|
|
39
|
-
console.log('No audit history for server: ' + server);
|
|
40
|
-
console.log('Available servers: ' + [...new Set(data.map(e => e.serverName || e.server))].join(', '));
|
|
41
|
-
process.exit(0);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Sort by date
|
|
45
|
-
entries.sort((a, b) => new Date(a.timestamp || a.date) - new Date(b.timestamp || b.date));
|
|
46
|
-
|
|
47
|
-
// Group by server
|
|
48
|
-
const byServer = {};
|
|
49
|
-
for (const e of entries) {
|
|
50
|
-
const name = e.serverName || e.server || 'unknown';
|
|
51
|
-
if (!byServer[name]) byServer[name] = [];
|
|
52
|
-
byServer[name].push(e);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
console.log('=== Audit Score Trend ===');
|
|
56
|
-
console.log('');
|
|
57
|
-
|
|
58
|
-
for (const [name, history] of Object.entries(byServer)) {
|
|
59
|
-
console.log('Server: ' + name);
|
|
60
|
-
console.log('-'.repeat(50));
|
|
61
|
-
|
|
62
|
-
let prev = null;
|
|
63
|
-
for (const e of history) {
|
|
64
|
-
const score = e.overallScore ?? e.score ?? 0;
|
|
65
|
-
const date = (e.timestamp || e.date || '').split('T')[0];
|
|
66
|
-
let delta = '';
|
|
67
|
-
if (prev !== null) {
|
|
68
|
-
const diff = score - prev;
|
|
69
|
-
delta = diff > 0 ? ' (+' + diff + ')' : diff < 0 ? ' (' + diff + ')' : ' (=)';
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Visual bar
|
|
73
|
-
const filled = Math.round(score / 5);
|
|
74
|
-
const bar = '█'.repeat(filled) + '░'.repeat(20 - filled);
|
|
75
|
-
|
|
76
|
-
console.log(' ' + date + ' ' + String(score).padStart(3) + '/100 ' + bar + delta);
|
|
77
|
-
prev = score;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Latest bucket scores if available
|
|
81
|
-
const latest = history[history.length - 1];
|
|
82
|
-
if (latest.bucketScores) {
|
|
83
|
-
console.log('');
|
|
84
|
-
console.log(' Latest bucket breakdown:');
|
|
85
|
-
for (const [bucket, score] of Object.entries(latest.bucketScores)) {
|
|
86
|
-
console.log(' ' + bucket.padEnd(15) + ': ' + score);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
console.log('');
|
|
90
|
-
}
|
|
91
|
-
" "$HISTORY_FILE" "$SERVER"
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# trend_report.sh — Generate audit score trend from audit-history.json.
|
|
3
|
+
# Usage: bash trend_report.sh [server-name]
|
|
4
|
+
# OR: bash trend_report.sh --all
|
|
5
|
+
#
|
|
6
|
+
# Reads ~/.kastell/audit-history.json (maintained by kastell-auditor agent).
|
|
7
|
+
# Shows score over time with delta indicators.
|
|
8
|
+
#
|
|
9
|
+
# Requires: node
|
|
10
|
+
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
HISTORY_FILE="${KASTELL_HOME:-$HOME/.kastell}/audit-history.json"
|
|
14
|
+
SERVER="${1:-}"
|
|
15
|
+
|
|
16
|
+
if [[ ! -f "$HISTORY_FILE" ]]; then
|
|
17
|
+
echo "No audit history found at: $HISTORY_FILE"
|
|
18
|
+
echo "Run 'kastell audit --server <name>' to generate data."
|
|
19
|
+
exit 0
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
node -e "
|
|
23
|
+
const fs = require('fs');
|
|
24
|
+
const data = JSON.parse(fs.readFileSync(process.argv[1], 'utf8'));
|
|
25
|
+
const server = process.argv[2] || '';
|
|
26
|
+
const showAll = server === '--all';
|
|
27
|
+
|
|
28
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
29
|
+
console.log('No audit history entries found.');
|
|
30
|
+
process.exit(0);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Filter by server if specified
|
|
34
|
+
let entries = showAll ? data : server
|
|
35
|
+
? data.filter(e => e.serverName === server || e.server === server)
|
|
36
|
+
: data;
|
|
37
|
+
|
|
38
|
+
if (entries.length === 0) {
|
|
39
|
+
console.log('No audit history for server: ' + server);
|
|
40
|
+
console.log('Available servers: ' + [...new Set(data.map(e => e.serverName || e.server))].join(', '));
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Sort by date
|
|
45
|
+
entries.sort((a, b) => new Date(a.timestamp || a.date) - new Date(b.timestamp || b.date));
|
|
46
|
+
|
|
47
|
+
// Group by server
|
|
48
|
+
const byServer = {};
|
|
49
|
+
for (const e of entries) {
|
|
50
|
+
const name = e.serverName || e.server || 'unknown';
|
|
51
|
+
if (!byServer[name]) byServer[name] = [];
|
|
52
|
+
byServer[name].push(e);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
console.log('=== Audit Score Trend ===');
|
|
56
|
+
console.log('');
|
|
57
|
+
|
|
58
|
+
for (const [name, history] of Object.entries(byServer)) {
|
|
59
|
+
console.log('Server: ' + name);
|
|
60
|
+
console.log('-'.repeat(50));
|
|
61
|
+
|
|
62
|
+
let prev = null;
|
|
63
|
+
for (const e of history) {
|
|
64
|
+
const score = e.overallScore ?? e.score ?? 0;
|
|
65
|
+
const date = (e.timestamp || e.date || '').split('T')[0];
|
|
66
|
+
let delta = '';
|
|
67
|
+
if (prev !== null) {
|
|
68
|
+
const diff = score - prev;
|
|
69
|
+
delta = diff > 0 ? ' (+' + diff + ')' : diff < 0 ? ' (' + diff + ')' : ' (=)';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Visual bar
|
|
73
|
+
const filled = Math.round(score / 5);
|
|
74
|
+
const bar = '█'.repeat(filled) + '░'.repeat(20 - filled);
|
|
75
|
+
|
|
76
|
+
console.log(' ' + date + ' ' + String(score).padStart(3) + '/100 ' + bar + delta);
|
|
77
|
+
prev = score;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Latest bucket scores if available
|
|
81
|
+
const latest = history[history.length - 1];
|
|
82
|
+
if (latest.bucketScores) {
|
|
83
|
+
console.log('');
|
|
84
|
+
console.log(' Latest bucket breakdown:');
|
|
85
|
+
for (const [bucket, score] of Object.entries(latest.bucketScores)) {
|
|
86
|
+
console.log(' ' + bucket.padEnd(15) + ': ' + score);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
console.log('');
|
|
90
|
+
}
|
|
91
|
+
" "$HISTORY_FILE" "$SERVER"
|
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// PreToolUse hook: Block kastell destroy / server-delete commands (hard block)
|
|
3
|
-
|
|
4
|
-
// MANDATORY stdin guard — exit silently if stdin unavailable (e.g. after /clear)
|
|
5
|
-
if (!process.stdin || process.stdin.destroyed || !process.stdin.readable) {
|
|
6
|
-
process.exit(0);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
let input = '';
|
|
10
|
-
const stdinTimeout = setTimeout(() => process.exit(0), 1500);
|
|
11
|
-
process.stdin.setEncoding('utf8');
|
|
12
|
-
process.stdin.on('error', () => process.exit(0));
|
|
13
|
-
process.stdin.on('data', chunk => { input += chunk; });
|
|
14
|
-
process.stdin.on('end', () => {
|
|
15
|
-
clearTimeout(stdinTimeout);
|
|
16
|
-
try {
|
|
17
|
-
const data = JSON.parse(input);
|
|
18
|
-
const cmd = (data.tool_input && data.tool_input.command) || '';
|
|
19
|
-
|
|
20
|
-
if (/\bkastell\s+(destroy|server-delete)\b/.test(cmd)) {
|
|
21
|
-
process.stdout.write(JSON.stringify({
|
|
22
|
-
decision: 'block',
|
|
23
|
-
reason: 'Destructive kastell operation detected. Use kastell destroy with --force flag directly in terminal, not through Claude Code.',
|
|
24
|
-
}));
|
|
25
|
-
process.exit(2);
|
|
26
|
-
}
|
|
27
|
-
} catch {}
|
|
28
|
-
|
|
29
|
-
// Not destructive or parse error — allow
|
|
30
|
-
process.exit(0);
|
|
31
|
-
});
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// PreToolUse hook: Block kastell destroy / server-delete commands (hard block)
|
|
3
|
+
|
|
4
|
+
// MANDATORY stdin guard — exit silently if stdin unavailable (e.g. after /clear)
|
|
5
|
+
if (!process.stdin || process.stdin.destroyed || !process.stdin.readable) {
|
|
6
|
+
process.exit(0);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let input = '';
|
|
10
|
+
const stdinTimeout = setTimeout(() => process.exit(0), 1500);
|
|
11
|
+
process.stdin.setEncoding('utf8');
|
|
12
|
+
process.stdin.on('error', () => process.exit(0));
|
|
13
|
+
process.stdin.on('data', chunk => { input += chunk; });
|
|
14
|
+
process.stdin.on('end', () => {
|
|
15
|
+
clearTimeout(stdinTimeout);
|
|
16
|
+
try {
|
|
17
|
+
const data = JSON.parse(input);
|
|
18
|
+
const cmd = (data.tool_input && data.tool_input.command) || '';
|
|
19
|
+
|
|
20
|
+
if (/\bkastell\s+(destroy|server-delete)\b/.test(cmd)) {
|
|
21
|
+
process.stdout.write(JSON.stringify({
|
|
22
|
+
decision: 'block',
|
|
23
|
+
reason: 'Destructive kastell operation detected. Use kastell destroy with --force flag directly in terminal, not through Claude Code.',
|
|
24
|
+
}));
|
|
25
|
+
process.exit(2);
|
|
26
|
+
}
|
|
27
|
+
} catch {}
|
|
28
|
+
|
|
29
|
+
// Not destructive or parse error — allow
|
|
30
|
+
process.exit(0);
|
|
31
|
+
});
|
|
@@ -1,57 +1,57 @@
|
|
|
1
|
-
{
|
|
2
|
-
"hooks": {
|
|
3
|
-
"PreToolUse": [
|
|
4
|
-
{
|
|
5
|
-
"matcher": "Bash",
|
|
6
|
-
"if": "Bash(*kastell*)",
|
|
7
|
-
"hooks": [
|
|
8
|
-
{
|
|
9
|
-
"type": "command",
|
|
10
|
-
"command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/destroy-block.cjs"
|
|
11
|
-
}
|
|
12
|
-
]
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
"matcher": "Bash",
|
|
16
|
-
"if": "Bash(*git commit*)",
|
|
17
|
-
"hooks": [
|
|
18
|
-
{
|
|
19
|
-
"type": "command",
|
|
20
|
-
"command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/pre-commit-audit-guard.cjs"
|
|
21
|
-
}
|
|
22
|
-
]
|
|
23
|
-
}
|
|
24
|
-
],
|
|
25
|
-
"PostToolUse": [
|
|
26
|
-
{
|
|
27
|
-
"matcher": "Bash",
|
|
28
|
-
"hooks": [
|
|
29
|
-
{
|
|
30
|
-
"type": "command",
|
|
31
|
-
"command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/session-log.cjs"
|
|
32
|
-
}
|
|
33
|
-
]
|
|
34
|
-
}
|
|
35
|
-
],
|
|
36
|
-
"SessionStart": [
|
|
37
|
-
{
|
|
38
|
-
"hooks": [
|
|
39
|
-
{
|
|
40
|
-
"type": "command",
|
|
41
|
-
"command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/session-audit.cjs"
|
|
42
|
-
}
|
|
43
|
-
]
|
|
44
|
-
}
|
|
45
|
-
],
|
|
46
|
-
"Stop": [
|
|
47
|
-
{
|
|
48
|
-
"hooks": [
|
|
49
|
-
{
|
|
50
|
-
"type": "command",
|
|
51
|
-
"command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/stop-quality-check.cjs"
|
|
52
|
-
}
|
|
53
|
-
]
|
|
54
|
-
}
|
|
55
|
-
]
|
|
56
|
-
}
|
|
57
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"PreToolUse": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "Bash",
|
|
6
|
+
"if": "Bash(*kastell*)",
|
|
7
|
+
"hooks": [
|
|
8
|
+
{
|
|
9
|
+
"type": "command",
|
|
10
|
+
"command": "node ${CLAUDE_PLUGIN_ROOT}/kastell-plugin/hooks/destroy-block.cjs"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"matcher": "Bash",
|
|
16
|
+
"if": "Bash(*git commit*)",
|
|
17
|
+
"hooks": [
|
|
18
|
+
{
|
|
19
|
+
"type": "command",
|
|
20
|
+
"command": "node ${CLAUDE_PLUGIN_ROOT}/kastell-plugin/hooks/pre-commit-audit-guard.cjs"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"PostToolUse": [
|
|
26
|
+
{
|
|
27
|
+
"matcher": "Bash",
|
|
28
|
+
"hooks": [
|
|
29
|
+
{
|
|
30
|
+
"type": "command",
|
|
31
|
+
"command": "node ${CLAUDE_PLUGIN_ROOT}/kastell-plugin/hooks/session-log.cjs"
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"SessionStart": [
|
|
37
|
+
{
|
|
38
|
+
"hooks": [
|
|
39
|
+
{
|
|
40
|
+
"type": "command",
|
|
41
|
+
"command": "node ${CLAUDE_PLUGIN_ROOT}/kastell-plugin/hooks/session-audit.cjs"
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
"Stop": [
|
|
47
|
+
{
|
|
48
|
+
"hooks": [
|
|
49
|
+
{
|
|
50
|
+
"type": "command",
|
|
51
|
+
"command": "node ${CLAUDE_PLUGIN_ROOT}/kastell-plugin/hooks/stop-quality-check.cjs"
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
}
|