@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.
Files changed (54) hide show
  1. package/README.en.md +7 -7
  2. package/README.md +7 -7
  3. package/bin/gaia-doctor.js +392 -0
  4. package/bin/gaia-init.js +714 -792
  5. package/bin/gaia-review.js +267 -0
  6. package/bin/gaia-update.js +120 -97
  7. package/commands/README.en.md +15 -38
  8. package/commands/README.md +15 -38
  9. package/commands/speckit.init.md +5 -5
  10. package/commands/speckit.specify.md +1 -1
  11. package/config/README.en.md +24 -160
  12. package/config/README.md +13 -62
  13. package/config/classification-rules.json +170 -0
  14. package/hooks/README.md +3 -3
  15. package/hooks/modules/README.md +2 -6
  16. package/hooks/modules/agents/__init__.py +2 -26
  17. package/hooks/modules/context/discovery_classifier.py +596 -0
  18. package/hooks/modules/tools/bash_validator.py +63 -28
  19. package/hooks/modules/workflow/__init__.py +2 -28
  20. package/hooks/pre_tool_use.py +75 -11
  21. package/hooks/subagent_stop.py +101 -31
  22. package/index.js +1 -1
  23. package/package.json +5 -3
  24. package/speckit/README.en.md +1 -1
  25. package/templates/CLAUDE.template.md +31 -12
  26. package/templates/README.en.md +26 -92
  27. package/templates/README.md +7 -23
  28. package/tests/README.en.md +43 -43
  29. package/tests/README.md +43 -43
  30. package/tests/hooks/modules/context/__init__.py +0 -0
  31. package/tests/hooks/modules/context/test_discovery_classifier.py +190 -0
  32. package/tests/hooks/modules/security/test_gitops_validator.py +357 -0
  33. package/tests/hooks/modules/security/test_safe_commands.py +1 -1
  34. package/tests/hooks/modules/skills/__init__.py +0 -0
  35. package/tests/hooks/modules/skills/test_skill_loader.py +398 -0
  36. package/tests/hooks/modules/tools/test_bash_validator.py +32 -11
  37. package/tests/hooks/test_subagent_stop_discovery.py +159 -0
  38. package/tests/system/test_schema_compatibility.py +97 -0
  39. package/tests/tools/test_episodic.py +463 -0
  40. package/tests/tools/test_pending_updates.py +549 -0
  41. package/tests/tools/test_review_engine.py +203 -0
  42. package/tools/context/README.md +1 -1
  43. package/tools/context/context_lazy_loader.py +1 -1
  44. package/tools/context/context_provider.py +2 -3
  45. package/tools/context/pending_updates.py +791 -0
  46. package/tools/memory/episodic.py +0 -1
  47. package/tools/review/__init__.py +1 -0
  48. package/tools/review/review_engine.py +157 -0
  49. package/tools/validation/README.md +1 -1
  50. package/hooks/modules/agents/anomaly_detector.py +0 -228
  51. package/hooks/modules/agents/subagent_metrics.py +0 -162
  52. package/hooks/modules/workflow/episodic_capture.py +0 -266
  53. package/hooks/modules/workflow/phase_validator.py +0 -306
  54. 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
- - **359 tests** at 100% passing
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 (359 tests)
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.0.0**
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/agent-catalog.md](./config/agent-catalog.md) - System overview
111
- - [config/orchestration-workflow.md](./config/orchestration-workflow.md) - Orchestration workflow
112
- - [config/git-standards.md](./config/git-standards.md) - Git standards
113
- - [config/agent-catalog.md](./config/agent-catalog.md) - Context contracts
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
- - **359 tests** al 100% pasando
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 (359 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.0.0**
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/agent-catalog.md](./config/agent-catalog.md) - Vista general del sistema
111
- - [config/orchestration-workflow.md](./config/orchestration-workflow.md) - Workflow de orquestacion
112
- - [config/git-standards.md](./config/git-standards.md) - Estandares Git
113
- - [config/agent-catalog.md](./config/agent-catalog.md) - Contratos de contexto
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();