cc-health-check 1.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/LICENSE +21 -0
- package/README.md +154 -0
- package/cli.mjs +622 -0
- package/package.json +33 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 yurukusa
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# cc-health-check
|
|
2
|
+
|
|
3
|
+
> **Is your Claude Code setup actually production-ready?**
|
|
4
|
+
>
|
|
5
|
+
> 108 hours of autonomous AI operation taught us what breaks first. This tool checks your setup against 20 real failure patterns — in 30 seconds.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
npx cc-health-check
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**No installation required. Nothing leaves your machine.**
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Why this exists
|
|
16
|
+
|
|
17
|
+
Claude Code can run autonomously for hours. But most setups aren't built for that. Files get deleted. Costs spike. The AI loops on errors. Push-to-main happens without review.
|
|
18
|
+
|
|
19
|
+
These aren't hypothetical. They're what actually happens without the right guardrails.
|
|
20
|
+
|
|
21
|
+
cc-health-check scans your `.claude/settings.json` and `CLAUDE.md` for 20 known failure patterns, gives you a score, and tells you exactly what to fix.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Quick start (no install)
|
|
26
|
+
|
|
27
|
+
**Web version** — paste in your CLAUDE.md, get your score instantly:
|
|
28
|
+
👉 https://yurukusa.github.io/cc-health-check/
|
|
29
|
+
|
|
30
|
+
**CLI** — scans your local setup automatically:
|
|
31
|
+
```bash
|
|
32
|
+
npx cc-health-check
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## What it checks
|
|
38
|
+
|
|
39
|
+
| Dimension | Checks | What it looks for |
|
|
40
|
+
|-----------|--------|-------------------|
|
|
41
|
+
| Safety Guards | 4 | PreToolUse hooks, secret handling, branch protection, error gates |
|
|
42
|
+
| Code Quality | 4 | Syntax checking, error tracking, DoD checklists, output verification |
|
|
43
|
+
| Monitoring | 3 | Context window alerts, activity logging, daily summaries |
|
|
44
|
+
| Recovery | 3 | Backup branches, watchdog, loop detection |
|
|
45
|
+
| Autonomy | 3 | Task queues, question blocking, persistent state |
|
|
46
|
+
| Coordination | 3 | Decision logs, multi-agent support, lesson capture |
|
|
47
|
+
|
|
48
|
+
## Sample output
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
Claude Code Health Check v1.0
|
|
52
|
+
═══════════════════════════════════════
|
|
53
|
+
|
|
54
|
+
▸ Safety Guards
|
|
55
|
+
[PASS] PreToolUse hook blocks dangerous commands
|
|
56
|
+
[PASS] API keys stored in dedicated files
|
|
57
|
+
[FAIL] Setup prevents pushing to main/master without review
|
|
58
|
+
[PASS] Error-aware gate blocks external calls when errors exist
|
|
59
|
+
|
|
60
|
+
Score: 63/100 — Getting There
|
|
61
|
+
|
|
62
|
+
Top fixes:
|
|
63
|
+
→ Add a PreToolUse hook that blocks destructive commands.
|
|
64
|
+
→ Scan bash output for error patterns in PostToolUse hooks.
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Scores
|
|
68
|
+
|
|
69
|
+
| Score | Grade |
|
|
70
|
+
|-------|-------|
|
|
71
|
+
| 80-100 | Production Ready |
|
|
72
|
+
| 60-79 | Getting There |
|
|
73
|
+
| 35-59 | Needs Work |
|
|
74
|
+
| 0-34 | Critical |
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Got a low score?
|
|
79
|
+
|
|
80
|
+
**[claude-code-hooks](https://github.com/yurukusa/claude-code-hooks)** covers 18 of the 20 checks — drop-in hooks and templates extracted from 108 hours of real autonomous operation.
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# See what you're missing
|
|
84
|
+
npx cc-health-check
|
|
85
|
+
|
|
86
|
+
# Fix it
|
|
87
|
+
git clone https://github.com/yurukusa/claude-code-hooks
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## How it works
|
|
93
|
+
|
|
94
|
+
1. Reads `~/.claude/settings.json` for hook configurations
|
|
95
|
+
2. Scans `CLAUDE.md` files (global + project) for patterns
|
|
96
|
+
3. Checks for common files (`mission.md`, `proof-log/`, `task-queue.yaml`)
|
|
97
|
+
4. Scores each check (pass/fail) and calculates dimension scores
|
|
98
|
+
5. Outputs actionable recommendations sorted by impact
|
|
99
|
+
|
|
100
|
+
**Zero dependencies. No data sent anywhere. Runs entirely local.**
|
|
101
|
+
|
|
102
|
+
## JSON output
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
npx cc-health-check --json
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Returns structured JSON with score, grade, dimensions, and per-check results. Useful for CI pipelines, dashboards, or programmatic analysis.
|
|
109
|
+
|
|
110
|
+
## README badge
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
npx cc-health-check --badge
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Generates a shields.io badge URL for your README:
|
|
117
|
+
|
|
118
|
+

|
|
119
|
+
|
|
120
|
+
## CI integration
|
|
121
|
+
|
|
122
|
+
Exit code `0` if score >= 60, `1` otherwise.
|
|
123
|
+
|
|
124
|
+
```yaml
|
|
125
|
+
# .github/workflows/health-check.yml
|
|
126
|
+
name: Claude Code Health Check
|
|
127
|
+
on:
|
|
128
|
+
push:
|
|
129
|
+
paths: ['.claude/**', 'CLAUDE.md']
|
|
130
|
+
jobs:
|
|
131
|
+
check:
|
|
132
|
+
runs-on: ubuntu-latest
|
|
133
|
+
steps:
|
|
134
|
+
- uses: actions/checkout@v4
|
|
135
|
+
- run: npx cc-health-check@latest
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## The cc-toolkit
|
|
141
|
+
|
|
142
|
+
| Tool | What it does |
|
|
143
|
+
|------|--------------|
|
|
144
|
+
| **cc-health-check** | Is your AI **setup** safe? (you are here) |
|
|
145
|
+
| [claude-code-hooks](https://github.com/yurukusa/claude-code-hooks) | Fix your score — 10 hooks + 5 templates |
|
|
146
|
+
| [cc-session-stats](https://github.com/yurukusa/cc-session-stats) | How much are you **using** AI? |
|
|
147
|
+
| [cc-audit-log](https://github.com/yurukusa/cc-audit-log) | What did your AI **do**? |
|
|
148
|
+
| [cc-cost-check](https://github.com/yurukusa/cc-cost-check) | Cost per commit calculator |
|
|
149
|
+
| [cc-wrapped](https://yurukusa.github.io/cc-wrapped/) | Your AI year in review |
|
|
150
|
+
| [cc-roast](https://yurukusa.github.io/cc-roast/) | Your CLAUDE.md, brutally honest |
|
|
151
|
+
|
|
152
|
+
## License
|
|
153
|
+
|
|
154
|
+
MIT
|
package/cli.mjs
ADDED
|
@@ -0,0 +1,622 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// cc-health-check — CLI diagnostic for Claude Code setups
|
|
4
|
+
// Automatically detects settings, hooks, and patterns to score your setup.
|
|
5
|
+
|
|
6
|
+
import { readFileSync, existsSync, readdirSync, statSync } from 'node:fs';
|
|
7
|
+
import { join, resolve } from 'node:path';
|
|
8
|
+
import { homedir } from 'node:os';
|
|
9
|
+
|
|
10
|
+
const HOME = homedir();
|
|
11
|
+
const CC_DIR = join(HOME, '.claude');
|
|
12
|
+
const SETTINGS_PATH = join(CC_DIR, 'settings.json');
|
|
13
|
+
|
|
14
|
+
// ─── Color helpers (no dependencies) ───
|
|
15
|
+
const c = {
|
|
16
|
+
reset: '\x1b[0m',
|
|
17
|
+
bold: '\x1b[1m',
|
|
18
|
+
dim: '\x1b[2m',
|
|
19
|
+
green: '\x1b[32m',
|
|
20
|
+
yellow: '\x1b[33m',
|
|
21
|
+
red: '\x1b[31m',
|
|
22
|
+
cyan: '\x1b[36m',
|
|
23
|
+
magenta: '\x1b[35m',
|
|
24
|
+
white: '\x1b[37m',
|
|
25
|
+
bgGreen: '\x1b[42m',
|
|
26
|
+
bgYellow: '\x1b[43m',
|
|
27
|
+
bgRed: '\x1b[41m',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const PASS = `${c.green}[PASS]${c.reset}`;
|
|
31
|
+
const WARN = `${c.yellow}[WARN]${c.reset}`;
|
|
32
|
+
const FAIL = `${c.red}[FAIL]${c.reset}`;
|
|
33
|
+
const INFO = `${c.cyan}[INFO]${c.reset}`;
|
|
34
|
+
|
|
35
|
+
// ─── Utility ───
|
|
36
|
+
function readJSON(path) {
|
|
37
|
+
try {
|
|
38
|
+
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
39
|
+
} catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function fileContains(path, patterns) {
|
|
45
|
+
try {
|
|
46
|
+
const content = readFileSync(path, 'utf-8').toLowerCase();
|
|
47
|
+
return patterns.some(p => content.includes(p.toLowerCase()));
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function findFilesRecursive(dir, maxDepth = 3, depth = 0) {
|
|
54
|
+
if (depth > maxDepth || !existsSync(dir)) return [];
|
|
55
|
+
const files = [];
|
|
56
|
+
try {
|
|
57
|
+
for (const entry of readdirSync(dir)) {
|
|
58
|
+
const full = join(dir, entry);
|
|
59
|
+
try {
|
|
60
|
+
const stat = statSync(full);
|
|
61
|
+
if (stat.isFile()) files.push(full);
|
|
62
|
+
else if (stat.isDirectory() && !entry.startsWith('.') && entry !== 'node_modules') {
|
|
63
|
+
files.push(...findFilesRecursive(full, maxDepth, depth + 1));
|
|
64
|
+
}
|
|
65
|
+
} catch { /* permission denied, skip */ }
|
|
66
|
+
}
|
|
67
|
+
} catch { /* permission denied, skip */ }
|
|
68
|
+
return files;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function getHookScripts(settings, eventType) {
|
|
72
|
+
if (!settings?.hooks) return [];
|
|
73
|
+
const hooks = settings.hooks[eventType];
|
|
74
|
+
if (!hooks) return [];
|
|
75
|
+
return (Array.isArray(hooks) ? hooks : [hooks])
|
|
76
|
+
.map(h => typeof h === 'string' ? h : h?.command || h?.script || '')
|
|
77
|
+
.filter(Boolean);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function getAllHookCommands(settings) {
|
|
81
|
+
if (!settings?.hooks) return [];
|
|
82
|
+
const all = [];
|
|
83
|
+
for (const [event, matchers] of Object.entries(settings.hooks)) {
|
|
84
|
+
// Claude Code format: { EventName: [{ matcher: "", hooks: [{ type, command }] }] }
|
|
85
|
+
const matcherList = Array.isArray(matchers) ? matchers : [matchers];
|
|
86
|
+
for (const matcher of matcherList) {
|
|
87
|
+
if (matcher?.hooks && Array.isArray(matcher.hooks)) {
|
|
88
|
+
for (const h of matcher.hooks) {
|
|
89
|
+
const cmd = h?.command || h?.script || '';
|
|
90
|
+
if (cmd) all.push({ event, command: cmd });
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
// Fallback: flat format { EventName: [{ command: "..." }] }
|
|
94
|
+
const cmd = typeof matcher === 'string' ? matcher : matcher?.command || matcher?.script || '';
|
|
95
|
+
if (cmd) all.push({ event, command: cmd });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return all;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ─── Load environment ───
|
|
103
|
+
const settings = readJSON(SETTINGS_PATH);
|
|
104
|
+
const allHooks = settings ? getAllHookCommands(settings) : [];
|
|
105
|
+
const allHookText = allHooks.map(h => h.command).join('\n').toLowerCase();
|
|
106
|
+
|
|
107
|
+
// Find CLAUDE.md files
|
|
108
|
+
const claudeMdPaths = [
|
|
109
|
+
join(HOME, 'CLAUDE.md'),
|
|
110
|
+
join(HOME, '.claude', 'CLAUDE.md'),
|
|
111
|
+
];
|
|
112
|
+
// Also check current directory
|
|
113
|
+
const cwd = process.cwd();
|
|
114
|
+
if (cwd !== HOME) {
|
|
115
|
+
claudeMdPaths.push(join(cwd, 'CLAUDE.md'));
|
|
116
|
+
claudeMdPaths.push(join(cwd, '.claude', 'CLAUDE.md'));
|
|
117
|
+
}
|
|
118
|
+
const claudeMdContents = claudeMdPaths
|
|
119
|
+
.filter(p => existsSync(p))
|
|
120
|
+
.map(p => readFileSync(p, 'utf-8').toLowerCase())
|
|
121
|
+
.join('\n');
|
|
122
|
+
|
|
123
|
+
// Check for common files
|
|
124
|
+
const hasMemoryDir = existsSync(join(CC_DIR, 'memory')) || existsSync(join(CC_DIR, 'projects'));
|
|
125
|
+
const hasMissionMd = existsSync(join(HOME, 'ops', 'mission.md')) ||
|
|
126
|
+
existsSync(join(cwd, 'mission.md')) ||
|
|
127
|
+
existsSync(join(cwd, 'tasks', 'todo.md'));
|
|
128
|
+
|
|
129
|
+
// ─── 20 Checks ───
|
|
130
|
+
const checks = [
|
|
131
|
+
// === SAFETY (4 checks, 5pts each = 20) ===
|
|
132
|
+
{
|
|
133
|
+
cat: 'Safety Guards',
|
|
134
|
+
q: 'PreToolUse hook blocks dangerous commands (rm -rf, git reset --hard)',
|
|
135
|
+
w: 5,
|
|
136
|
+
test() {
|
|
137
|
+
const preHooks = allHooks.filter(h => h.event.toLowerCase().includes('pretooluse'));
|
|
138
|
+
if (preHooks.length === 0) return { pass: false, detail: 'No PreToolUse hooks found' };
|
|
139
|
+
const hasGuard = preHooks.some(h => {
|
|
140
|
+
const cmd = h.command.toLowerCase();
|
|
141
|
+
return cmd.includes('rm') || cmd.includes('guard') || cmd.includes('safe') ||
|
|
142
|
+
cmd.includes('block') || cmd.includes('deny') || cmd.includes('cdp');
|
|
143
|
+
});
|
|
144
|
+
// Also check if hook scripts contain safety patterns
|
|
145
|
+
const scriptFiles = preHooks.map(h => {
|
|
146
|
+
const parts = h.command.split(/\s+/);
|
|
147
|
+
return parts.find(p => p.endsWith('.sh') || p.endsWith('.js') || p.endsWith('.py'));
|
|
148
|
+
}).filter(Boolean);
|
|
149
|
+
|
|
150
|
+
for (const sf of scriptFiles) {
|
|
151
|
+
const fullPath = sf.startsWith('/') ? sf : join(HOME, sf);
|
|
152
|
+
if (fileContains(fullPath, ['rm -rf', 'reset --hard', 'force', 'block', 'deny', 'BLOCK'])) {
|
|
153
|
+
return { pass: true, detail: `Safety hook found: ${sf}` };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return hasGuard
|
|
157
|
+
? { pass: true, detail: `${preHooks.length} PreToolUse hook(s) with safety patterns` }
|
|
158
|
+
: { pass: false, detail: `${preHooks.length} PreToolUse hook(s) found but no safety patterns detected` };
|
|
159
|
+
},
|
|
160
|
+
fix: 'Add a PreToolUse hook that blocks destructive commands. A single shell script can catch rm -rf, force push, and database drops.',
|
|
161
|
+
hook: 'hooks/branch-guard.sh',
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
cat: 'Safety Guards',
|
|
165
|
+
q: 'API keys stored in dedicated files (not hardcoded in CLAUDE.md)',
|
|
166
|
+
w: 5,
|
|
167
|
+
test() {
|
|
168
|
+
const hasSecrets = claudeMdContents.match(/sk-[a-z0-9]{20,}|ghp_[a-z0-9]{36}|token\s*[:=]\s*["\'][^"\']{20,}/i);
|
|
169
|
+
if (hasSecrets) return { pass: false, detail: 'Possible API key found in CLAUDE.md' };
|
|
170
|
+
const hasCredFile = existsSync(join(HOME, '.credentials')) ||
|
|
171
|
+
existsSync(join(HOME, '.env')) ||
|
|
172
|
+
existsSync(join(HOME, '.secrets'));
|
|
173
|
+
return { pass: true, detail: hasCredFile ? 'Credentials stored in dedicated file' : 'No leaked keys detected in CLAUDE.md' };
|
|
174
|
+
},
|
|
175
|
+
fix: 'Move API keys out of CLAUDE.md into ~/.credentials or environment variables.',
|
|
176
|
+
hook: 'templates/CLAUDE-autonomous.md',
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
cat: 'Safety Guards',
|
|
180
|
+
q: 'Setup prevents pushing to main/master without review',
|
|
181
|
+
w: 5,
|
|
182
|
+
test() {
|
|
183
|
+
const hasBranchGuard = allHookText.includes('main') || allHookText.includes('master') ||
|
|
184
|
+
allHookText.includes('branch') || allHookText.includes('push');
|
|
185
|
+
const claudeHasRule = claudeMdContents.includes('feature branch') ||
|
|
186
|
+
claudeMdContents.includes('push') && claudeMdContents.includes('main');
|
|
187
|
+
return (hasBranchGuard || claudeHasRule)
|
|
188
|
+
? { pass: true, detail: 'Branch protection detected' }
|
|
189
|
+
: { pass: false, detail: 'No branch protection rules found' };
|
|
190
|
+
},
|
|
191
|
+
fix: 'Add a PreToolUse hook that checks the target branch before git push. Block direct pushes to main/master.',
|
|
192
|
+
hook: 'hooks/branch-guard.sh',
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
cat: 'Safety Guards',
|
|
196
|
+
q: 'Error-aware gate blocks external calls when errors exist',
|
|
197
|
+
w: 5,
|
|
198
|
+
test() {
|
|
199
|
+
const hasErrGate = allHookText.includes('error') || allHookText.includes('err-tracker') ||
|
|
200
|
+
allHookText.includes('err_code');
|
|
201
|
+
const claudeHasErrRule = claudeMdContents.includes('error') && claudeMdContents.includes('block');
|
|
202
|
+
return (hasErrGate || claudeHasErrRule)
|
|
203
|
+
? { pass: true, detail: 'Error-aware gating detected' }
|
|
204
|
+
: { pass: false, detail: 'No error-aware gate found' };
|
|
205
|
+
},
|
|
206
|
+
fix: 'Add an error-tracker that prevents publishing or pushing when unresolved errors exist.',
|
|
207
|
+
hook: 'hooks/error-gate.sh',
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
// === QUALITY (4 checks, 5pts each = 20) ===
|
|
211
|
+
{
|
|
212
|
+
cat: 'Code Quality',
|
|
213
|
+
q: 'Syntax checks run after every file edit (PostToolUse hook)',
|
|
214
|
+
w: 5,
|
|
215
|
+
test() {
|
|
216
|
+
const postHooks = allHooks.filter(h => h.event.toLowerCase().includes('posttooluse'));
|
|
217
|
+
const hasSyntax = postHooks.some(h => {
|
|
218
|
+
const cmd = h.command.toLowerCase();
|
|
219
|
+
return cmd.includes('syntax') || cmd.includes('compile') || cmd.includes('lint') ||
|
|
220
|
+
cmd.includes('py_compile') || cmd.includes('eslint') || cmd.includes('check');
|
|
221
|
+
});
|
|
222
|
+
return hasSyntax
|
|
223
|
+
? { pass: true, detail: 'Post-edit syntax checking configured' }
|
|
224
|
+
: { pass: false, detail: 'No syntax check hook found in PostToolUse' };
|
|
225
|
+
},
|
|
226
|
+
fix: 'Add a PostToolUse hook on Edit/Write that runs language-specific syntax checks (py_compile, eslint, bash -n).',
|
|
227
|
+
hook: 'hooks/syntax-check.sh',
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
cat: 'Code Quality',
|
|
231
|
+
q: 'Error detection and tracking from command output',
|
|
232
|
+
w: 5,
|
|
233
|
+
test() {
|
|
234
|
+
const hasErrDetect = allHookText.includes('error') || allHookText.includes('stderr') ||
|
|
235
|
+
allHookText.includes('exit_code') || allHookText.includes('err-code');
|
|
236
|
+
return hasErrDetect
|
|
237
|
+
? { pass: true, detail: 'Error detection patterns found in hooks' }
|
|
238
|
+
: { pass: false, detail: 'No error detection in command output' };
|
|
239
|
+
},
|
|
240
|
+
fix: 'Scan bash output for error patterns in PostToolUse hooks. Track repeated errors and escalate.',
|
|
241
|
+
hook: 'hooks/activity-logger.sh',
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
cat: 'Code Quality',
|
|
245
|
+
q: 'Definition of Done (DoD) checklist exists for task completion',
|
|
246
|
+
w: 5,
|
|
247
|
+
test() {
|
|
248
|
+
const hasDod = claudeMdContents.includes('definition of done') || claudeMdContents.includes('dod') ||
|
|
249
|
+
claudeMdContents.includes('done checklist') || claudeMdContents.includes('completion criteria');
|
|
250
|
+
const dodFile = existsSync(join(CC_DIR, 'dod-checklists.md')) ||
|
|
251
|
+
existsSync(join(cwd, 'dod-checklists.md'));
|
|
252
|
+
return (hasDod || dodFile)
|
|
253
|
+
? { pass: true, detail: 'DoD criteria found' }
|
|
254
|
+
: { pass: false, detail: 'No Definition of Done checklist detected' };
|
|
255
|
+
},
|
|
256
|
+
fix: 'Define what "done" means: tests pass, no open errors, syntax clean, docs updated.',
|
|
257
|
+
hook: 'templates/dod-checklists.md',
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
cat: 'Code Quality',
|
|
261
|
+
q: 'AI verifies its own output (screenshots, GET requests after publishing)',
|
|
262
|
+
w: 5,
|
|
263
|
+
test() {
|
|
264
|
+
const hasVerify = claudeMdContents.includes('verify') || claudeMdContents.includes('screenshot') ||
|
|
265
|
+
claudeMdContents.includes('confirmation') || claudeMdContents.includes('proof');
|
|
266
|
+
return hasVerify
|
|
267
|
+
? { pass: true, detail: 'Output verification instructions found' }
|
|
268
|
+
: { pass: false, detail: 'No output verification pattern detected' };
|
|
269
|
+
},
|
|
270
|
+
fix: 'Add verification steps to your workflow: after publishing or deploying, confirm the result matches expectations.',
|
|
271
|
+
hook: 'templates/CLAUDE-autonomous.md',
|
|
272
|
+
},
|
|
273
|
+
|
|
274
|
+
// === MONITORING (3 checks, 5pts each = 15) ===
|
|
275
|
+
{
|
|
276
|
+
cat: 'Monitoring',
|
|
277
|
+
q: 'Context window usage monitored with alerts before it fills up',
|
|
278
|
+
w: 5,
|
|
279
|
+
test() {
|
|
280
|
+
const hasContextMon = allHookText.includes('context') || allHookText.includes('compact') ||
|
|
281
|
+
allHookText.includes('token');
|
|
282
|
+
return hasContextMon
|
|
283
|
+
? { pass: true, detail: 'Context window monitoring detected' }
|
|
284
|
+
: { pass: false, detail: 'No context window monitoring' };
|
|
285
|
+
},
|
|
286
|
+
fix: 'Add a PostToolUse hook that checks context percentage and alerts before it fills up. Auto-compact at critical levels.',
|
|
287
|
+
hook: 'hooks/context-monitor.sh',
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
cat: 'Monitoring',
|
|
291
|
+
q: 'Activity logging tracks what commands ran, when, and what changed',
|
|
292
|
+
w: 5,
|
|
293
|
+
test() {
|
|
294
|
+
const hasActivityLog = allHookText.includes('activity') || allHookText.includes('log') ||
|
|
295
|
+
allHookText.includes('jsonl') || allHookText.includes('audit');
|
|
296
|
+
return hasActivityLog
|
|
297
|
+
? { pass: true, detail: 'Activity logging detected' }
|
|
298
|
+
: { pass: false, detail: 'No activity logging configured' };
|
|
299
|
+
},
|
|
300
|
+
fix: 'Add a PostToolUse hook that logs every tool use to a JSONL file with timestamps.',
|
|
301
|
+
hook: 'hooks/activity-logger.sh',
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
cat: 'Monitoring',
|
|
305
|
+
q: 'Daily summaries of AI work are generated (proof-log, session reports)',
|
|
306
|
+
w: 5,
|
|
307
|
+
test() {
|
|
308
|
+
const hasProofLog = allHookText.includes('proof') || allHookText.includes('summary') ||
|
|
309
|
+
allHookText.includes('session') || allHookText.includes('digest');
|
|
310
|
+
const proofLogDir = existsSync(join(HOME, 'ops', 'proof-log'));
|
|
311
|
+
return (hasProofLog || proofLogDir)
|
|
312
|
+
? { pass: true, detail: 'Daily summarization configured' }
|
|
313
|
+
: { pass: false, detail: 'No daily summary generation' };
|
|
314
|
+
},
|
|
315
|
+
fix: 'Write a Stop hook that generates a 5W1H summary at session end. Makes handoffs and audits trivial.',
|
|
316
|
+
hook: 'hooks/proof-log-session.sh',
|
|
317
|
+
},
|
|
318
|
+
|
|
319
|
+
// === RECOVERY (3 checks, 5pts each = 15) ===
|
|
320
|
+
{
|
|
321
|
+
cat: 'Recovery',
|
|
322
|
+
q: 'Git backup branches created before major changes',
|
|
323
|
+
w: 5,
|
|
324
|
+
test() {
|
|
325
|
+
const hasBackup = claudeMdContents.includes('backup') || claudeMdContents.includes('backup/before');
|
|
326
|
+
return hasBackup
|
|
327
|
+
? { pass: true, detail: 'Backup branch instructions found in CLAUDE.md' }
|
|
328
|
+
: { pass: false, detail: 'No backup branch strategy detected' };
|
|
329
|
+
},
|
|
330
|
+
fix: 'Add "git checkout -b backup/before-changes" to your CLAUDE.md instructions before risky operations.',
|
|
331
|
+
hook: 'templates/CLAUDE-autonomous.md',
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
cat: 'Recovery',
|
|
335
|
+
q: 'Watchdog detects and recovers from hangs/idle states',
|
|
336
|
+
w: 5,
|
|
337
|
+
test() {
|
|
338
|
+
const hasWatchdog = allHookText.includes('watchdog') || allHookText.includes('idle') ||
|
|
339
|
+
allHookText.includes('nudge') || allHookText.includes('heartbeat');
|
|
340
|
+
// Check for common watchdog scripts
|
|
341
|
+
const watchdogExists = existsSync(join(HOME, 'bin', 'cc-solo-watchdog')) ||
|
|
342
|
+
existsSync(join(HOME, '.claude', 'cc-solo-watchdog'));
|
|
343
|
+
return (hasWatchdog || watchdogExists)
|
|
344
|
+
? { pass: true, detail: 'Watchdog mechanism detected' }
|
|
345
|
+
: { pass: false, detail: 'No watchdog for hang/idle detection' };
|
|
346
|
+
},
|
|
347
|
+
fix: 'Implement a tmux-based watchdog that detects idle/frozen states and automatically nudges or restarts the agent.',
|
|
348
|
+
hook: 'hooks/session-start-marker.sh',
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
cat: 'Recovery',
|
|
352
|
+
q: 'Fallback plan exists for when AI gets stuck in a loop',
|
|
353
|
+
w: 5,
|
|
354
|
+
test() {
|
|
355
|
+
const hasLoopDetect = claudeMdContents.includes('loop') || claudeMdContents.includes('retry') ||
|
|
356
|
+
claudeMdContents.includes('3 times') || claudeMdContents.includes('escalat');
|
|
357
|
+
const hasRootCause = allHookText.includes('root-cause') || allHookText.includes('loop');
|
|
358
|
+
return (hasLoopDetect || hasRootCause)
|
|
359
|
+
? { pass: true, detail: 'Loop detection / retry limits found' }
|
|
360
|
+
: { pass: false, detail: 'No loop detection or retry limits' };
|
|
361
|
+
},
|
|
362
|
+
fix: 'Track repeated command patterns. If the same error appears 3+ times, break the loop and escalate.',
|
|
363
|
+
hook: 'templates/LESSONS.md',
|
|
364
|
+
},
|
|
365
|
+
|
|
366
|
+
// === AUTONOMY (3 checks, 5pts each = 15) ===
|
|
367
|
+
{
|
|
368
|
+
cat: 'Autonomy',
|
|
369
|
+
q: 'AI can run tasks from a queue without human prompting',
|
|
370
|
+
w: 5,
|
|
371
|
+
test() {
|
|
372
|
+
const hasQueue = existsSync(join(HOME, 'ops', 'task-queue.yaml')) ||
|
|
373
|
+
existsSync(join(cwd, 'task-queue.yaml')) ||
|
|
374
|
+
existsSync(join(cwd, 'tasks', 'todo.md'));
|
|
375
|
+
const claudeHasQueue = claudeMdContents.includes('task queue') || claudeMdContents.includes('task-queue');
|
|
376
|
+
return (hasQueue || claudeHasQueue)
|
|
377
|
+
? { pass: true, detail: 'Task queue mechanism found' }
|
|
378
|
+
: { pass: false, detail: 'No task queue for autonomous execution' };
|
|
379
|
+
},
|
|
380
|
+
fix: 'Create a task-queue.yaml with status tracking (pending/in-progress/done) that the AI reads and executes.',
|
|
381
|
+
hook: 'templates/task-queue.yaml',
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
cat: 'Autonomy',
|
|
385
|
+
q: 'Setup blocks the AI from asking unnecessary questions',
|
|
386
|
+
w: 5,
|
|
387
|
+
test() {
|
|
388
|
+
const hasNoAsk = allHookText.includes('no-ask') || allHookText.includes('question') ||
|
|
389
|
+
claudeMdContents.includes("don't ask") || claudeMdContents.includes('質問') ||
|
|
390
|
+
claudeMdContents.includes('自分で判断');
|
|
391
|
+
return hasNoAsk
|
|
392
|
+
? { pass: true, detail: 'Question-blocking rules detected' }
|
|
393
|
+
: { pass: false, detail: 'No rules to prevent unnecessary questions' };
|
|
394
|
+
},
|
|
395
|
+
fix: 'Add a hook or CLAUDE.md rule that redirects question-asking patterns to autonomous decision-making.',
|
|
396
|
+
hook: 'hooks/no-ask-human.sh',
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
cat: 'Autonomy',
|
|
400
|
+
q: 'AI can continue working across session restarts (persistent state)',
|
|
401
|
+
w: 5,
|
|
402
|
+
test() {
|
|
403
|
+
const hasPersist = hasMemoryDir || hasMissionMd ||
|
|
404
|
+
claudeMdContents.includes('memory') || claudeMdContents.includes('mission.md') ||
|
|
405
|
+
claudeMdContents.includes('persistent');
|
|
406
|
+
return hasPersist
|
|
407
|
+
? { pass: true, detail: 'State persistence mechanism found' }
|
|
408
|
+
: { pass: false, detail: 'No persistent state mechanism' };
|
|
409
|
+
},
|
|
410
|
+
fix: 'Use mission.md or MEMORY.md to maintain state across context compactions and session restarts.',
|
|
411
|
+
hook: 'templates/mission.md',
|
|
412
|
+
},
|
|
413
|
+
|
|
414
|
+
// === COORDINATION (3 checks, 5+3+2 = 10) ===
|
|
415
|
+
{
|
|
416
|
+
cat: 'Coordination',
|
|
417
|
+
q: 'Decision audit trail logs why each decision was made',
|
|
418
|
+
w: 5,
|
|
419
|
+
test() {
|
|
420
|
+
const hasDecLog = allHookText.includes('decision') || allHookText.includes('rationale') ||
|
|
421
|
+
existsSync(join(HOME, 'ops', 'decision-log.jsonl'));
|
|
422
|
+
return hasDecLog
|
|
423
|
+
? { pass: true, detail: 'Decision logging found' }
|
|
424
|
+
: { pass: false, detail: 'No decision audit trail' };
|
|
425
|
+
},
|
|
426
|
+
fix: 'Track decisions with rationale — what was decided, why, and what alternatives were rejected.',
|
|
427
|
+
hook: 'hooks/decision-warn.sh',
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
cat: 'Coordination',
|
|
431
|
+
q: 'AI can coordinate with other AI instances or tools',
|
|
432
|
+
w: 3,
|
|
433
|
+
test() {
|
|
434
|
+
const hasCoord = claudeMdContents.includes('multi-agent') || claudeMdContents.includes('codex') ||
|
|
435
|
+
claudeMdContents.includes('team') || claudeMdContents.includes('subagent') ||
|
|
436
|
+
allHookText.includes('relay') || allHookText.includes('tachikoma');
|
|
437
|
+
return hasCoord
|
|
438
|
+
? { pass: true, detail: 'Multi-agent coordination found' }
|
|
439
|
+
: { pass: false, detail: 'No multi-agent coordination' };
|
|
440
|
+
},
|
|
441
|
+
fix: 'Enable file-based or tmux-based messaging between AI instances for parallel work.',
|
|
442
|
+
hook: 'templates/CLAUDE-autonomous.md',
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
cat: 'Coordination',
|
|
446
|
+
q: 'Structured way to capture and reuse lessons learned',
|
|
447
|
+
w: 2,
|
|
448
|
+
test() {
|
|
449
|
+
const hasLessons = existsSync(join(cwd, 'tasks', 'lessons.md')) ||
|
|
450
|
+
existsSync(join(cwd, 'LESSONS.md')) ||
|
|
451
|
+
claudeMdContents.includes('lesson') || claudeMdContents.includes('教訓');
|
|
452
|
+
return hasLessons
|
|
453
|
+
? { pass: true, detail: 'Lesson capture mechanism found' }
|
|
454
|
+
: { pass: false, detail: 'No structured lesson capture' };
|
|
455
|
+
},
|
|
456
|
+
fix: 'Maintain a LESSONS.md file to log errors and their fixes for future reference.',
|
|
457
|
+
hook: 'templates/LESSONS.md',
|
|
458
|
+
},
|
|
459
|
+
];
|
|
460
|
+
|
|
461
|
+
// ─── Run all checks ───
|
|
462
|
+
function runChecks() {
|
|
463
|
+
const totalPts = checks.reduce((s, ch) => s + ch.w, 0);
|
|
464
|
+
let earned = 0;
|
|
465
|
+
const results = [];
|
|
466
|
+
const dimScores = {};
|
|
467
|
+
const dimTotals = {};
|
|
468
|
+
|
|
469
|
+
for (const ch of checks) {
|
|
470
|
+
if (!dimScores[ch.cat]) {
|
|
471
|
+
dimScores[ch.cat] = 0;
|
|
472
|
+
dimTotals[ch.cat] = 0;
|
|
473
|
+
}
|
|
474
|
+
dimTotals[ch.cat] += ch.w;
|
|
475
|
+
const result = ch.test();
|
|
476
|
+
const pts = result.pass ? ch.w : 0;
|
|
477
|
+
earned += pts;
|
|
478
|
+
dimScores[ch.cat] += pts;
|
|
479
|
+
results.push({ cat: ch.cat, q: ch.q, w: ch.w, fix: ch.fix, hook: ch.hook, result, pts });
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const pct = Math.round((earned / totalPts) * 100);
|
|
483
|
+
let grade;
|
|
484
|
+
if (pct >= 80) grade = 'Production Ready';
|
|
485
|
+
else if (pct >= 60) grade = 'Getting There';
|
|
486
|
+
else if (pct >= 35) grade = 'Needs Work';
|
|
487
|
+
else grade = 'Critical';
|
|
488
|
+
|
|
489
|
+
return { results, dimScores, dimTotals, earned, totalPts, pct, grade };
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function printHuman(data) {
|
|
493
|
+
const { results, dimScores, dimTotals, earned, totalPts, pct, grade } = data;
|
|
494
|
+
|
|
495
|
+
console.log('');
|
|
496
|
+
console.log(`${c.bold}${c.cyan} Claude Code Health Check v1.0${c.reset}`);
|
|
497
|
+
console.log(`${c.dim} ═══════════════════════════════════════${c.reset}`);
|
|
498
|
+
console.log(`${c.dim} Scanning: ${CC_DIR}${c.reset}`);
|
|
499
|
+
console.log('');
|
|
500
|
+
|
|
501
|
+
let currentCat = '';
|
|
502
|
+
for (const r of results) {
|
|
503
|
+
if (r.cat !== currentCat) {
|
|
504
|
+
currentCat = r.cat;
|
|
505
|
+
console.log(` ${c.bold}${c.magenta}▸ ${r.cat}${c.reset}`);
|
|
506
|
+
}
|
|
507
|
+
const icon = r.result.pass ? PASS : FAIL;
|
|
508
|
+
console.log(` ${icon} ${r.q}`);
|
|
509
|
+
if (!r.result.pass) {
|
|
510
|
+
console.log(` ${c.dim}${r.result.detail}${c.reset}`);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
let gradeColor;
|
|
515
|
+
if (pct >= 80) gradeColor = c.green;
|
|
516
|
+
else if (pct >= 60) gradeColor = c.yellow;
|
|
517
|
+
else gradeColor = c.red;
|
|
518
|
+
|
|
519
|
+
console.log('');
|
|
520
|
+
console.log(` ${c.dim}───────────────────────────────────────${c.reset}`);
|
|
521
|
+
console.log(` ${c.bold}Score: ${gradeColor}${pct}/100 — ${grade}${c.reset}`);
|
|
522
|
+
console.log(` ${c.dim}(${earned}/${totalPts} points)${c.reset}`);
|
|
523
|
+
console.log('');
|
|
524
|
+
|
|
525
|
+
console.log(` ${c.bold}Dimensions:${c.reset}`);
|
|
526
|
+
for (const [cat, total] of Object.entries(dimTotals)) {
|
|
527
|
+
const score = dimScores[cat];
|
|
528
|
+
const dimPct = Math.round((score / total) * 100);
|
|
529
|
+
const barLen = 20;
|
|
530
|
+
const filled = Math.round((dimPct / 100) * barLen);
|
|
531
|
+
let barColor = c.green;
|
|
532
|
+
if (dimPct < 60) barColor = c.yellow;
|
|
533
|
+
if (dimPct < 34) barColor = c.red;
|
|
534
|
+
const bar = barColor + '█'.repeat(filled) + c.dim + '░'.repeat(barLen - filled) + c.reset;
|
|
535
|
+
console.log(` ${bar} ${cat} ${dimPct}%`);
|
|
536
|
+
}
|
|
537
|
+
console.log('');
|
|
538
|
+
|
|
539
|
+
const failures = results.filter(r => !r.result.pass).sort((a, b) => b.w - a.w);
|
|
540
|
+
if (failures.length > 0) {
|
|
541
|
+
console.log(` ${c.bold}Top fixes:${c.reset}`);
|
|
542
|
+
for (const f of failures.slice(0, 5)) {
|
|
543
|
+
console.log(` ${c.yellow}→${c.reset} ${f.fix}`);
|
|
544
|
+
if (f.hook) {
|
|
545
|
+
const url = `https://github.com/yurukusa/claude-code-hooks/blob/main/${f.hook}`;
|
|
546
|
+
console.log(` ${c.dim}↳ ${url}${c.reset}`);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
console.log('');
|
|
550
|
+
console.log(` ${c.cyan}Production hooks + templates for autonomous Claude Code:${c.reset}`);
|
|
551
|
+
console.log(` ${c.bold}https://yurukusa.gumroad.com/l/cc-codex-ops-kit${c.reset}`);
|
|
552
|
+
console.log(` ${c.dim}10 hooks + 5 templates covering 18/20 checks. From 108 hours of real autonomous operation.${c.reset}`);
|
|
553
|
+
} else {
|
|
554
|
+
console.log(` ${c.green}${c.bold}All 20 checks passed! Your setup is production-ready.${c.reset}`);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
console.log('');
|
|
558
|
+
const dims = Object.entries(dimTotals).map(([cat, total]) => {
|
|
559
|
+
const dimPct = Math.round((dimScores[cat] / total) * 100);
|
|
560
|
+
return `${cat}: ${dimPct}%`;
|
|
561
|
+
}).join(' | ');
|
|
562
|
+
console.log(` ${c.dim}Share: "My Claude Code Health Score: ${pct}/100 (${dims})" #ClaudeCode${c.reset}`);
|
|
563
|
+
console.log('');
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function printJSON(data) {
|
|
567
|
+
const { results, dimScores, dimTotals, earned, totalPts, pct, grade } = data;
|
|
568
|
+
const output = {
|
|
569
|
+
version: '1.0',
|
|
570
|
+
score: pct,
|
|
571
|
+
grade,
|
|
572
|
+
points: { earned, total: totalPts },
|
|
573
|
+
dimensions: {},
|
|
574
|
+
checks: [],
|
|
575
|
+
};
|
|
576
|
+
for (const [cat, total] of Object.entries(dimTotals)) {
|
|
577
|
+
output.dimensions[cat] = {
|
|
578
|
+
score: dimScores[cat],
|
|
579
|
+
total,
|
|
580
|
+
percent: Math.round((dimScores[cat] / total) * 100),
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
for (const r of results) {
|
|
584
|
+
output.checks.push({
|
|
585
|
+
dimension: r.cat,
|
|
586
|
+
check: r.q,
|
|
587
|
+
pass: r.result.pass,
|
|
588
|
+
detail: r.result.detail,
|
|
589
|
+
weight: r.w,
|
|
590
|
+
fix: r.result.pass ? undefined : r.fix,
|
|
591
|
+
hook: r.result.pass ? undefined : r.hook,
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
console.log(JSON.stringify(output, null, 2));
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function printBadge(data) {
|
|
598
|
+
const { pct, grade } = data;
|
|
599
|
+
let color = 'brightgreen';
|
|
600
|
+
if (pct < 80) color = 'yellow';
|
|
601
|
+
if (pct < 60) color = 'orange';
|
|
602
|
+
if (pct < 35) color = 'red';
|
|
603
|
+
const label = encodeURIComponent('Claude Code Health');
|
|
604
|
+
const msg = encodeURIComponent(`${pct}% — ${grade}`);
|
|
605
|
+
const url = `https://img.shields.io/badge/${label}-${msg}-${color}`;
|
|
606
|
+
console.log(url);
|
|
607
|
+
console.log('');
|
|
608
|
+
console.log(`Markdown: `);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// ─── Main ───
|
|
612
|
+
const jsonMode = process.argv.includes('--json');
|
|
613
|
+
const badgeMode = process.argv.includes('--badge');
|
|
614
|
+
const data = runChecks();
|
|
615
|
+
if (jsonMode) {
|
|
616
|
+
printJSON(data);
|
|
617
|
+
} else if (badgeMode) {
|
|
618
|
+
printBadge(data);
|
|
619
|
+
} else {
|
|
620
|
+
printHuman(data);
|
|
621
|
+
}
|
|
622
|
+
process.exit(data.pct >= 60 ? 0 : 1);
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cc-health-check",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI diagnostic for your Claude Code setup. 20 checks across 6 dimensions.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"cc-health-check": "./cli.mjs"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"keywords": [
|
|
10
|
+
"claude-code",
|
|
11
|
+
"claude",
|
|
12
|
+
"ai",
|
|
13
|
+
"diagnostic",
|
|
14
|
+
"health-check",
|
|
15
|
+
"hooks",
|
|
16
|
+
"autonomous",
|
|
17
|
+
"agent"
|
|
18
|
+
],
|
|
19
|
+
"author": "yurukusa",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.com/yurukusa/cc-health-check"
|
|
24
|
+
},
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"cli.mjs",
|
|
30
|
+
"README.md",
|
|
31
|
+
"LICENSE"
|
|
32
|
+
]
|
|
33
|
+
}
|