@jaguilar87/gaia-ops 3.8.0 β 3.9.1
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-review.js +267 -0
- package/config/classification-rules.json +170 -0
- package/hooks/modules/context/discovery_classifier.py +596 -0
- package/hooks/pre_tool_use.py +44 -1
- package/hooks/subagent_stop.py +101 -2
- package/package.json +3 -2
- package/tests/hooks/modules/context/__init__.py +0 -0
- package/tests/hooks/modules/context/test_discovery_classifier.py +190 -0
- package/tests/hooks/test_subagent_stop_discovery.py +159 -0
- package/tests/tools/test_pending_updates.py +549 -0
- package/tests/tools/test_review_engine.py +203 -0
- package/tools/context/pending_updates.py +791 -0
- package/tools/review/__init__.py +1 -0
- package/tools/review/review_engine.py +157 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @jaguilar87/gaia-ops - Pending Context Updates Review CLI
|
|
5
|
+
*
|
|
6
|
+
* Interactive tool for reviewing, approving, and rejecting
|
|
7
|
+
* pending context update suggestions from agents.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* npx gaia-review # Interactive review mode
|
|
11
|
+
* npx gaia-review --list # List pending updates
|
|
12
|
+
* npx gaia-review --approve ID # Approve specific update
|
|
13
|
+
* npx gaia-review --reject ID # Reject specific update
|
|
14
|
+
* npx gaia-review --stats # Show statistics
|
|
15
|
+
* npx gaia-review --json # Output as JSON
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { execSync } from 'child_process';
|
|
19
|
+
import { existsSync } from 'fs';
|
|
20
|
+
import { join } from 'path';
|
|
21
|
+
import chalk from 'chalk';
|
|
22
|
+
import yargs from 'yargs';
|
|
23
|
+
import { hideBin } from 'yargs/helpers';
|
|
24
|
+
import prompts from 'prompts';
|
|
25
|
+
|
|
26
|
+
const CWD = process.cwd();
|
|
27
|
+
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// Python Backend Calls
|
|
30
|
+
// ============================================================================
|
|
31
|
+
|
|
32
|
+
function findReviewEngine() {
|
|
33
|
+
const candidates = [
|
|
34
|
+
join(CWD, '.claude', 'tools', 'review', 'review_engine.py'),
|
|
35
|
+
join(CWD, 'node_modules', '@jaguilar87', 'gaia-ops', 'tools', 'review', 'review_engine.py'),
|
|
36
|
+
join(import.meta.dirname, '..', 'tools', 'review', 'review_engine.py'),
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
for (const path of candidates) {
|
|
40
|
+
if (existsSync(path)) return path;
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function callReviewEngine(action, opts = {}) {
|
|
46
|
+
const enginePath = findReviewEngine();
|
|
47
|
+
if (!enginePath) {
|
|
48
|
+
console.error(chalk.red('Error: review_engine.py not found'));
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let cmd = `python3 "${enginePath}" ${action}`;
|
|
53
|
+
if (opts.updateId) cmd += ` --update-id "${opts.updateId}"`;
|
|
54
|
+
if (opts.contextPath) cmd += ` --context-path "${opts.contextPath}"`;
|
|
55
|
+
cmd += ' --json';
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const stdout = execSync(cmd, { encoding: 'utf-8', cwd: CWD, timeout: 30000 });
|
|
59
|
+
return JSON.parse(stdout.trim());
|
|
60
|
+
} catch (err) {
|
|
61
|
+
if (err.stdout) {
|
|
62
|
+
try { return JSON.parse(err.stdout.trim()); } catch { /* fall through */ }
|
|
63
|
+
}
|
|
64
|
+
return { error: err.message };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ============================================================================
|
|
69
|
+
// Display Helpers
|
|
70
|
+
// ============================================================================
|
|
71
|
+
|
|
72
|
+
function statusColor(status) {
|
|
73
|
+
switch (status) {
|
|
74
|
+
case 'pending': return chalk.yellow(status);
|
|
75
|
+
case 'approved': return chalk.green(status);
|
|
76
|
+
case 'rejected': return chalk.red(status);
|
|
77
|
+
case 'applied': return chalk.blue(status);
|
|
78
|
+
default: return status;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function categoryIcon(category) {
|
|
83
|
+
const icons = {
|
|
84
|
+
new_resource: 'π',
|
|
85
|
+
configuration_issue: 'βοΈ',
|
|
86
|
+
drift_detected: 'π',
|
|
87
|
+
dependency_discovered: 'π',
|
|
88
|
+
topology_change: 'πΊοΈ',
|
|
89
|
+
};
|
|
90
|
+
return icons[category] || 'π';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function displayTable(updates) {
|
|
94
|
+
if (!updates || updates.length === 0) {
|
|
95
|
+
console.log(chalk.dim(' No pending updates found.'));
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Header
|
|
100
|
+
const header = ` ${'ID'.padEnd(14)} ${'Category'.padEnd(22)} ${'Summary'.padEnd(50)} ${'Seen'.padEnd(5)} ${'Status'.padEnd(10)}`;
|
|
101
|
+
console.log(chalk.bold.underline(header));
|
|
102
|
+
|
|
103
|
+
for (const u of updates) {
|
|
104
|
+
const icon = categoryIcon(u.category);
|
|
105
|
+
const id = (u.update_id || '').substring(0, 12);
|
|
106
|
+
const cat = `${icon} ${u.category}`.padEnd(22);
|
|
107
|
+
const summary = (u.summary || '').substring(0, 48).padEnd(50);
|
|
108
|
+
const seen = String(u.seen_count || 1).padEnd(5);
|
|
109
|
+
const status = statusColor(u.status || 'pending');
|
|
110
|
+
console.log(` ${chalk.cyan(id.padEnd(14))} ${cat} ${summary} ${seen} ${status}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function displayStats(stats) {
|
|
115
|
+
console.log(chalk.bold('\n Pending Update Statistics\n'));
|
|
116
|
+
console.log(` Total: ${chalk.bold(stats.total || 0)}`);
|
|
117
|
+
console.log(` Pending: ${chalk.yellow(stats.pending || 0)}`);
|
|
118
|
+
console.log(` Approved: ${chalk.green(stats.approved || 0)}`);
|
|
119
|
+
console.log(` Rejected: ${chalk.red(stats.rejected || 0)}`);
|
|
120
|
+
console.log(` Applied: ${chalk.blue(stats.applied || 0)}`);
|
|
121
|
+
|
|
122
|
+
if (stats.by_category) {
|
|
123
|
+
console.log(chalk.bold('\n By Category:'));
|
|
124
|
+
for (const [cat, count] of Object.entries(stats.by_category)) {
|
|
125
|
+
console.log(` ${categoryIcon(cat)} ${cat}: ${count}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (stats.by_agent) {
|
|
130
|
+
console.log(chalk.bold('\n By Agent:'));
|
|
131
|
+
for (const [agent, count] of Object.entries(stats.by_agent)) {
|
|
132
|
+
console.log(` ${agent}: ${count}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ============================================================================
|
|
138
|
+
// Interactive Review Mode
|
|
139
|
+
// ============================================================================
|
|
140
|
+
|
|
141
|
+
async function interactiveReview() {
|
|
142
|
+
console.log(chalk.bold('\n Gaia Review - Interactive Mode\n'));
|
|
143
|
+
|
|
144
|
+
const result = callReviewEngine('list');
|
|
145
|
+
if (result.error) {
|
|
146
|
+
console.error(chalk.red(` Error: ${result.error}`));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const updates = result.updates || [];
|
|
151
|
+
if (updates.length === 0) {
|
|
152
|
+
console.log(chalk.green(' No pending updates to review.'));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
console.log(` Found ${chalk.bold(updates.length)} pending update(s):\n`);
|
|
157
|
+
displayTable(updates);
|
|
158
|
+
console.log();
|
|
159
|
+
|
|
160
|
+
// Find context path
|
|
161
|
+
const ctxPath = join(CWD, '.claude', 'project-context', 'project-context.json');
|
|
162
|
+
|
|
163
|
+
for (const update of updates) {
|
|
164
|
+
const id = (update.update_id || '').substring(0, 12);
|
|
165
|
+
const response = await prompts({
|
|
166
|
+
type: 'select',
|
|
167
|
+
name: 'action',
|
|
168
|
+
message: `${categoryIcon(update.category)} ${update.summary} (${id})`,
|
|
169
|
+
choices: [
|
|
170
|
+
{ title: chalk.green('Approve'), value: 'approve' },
|
|
171
|
+
{ title: chalk.red('Reject'), value: 'reject' },
|
|
172
|
+
{ title: chalk.dim('Skip'), value: 'skip' },
|
|
173
|
+
{ title: chalk.dim('Quit'), value: 'quit' },
|
|
174
|
+
],
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
if (!response.action || response.action === 'quit') break;
|
|
178
|
+
if (response.action === 'skip') continue;
|
|
179
|
+
|
|
180
|
+
const actionResult = callReviewEngine(response.action, {
|
|
181
|
+
updateId: update.update_id,
|
|
182
|
+
contextPath: response.action === 'approve' ? ctxPath : undefined,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
if (actionResult.error) {
|
|
186
|
+
console.log(chalk.red(` Error: ${actionResult.error}`));
|
|
187
|
+
} else if (response.action === 'approve') {
|
|
188
|
+
const applied = actionResult.applied ? chalk.green('applied') : chalk.yellow('approved (not applied)');
|
|
189
|
+
console.log(` ${chalk.green('β')} Approved and ${applied}`);
|
|
190
|
+
} else {
|
|
191
|
+
console.log(` ${chalk.red('β')} Rejected`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ============================================================================
|
|
197
|
+
// CLI
|
|
198
|
+
// ============================================================================
|
|
199
|
+
|
|
200
|
+
const argv = yargs(hideBin(process.argv))
|
|
201
|
+
.usage('Usage: $0 [options]')
|
|
202
|
+
.option('list', { alias: 'l', describe: 'List all pending updates', type: 'boolean' })
|
|
203
|
+
.option('approve', { alias: 'a', describe: 'Approve update by ID', type: 'string' })
|
|
204
|
+
.option('reject', { alias: 'r', describe: 'Reject update by ID', type: 'string' })
|
|
205
|
+
.option('stats', { alias: 's', describe: 'Show statistics', type: 'boolean' })
|
|
206
|
+
.option('json', { describe: 'Output as JSON', type: 'boolean' })
|
|
207
|
+
.option('context-path', { describe: 'Path to project-context.json', type: 'string' })
|
|
208
|
+
.help()
|
|
209
|
+
.alias('help', 'h')
|
|
210
|
+
.parse();
|
|
211
|
+
|
|
212
|
+
async function main() {
|
|
213
|
+
if (argv.list) {
|
|
214
|
+
const result = callReviewEngine('list');
|
|
215
|
+
if (argv.json) {
|
|
216
|
+
console.log(JSON.stringify(result, null, 2));
|
|
217
|
+
} else {
|
|
218
|
+
if (result.error) {
|
|
219
|
+
console.error(chalk.red(`Error: ${result.error}`));
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
console.log(chalk.bold(`\n Pending Updates (${result.count || 0}):\n`));
|
|
223
|
+
displayTable(result.updates);
|
|
224
|
+
console.log();
|
|
225
|
+
}
|
|
226
|
+
} else if (argv.approve) {
|
|
227
|
+
const ctxPath = argv.contextPath || join(CWD, '.claude', 'project-context', 'project-context.json');
|
|
228
|
+
const result = callReviewEngine('approve', { updateId: argv.approve, contextPath: ctxPath });
|
|
229
|
+
if (argv.json) {
|
|
230
|
+
console.log(JSON.stringify(result, null, 2));
|
|
231
|
+
} else if (result.error) {
|
|
232
|
+
console.error(chalk.red(`Error: ${result.error}`));
|
|
233
|
+
process.exit(1);
|
|
234
|
+
} else {
|
|
235
|
+
console.log(chalk.green(`β Update ${argv.approve} approved and ${result.applied ? 'applied' : 'queued'}`));
|
|
236
|
+
}
|
|
237
|
+
} else if (argv.reject) {
|
|
238
|
+
const result = callReviewEngine('reject', { updateId: argv.reject });
|
|
239
|
+
if (argv.json) {
|
|
240
|
+
console.log(JSON.stringify(result, null, 2));
|
|
241
|
+
} else if (result.error) {
|
|
242
|
+
console.error(chalk.red(`Error: ${result.error}`));
|
|
243
|
+
process.exit(1);
|
|
244
|
+
} else {
|
|
245
|
+
console.log(chalk.red(`β Update ${argv.reject} rejected`));
|
|
246
|
+
}
|
|
247
|
+
} else if (argv.stats) {
|
|
248
|
+
const result = callReviewEngine('stats');
|
|
249
|
+
if (argv.json) {
|
|
250
|
+
console.log(JSON.stringify(result, null, 2));
|
|
251
|
+
} else if (result.error) {
|
|
252
|
+
console.error(chalk.red(`Error: ${result.error}`));
|
|
253
|
+
process.exit(1);
|
|
254
|
+
} else {
|
|
255
|
+
displayStats(result.statistics || {});
|
|
256
|
+
console.log();
|
|
257
|
+
}
|
|
258
|
+
} else {
|
|
259
|
+
// Default: interactive mode
|
|
260
|
+
await interactiveReview();
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
main().catch(err => {
|
|
265
|
+
console.error(chalk.red(`Fatal error: ${err.message}`));
|
|
266
|
+
process.exit(1);
|
|
267
|
+
});
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0.0",
|
|
3
|
+
"description": "Rules for classifying agent output as structural vs operational discoveries",
|
|
4
|
+
"rules": [
|
|
5
|
+
{
|
|
6
|
+
"id": "rule_new_namespace",
|
|
7
|
+
"category": "new_resource",
|
|
8
|
+
"target_section": "cluster_details",
|
|
9
|
+
"patterns": [
|
|
10
|
+
"(?:discovered|found|detected)\\s+(?:new\\s+)?namespace\\s+['\"]?([\\w-]+)['\"]?",
|
|
11
|
+
"namespace\\s+['\"]?([\\w-]+)['\"]?\\s+(?:exists|is present|was found)\\s+(?:but\\s+)?not\\s+in\\s+(?:project[- ]?)?context"
|
|
12
|
+
],
|
|
13
|
+
"negative_patterns": [
|
|
14
|
+
"kubectl\\s+(?:create|delete)\\s+namespace",
|
|
15
|
+
"creating\\s+namespace",
|
|
16
|
+
"deleting\\s+namespace"
|
|
17
|
+
],
|
|
18
|
+
"confidence_weight": 0.8,
|
|
19
|
+
"extract_fields": {
|
|
20
|
+
"name": "$1"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"id": "rule_new_service",
|
|
25
|
+
"category": "new_resource",
|
|
26
|
+
"target_section": "application_services",
|
|
27
|
+
"patterns": [
|
|
28
|
+
"(?:discovered|found|detected)\\s+(?:new\\s+)?service\\s+['\"]?([\\w-]+)['\"]?\\s+(?:running|deployed|active)\\s+in\\s+(?:namespace\\s+)?['\"]?([\\w-]+)['\"]?",
|
|
29
|
+
"service\\s+['\"]?([\\w-]+)['\"]?\\s+is\\s+(?:running|active|deployed)\\s+(?:on|at)\\s+port\\s+(\\d+)",
|
|
30
|
+
"(?:new|unknown)\\s+(?:helm\\s+)?release\\s+['\"]?([\\w-]+)['\"]?"
|
|
31
|
+
],
|
|
32
|
+
"negative_patterns": [
|
|
33
|
+
"kubectl\\s+(?:create|apply|delete)\\s+(?:service|svc)",
|
|
34
|
+
"helm\\s+(?:install|upgrade|uninstall)"
|
|
35
|
+
],
|
|
36
|
+
"confidence_weight": 0.85,
|
|
37
|
+
"extract_fields": {
|
|
38
|
+
"service_name": "$1",
|
|
39
|
+
"namespace": "$2"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"id": "rule_new_bucket",
|
|
44
|
+
"category": "new_resource",
|
|
45
|
+
"target_section": "infrastructure_topology",
|
|
46
|
+
"patterns": [
|
|
47
|
+
"(?:discovered|found|detected)\\s+(?:GCS\\s+)?bucket\\s+['\"]?([\\w.-]+)['\"]?",
|
|
48
|
+
"bucket\\s+['\"]?([\\w.-]+)['\"]?\\s+exists\\s+in\\s+(?:region\\s+)?['\"]?([\\w-]+)['\"]?"
|
|
49
|
+
],
|
|
50
|
+
"negative_patterns": [
|
|
51
|
+
"gsutil\\s+(?:mb|rb)",
|
|
52
|
+
"creating\\s+bucket"
|
|
53
|
+
],
|
|
54
|
+
"confidence_weight": 0.8,
|
|
55
|
+
"extract_fields": {
|
|
56
|
+
"bucket_name": "$1",
|
|
57
|
+
"region": "$2"
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"id": "rule_iam_binding",
|
|
62
|
+
"category": "new_resource",
|
|
63
|
+
"target_section": "infrastructure_topology",
|
|
64
|
+
"patterns": [
|
|
65
|
+
"(?:IAM|iam)\\s+binding\\s+(?:between|for|on)\\s+['\"]?([\\w@.-]+)['\"]?\\s+(?:and|to|on)\\s+['\"]?([\\w@.-]+)['\"]?",
|
|
66
|
+
"(?:workload\\s+identity|WI)\\s+binding\\s+.*?['\"]?([\\w@.-]+)['\"]?"
|
|
67
|
+
],
|
|
68
|
+
"negative_patterns": [
|
|
69
|
+
"gcloud\\s+iam",
|
|
70
|
+
"adding\\s+(?:IAM|iam)\\s+binding"
|
|
71
|
+
],
|
|
72
|
+
"confidence_weight": 0.8,
|
|
73
|
+
"extract_fields": {
|
|
74
|
+
"principal": "$1",
|
|
75
|
+
"resource": "$2"
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"id": "rule_configuration_issue",
|
|
80
|
+
"category": "configuration_issue",
|
|
81
|
+
"target_section": "project_details",
|
|
82
|
+
"patterns": [
|
|
83
|
+
"configuration\\s+issue[:\\s]+(.+?)(?:\\.|$)",
|
|
84
|
+
"(?:misconfigur|wrong|incorrect|broken)(?:ed|ation)?\\s+(.+?)(?:\\.|$)",
|
|
85
|
+
"(?:references|points to)\\s+(?:wrong|incorrect)\\s+(?:project|bucket|service|account)\\s+['\"]?([\\w.-]+)['\"]?",
|
|
86
|
+
"(?:should be|expected)\\s+['\"]?([\\w.-]+)['\"]?\\s+but\\s+(?:is|found|actual)\\s+['\"]?([\\w.-]+)['\"]?"
|
|
87
|
+
],
|
|
88
|
+
"negative_patterns": [
|
|
89
|
+
"fixing\\s+configuration",
|
|
90
|
+
"applied\\s+fix",
|
|
91
|
+
"reconfiguring"
|
|
92
|
+
],
|
|
93
|
+
"confidence_weight": 0.85,
|
|
94
|
+
"extract_fields": {
|
|
95
|
+
"description": "$1"
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"id": "rule_drift_detected",
|
|
100
|
+
"category": "drift_detected",
|
|
101
|
+
"target_section": "application_services",
|
|
102
|
+
"patterns": [
|
|
103
|
+
"(?:drift|mismatch)\\s+detected[:\\s]+(.+?)(?:\\.|$)",
|
|
104
|
+
"actual\\s+(?:state|value|config)\\s+differs\\s+from\\s+(?:declared|expected|documented)",
|
|
105
|
+
"(?:running|actual|live)\\s+(?:on|at)\\s+port\\s+(\\d+)\\s+but\\s+(?:context|config|documented)\\s+says\\s+(\\d+)",
|
|
106
|
+
"(?:is|currently)\\s+(?:running|using|set to)\\s+['\"]?([\\w.-]+)['\"]?\\s+but\\s+(?:context|config)\\s+(?:says|shows)\\s+['\"]?([\\w.-]+)['\"]?"
|
|
107
|
+
],
|
|
108
|
+
"negative_patterns": [
|
|
109
|
+
"resolving\\s+drift",
|
|
110
|
+
"fixing\\s+drift"
|
|
111
|
+
],
|
|
112
|
+
"confidence_weight": 0.8,
|
|
113
|
+
"extract_fields": {
|
|
114
|
+
"actual": "$1",
|
|
115
|
+
"expected": "$2"
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"id": "rule_dependency_discovered",
|
|
120
|
+
"category": "dependency_discovered",
|
|
121
|
+
"target_section": "application_services",
|
|
122
|
+
"patterns": [
|
|
123
|
+
"(?:service|app)\\s+['\"]?([\\w-]+)['\"]?\\s+depends\\s+on\\s+['\"]?([\\w-]+)['\"]?",
|
|
124
|
+
"(?:discovered|found)\\s+dependency\\s+(?:between|from)\\s+['\"]?([\\w-]+)['\"]?\\s+(?:to|and)\\s+['\"]?([\\w-]+)['\"]?",
|
|
125
|
+
"['\"]?([\\w-]+)['\"]?\\s+(?:calls|connects to|requires)\\s+['\"]?([\\w-]+)['\"]?\\s+via\\s+(?:HTTP|gRPC|TCP)"
|
|
126
|
+
],
|
|
127
|
+
"negative_patterns": [
|
|
128
|
+
"adding\\s+dependency",
|
|
129
|
+
"installing\\s+dependency"
|
|
130
|
+
],
|
|
131
|
+
"confidence_weight": 0.75,
|
|
132
|
+
"extract_fields": {
|
|
133
|
+
"source": "$1",
|
|
134
|
+
"target": "$2"
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"id": "rule_ingress_host",
|
|
139
|
+
"category": "topology_change",
|
|
140
|
+
"target_section": "infrastructure_topology",
|
|
141
|
+
"patterns": [
|
|
142
|
+
"(?:new|discovered|found)\\s+ingress\\s+host\\s+['\"]?([\\w.-]+)['\"]?",
|
|
143
|
+
"ingress\\s+['\"]?([\\w.-]+)['\"]?\\s+routes\\s+to\\s+['\"]?([\\w-]+)['\"]?",
|
|
144
|
+
"(?:external|public)\\s+(?:endpoint|URL|host)\\s+['\"]?([\\w.-]+)['\"]?"
|
|
145
|
+
],
|
|
146
|
+
"negative_patterns": [
|
|
147
|
+
"kubectl\\s+(?:create|apply)\\s+ingress",
|
|
148
|
+
"creating\\s+ingress"
|
|
149
|
+
],
|
|
150
|
+
"confidence_weight": 0.8,
|
|
151
|
+
"extract_fields": {
|
|
152
|
+
"host": "$1",
|
|
153
|
+
"backend": "$2"
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
],
|
|
157
|
+
"global_negative_patterns": [
|
|
158
|
+
"^\\s*\\$\\s+",
|
|
159
|
+
"^\\s*#\\s+",
|
|
160
|
+
"running\\s+command",
|
|
161
|
+
"executing:",
|
|
162
|
+
"output:",
|
|
163
|
+
"^\\s*(?:NAME|STATUS|READY|AGE|RESTARTS)\\s+",
|
|
164
|
+
"latency.*?\\d+ms",
|
|
165
|
+
"CPU\\s+(?:usage|utilization)",
|
|
166
|
+
"memory.*?\\d+[MGK]i",
|
|
167
|
+
"\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}"
|
|
168
|
+
],
|
|
169
|
+
"confidence_threshold": 0.7
|
|
170
|
+
}
|