@jaguilar87/gaia-ops 3.7.0 → 3.9.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.en.md +7 -7
- package/README.md +7 -7
- package/bin/gaia-doctor.js +392 -0
- package/bin/gaia-init.js +714 -792
- package/bin/gaia-review.js +267 -0
- package/bin/gaia-update.js +120 -97
- package/commands/README.en.md +15 -38
- package/commands/README.md +15 -38
- package/commands/speckit.init.md +5 -5
- package/commands/speckit.specify.md +1 -1
- package/config/README.en.md +24 -160
- package/config/README.md +13 -62
- package/config/classification-rules.json +170 -0
- package/hooks/README.md +3 -3
- package/hooks/modules/README.md +2 -6
- package/hooks/modules/agents/__init__.py +2 -26
- package/hooks/modules/context/discovery_classifier.py +596 -0
- package/hooks/modules/tools/bash_validator.py +63 -28
- package/hooks/modules/workflow/__init__.py +2 -28
- package/hooks/pre_tool_use.py +75 -11
- package/hooks/subagent_stop.py +101 -31
- package/index.js +1 -1
- package/package.json +5 -3
- package/speckit/README.en.md +1 -1
- package/templates/CLAUDE.template.md +31 -12
- package/templates/README.en.md +26 -92
- package/templates/README.md +7 -23
- package/tests/README.en.md +43 -43
- package/tests/README.md +43 -43
- package/tests/hooks/modules/context/__init__.py +0 -0
- package/tests/hooks/modules/context/test_discovery_classifier.py +190 -0
- package/tests/hooks/modules/security/test_gitops_validator.py +357 -0
- package/tests/hooks/modules/security/test_safe_commands.py +1 -1
- package/tests/hooks/modules/skills/__init__.py +0 -0
- package/tests/hooks/modules/skills/test_skill_loader.py +398 -0
- package/tests/hooks/modules/tools/test_bash_validator.py +32 -11
- package/tests/hooks/test_subagent_stop_discovery.py +159 -0
- package/tests/system/test_schema_compatibility.py +97 -0
- package/tests/tools/test_episodic.py +463 -0
- package/tests/tools/test_pending_updates.py +549 -0
- package/tests/tools/test_review_engine.py +203 -0
- package/tools/context/README.md +1 -1
- package/tools/context/context_lazy_loader.py +1 -1
- package/tools/context/context_provider.py +2 -3
- package/tools/context/pending_updates.py +791 -0
- package/tools/memory/episodic.py +0 -1
- package/tools/review/__init__.py +1 -0
- package/tools/review/review_engine.py +157 -0
- package/tools/validation/README.md +1 -1
- package/hooks/modules/agents/anomaly_detector.py +0 -228
- package/hooks/modules/agents/subagent_metrics.py +0 -162
- package/hooks/modules/workflow/episodic_capture.py +0 -266
- package/hooks/modules/workflow/phase_validator.py +0 -306
- package/hooks/modules/workflow/state_tracker.py +0 -173
package/README.en.md
CHANGED
|
@@ -21,7 +21,7 @@ Multi-agent orchestration system for Claude Code - DevOps automation toolkit.
|
|
|
21
21
|
- **Hybrid standards pre-loading** - 78% token reduction per invocation
|
|
22
22
|
- **Approval gates** for T3 operations
|
|
23
23
|
- **Git commit validation** with Conventional Commits
|
|
24
|
-
- **
|
|
24
|
+
- **669 tests** at 100% passing
|
|
25
25
|
|
|
26
26
|
## Installation
|
|
27
27
|
|
|
@@ -81,7 +81,7 @@ node_modules/@jaguilar87/gaia-ops/
|
|
|
81
81
|
├── config/ # Configuration and documentation
|
|
82
82
|
├── templates/ # Installation templates
|
|
83
83
|
├── speckit/ # Spec-Kit methodology
|
|
84
|
-
└── tests/ # Test suite (
|
|
84
|
+
└── tests/ # Test suite (669 tests)
|
|
85
85
|
```
|
|
86
86
|
|
|
87
87
|
## API
|
|
@@ -101,16 +101,16 @@ This package follows [Semantic Versioning](https://semver.org/):
|
|
|
101
101
|
- **MINOR:** New features
|
|
102
102
|
- **PATCH:** Bug fixes
|
|
103
103
|
|
|
104
|
-
Current version: **3.
|
|
104
|
+
Current version: **3.8.0**
|
|
105
105
|
|
|
106
106
|
See [CHANGELOG.md](./CHANGELOG.md) for version history.
|
|
107
107
|
|
|
108
108
|
## Documentation
|
|
109
109
|
|
|
110
|
-
- [config/
|
|
111
|
-
- [
|
|
112
|
-
- [
|
|
113
|
-
- [
|
|
110
|
+
- [config/](./config/) - Configuration (git standards, skill triggers, universal rules)
|
|
111
|
+
- [agents/](./agents/) - Specialist agent definitions
|
|
112
|
+
- [commands/](./commands/) - Slash commands (spec-kit)
|
|
113
|
+
- [hooks/](./hooks/) - Hook system (security, validation)
|
|
114
114
|
|
|
115
115
|
## Requirements
|
|
116
116
|
|
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ Sistema de orquestacion multi-agente para Claude Code - Toolkit de automatizacio
|
|
|
21
21
|
- **Pre-carga hibrida de standards** - 78% reduccion de tokens por invocacion
|
|
22
22
|
- **Puertas de aprobacion** para operaciones T3
|
|
23
23
|
- **Validacion de commits Git** con Conventional Commits
|
|
24
|
-
- **
|
|
24
|
+
- **669 tests** al 100% pasando
|
|
25
25
|
|
|
26
26
|
## Instalacion
|
|
27
27
|
|
|
@@ -81,7 +81,7 @@ node_modules/@jaguilar87/gaia-ops/
|
|
|
81
81
|
├── config/ # Configuracion y documentacion
|
|
82
82
|
├── templates/ # Templates de instalacion
|
|
83
83
|
├── speckit/ # Metodologia Spec-Kit
|
|
84
|
-
└── tests/ # Suite de tests (
|
|
84
|
+
└── tests/ # Suite de tests (669 tests)
|
|
85
85
|
```
|
|
86
86
|
|
|
87
87
|
## API
|
|
@@ -101,16 +101,16 @@ Este paquete sigue [Versionamiento Semantico](https://semver.org/):
|
|
|
101
101
|
- **MINOR:** Nuevas caracteristicas
|
|
102
102
|
- **PATCH:** Correcciones de bugs
|
|
103
103
|
|
|
104
|
-
Version actual: **3.
|
|
104
|
+
Version actual: **3.8.0**
|
|
105
105
|
|
|
106
106
|
Ver el historial de versiones en [npm](https://www.npmjs.com/package/@jaguilar87/gaia-ops).
|
|
107
107
|
|
|
108
108
|
## Documentacion
|
|
109
109
|
|
|
110
|
-
- [config/
|
|
111
|
-
- [
|
|
112
|
-
- [
|
|
113
|
-
- [
|
|
110
|
+
- [config/](./config/) - Configuracion (git standards, skill triggers, reglas universales)
|
|
111
|
+
- [agents/](./agents/) - Definiciones de agentes especialistas
|
|
112
|
+
- [commands/](./commands/) - Slash commands (spec-kit)
|
|
113
|
+
- [hooks/](./hooks/) - Sistema de hooks (seguridad, validacion)
|
|
114
114
|
|
|
115
115
|
## Requisitos
|
|
116
116
|
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @jaguilar87/gaia-ops - Health Check CLI
|
|
5
|
+
*
|
|
6
|
+
* Verifies the complete Gaia-Ops installation is healthy.
|
|
7
|
+
* Run after install, update, or when things seem broken.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* npx gaia-doctor # Full health check
|
|
11
|
+
* npx gaia-doctor --fix # Attempt auto-fix for common issues
|
|
12
|
+
* npx gaia-doctor --json # Output as JSON (for CI)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { join, relative } from 'path';
|
|
16
|
+
import fs from 'fs/promises';
|
|
17
|
+
import { existsSync } from 'fs';
|
|
18
|
+
import { exec } from 'child_process';
|
|
19
|
+
import { promisify } from 'util';
|
|
20
|
+
import chalk from 'chalk';
|
|
21
|
+
import yargs from 'yargs';
|
|
22
|
+
import { hideBin } from 'yargs/helpers';
|
|
23
|
+
|
|
24
|
+
const execAsync = promisify(exec);
|
|
25
|
+
const CWD = process.cwd();
|
|
26
|
+
|
|
27
|
+
// ============================================================================
|
|
28
|
+
// Health Checks
|
|
29
|
+
// ============================================================================
|
|
30
|
+
|
|
31
|
+
async function checkSymlinks() {
|
|
32
|
+
const names = ['agents', 'tools', 'hooks', 'commands', 'templates', 'config', 'speckit', 'CHANGELOG.md'];
|
|
33
|
+
const results = [];
|
|
34
|
+
let valid = 0;
|
|
35
|
+
|
|
36
|
+
for (const name of names) {
|
|
37
|
+
const linkPath = join(CWD, '.claude', name);
|
|
38
|
+
const exists = existsSync(linkPath);
|
|
39
|
+
|
|
40
|
+
if (exists) {
|
|
41
|
+
// Verify symlink target actually resolves
|
|
42
|
+
try {
|
|
43
|
+
await fs.realpath(linkPath);
|
|
44
|
+
valid++;
|
|
45
|
+
results.push({ name, status: 'ok' });
|
|
46
|
+
} catch {
|
|
47
|
+
results.push({ name, status: 'broken', fix: `rm .claude/${name} && gaia-init` });
|
|
48
|
+
}
|
|
49
|
+
} else {
|
|
50
|
+
results.push({ name, status: 'missing', fix: 'Run gaia-init to recreate' });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
name: 'Symlinks',
|
|
56
|
+
ok: valid === names.length,
|
|
57
|
+
detail: `${valid}/${names.length} valid`,
|
|
58
|
+
fix: valid < names.length ? 'Run gaia-init to recreate symlinks' : null,
|
|
59
|
+
sub: results
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function checkClaudeMd() {
|
|
64
|
+
const path = join(CWD, 'CLAUDE.md');
|
|
65
|
+
|
|
66
|
+
if (!existsSync(path)) {
|
|
67
|
+
return { name: 'CLAUDE.md', ok: false, detail: 'Missing', fix: 'Run gaia-init' };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const content = await fs.readFile(path, 'utf-8');
|
|
71
|
+
const issues = [];
|
|
72
|
+
|
|
73
|
+
if (content.includes('{{')) {
|
|
74
|
+
issues.push('Contains raw {{placeholders}} - template was not processed');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (content.length < 100) {
|
|
78
|
+
issues.push('File is suspiciously short');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!content.includes('Binary Delegation') && !content.includes('orchestrator')) {
|
|
82
|
+
issues.push('Missing core orchestrator instructions');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (issues.length > 0) {
|
|
86
|
+
return { name: 'CLAUDE.md', ok: false, detail: issues.join('; '), fix: 'Run gaia-init to regenerate' };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const lines = content.split('\n').length;
|
|
90
|
+
return { name: 'CLAUDE.md', ok: true, detail: `Valid (${lines} lines)` };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function checkSettingsJson() {
|
|
94
|
+
const path = join(CWD, '.claude', 'settings.json');
|
|
95
|
+
|
|
96
|
+
if (!existsSync(path)) {
|
|
97
|
+
return { name: 'settings.json', ok: false, detail: 'Missing', fix: 'Run gaia-init' };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const data = JSON.parse(await fs.readFile(path, 'utf-8'));
|
|
102
|
+
const issues = [];
|
|
103
|
+
|
|
104
|
+
// Check hooks are configured
|
|
105
|
+
if (!data.hooks) {
|
|
106
|
+
issues.push('No hooks configured');
|
|
107
|
+
} else {
|
|
108
|
+
const hookTypes = Object.keys(data.hooks);
|
|
109
|
+
if (!hookTypes.includes('PreToolUse')) issues.push('Missing PreToolUse hook');
|
|
110
|
+
if (!hookTypes.includes('PostToolUse')) issues.push('Missing PostToolUse hook');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Check permissions exist
|
|
114
|
+
if (!data.permissions) {
|
|
115
|
+
issues.push('No permissions configured');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (issues.length > 0) {
|
|
119
|
+
return { name: 'settings.json', ok: false, detail: issues.join('; '), fix: 'Run gaia-init' };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const hookCount = data.hooks ? Object.keys(data.hooks).length : 0;
|
|
123
|
+
const permCount = data.permissions ? Object.values(data.permissions).flat().length : 0;
|
|
124
|
+
return { name: 'settings.json', ok: true, detail: `${hookCount} hook types, ${permCount} rules` };
|
|
125
|
+
} catch {
|
|
126
|
+
return { name: 'settings.json', ok: false, detail: 'Invalid JSON', fix: 'Delete and run gaia-init' };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function checkProjectContext() {
|
|
131
|
+
const path = join(CWD, '.claude', 'project-context', 'project-context.json');
|
|
132
|
+
|
|
133
|
+
if (!existsSync(path)) {
|
|
134
|
+
return { name: 'project-context', ok: false, detail: 'Missing', fix: 'Run gaia-init or /speckit.init' };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
const data = JSON.parse(await fs.readFile(path, 'utf-8'));
|
|
139
|
+
const issues = [];
|
|
140
|
+
|
|
141
|
+
if (!data.metadata) issues.push('Missing metadata section');
|
|
142
|
+
if (!data.paths) issues.push('Missing paths section');
|
|
143
|
+
if (!data.sections) issues.push('Missing sections');
|
|
144
|
+
|
|
145
|
+
if (data.metadata) {
|
|
146
|
+
if (!data.metadata.cloud_provider) issues.push('No cloud provider set');
|
|
147
|
+
if (!data.metadata.primary_region) issues.push('No region set');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (data.sections) {
|
|
151
|
+
const sectionCount = Object.keys(data.sections).length;
|
|
152
|
+
if (sectionCount < 3) issues.push(`Only ${sectionCount} sections (expected >=3)`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (issues.length > 0) {
|
|
156
|
+
return { name: 'project-context', ok: false, detail: issues.join('; '), fix: 'Run /speckit.init to enrich' };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const sectionCount = Object.keys(data.sections).length;
|
|
160
|
+
const cloud = data.metadata.cloud_provider?.toUpperCase() || '?';
|
|
161
|
+
return { name: 'project-context', ok: true, detail: `${sectionCount} sections, ${cloud}` };
|
|
162
|
+
} catch {
|
|
163
|
+
return { name: 'project-context', ok: false, detail: 'Invalid JSON', fix: 'Regenerate with /speckit.init' };
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function checkPython() {
|
|
168
|
+
try {
|
|
169
|
+
const { stdout } = await execAsync('python3 --version');
|
|
170
|
+
const version = stdout.trim();
|
|
171
|
+
const match = version.match(/(\d+)\.(\d+)/);
|
|
172
|
+
|
|
173
|
+
if (match) {
|
|
174
|
+
const major = parseInt(match[1]);
|
|
175
|
+
const minor = parseInt(match[2]);
|
|
176
|
+
if (major < 3 || (major === 3 && minor < 9)) {
|
|
177
|
+
return { name: 'Python', ok: false, detail: `${version} (need >=3.9)`, fix: 'Upgrade Python to 3.9+' };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return { name: 'Python', ok: true, detail: version };
|
|
182
|
+
} catch {
|
|
183
|
+
return { name: 'Python', ok: false, detail: 'Not found', fix: 'Install Python 3.9+' };
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function checkClaudeCode() {
|
|
188
|
+
try {
|
|
189
|
+
const { stdout } = await execAsync('claude --version 2>/dev/null || claude-code --version 2>/dev/null');
|
|
190
|
+
return { name: 'Claude Code', ok: true, detail: stdout.trim().split('\n')[0] };
|
|
191
|
+
} catch {
|
|
192
|
+
return { name: 'Claude Code', ok: false, detail: 'Not installed', fix: 'npm install -g @anthropic-ai/claude-code' };
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function checkHooks() {
|
|
197
|
+
const hooks = [
|
|
198
|
+
{ file: 'pre_tool_use.py', required: true },
|
|
199
|
+
{ file: 'post_tool_use.py', required: true },
|
|
200
|
+
{ file: 'subagent_stop.py', required: false }
|
|
201
|
+
];
|
|
202
|
+
|
|
203
|
+
const issues = [];
|
|
204
|
+
let valid = 0;
|
|
205
|
+
|
|
206
|
+
for (const { file, required } of hooks) {
|
|
207
|
+
const hookPath = join(CWD, '.claude', 'hooks', file);
|
|
208
|
+
if (existsSync(hookPath)) {
|
|
209
|
+
valid++;
|
|
210
|
+
} else if (required) {
|
|
211
|
+
issues.push(`${file} missing`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (issues.length > 0) {
|
|
216
|
+
return { name: 'Hooks', ok: false, detail: issues.join('; '), fix: 'Recreate symlinks: gaia-init' };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return { name: 'Hooks', ok: true, detail: `${valid}/${hooks.length} found` };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function checkProjectDirs() {
|
|
223
|
+
const contextPath = join(CWD, '.claude', 'project-context', 'project-context.json');
|
|
224
|
+
if (!existsSync(contextPath)) {
|
|
225
|
+
return { name: 'Project dirs', ok: true, detail: 'Skipped (no context)' };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
const data = JSON.parse(await fs.readFile(contextPath, 'utf-8'));
|
|
230
|
+
const paths = data.paths || {};
|
|
231
|
+
const issues = [];
|
|
232
|
+
|
|
233
|
+
for (const [key, dirPath] of Object.entries(paths)) {
|
|
234
|
+
if (dirPath && !existsSync(join(CWD, dirPath))) {
|
|
235
|
+
issues.push(`${key}: ${dirPath} not found`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (issues.length > 0) {
|
|
240
|
+
return { name: 'Project dirs', ok: false, detail: issues.join('; '), fix: 'Create missing directories or update paths' };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return { name: 'Project dirs', ok: true, detail: `${Object.keys(paths).length} paths verified` };
|
|
244
|
+
} catch {
|
|
245
|
+
return { name: 'Project dirs', ok: true, detail: 'Skipped (parse error)' };
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// ============================================================================
|
|
250
|
+
// Auto-fix
|
|
251
|
+
// ============================================================================
|
|
252
|
+
|
|
253
|
+
async function autoFix() {
|
|
254
|
+
console.log(chalk.cyan('\n Attempting auto-fix...\n'));
|
|
255
|
+
|
|
256
|
+
let fixed = 0;
|
|
257
|
+
|
|
258
|
+
// Fix broken symlinks
|
|
259
|
+
const claudeDir = join(CWD, '.claude');
|
|
260
|
+
if (existsSync(claudeDir)) {
|
|
261
|
+
const packagePath = join(CWD, 'node_modules', '@jaguilar87', 'gaia-ops');
|
|
262
|
+
if (existsSync(packagePath)) {
|
|
263
|
+
const relPath = relative(claudeDir, packagePath);
|
|
264
|
+
const names = ['agents', 'tools', 'hooks', 'commands', 'templates', 'config', 'speckit'];
|
|
265
|
+
|
|
266
|
+
for (const name of names) {
|
|
267
|
+
const link = join(claudeDir, name);
|
|
268
|
+
if (!existsSync(link)) {
|
|
269
|
+
try {
|
|
270
|
+
await fs.symlink(join(relPath, name), link);
|
|
271
|
+
console.log(chalk.green(` Fixed: .claude/${name} symlink`));
|
|
272
|
+
fixed++;
|
|
273
|
+
} catch {
|
|
274
|
+
console.log(chalk.red(` Failed: .claude/${name}`));
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Fix CLAUDE.md with raw placeholders
|
|
282
|
+
const claudeMdPath = join(CWD, 'CLAUDE.md');
|
|
283
|
+
if (existsSync(claudeMdPath)) {
|
|
284
|
+
const content = await fs.readFile(claudeMdPath, 'utf-8');
|
|
285
|
+
if (content.includes('{{')) {
|
|
286
|
+
const templatePath = join(CWD, '.claude', 'templates', 'CLAUDE.template.md');
|
|
287
|
+
if (existsSync(templatePath)) {
|
|
288
|
+
await fs.copyFile(templatePath, claudeMdPath);
|
|
289
|
+
console.log(chalk.green(' Fixed: CLAUDE.md regenerated from template'));
|
|
290
|
+
fixed++;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Create missing project dirs
|
|
296
|
+
const contextPath = join(CWD, '.claude', 'project-context', 'project-context.json');
|
|
297
|
+
if (existsSync(contextPath)) {
|
|
298
|
+
try {
|
|
299
|
+
const data = JSON.parse(await fs.readFile(contextPath, 'utf-8'));
|
|
300
|
+
for (const dirPath of Object.values(data.paths || {})) {
|
|
301
|
+
const abs = join(CWD, dirPath);
|
|
302
|
+
if (!existsSync(abs)) {
|
|
303
|
+
await fs.mkdir(abs, { recursive: true });
|
|
304
|
+
console.log(chalk.green(` Fixed: Created ${dirPath}`));
|
|
305
|
+
fixed++;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
} catch {
|
|
309
|
+
// Skip
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
console.log(chalk.cyan(`\n ${fixed} issue(s) fixed\n`));
|
|
314
|
+
return fixed;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ============================================================================
|
|
318
|
+
// Main
|
|
319
|
+
// ============================================================================
|
|
320
|
+
|
|
321
|
+
async function main() {
|
|
322
|
+
const args = yargs(hideBin(process.argv))
|
|
323
|
+
.usage('Usage: $0 [options]')
|
|
324
|
+
.option('fix', { type: 'boolean', description: 'Attempt auto-fix for common issues', default: false })
|
|
325
|
+
.option('json', { type: 'boolean', description: 'Output as JSON', default: false })
|
|
326
|
+
.help('h').alias('h', 'help')
|
|
327
|
+
.version('1.0.0')
|
|
328
|
+
.parse();
|
|
329
|
+
|
|
330
|
+
// Run all checks
|
|
331
|
+
const checks = [
|
|
332
|
+
checkClaudeCode,
|
|
333
|
+
checkSymlinks,
|
|
334
|
+
checkClaudeMd,
|
|
335
|
+
checkSettingsJson,
|
|
336
|
+
checkProjectContext,
|
|
337
|
+
checkPython,
|
|
338
|
+
checkHooks,
|
|
339
|
+
checkProjectDirs
|
|
340
|
+
];
|
|
341
|
+
|
|
342
|
+
const results = [];
|
|
343
|
+
for (const check of checks) {
|
|
344
|
+
try {
|
|
345
|
+
results.push(await check());
|
|
346
|
+
} catch (error) {
|
|
347
|
+
results.push({ name: check.name, ok: false, detail: `Error: ${error.message}` });
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// JSON output mode
|
|
352
|
+
if (args.json) {
|
|
353
|
+
const allOk = results.every(r => r.ok);
|
|
354
|
+
console.log(JSON.stringify({ healthy: allOk, checks: results }, null, 2));
|
|
355
|
+
process.exit(allOk ? 0 : 1);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Human-readable output
|
|
359
|
+
console.log(chalk.cyan('\n Gaia-Ops Health Check\n'));
|
|
360
|
+
|
|
361
|
+
let allOk = true;
|
|
362
|
+
|
|
363
|
+
for (const result of results) {
|
|
364
|
+
const icon = result.ok ? chalk.green('✓') : chalk.yellow('⚠');
|
|
365
|
+
const detail = result.ok ? chalk.gray(result.detail || '') : chalk.yellow(result.detail);
|
|
366
|
+
console.log(` ${icon} ${result.name.padEnd(18)} ${detail}`);
|
|
367
|
+
|
|
368
|
+
if (!result.ok && result.fix) {
|
|
369
|
+
console.log(chalk.gray(` Fix: ${result.fix}`));
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (!result.ok) allOk = false;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
console.log('');
|
|
376
|
+
|
|
377
|
+
if (allOk) {
|
|
378
|
+
console.log(chalk.green.bold(' Status: HEALTHY\n'));
|
|
379
|
+
} else {
|
|
380
|
+
console.log(chalk.yellow.bold(' Status: ISSUES FOUND\n'));
|
|
381
|
+
|
|
382
|
+
if (args.fix) {
|
|
383
|
+
await autoFix();
|
|
384
|
+
} else {
|
|
385
|
+
console.log(chalk.gray(' Run with --fix to attempt auto-repair\n'));
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
process.exit(allOk ? 0 : 1);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
main();
|