@jaguilar87/gaia-ops 3.9.0 → 3.9.2
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/bin/gaia-update.js +236 -105
- package/hooks/pre_tool_use.py +8 -1
- package/package.json +3 -2
package/bin/gaia-update.js
CHANGED
|
@@ -1,117 +1,130 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @jaguilar87/gaia-ops -
|
|
4
|
+
* @jaguilar87/gaia-ops - Update script
|
|
5
5
|
*
|
|
6
6
|
* Runs automatically on npm install/update (postinstall hook).
|
|
7
|
+
* Also available as: npx gaia-update
|
|
7
8
|
*
|
|
8
9
|
* Behavior:
|
|
9
10
|
* - First-time install (.claude/ doesn't exist): skip silently (gaia-init handles it)
|
|
10
11
|
* - Update (.claude/ exists):
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* 1. Show version transition (previous → current)
|
|
13
|
+
* 2. CLAUDE.md: overwrite safely (template is static)
|
|
14
|
+
* 3. settings.json: MERGE new rules, preserve user additions
|
|
15
|
+
* 4. Symlinks: recreate if missing, fix broken ones
|
|
16
|
+
* 5. Verify: hooks, python, project-context, config files
|
|
17
|
+
* 6. Report: summary with any issues found
|
|
14
18
|
*
|
|
15
|
-
* Usage:
|
|
19
|
+
* Usage:
|
|
20
|
+
* npm update @jaguilar87/gaia-ops # Automatic via postinstall
|
|
21
|
+
* npx gaia-update # Manual trigger
|
|
22
|
+
* npx gaia-update --verbose # Show all checks
|
|
16
23
|
*/
|
|
17
24
|
|
|
18
25
|
import { fileURLToPath } from 'url';
|
|
19
|
-
import { dirname, join } from 'path';
|
|
26
|
+
import { dirname, join, relative } from 'path';
|
|
20
27
|
import fs from 'fs/promises';
|
|
21
28
|
import { existsSync } from 'fs';
|
|
29
|
+
import { exec } from 'child_process';
|
|
30
|
+
import { promisify } from 'util';
|
|
22
31
|
import chalk from 'chalk';
|
|
23
32
|
import ora from 'ora';
|
|
24
33
|
|
|
34
|
+
const execAsync = promisify(exec);
|
|
25
35
|
const __filename = fileURLToPath(import.meta.url);
|
|
26
36
|
const __dirname = dirname(__filename);
|
|
27
37
|
const CWD = process.env.INIT_CWD || process.cwd();
|
|
38
|
+
const VERBOSE = process.argv.includes('--verbose') || process.argv.includes('-v');
|
|
39
|
+
|
|
40
|
+
// ============================================================================
|
|
41
|
+
// Version Detection
|
|
42
|
+
// ============================================================================
|
|
43
|
+
|
|
44
|
+
async function detectVersions() {
|
|
45
|
+
const current = await readPackageVersion(join(__dirname, '..', 'package.json'));
|
|
46
|
+
|
|
47
|
+
// Try to find previous version from the installed package.json backup or lock
|
|
48
|
+
let previous = null;
|
|
49
|
+
try {
|
|
50
|
+
const lockPath = join(CWD, 'package-lock.json');
|
|
51
|
+
if (existsSync(lockPath)) {
|
|
52
|
+
const lock = JSON.parse(await fs.readFile(lockPath, 'utf-8'));
|
|
53
|
+
const dep = lock.packages?.['node_modules/@jaguilar87/gaia-ops']
|
|
54
|
+
|| lock.dependencies?.['@jaguilar87/gaia-ops'];
|
|
55
|
+
if (dep) previous = dep.version;
|
|
56
|
+
}
|
|
57
|
+
} catch { /* ignore */ }
|
|
58
|
+
|
|
59
|
+
return { previous, current };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function readPackageVersion(path) {
|
|
63
|
+
try {
|
|
64
|
+
const pkg = JSON.parse(await fs.readFile(path, 'utf-8'));
|
|
65
|
+
return pkg.version;
|
|
66
|
+
} catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ============================================================================
|
|
72
|
+
// Update Steps
|
|
73
|
+
// ============================================================================
|
|
28
74
|
|
|
29
|
-
/**
|
|
30
|
-
* Update CLAUDE.md from template (safe overwrite - template is static).
|
|
31
|
-
*/
|
|
32
75
|
async function updateClaudeMd() {
|
|
33
76
|
const spinner = ora('Updating CLAUDE.md...').start();
|
|
34
|
-
|
|
35
77
|
try {
|
|
36
78
|
const templatePath = join(__dirname, '../templates/CLAUDE.template.md');
|
|
37
79
|
const claudeDir = join(CWD, '.claude');
|
|
38
80
|
|
|
39
|
-
if (!existsSync(templatePath)) {
|
|
40
|
-
spinner.
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (!existsSync(claudeDir)) {
|
|
45
|
-
spinner.info('First-time installation detected - skipping');
|
|
81
|
+
if (!existsSync(templatePath) || !existsSync(claudeDir)) {
|
|
82
|
+
spinner.info('Skipped (template or .claude/ not found)');
|
|
46
83
|
return false;
|
|
47
84
|
}
|
|
48
85
|
|
|
49
86
|
const claudeMdPath = join(CWD, 'CLAUDE.md');
|
|
50
87
|
await fs.copyFile(templatePath, claudeMdPath);
|
|
51
|
-
|
|
52
|
-
spinner.succeed('CLAUDE.md updated (static template)');
|
|
88
|
+
spinner.succeed('CLAUDE.md updated');
|
|
53
89
|
return true;
|
|
54
90
|
} catch (error) {
|
|
55
|
-
spinner.fail(`
|
|
91
|
+
spinner.fail(`CLAUDE.md: ${error.message}`);
|
|
56
92
|
return false;
|
|
57
93
|
}
|
|
58
94
|
}
|
|
59
95
|
|
|
60
|
-
/**
|
|
61
|
-
* Merge settings.json: add new template rules, preserve user additions.
|
|
62
|
-
*
|
|
63
|
-
* Strategy:
|
|
64
|
-
* - hooks: always replace from template (hooks are code, not config)
|
|
65
|
-
* - permissions.allow: union (template + user custom rules)
|
|
66
|
-
* - permissions.deny: union (template + user custom rules)
|
|
67
|
-
* - permissions.ask: union (template + user custom rules)
|
|
68
|
-
* - Other top-level keys: template wins (new features)
|
|
69
|
-
*/
|
|
70
96
|
async function updateSettingsJson() {
|
|
71
|
-
const spinner = ora('
|
|
72
|
-
|
|
97
|
+
const spinner = ora('Merging settings.json...').start();
|
|
73
98
|
try {
|
|
74
99
|
const templatePath = join(__dirname, '../templates/settings.template.json');
|
|
75
100
|
const settingsPath = join(CWD, '.claude', 'settings.json');
|
|
76
|
-
const claudeDir = join(CWD, '.claude');
|
|
77
101
|
|
|
78
|
-
if (!existsSync(templatePath)) {
|
|
79
|
-
spinner.
|
|
80
|
-
return false;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (!existsSync(claudeDir)) {
|
|
84
|
-
spinner.info('First-time installation detected - skipping');
|
|
102
|
+
if (!existsSync(templatePath) || !existsSync(join(CWD, '.claude'))) {
|
|
103
|
+
spinner.info('Skipped');
|
|
85
104
|
return false;
|
|
86
105
|
}
|
|
87
106
|
|
|
88
107
|
const template = JSON.parse(await fs.readFile(templatePath, 'utf-8'));
|
|
89
108
|
|
|
90
|
-
// If no existing settings, just write the template
|
|
91
109
|
if (!existsSync(settingsPath)) {
|
|
92
110
|
await fs.writeFile(settingsPath, JSON.stringify(template, null, 2), 'utf-8');
|
|
93
111
|
spinner.succeed('settings.json created from template');
|
|
94
112
|
return true;
|
|
95
113
|
}
|
|
96
114
|
|
|
97
|
-
// Read existing settings
|
|
98
115
|
let existing;
|
|
99
116
|
try {
|
|
100
117
|
existing = JSON.parse(await fs.readFile(settingsPath, 'utf-8'));
|
|
101
118
|
} catch {
|
|
102
|
-
// Invalid JSON - replace with template
|
|
103
119
|
await fs.writeFile(settingsPath, JSON.stringify(template, null, 2), 'utf-8');
|
|
104
|
-
spinner.succeed('settings.json replaced (was invalid
|
|
120
|
+
spinner.succeed('settings.json replaced (was invalid)');
|
|
105
121
|
return true;
|
|
106
122
|
}
|
|
107
123
|
|
|
108
|
-
// Merge
|
|
124
|
+
// Merge: hooks from template, permissions union
|
|
109
125
|
const merged = { ...template };
|
|
110
|
-
|
|
111
|
-
// Hooks: always from template (these are code references, not user config)
|
|
112
126
|
merged.hooks = template.hooks;
|
|
113
127
|
|
|
114
|
-
// Permissions: union of template + user custom rules
|
|
115
128
|
if (existing.permissions || template.permissions) {
|
|
116
129
|
merged.permissions = mergePermissions(
|
|
117
130
|
template.permissions || {},
|
|
@@ -120,20 +133,16 @@ async function updateSettingsJson() {
|
|
|
120
133
|
}
|
|
121
134
|
|
|
122
135
|
await fs.writeFile(settingsPath, JSON.stringify(merged, null, 2), 'utf-8');
|
|
123
|
-
spinner.succeed('settings.json merged (
|
|
136
|
+
spinner.succeed('settings.json merged (custom rules preserved)');
|
|
124
137
|
return true;
|
|
125
138
|
} catch (error) {
|
|
126
|
-
spinner.fail(`
|
|
139
|
+
spinner.fail(`settings.json: ${error.message}`);
|
|
127
140
|
return false;
|
|
128
141
|
}
|
|
129
142
|
}
|
|
130
143
|
|
|
131
|
-
/**
|
|
132
|
-
* Merge permission arrays: union of template + user, template first.
|
|
133
|
-
*/
|
|
134
144
|
function mergePermissions(template, existing) {
|
|
135
145
|
const result = {};
|
|
136
|
-
|
|
137
146
|
const keys = new Set([...Object.keys(template), ...Object.keys(existing)]);
|
|
138
147
|
|
|
139
148
|
for (const key of keys) {
|
|
@@ -141,7 +150,6 @@ function mergePermissions(template, existing) {
|
|
|
141
150
|
const eVal = existing[key];
|
|
142
151
|
|
|
143
152
|
if (Array.isArray(tVal) && Array.isArray(eVal)) {
|
|
144
|
-
// Union: template rules first, then user additions not in template
|
|
145
153
|
const templateSet = new Set(tVal);
|
|
146
154
|
const userAdditions = eVal.filter(rule => !templateSet.has(rule));
|
|
147
155
|
result[key] = [...tVal, ...userAdditions];
|
|
@@ -155,44 +163,45 @@ function mergePermissions(template, existing) {
|
|
|
155
163
|
return result;
|
|
156
164
|
}
|
|
157
165
|
|
|
158
|
-
|
|
159
|
-
* Recreate missing symlinks in .claude/ directory.
|
|
160
|
-
*/
|
|
161
|
-
async function recreateSymlinks() {
|
|
166
|
+
async function updateSymlinks() {
|
|
162
167
|
const spinner = ora('Checking symlinks...').start();
|
|
163
|
-
|
|
164
168
|
try {
|
|
165
169
|
const claudeDir = join(CWD, '.claude');
|
|
166
|
-
|
|
167
170
|
if (!existsSync(claudeDir)) {
|
|
168
|
-
spinner.info('
|
|
169
|
-
return false;
|
|
171
|
+
spinner.info('Skipped (.claude/ not found)');
|
|
172
|
+
return { updated: false, fixed: 0, total: 0 };
|
|
170
173
|
}
|
|
171
174
|
|
|
172
175
|
const packagePath = join(CWD, 'node_modules', '@jaguilar87', 'gaia-ops');
|
|
173
176
|
if (!existsSync(packagePath)) {
|
|
174
177
|
spinner.fail('Package not found in node_modules');
|
|
175
|
-
return false;
|
|
178
|
+
return { updated: false, fixed: 0, total: 0 };
|
|
176
179
|
}
|
|
177
180
|
|
|
178
|
-
const { relative } = await import('path');
|
|
179
181
|
const relativePath = relative(claudeDir, packagePath);
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
'agents', 'tools', 'hooks', 'commands',
|
|
183
|
-
'templates', 'config', 'speckit'
|
|
184
|
-
];
|
|
185
|
-
|
|
186
|
-
let recreated = 0;
|
|
182
|
+
const symlinks = ['agents', 'tools', 'hooks', 'commands', 'templates', 'config', 'speckit'];
|
|
183
|
+
let fixed = 0;
|
|
187
184
|
|
|
188
185
|
for (const name of symlinks) {
|
|
189
186
|
const link = join(claudeDir, name);
|
|
187
|
+
const target = join(relativePath, name);
|
|
188
|
+
|
|
190
189
|
if (!existsSync(link)) {
|
|
191
190
|
try {
|
|
192
|
-
await fs.symlink(
|
|
193
|
-
|
|
191
|
+
await fs.symlink(target, link);
|
|
192
|
+
fixed++;
|
|
193
|
+
} catch { /* skip */ }
|
|
194
|
+
} else {
|
|
195
|
+
// Check if symlink is broken (target doesn't resolve)
|
|
196
|
+
try {
|
|
197
|
+
await fs.realpath(link);
|
|
194
198
|
} catch {
|
|
195
|
-
//
|
|
199
|
+
// Broken symlink — remove and recreate
|
|
200
|
+
try {
|
|
201
|
+
await fs.unlink(link);
|
|
202
|
+
await fs.symlink(target, link);
|
|
203
|
+
fixed++;
|
|
204
|
+
} catch { /* skip */ }
|
|
196
205
|
}
|
|
197
206
|
}
|
|
198
207
|
}
|
|
@@ -202,52 +211,174 @@ async function recreateSymlinks() {
|
|
|
202
211
|
if (!existsSync(changelogLink)) {
|
|
203
212
|
try {
|
|
204
213
|
await fs.symlink(join(relativePath, 'CHANGELOG.md'), changelogLink);
|
|
205
|
-
|
|
206
|
-
} catch {
|
|
207
|
-
// Skip
|
|
208
|
-
}
|
|
214
|
+
fixed++;
|
|
215
|
+
} catch { /* skip */ }
|
|
209
216
|
}
|
|
210
217
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
218
|
+
const total = symlinks.length + 1;
|
|
219
|
+
if (fixed > 0) {
|
|
220
|
+
spinner.succeed(`Symlinks: fixed ${fixed}/${total}`);
|
|
221
|
+
} else {
|
|
222
|
+
spinner.succeed(`Symlinks: ${total}/${total} valid`);
|
|
214
223
|
}
|
|
215
224
|
|
|
216
|
-
|
|
217
|
-
return false;
|
|
225
|
+
return { updated: fixed > 0, fixed, total };
|
|
218
226
|
} catch (error) {
|
|
219
|
-
spinner.fail(`
|
|
220
|
-
return false;
|
|
227
|
+
spinner.fail(`Symlinks: ${error.message}`);
|
|
228
|
+
return { updated: false, fixed: 0, total: 0 };
|
|
221
229
|
}
|
|
222
230
|
}
|
|
223
231
|
|
|
224
|
-
|
|
225
|
-
|
|
232
|
+
// ============================================================================
|
|
233
|
+
// Post-Update Verification
|
|
234
|
+
// ============================================================================
|
|
235
|
+
|
|
236
|
+
async function runVerification() {
|
|
237
|
+
const spinner = ora('Verifying installation health...').start();
|
|
238
|
+
const checks = [];
|
|
239
|
+
const issues = [];
|
|
240
|
+
|
|
241
|
+
// 1. Hooks exist and are reachable
|
|
242
|
+
const hookFiles = ['pre_tool_use.py', 'post_tool_use.py', 'subagent_stop.py'];
|
|
243
|
+
for (const hook of hookFiles) {
|
|
244
|
+
const path = join(CWD, '.claude', 'hooks', hook);
|
|
245
|
+
if (existsSync(path)) {
|
|
246
|
+
checks.push({ name: hook, ok: true });
|
|
247
|
+
} else {
|
|
248
|
+
checks.push({ name: hook, ok: false });
|
|
249
|
+
issues.push(`Hook missing: .claude/hooks/${hook}`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
226
252
|
|
|
253
|
+
// 2. Python available
|
|
227
254
|
try {
|
|
228
|
-
const
|
|
229
|
-
|
|
255
|
+
const { stdout } = await execAsync('python3 --version', { timeout: 5000 });
|
|
256
|
+
checks.push({ name: 'python3', ok: true, detail: stdout.trim() });
|
|
257
|
+
} catch {
|
|
258
|
+
checks.push({ name: 'python3', ok: false });
|
|
259
|
+
issues.push('Python 3 not found (required for hooks)');
|
|
260
|
+
}
|
|
230
261
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
262
|
+
// 3. project-context.json exists and is valid
|
|
263
|
+
const ctxPath = join(CWD, '.claude', 'project-context', 'project-context.json');
|
|
264
|
+
if (existsSync(ctxPath)) {
|
|
265
|
+
try {
|
|
266
|
+
const ctx = JSON.parse(await fs.readFile(ctxPath, 'utf-8'));
|
|
267
|
+
const sections = Object.keys(ctx.sections || {}).length;
|
|
268
|
+
checks.push({ name: 'project-context.json', ok: sections >= 3, detail: `${sections} sections` });
|
|
269
|
+
if (sections < 3) issues.push('project-context.json has fewer than 3 sections');
|
|
270
|
+
} catch {
|
|
271
|
+
checks.push({ name: 'project-context.json', ok: false });
|
|
272
|
+
issues.push('project-context.json is invalid JSON');
|
|
234
273
|
}
|
|
274
|
+
} else {
|
|
275
|
+
checks.push({ name: 'project-context.json', ok: false });
|
|
276
|
+
issues.push('project-context.json not found (run gaia-init)');
|
|
277
|
+
}
|
|
235
278
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
279
|
+
// 4. Config files accessible
|
|
280
|
+
const configFiles = ['classification-rules.json', 'git_standards.json', 'universal-rules.json'];
|
|
281
|
+
for (const cfg of configFiles) {
|
|
282
|
+
const path = join(CWD, '.claude', 'config', cfg);
|
|
283
|
+
if (existsSync(path)) {
|
|
284
|
+
checks.push({ name: cfg, ok: true });
|
|
285
|
+
} else {
|
|
286
|
+
checks.push({ name: cfg, ok: false });
|
|
287
|
+
if (VERBOSE) issues.push(`Config missing: .claude/config/${cfg}`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
239
290
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
291
|
+
// 5. Agent definitions accessible
|
|
292
|
+
const agentFiles = ['terraform-architect.md', 'gitops-operator.md', 'cloud-troubleshooter.md', 'devops-developer.md', 'gaia.md'];
|
|
293
|
+
let agentsOk = 0;
|
|
294
|
+
for (const agent of agentFiles) {
|
|
295
|
+
if (existsSync(join(CWD, '.claude', 'agents', agent))) agentsOk++;
|
|
296
|
+
}
|
|
297
|
+
checks.push({ name: 'agent definitions', ok: agentsOk === agentFiles.length, detail: `${agentsOk}/${agentFiles.length}` });
|
|
298
|
+
if (agentsOk < agentFiles.length) issues.push(`${agentFiles.length - agentsOk} agent definition(s) missing`);
|
|
299
|
+
|
|
300
|
+
// 6. settings.json has hooks configured
|
|
301
|
+
const settingsPath = join(CWD, '.claude', 'settings.json');
|
|
302
|
+
if (existsSync(settingsPath)) {
|
|
303
|
+
try {
|
|
304
|
+
const settings = JSON.parse(await fs.readFile(settingsPath, 'utf-8'));
|
|
305
|
+
const hasHooks = settings.hooks && Object.keys(settings.hooks).length > 0;
|
|
306
|
+
checks.push({ name: 'hooks config', ok: hasHooks });
|
|
307
|
+
if (!hasHooks) issues.push('settings.json has no hooks configured');
|
|
308
|
+
} catch {
|
|
309
|
+
checks.push({ name: 'hooks config', ok: false });
|
|
310
|
+
issues.push('settings.json is invalid');
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const passed = checks.filter(c => c.ok).length;
|
|
315
|
+
const total = checks.length;
|
|
316
|
+
|
|
317
|
+
if (issues.length === 0) {
|
|
318
|
+
spinner.succeed(`Health check: ${passed}/${total} passed`);
|
|
319
|
+
} else {
|
|
320
|
+
spinner.warn(`Health check: ${passed}/${total} passed, ${issues.length} issue(s)`);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return { checks, issues, passed, total };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// ============================================================================
|
|
327
|
+
// Main
|
|
328
|
+
// ============================================================================
|
|
329
|
+
|
|
330
|
+
async function main() {
|
|
331
|
+
const claudeDir = join(CWD, '.claude');
|
|
332
|
+
const isUpdate = existsSync(claudeDir);
|
|
333
|
+
|
|
334
|
+
if (!isUpdate) {
|
|
335
|
+
// First-time install — gaia-init handles everything
|
|
336
|
+
process.exit(0);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Version info
|
|
340
|
+
const { previous, current } = await detectVersions();
|
|
341
|
+
const versionLine = previous && previous !== current
|
|
342
|
+
? `${chalk.gray(previous)} → ${chalk.green(current)}`
|
|
343
|
+
: chalk.green(current);
|
|
344
|
+
|
|
345
|
+
console.log(chalk.cyan(`\n gaia-ops update ${versionLine}\n`));
|
|
346
|
+
|
|
347
|
+
// Step 1-3: Update files
|
|
348
|
+
const claudeUpdated = await updateClaudeMd();
|
|
349
|
+
const settingsUpdated = await updateSettingsJson();
|
|
350
|
+
const { updated: symlinksUpdated, fixed: symlinksFix } = await updateSymlinks();
|
|
351
|
+
|
|
352
|
+
// Step 4: Verify
|
|
353
|
+
const { issues, passed, total } = await runVerification();
|
|
354
|
+
|
|
355
|
+
// Summary
|
|
356
|
+
const changes = [claudeUpdated, settingsUpdated, symlinksUpdated].filter(Boolean).length;
|
|
357
|
+
|
|
358
|
+
console.log('');
|
|
359
|
+
if (changes > 0 || issues.length > 0) {
|
|
360
|
+
// Changes summary
|
|
361
|
+
if (changes > 0) {
|
|
362
|
+
console.log(chalk.green(` ${changes} file(s) updated`));
|
|
363
|
+
if (settingsUpdated) console.log(chalk.gray(' settings.json: new rules merged, custom rules preserved'));
|
|
364
|
+
if (symlinksFix > 0) console.log(chalk.gray(` ${symlinksFix} symlink(s) fixed`));
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Issues
|
|
368
|
+
if (issues.length > 0) {
|
|
369
|
+
console.log(chalk.yellow(`\n ${issues.length} issue(s) found:`));
|
|
370
|
+
for (const issue of issues) {
|
|
371
|
+
console.log(chalk.yellow(` - ${issue}`));
|
|
244
372
|
}
|
|
245
|
-
console.log(chalk.gray(' Run `npx gaia-doctor` to verify installation health\n'));
|
|
246
373
|
}
|
|
247
|
-
}
|
|
248
|
-
console.
|
|
249
|
-
process.exit(0); // Don't fail npm install
|
|
374
|
+
} else {
|
|
375
|
+
console.log(chalk.green(' Everything up to date'));
|
|
250
376
|
}
|
|
377
|
+
|
|
378
|
+
console.log(chalk.gray(`\n Health: ${passed}/${total} checks passed\n`));
|
|
251
379
|
}
|
|
252
380
|
|
|
253
|
-
main()
|
|
381
|
+
main().catch(error => {
|
|
382
|
+
console.error(chalk.red(`\n Update failed: ${error.message}\n`));
|
|
383
|
+
process.exit(0); // Never fail npm install
|
|
384
|
+
});
|
package/hooks/pre_tool_use.py
CHANGED
|
@@ -284,7 +284,14 @@ def _inject_project_context(parameters: dict) -> dict:
|
|
|
284
284
|
|
|
285
285
|
{json.dumps(context_payload, indent=2)}
|
|
286
286
|
|
|
287
|
-
{skills_content}{pending_warning}
|
|
287
|
+
{skills_content}{pending_warning}# Discovery Protocol
|
|
288
|
+
When you find resources (namespaces, services, endpoints, configurations) that are NOT listed in the Project Context above, explicitly report the difference using phrases like:
|
|
289
|
+
- "Found namespace 'X' exists but not in project context"
|
|
290
|
+
- "Discovered new service 'Y' running in namespace 'Z'"
|
|
291
|
+
- "Drift detected: actual value is 'A' but context says 'B'"
|
|
292
|
+
This helps keep the project context up to date.
|
|
293
|
+
|
|
294
|
+
---
|
|
288
295
|
|
|
289
296
|
# User Task
|
|
290
297
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jaguilar87/gaia-ops",
|
|
3
|
-
"version": "3.9.
|
|
3
|
+
"version": "3.9.2",
|
|
4
4
|
"description": "Multi-agent orchestration system for Claude Code - DevOps automation toolkit",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"gaia-cleanup": "bin/gaia-cleanup.js",
|
|
11
11
|
"gaia-uninstall": "bin/gaia-uninstall.js",
|
|
12
12
|
"gaia-metrics": "bin/gaia-metrics.js",
|
|
13
|
-
"gaia-review": "bin/gaia-review.js"
|
|
13
|
+
"gaia-review": "bin/gaia-review.js",
|
|
14
|
+
"gaia-update": "bin/gaia-update.js"
|
|
14
15
|
},
|
|
15
16
|
"keywords": [
|
|
16
17
|
"claude-code",
|