@jaguilar87/gaia-ops 2.5.8 → 2.6.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/bin/gaia-cleanup.js +7 -5
- package/bin/gaia-uninstall.js +111 -0
- package/config/delegation-matrix.md +122 -0
- package/config/metrics_targets.json +37 -0
- package/hooks/post_phase_hook.py +97 -0
- package/hooks/pre_phase_hook.py +222 -0
- package/hooks/pre_tool_use.py +33 -0
- package/package.json +3 -2
- package/templates/CLAUDE.template.md +113 -0
- package/templates/settings.template.json +625 -188
- package/tests/context/test_lazy_loading.py +298 -0
- package/tests/guards/test_workflow_enforcer.py +147 -0
- package/tests/permissions-validation/empirical-permission-testing.md +3 -0
- package/tools/0-guards/delegation_matrix.py +270 -0
- package/tools/0-guards/guards_config.json +41 -0
- package/tools/0-guards/workflow_enforcer.py +358 -0
- package/tools/1-routing/agent_router.py +52 -0
- package/tools/2-context/benchmark_context.py +389 -0
- package/tools/2-context/benchmark_results.json +30 -0
- package/tools/2-context/context_compressor.py +440 -0
- package/tools/2-context/context_lazy_loader.py +402 -0
- package/tools/2-context/context_selector.py +451 -0
- package/tools/8-metrics/metrics_collector.py +390 -0
- package/tools/8-metrics/metrics_dashboard.py +192 -0
package/bin/gaia-cleanup.js
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
import { join, dirname, resolve } from 'path';
|
|
27
27
|
import fs from 'fs/promises';
|
|
28
|
-
import { existsSync } from 'fs';
|
|
28
|
+
import { existsSync, lstatSync } from 'fs';
|
|
29
29
|
import chalk from 'chalk';
|
|
30
30
|
import ora from 'ora';
|
|
31
31
|
|
|
@@ -136,13 +136,15 @@ async function removeSymlinks() {
|
|
|
136
136
|
|
|
137
137
|
let removed = 0;
|
|
138
138
|
for (const symlinkPath of symlinks) {
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
try {
|
|
140
|
+
// Use lstat to check if path exists as a symlink (works for broken symlinks too)
|
|
141
|
+
const stats = lstatSync(symlinkPath);
|
|
142
|
+
if (stats.isSymbolicLink() || stats.isFile()) {
|
|
141
143
|
await fs.unlink(symlinkPath);
|
|
142
144
|
removed++;
|
|
143
|
-
} catch (error) {
|
|
144
|
-
// Ignore errors
|
|
145
145
|
}
|
|
146
|
+
} catch (error) {
|
|
147
|
+
// Path doesn't exist or other error, skip
|
|
146
148
|
}
|
|
147
149
|
}
|
|
148
150
|
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @jaguilar87/gaia-ops - Uninstall wrapper
|
|
5
|
+
*
|
|
6
|
+
* Safely uninstalls gaia-ops by:
|
|
7
|
+
* 1. Running gaia-cleanup to remove all generated files
|
|
8
|
+
* 2. Running npm uninstall to remove the package
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* npx gaia-uninstall
|
|
12
|
+
* OR
|
|
13
|
+
* npm exec gaia-uninstall
|
|
14
|
+
*
|
|
15
|
+
* This ensures a clean uninstallation with no leftover files.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { execSync } from 'child_process';
|
|
19
|
+
import { fileURLToPath } from 'url';
|
|
20
|
+
import { dirname, join } from 'path';
|
|
21
|
+
import { existsSync } from 'fs';
|
|
22
|
+
import chalk from 'chalk';
|
|
23
|
+
import ora from 'ora';
|
|
24
|
+
|
|
25
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
26
|
+
const __dirname = dirname(__filename);
|
|
27
|
+
const CWD = process.env.INIT_CWD || process.cwd();
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Run gaia-cleanup to remove generated files
|
|
31
|
+
*/
|
|
32
|
+
async function runCleanup() {
|
|
33
|
+
const spinner = ora('Running cleanup...').start();
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
// Import and execute gaia-cleanup
|
|
37
|
+
const cleanupScript = join(__dirname, 'gaia-cleanup.js');
|
|
38
|
+
|
|
39
|
+
if (!existsSync(cleanupScript)) {
|
|
40
|
+
spinner.fail('Cleanup script not found');
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Execute cleanup by importing it
|
|
45
|
+
await import(`file://${cleanupScript}`);
|
|
46
|
+
|
|
47
|
+
spinner.succeed('Cleanup completed');
|
|
48
|
+
return true;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
spinner.fail(`Cleanup failed: ${error.message}`);
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Run npm uninstall
|
|
57
|
+
*/
|
|
58
|
+
function runUninstall() {
|
|
59
|
+
const spinner = ora('Uninstalling @jaguilar87/gaia-ops...').start();
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
execSync('npm uninstall @jaguilar87/gaia-ops', {
|
|
63
|
+
cwd: CWD,
|
|
64
|
+
stdio: 'inherit'
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
spinner.succeed('Package uninstalled');
|
|
68
|
+
return true;
|
|
69
|
+
} catch (error) {
|
|
70
|
+
spinner.fail(`Uninstall failed: ${error.message}`);
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Main function
|
|
77
|
+
*/
|
|
78
|
+
async function main() {
|
|
79
|
+
console.log(chalk.cyan('\n🗑️ @jaguilar87/gaia-ops uninstaller\n'));
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
// Step 1: Run cleanup
|
|
83
|
+
const cleanupSuccess = await runCleanup();
|
|
84
|
+
|
|
85
|
+
if (!cleanupSuccess) {
|
|
86
|
+
console.log(chalk.yellow('\n⚠️ Cleanup had issues, but continuing with uninstall...\n'));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
console.log('');
|
|
90
|
+
|
|
91
|
+
// Step 2: Run uninstall
|
|
92
|
+
const uninstallSuccess = runUninstall();
|
|
93
|
+
|
|
94
|
+
if (uninstallSuccess) {
|
|
95
|
+
console.log(chalk.green('\n✅ Uninstall complete!\n'));
|
|
96
|
+
console.log(chalk.gray('All gaia-ops files have been removed.'));
|
|
97
|
+
console.log(chalk.gray('Your project data (logs, tests, project-context) was preserved.\n'));
|
|
98
|
+
} else {
|
|
99
|
+
console.log(chalk.red('\n❌ Uninstall failed\n'));
|
|
100
|
+
console.log(chalk.yellow('You can try manually:'));
|
|
101
|
+
console.log(chalk.gray(' 1. npx gaia-cleanup'));
|
|
102
|
+
console.log(chalk.gray(' 2. npm uninstall @jaguilar87/gaia-ops\n'));
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.error(chalk.red(`\n❌ Uninstall error: ${error.message}\n`));
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
main();
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Binary Delegation Matrix
|
|
2
|
+
|
|
3
|
+
**Version:** 1.0.0
|
|
4
|
+
**Purpose:** Deterministic decision of when to delegate vs execute locally
|
|
5
|
+
|
|
6
|
+
## Decision Rules (Priority Order)
|
|
7
|
+
|
|
8
|
+
| # | Condition | Decision | Confidence | Reason |
|
|
9
|
+
|---|-----------|----------|------------|---------|
|
|
10
|
+
| 1 | `has_task_id AND task_agent != None` | DELEGATE | 1.0 | Task metadata routing |
|
|
11
|
+
| 2 | `security_tier == "T3"` | DELEGATE | 1.0 | T3 requires agent + approval |
|
|
12
|
+
| 3 | `file_count >= 3` | DELEGATE | 0.9 | Multi-file threshold |
|
|
13
|
+
| 4 | `file_span_multiple_dirs == True` | DELEGATE | 0.9 | Multiple directories |
|
|
14
|
+
| 5 | `has_infrastructure_keywords AND requires_context` | DELEGATE | 0.85 | Infrastructure + context |
|
|
15
|
+
| 6 | `has_chained_commands == True` | DELEGATE | 0.8 | Chained commands safety |
|
|
16
|
+
| 7 | `tier == "T0" AND file_count <= 1 AND !has_approval_keywords` | LOCAL | 0.9 | Atomic T0 operation |
|
|
17
|
+
| 8 | `tier == "T1" AND file_count <= 1 AND !requires_credentials` | LOCAL | 0.85 | Simple T1 validation |
|
|
18
|
+
| 9 | DEFAULT (fallback) | DELEGATE | 0.5 | Safety default |
|
|
19
|
+
|
|
20
|
+
## Binary Conditions Extracted
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
@dataclass
|
|
24
|
+
class DelegationConditions:
|
|
25
|
+
file_count: int # Count of files to modify
|
|
26
|
+
file_span_multiple_dirs: bool # Files in multiple directories
|
|
27
|
+
has_chained_commands: bool # Uses && or pipes
|
|
28
|
+
has_infrastructure_keywords: bool # terraform/kubectl/etc
|
|
29
|
+
has_approval_keywords: bool # apply/deploy/push/delete
|
|
30
|
+
security_tier: str # T0, T1, T2, T3
|
|
31
|
+
requires_context: bool # Needs project-context.json
|
|
32
|
+
requires_credentials: bool # Needs GCP/AWS/K8s creds
|
|
33
|
+
has_task_id: bool # Mentions task ID
|
|
34
|
+
task_agent: str # Agent from task metadata
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Examples
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
# Example 1: Simple git status (LOCAL)
|
|
41
|
+
conditions = DelegationConditions(
|
|
42
|
+
file_count=0,
|
|
43
|
+
file_span_multiple_dirs=False,
|
|
44
|
+
has_infrastructure_keywords=False,
|
|
45
|
+
security_tier="T0"
|
|
46
|
+
)
|
|
47
|
+
→ Decision: LOCAL (Rule 7: Atomic T0 operation)
|
|
48
|
+
|
|
49
|
+
# Example 2: Terraform apply (DELEGATE)
|
|
50
|
+
conditions = DelegationConditions(
|
|
51
|
+
has_infrastructure_keywords=True,
|
|
52
|
+
has_approval_keywords=True,
|
|
53
|
+
security_tier="T3"
|
|
54
|
+
)
|
|
55
|
+
→ Decision: DELEGATE (Rule 2: T3 requires agent)
|
|
56
|
+
|
|
57
|
+
# Example 3: Multi-file edit (DELEGATE)
|
|
58
|
+
conditions = DelegationConditions(
|
|
59
|
+
file_count=5,
|
|
60
|
+
file_span_multiple_dirs=True,
|
|
61
|
+
security_tier="T1"
|
|
62
|
+
)
|
|
63
|
+
→ Decision: DELEGATE (Rule 3: Multi-file threshold)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Integration with Orchestrator
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from agent_router import should_delegate
|
|
70
|
+
|
|
71
|
+
# At orchestrator entry point
|
|
72
|
+
result = should_delegate(user_request, context={
|
|
73
|
+
"file_count": 3,
|
|
74
|
+
"multiple_directories": True
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
if result["delegate"]:
|
|
78
|
+
agent = result["suggested_agent"]
|
|
79
|
+
# Proceed with agent invocation
|
|
80
|
+
else:
|
|
81
|
+
# Execute locally (orchestrator)
|
|
82
|
+
pass
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Confidence Levels
|
|
86
|
+
|
|
87
|
+
- **1.0**: Absolute confidence (deterministic rules)
|
|
88
|
+
- **0.9**: High confidence (clear patterns)
|
|
89
|
+
- **0.85**: Medium-high confidence (strong indicators)
|
|
90
|
+
- **0.8**: Medium confidence (good heuristics)
|
|
91
|
+
- **0.5**: Low confidence (fallback/safety)
|
|
92
|
+
|
|
93
|
+
## Security Considerations
|
|
94
|
+
|
|
95
|
+
1. **T3 operations ALWAYS delegate** - No exceptions
|
|
96
|
+
2. **Chained commands prefer delegation** - Safety over convenience
|
|
97
|
+
3. **Default to delegation when uncertain** - Better safe than sorry
|
|
98
|
+
4. **Infrastructure operations require context** - Never execute without proper context
|
|
99
|
+
|
|
100
|
+
## Testing the Matrix
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
# Run standalone test
|
|
104
|
+
python3 .claude/tools/0-guards/delegation_matrix.py
|
|
105
|
+
|
|
106
|
+
# Test integration with router
|
|
107
|
+
python3 -c "from tools.1-routing.agent_router import should_delegate; \
|
|
108
|
+
print(should_delegate('terraform apply', {'file_count': 1}))"
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Monitoring
|
|
112
|
+
|
|
113
|
+
Delegation decisions are logged to:
|
|
114
|
+
- `.claude/logs/delegation.jsonl` (if logging enabled)
|
|
115
|
+
- Included in metrics collection for KPI tracking
|
|
116
|
+
|
|
117
|
+
## Future Improvements
|
|
118
|
+
|
|
119
|
+
1. **Machine Learning Enhancement**: Train on actual delegation outcomes
|
|
120
|
+
2. **Custom Rules**: Allow project-specific delegation rules
|
|
121
|
+
3. **Context Awareness**: Consider recent operations for better decisions
|
|
122
|
+
4. **Performance Metrics**: Track decision accuracy and adjust thresholds
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"kpi_targets": {
|
|
3
|
+
"routing_accuracy": {
|
|
4
|
+
"avg_confidence_min": 0.7,
|
|
5
|
+
"semantic_routing_rate_min": 0.6
|
|
6
|
+
},
|
|
7
|
+
"delegation_effectiveness": {
|
|
8
|
+
"avg_confidence_min": 0.8
|
|
9
|
+
},
|
|
10
|
+
"guard_effectiveness": {
|
|
11
|
+
"pass_rate_min": 0.9,
|
|
12
|
+
"phase_4_pass_rate_min": 1.0
|
|
13
|
+
},
|
|
14
|
+
"phase_completion": {
|
|
15
|
+
"phase_4_skip_rate_t3_max": 0.0
|
|
16
|
+
},
|
|
17
|
+
"approval_gate": {
|
|
18
|
+
"approval_rate_min": 0.5,
|
|
19
|
+
"avg_response_time_max_seconds": 300
|
|
20
|
+
},
|
|
21
|
+
"agent_performance": {
|
|
22
|
+
"success_rate_min": 0.8,
|
|
23
|
+
"avg_duration_max_ms": 30000
|
|
24
|
+
},
|
|
25
|
+
"overall_health": {
|
|
26
|
+
"score_min": 0.8
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"alerting": {
|
|
30
|
+
"enabled": true,
|
|
31
|
+
"critical_thresholds": {
|
|
32
|
+
"phase_4_skip_rate_t3": 0.0,
|
|
33
|
+
"guard_pass_rate": 0.85,
|
|
34
|
+
"agent_success_rate": 0.7
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Post-Phase Hook - Validar resultados DESPUÉS de cada fase.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
import logging
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Dict, Any
|
|
10
|
+
|
|
11
|
+
sys.path.insert(0, str(Path(__file__).parent.parent / "tools" / "0-guards"))
|
|
12
|
+
|
|
13
|
+
from workflow_enforcer import get_enforcer, GuardViolation
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def post_phase_4_approval(
|
|
19
|
+
tier: str,
|
|
20
|
+
user_response: str,
|
|
21
|
+
validation_result: Dict[str, Any]
|
|
22
|
+
) -> Dict[str, Any]:
|
|
23
|
+
"""
|
|
24
|
+
Validar que T3 operations recibieron approval.
|
|
25
|
+
"""
|
|
26
|
+
enforcer = get_enforcer()
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
if tier == "T3":
|
|
30
|
+
enforcer.enforce(
|
|
31
|
+
"guard_phase_4_approval_validation",
|
|
32
|
+
validation_result=validation_result
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
return {"valid": True, "reason": "Approval validation passed"}
|
|
36
|
+
|
|
37
|
+
except GuardViolation as e:
|
|
38
|
+
return {"valid": False, "reason": str(e)}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def post_phase_6_ssot_update(
|
|
42
|
+
tier: str,
|
|
43
|
+
ssot_updated: bool
|
|
44
|
+
) -> Dict[str, Any]:
|
|
45
|
+
"""
|
|
46
|
+
Validar que T3 operations actualizaron SSOT.
|
|
47
|
+
"""
|
|
48
|
+
enforcer = get_enforcer()
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
enforcer.enforce(
|
|
52
|
+
"guard_phase_6_ssot_update_after_t3",
|
|
53
|
+
tier=tier,
|
|
54
|
+
ssot_updated=ssot_updated
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
return {"valid": True, "reason": "SSOT update validation passed"}
|
|
58
|
+
|
|
59
|
+
except GuardViolation as e:
|
|
60
|
+
return {"valid": False, "reason": str(e)}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# CLI for testing
|
|
64
|
+
if __name__ == "__main__":
|
|
65
|
+
logging.basicConfig(level=logging.INFO)
|
|
66
|
+
|
|
67
|
+
print("🧪 Testing Post-Phase Hooks...\n")
|
|
68
|
+
|
|
69
|
+
# Test post-phase 4 validation (T3 without approval)
|
|
70
|
+
result = post_phase_4_approval(
|
|
71
|
+
tier="T3",
|
|
72
|
+
user_response="reject",
|
|
73
|
+
validation_result={"approved": False, "action": "abort"}
|
|
74
|
+
)
|
|
75
|
+
print(f"Post-Phase 4 (T3 rejected): {result}")
|
|
76
|
+
|
|
77
|
+
# Test post-phase 4 validation (T3 with approval)
|
|
78
|
+
result = post_phase_4_approval(
|
|
79
|
+
tier="T3",
|
|
80
|
+
user_response="approve",
|
|
81
|
+
validation_result={"approved": True, "action": "proceed"}
|
|
82
|
+
)
|
|
83
|
+
print(f"Post-Phase 4 (T3 approved): {result}")
|
|
84
|
+
|
|
85
|
+
# Test post-phase 6 validation (T3 without SSOT update)
|
|
86
|
+
result = post_phase_6_ssot_update(
|
|
87
|
+
tier="T3",
|
|
88
|
+
ssot_updated=False
|
|
89
|
+
)
|
|
90
|
+
print(f"Post-Phase 6 (T3 no SSOT update): {result}")
|
|
91
|
+
|
|
92
|
+
# Test post-phase 6 validation (T3 with SSOT update)
|
|
93
|
+
result = post_phase_6_ssot_update(
|
|
94
|
+
tier="T3",
|
|
95
|
+
ssot_updated=True
|
|
96
|
+
)
|
|
97
|
+
print(f"Post-Phase 6 (T3 SSOT updated): {result}")
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Pre-Phase Hook - Ejecutar guards ANTES de cada fase.
|
|
4
|
+
Se invoca desde el orchestrator antes de comenzar una fase.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
import logging
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Dict, Any, Optional
|
|
11
|
+
|
|
12
|
+
# Add tools to path
|
|
13
|
+
sys.path.insert(0, str(Path(__file__).parent.parent / "tools" / "0-guards"))
|
|
14
|
+
|
|
15
|
+
from workflow_enforcer import get_enforcer, GuardViolation
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def pre_phase_0_clarification(
|
|
21
|
+
ambiguity_score: float,
|
|
22
|
+
user_prompt: str
|
|
23
|
+
) -> Dict[str, Any]:
|
|
24
|
+
"""
|
|
25
|
+
Ejecutar guards antes de Phase 0 (Clarification).
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
{"allowed": bool, "reason": str}
|
|
29
|
+
"""
|
|
30
|
+
enforcer = get_enforcer()
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
# Guard: Ambiguity threshold
|
|
34
|
+
enforcer.enforce(
|
|
35
|
+
"guard_phase_0_ambiguity_threshold",
|
|
36
|
+
ambiguity_score=ambiguity_score,
|
|
37
|
+
threshold=0.3
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
"allowed": True,
|
|
42
|
+
"reason": "Phase 0 guards passed"
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
except GuardViolation as e:
|
|
46
|
+
return {
|
|
47
|
+
"allowed": False,
|
|
48
|
+
"reason": str(e)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def pre_phase_1_routing(
|
|
53
|
+
agent_name: str,
|
|
54
|
+
routing_confidence: float,
|
|
55
|
+
available_agents: list
|
|
56
|
+
) -> Dict[str, Any]:
|
|
57
|
+
"""
|
|
58
|
+
Ejecutar guards antes de Phase 1 (Routing).
|
|
59
|
+
"""
|
|
60
|
+
enforcer = get_enforcer()
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
# Guard: Routing confidence
|
|
64
|
+
enforcer.enforce(
|
|
65
|
+
"guard_phase_1_routing_confidence",
|
|
66
|
+
routing_confidence=routing_confidence,
|
|
67
|
+
min_confidence=0.5
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Guard: Agent exists
|
|
71
|
+
enforcer.enforce(
|
|
72
|
+
"guard_phase_1_agent_exists",
|
|
73
|
+
agent_name=agent_name,
|
|
74
|
+
available_agents=available_agents
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
return {"allowed": True, "reason": "Phase 1 guards passed"}
|
|
78
|
+
|
|
79
|
+
except GuardViolation as e:
|
|
80
|
+
return {"allowed": False, "reason": str(e)}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def pre_phase_2_context(
|
|
84
|
+
context_payload: Dict[str, Any],
|
|
85
|
+
agent_name: str
|
|
86
|
+
) -> Dict[str, Any]:
|
|
87
|
+
"""
|
|
88
|
+
Ejecutar guards antes de Phase 2 (Context Provisioning).
|
|
89
|
+
"""
|
|
90
|
+
enforcer = get_enforcer()
|
|
91
|
+
|
|
92
|
+
# Determinar required sections según el agente
|
|
93
|
+
agent_requirements = {
|
|
94
|
+
"terraform-architect": ["project_details", "terraform_infrastructure", "operational_guidelines"],
|
|
95
|
+
"gitops-operator": ["project_details", "gitops_configuration", "cluster_details"],
|
|
96
|
+
"gcp-troubleshooter": ["project_details", "cluster_details"],
|
|
97
|
+
"devops-developer": ["project_details", "operational_guidelines"]
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
required_sections = agent_requirements.get(agent_name, ["project_details"])
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
# Guard: Context completeness
|
|
104
|
+
enforcer.enforce(
|
|
105
|
+
"guard_phase_2_context_completeness",
|
|
106
|
+
context_payload=context_payload,
|
|
107
|
+
required_sections=required_sections
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
return {"allowed": True, "reason": "Phase 2 guards passed"}
|
|
111
|
+
|
|
112
|
+
except GuardViolation as e:
|
|
113
|
+
return {"allowed": False, "reason": str(e)}
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def pre_phase_4_approval(
|
|
117
|
+
tier: str,
|
|
118
|
+
realization_package: Dict[str, Any]
|
|
119
|
+
) -> Dict[str, Any]:
|
|
120
|
+
"""
|
|
121
|
+
Ejecutar guards antes de Phase 4 (Approval Gate).
|
|
122
|
+
|
|
123
|
+
CRITICAL: Este guard NO PUEDE fallar para T3 operations.
|
|
124
|
+
"""
|
|
125
|
+
enforcer = get_enforcer()
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
# Guard: Planning complete
|
|
129
|
+
enforcer.enforce(
|
|
130
|
+
"guard_phase_5_planning_complete",
|
|
131
|
+
realization_package=realization_package
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Note: El guard de approval_received se ejecuta DESPUÉS
|
|
135
|
+
# de recibir la respuesta del usuario en post_phase_4
|
|
136
|
+
|
|
137
|
+
return {"allowed": True, "reason": "Phase 4 pre-guards passed"}
|
|
138
|
+
|
|
139
|
+
except GuardViolation as e:
|
|
140
|
+
return {"allowed": False, "reason": str(e)}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def pre_phase_5_realization(
|
|
144
|
+
tier: str,
|
|
145
|
+
approval_validation: Dict[str, Any],
|
|
146
|
+
realization_package: Dict[str, Any]
|
|
147
|
+
) -> Dict[str, Any]:
|
|
148
|
+
"""
|
|
149
|
+
Ejecutar guards antes de Phase 5 (Realization).
|
|
150
|
+
|
|
151
|
+
CRITICAL: Valida que T3 operations tengan aprobación.
|
|
152
|
+
"""
|
|
153
|
+
enforcer = get_enforcer()
|
|
154
|
+
|
|
155
|
+
try:
|
|
156
|
+
# Guard: Approval mandatory for T3
|
|
157
|
+
enforcer.enforce(
|
|
158
|
+
"guard_phase_4_approval_mandatory",
|
|
159
|
+
tier=tier,
|
|
160
|
+
approval_received=approval_validation.get("approved", False)
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# Guard: Approval validation
|
|
164
|
+
if tier == "T3":
|
|
165
|
+
enforcer.enforce(
|
|
166
|
+
"guard_phase_4_approval_validation",
|
|
167
|
+
validation_result=approval_validation
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
return {"allowed": True, "reason": "Phase 5 guards passed"}
|
|
171
|
+
|
|
172
|
+
except GuardViolation as e:
|
|
173
|
+
return {"allowed": False, "reason": str(e)}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def pre_phase_6_ssot_update(
|
|
177
|
+
tier: str,
|
|
178
|
+
realization_success: bool
|
|
179
|
+
) -> Dict[str, Any]:
|
|
180
|
+
"""
|
|
181
|
+
Ejecutar guards antes de Phase 6 (SSOT Update).
|
|
182
|
+
"""
|
|
183
|
+
if not realization_success:
|
|
184
|
+
return {
|
|
185
|
+
"allowed": False,
|
|
186
|
+
"reason": "Cannot update SSOT: Realization failed"
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
# Phase 6 no tiene guards bloqueantes adicionales
|
|
190
|
+
# (el guard de ssot_updated se ejecuta DESPUÉS del update)
|
|
191
|
+
|
|
192
|
+
return {"allowed": True, "reason": "Phase 6 pre-guards passed"}
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
# CLI for testing
|
|
196
|
+
if __name__ == "__main__":
|
|
197
|
+
logging.basicConfig(level=logging.INFO)
|
|
198
|
+
|
|
199
|
+
print("🧪 Testing Pre-Phase Hooks...\n")
|
|
200
|
+
|
|
201
|
+
# Test Phase 4 pre-guard
|
|
202
|
+
result = pre_phase_4_approval(
|
|
203
|
+
tier="T3",
|
|
204
|
+
realization_package={"files": [], "git_operations": {}}
|
|
205
|
+
)
|
|
206
|
+
print(f"Phase 4 pre-guard: {result}")
|
|
207
|
+
|
|
208
|
+
# Test Phase 5 pre-guard (sin approval - debe fallar)
|
|
209
|
+
result = pre_phase_5_realization(
|
|
210
|
+
tier="T3",
|
|
211
|
+
approval_validation={"approved": False},
|
|
212
|
+
realization_package={}
|
|
213
|
+
)
|
|
214
|
+
print(f"Phase 5 pre-guard (no approval): {result}")
|
|
215
|
+
|
|
216
|
+
# Test Phase 5 pre-guard (con approval - debe pasar)
|
|
217
|
+
result = pre_phase_5_realization(
|
|
218
|
+
tier="T3",
|
|
219
|
+
approval_validation={"approved": True, "action": "proceed_to_realization"},
|
|
220
|
+
realization_package={}
|
|
221
|
+
)
|
|
222
|
+
print(f"Phase 5 pre-guard (with approval): {result}")
|
package/hooks/pre_tool_use.py
CHANGED
|
@@ -16,6 +16,29 @@ from tenacity import retry, stop_after_attempt, wait_exponential
|
|
|
16
16
|
|
|
17
17
|
from pre_kubectl_security import validate_gitops_workflow
|
|
18
18
|
|
|
19
|
+
# ============================================================================
|
|
20
|
+
# CLAUDE CODE ATTRIBUTION FOOTER DETECTION
|
|
21
|
+
# ============================================================================
|
|
22
|
+
|
|
23
|
+
def detect_claude_footers(command: str) -> bool:
|
|
24
|
+
"""
|
|
25
|
+
Detect Claude Code attribution footers in any command.
|
|
26
|
+
|
|
27
|
+
Looks for patterns like:
|
|
28
|
+
- "Generated with Claude Code"
|
|
29
|
+
- "Co-Authored-By: Claude"
|
|
30
|
+
"""
|
|
31
|
+
forbidden_patterns = [
|
|
32
|
+
r"Generated with\s+Claude Code",
|
|
33
|
+
r"Co-Authored-By:\s+Claude",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
for pattern in forbidden_patterns:
|
|
37
|
+
if re.search(pattern, command, re.IGNORECASE):
|
|
38
|
+
return True
|
|
39
|
+
|
|
40
|
+
return False
|
|
41
|
+
|
|
19
42
|
# Configure logging
|
|
20
43
|
logging.basicConfig(
|
|
21
44
|
level=logging.INFO,
|
|
@@ -423,6 +446,16 @@ class PolicyEngine:
|
|
|
423
446
|
if not is_allowed:
|
|
424
447
|
return is_allowed, tier, reason
|
|
425
448
|
|
|
449
|
+
# INTERCEPT: Detect Claude Code attribution footers in ANY command
|
|
450
|
+
if detect_claude_footers(command):
|
|
451
|
+
logger.warning(f"Command contains Claude Code attribution footers: {command[:100]}")
|
|
452
|
+
return False, SecurityTier.T3_BLOCKED, (
|
|
453
|
+
"❌ Command contains Claude Code attribution footers\n\n"
|
|
454
|
+
"Remove these patterns and retry:\n"
|
|
455
|
+
" • 'Generated with Claude Code'\n"
|
|
456
|
+
" • 'Co-Authored-By: Claude'"
|
|
457
|
+
)
|
|
458
|
+
|
|
426
459
|
# Enforce GitOps security rules for cluster-related commands
|
|
427
460
|
if any(keyword in command for keyword in ("kubectl", "helm", "flux")):
|
|
428
461
|
try:
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jaguilar87/gaia-ops",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"description": "Multi-agent orchestration system for Claude Code - DevOps automation toolkit",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
8
|
"gaia-init": "bin/gaia-init.js",
|
|
9
|
-
"gaia-cleanup": "bin/gaia-cleanup.js"
|
|
9
|
+
"gaia-cleanup": "bin/gaia-cleanup.js",
|
|
10
|
+
"gaia-uninstall": "bin/gaia-uninstall.js"
|
|
10
11
|
},
|
|
11
12
|
"keywords": [
|
|
12
13
|
"claude-code",
|