@jaguilar87/gaia 5.0.7 → 5.0.9
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +13 -0
- package/bin/README.md +6 -1
- package/bin/cli/approvals.py +486 -474
- package/bin/cli/brief.py +13 -0
- package/bin/cli/doctor.py +1 -1
- package/dist/gaia-ops/.claude-plugin/plugin.json +1 -1
- package/dist/gaia-ops/hooks/adapters/claude_code.py +92 -86
- package/dist/gaia-ops/hooks/modules/agents/handoff_persister.py +13 -2
- package/dist/gaia-ops/hooks/modules/context/context_injector.py +23 -7
- package/dist/gaia-ops/hooks/modules/events/event_writer.py +63 -96
- package/dist/gaia-ops/hooks/modules/security/__init__.py +0 -2
- package/dist/gaia-ops/hooks/modules/security/approval_cleanup.py +238 -69
- package/dist/gaia-ops/hooks/modules/security/approval_grants.py +506 -1103
- package/dist/gaia-ops/hooks/modules/security/mutative_verbs.py +24 -1
- package/dist/gaia-ops/hooks/modules/session/pending_scanner.py +150 -90
- package/dist/gaia-ops/hooks/modules/session/session_manifest.py +257 -28
- package/dist/gaia-ops/hooks/modules/tools/bash_validator.py +19 -0
- package/dist/gaia-ops/hooks/post_compact.py +1 -0
- package/dist/gaia-ops/hooks/pre_compact.py +1 -0
- package/dist/gaia-ops/hooks/user_prompt_submit.py +20 -0
- package/dist/gaia-ops/skills/agent-approval-protocol/SKILL.md +50 -14
- package/dist/gaia-ops/skills/agent-approval-protocol/reference.md +16 -9
- package/dist/gaia-ops/skills/agent-protocol/examples.md +12 -1
- package/dist/gaia-ops/skills/gaia-patterns/reference.md +2 -2
- package/dist/gaia-ops/skills/orchestrator-present-approval/SKILL.md +69 -22
- package/dist/gaia-ops/skills/orchestrator-present-approval/reference.md +16 -3
- package/dist/gaia-ops/skills/orchestrator-present-approval/template.md +20 -14
- package/dist/gaia-ops/skills/pending-approvals/SKILL.md +16 -11
- package/dist/gaia-ops/skills/subagent-request-approval/SKILL.md +28 -3
- package/dist/gaia-ops/skills/subagent-request-approval/reference.md +34 -8
- package/dist/gaia-ops/tools/migration/README.md +10 -12
- package/dist/gaia-ops/tools/scan/orchestrator.py +194 -10
- package/dist/gaia-ops/tools/scan/tests/test_integration.py +1 -2
- package/dist/gaia-security/.claude-plugin/plugin.json +1 -1
- package/dist/gaia-security/hooks/adapters/claude_code.py +92 -86
- package/dist/gaia-security/hooks/modules/agents/handoff_persister.py +13 -2
- package/dist/gaia-security/hooks/modules/context/context_injector.py +23 -7
- package/dist/gaia-security/hooks/modules/events/event_writer.py +63 -96
- package/dist/gaia-security/hooks/modules/security/__init__.py +0 -2
- package/dist/gaia-security/hooks/modules/security/approval_cleanup.py +238 -69
- package/dist/gaia-security/hooks/modules/security/approval_grants.py +506 -1103
- package/dist/gaia-security/hooks/modules/security/mutative_verbs.py +24 -1
- package/dist/gaia-security/hooks/modules/session/pending_scanner.py +150 -90
- package/dist/gaia-security/hooks/modules/session/session_manifest.py +257 -28
- package/dist/gaia-security/hooks/modules/tools/bash_validator.py +19 -0
- package/dist/gaia-security/hooks/user_prompt_submit.py +20 -0
- package/gaia/approvals/__init__.py +2 -1
- package/gaia/approvals/store.py +165 -15
- package/gaia/store/schema.sql +38 -1
- package/gaia/store/writer.py +400 -0
- package/hooks/adapters/claude_code.py +92 -86
- package/hooks/elicitation_result.py +20 -75
- package/hooks/modules/agents/handoff_persister.py +13 -2
- package/hooks/modules/context/context_injector.py +23 -7
- package/hooks/modules/events/event_writer.py +63 -96
- package/hooks/modules/security/__init__.py +0 -2
- package/hooks/modules/security/approval_cleanup.py +238 -69
- package/hooks/modules/security/approval_grants.py +506 -1103
- package/hooks/modules/security/mutative_verbs.py +24 -1
- package/hooks/modules/session/pending_scanner.py +150 -90
- package/hooks/modules/session/session_manifest.py +257 -28
- package/hooks/modules/tools/bash_validator.py +19 -0
- package/hooks/post_compact.py +1 -0
- package/hooks/pre_compact.py +1 -0
- package/hooks/user_prompt_submit.py +20 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/scripts/bootstrap_database.sh +66 -17
- package/scripts/migrations/README.md +26 -14
- package/scripts/migrations/schema.checksum +2 -2
- package/scripts/migrations/v18_to_v19.sql +36 -0
- package/scripts/migrations/v19_to_v20.sql +20 -0
- package/skills/agent-approval-protocol/SKILL.md +50 -14
- package/skills/agent-approval-protocol/reference.md +16 -9
- package/skills/agent-protocol/examples.md +12 -1
- package/skills/gaia-patterns/reference.md +2 -2
- package/skills/orchestrator-present-approval/SKILL.md +69 -22
- package/skills/orchestrator-present-approval/reference.md +16 -3
- package/skills/orchestrator-present-approval/template.md +20 -14
- package/skills/pending-approvals/SKILL.md +16 -11
- package/skills/subagent-request-approval/SKILL.md +28 -3
- package/skills/subagent-request-approval/reference.md +34 -8
- package/tools/migration/README.md +10 -12
- package/tools/scan/orchestrator.py +194 -10
- package/tools/scan/tests/test_integration.py +1 -2
- package/bin/cli/plans.py +0 -517
- package/dist/gaia-ops/tools/context/deep_merge.py +0 -159
- package/dist/gaia-ops/tools/migration/migrate_04_harness_events.py +0 -132
- package/dist/gaia-ops/tools/migration/migrate_04_harness_events.sh +0 -23
- package/dist/gaia-ops/tools/scan/merge.py +0 -213
- package/dist/gaia-ops/tools/scan/tests/test_merge.py +0 -269
- package/gaia/approvals/revert.py +0 -282
- package/tools/context/deep_merge.py +0 -159
- package/tools/migration/migrate_04_harness_events.py +0 -132
- package/tools/migration/migrate_04_harness_events.sh +0 -23
- package/tools/scan/merge.py +0 -213
- package/tools/scan/tests/test_merge.py +0 -269
|
@@ -11,23 +11,26 @@ Pipeline:
|
|
|
11
11
|
3. Collect and combine scanner sections (handling environment sub-keys)
|
|
12
12
|
4. Cross-populate derived fields
|
|
13
13
|
5. Return ScanOutput
|
|
14
|
+
|
|
15
|
+
Section ownership rules (inlined from the retired tools/scan/merge.py):
|
|
16
|
+
Rule 1: Scanner-owned sections -> full replace
|
|
17
|
+
Rule 2: Agent-enriched sections -> never touch
|
|
18
|
+
Rule 3: Mixed sections -> selective update at sub-key level
|
|
19
|
+
Rule 4: Unknown/user-custom sections -> preserve
|
|
20
|
+
Rule 5: Metadata -> always update
|
|
14
21
|
"""
|
|
15
22
|
|
|
23
|
+
import copy
|
|
16
24
|
import logging
|
|
17
25
|
import time
|
|
18
26
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
19
27
|
from dataclasses import dataclass, field
|
|
20
28
|
from datetime import datetime, timezone
|
|
21
29
|
from pathlib import Path
|
|
22
|
-
from typing import Any, Dict, List, Optional
|
|
30
|
+
from typing import Any, Dict, List, Optional, Set
|
|
23
31
|
|
|
24
32
|
from tools.scan import __version__ as scanner_package_version
|
|
25
33
|
from tools.scan.config import ScanConfig
|
|
26
|
-
from tools.scan.merge import (
|
|
27
|
-
AGENT_ENRICHED_SECTIONS,
|
|
28
|
-
collect_scanner_sections,
|
|
29
|
-
merge_context,
|
|
30
|
-
)
|
|
31
34
|
from tools.scan.registry import ScannerRegistry
|
|
32
35
|
from tools.scan.scanners.base import BaseScanner, ScanResult
|
|
33
36
|
from tools.scan.workspace import WorkspaceInfo, detect_workspace_type
|
|
@@ -35,6 +38,190 @@ from tools.scan.workspace import WorkspaceInfo, detect_workspace_type
|
|
|
35
38
|
logger = logging.getLogger(__name__)
|
|
36
39
|
|
|
37
40
|
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
# Section ownership constants (Rule 1 / Rule 2 / Rule 3)
|
|
43
|
+
# ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
# Sections fully owned by scanners -- replaced entirely on each scan (Rule 1)
|
|
46
|
+
# Top-level sections only; sub-key ownership handled separately
|
|
47
|
+
SCANNER_OWNED_TOP_LEVEL: Dict[str, str] = {
|
|
48
|
+
"project_identity": "stack",
|
|
49
|
+
"stack": "stack",
|
|
50
|
+
"git": "git",
|
|
51
|
+
"infrastructure": "infrastructure",
|
|
52
|
+
"orchestration": "orchestration",
|
|
53
|
+
# "environment" is NOT listed here because it has sub-key ownership
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# Sub-key ownership within the `environment` section (Rule 4 / sub-section)
|
|
57
|
+
# Maps environment sub-key -> owning scanner name
|
|
58
|
+
ENVIRONMENT_SUBKEY_OWNERS: Dict[str, str] = {
|
|
59
|
+
"tools": "tools",
|
|
60
|
+
"tool_preferences": "tools",
|
|
61
|
+
"os": "environment",
|
|
62
|
+
"runtimes": "environment",
|
|
63
|
+
"env_files": "environment",
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# Agent-enriched sections -- never modified by scanners (Rule 2)
|
|
67
|
+
AGENT_ENRICHED_SECTIONS: frozenset = frozenset([
|
|
68
|
+
"operational_guidelines",
|
|
69
|
+
"cluster_details",
|
|
70
|
+
"infrastructure_topology",
|
|
71
|
+
"monitoring_observability",
|
|
72
|
+
"architecture_overview",
|
|
73
|
+
"gcp_services",
|
|
74
|
+
"workload_identity",
|
|
75
|
+
])
|
|
76
|
+
|
|
77
|
+
# Mixed sections with partial scanner ownership (Rule 3)
|
|
78
|
+
# Maps section_name -> set of scanner-owned field names
|
|
79
|
+
MIXED_SECTION_SCANNER_FIELDS: Dict[str, Set[str]] = {
|
|
80
|
+
"terraform_infrastructure": {"layout"},
|
|
81
|
+
"gitops_configuration": {"repository"},
|
|
82
|
+
"application_services": {"base_path", "services"},
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# ---------------------------------------------------------------------------
|
|
87
|
+
# Section collection and merge helpers
|
|
88
|
+
# ---------------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
def collect_scanner_sections(
|
|
91
|
+
scanner_results: Dict[str, Any],
|
|
92
|
+
) -> Dict[str, Any]:
|
|
93
|
+
"""Collect and combine sections from all scanner results.
|
|
94
|
+
|
|
95
|
+
Handles the environment section specially: both `tools` and `environment`
|
|
96
|
+
scanners produce sub-keys under `environment`, so their outputs are
|
|
97
|
+
combined into a single `environment` section.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
scanner_results: Mapping of scanner_name -> ScanResult (must have
|
|
101
|
+
a `sections` attribute that is a dict).
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
Combined sections dict from all scanners.
|
|
105
|
+
"""
|
|
106
|
+
combined: Dict[str, Any] = {}
|
|
107
|
+
environment_parts: Dict[str, Any] = {}
|
|
108
|
+
|
|
109
|
+
for _scanner_name, scan_result in scanner_results.items():
|
|
110
|
+
sections = scan_result.sections if hasattr(scan_result, "sections") else {}
|
|
111
|
+
|
|
112
|
+
for section_name, section_data in sections.items():
|
|
113
|
+
if section_name == "environment":
|
|
114
|
+
# Merge environment sub-keys from both scanners
|
|
115
|
+
if isinstance(section_data, dict):
|
|
116
|
+
for key, value in section_data.items():
|
|
117
|
+
if key != "_source":
|
|
118
|
+
environment_parts[key] = value
|
|
119
|
+
else:
|
|
120
|
+
# Non-environment sections: direct assignment (last scanner wins,
|
|
121
|
+
# but each section should have exactly one owner)
|
|
122
|
+
combined[section_name] = section_data
|
|
123
|
+
|
|
124
|
+
# Reassemble environment section if we got any parts
|
|
125
|
+
if environment_parts:
|
|
126
|
+
combined["environment"] = {
|
|
127
|
+
"_source": "scanner:environment+tools",
|
|
128
|
+
**environment_parts,
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return combined
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _merge_environment_section(
|
|
135
|
+
result: Dict[str, Any],
|
|
136
|
+
scan_sections: Dict[str, Any],
|
|
137
|
+
) -> None:
|
|
138
|
+
"""Merge the `environment` section with sub-key level ownership.
|
|
139
|
+
|
|
140
|
+
Two scanners contribute to the `environment` section:
|
|
141
|
+
- `tools` scanner owns: tools, tool_preferences
|
|
142
|
+
- `environment` scanner owns: os, runtimes, env_files
|
|
143
|
+
|
|
144
|
+
Each scanner's sub-keys replace their owned portion; the other scanner's
|
|
145
|
+
sub-keys are preserved. The `_source` field gets a combined tag.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
result: The result dict being built (mutated in place).
|
|
149
|
+
scan_sections: Combined sections from all scanners.
|
|
150
|
+
"""
|
|
151
|
+
if "environment" not in scan_sections:
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
scan_env = scan_sections["environment"]
|
|
155
|
+
|
|
156
|
+
if "environment" not in result:
|
|
157
|
+
result["environment"] = {}
|
|
158
|
+
|
|
159
|
+
env = result["environment"]
|
|
160
|
+
|
|
161
|
+
# Replace each sub-key based on ownership
|
|
162
|
+
for subkey in ENVIRONMENT_SUBKEY_OWNERS:
|
|
163
|
+
if subkey in scan_env:
|
|
164
|
+
env[subkey] = copy.deepcopy(scan_env[subkey])
|
|
165
|
+
|
|
166
|
+
# Set combined _source tag
|
|
167
|
+
env["_source"] = "scanner:environment+tools"
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _merge_sections(
|
|
171
|
+
existing: Dict[str, Any],
|
|
172
|
+
scan_sections: Dict[str, Any],
|
|
173
|
+
) -> Dict[str, Any]:
|
|
174
|
+
"""Merge scanner results with existing project-context sections.
|
|
175
|
+
|
|
176
|
+
Applies the ownership rules to produce the final merged sections dict.
|
|
177
|
+
Called with existing={} in normal scan runs (display-only path).
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
existing: Current sections (may be empty when called from scan).
|
|
181
|
+
scan_sections: Combined sections from all scanners.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
Merged sections dict. The merge is deterministic: same inputs always
|
|
185
|
+
produce the same output.
|
|
186
|
+
"""
|
|
187
|
+
result = copy.deepcopy(existing)
|
|
188
|
+
|
|
189
|
+
# --- Rule 1: Scanner-owned top-level sections -> full replace ---
|
|
190
|
+
for section_name in SCANNER_OWNED_TOP_LEVEL:
|
|
191
|
+
if section_name in scan_sections:
|
|
192
|
+
result[section_name] = copy.deepcopy(scan_sections[section_name])
|
|
193
|
+
|
|
194
|
+
# --- Sub-section level ownership for `environment` ---
|
|
195
|
+
_merge_environment_section(result, scan_sections)
|
|
196
|
+
|
|
197
|
+
# --- Rule 2: Agent-enriched sections -> never touch ---
|
|
198
|
+
# These are already in `result` from the deepcopy of `existing`.
|
|
199
|
+
# (No action needed -- they are preserved by the deepcopy.)
|
|
200
|
+
|
|
201
|
+
# --- Rule 3: Mixed sections -> selective update ---
|
|
202
|
+
for section_name, scanner_fields in MIXED_SECTION_SCANNER_FIELDS.items():
|
|
203
|
+
if section_name in scan_sections:
|
|
204
|
+
scan_data = scan_sections[section_name]
|
|
205
|
+
if section_name not in result:
|
|
206
|
+
result[section_name] = {}
|
|
207
|
+
# Only update scanner-owned fields; preserve agent fields
|
|
208
|
+
for field_name in scanner_fields:
|
|
209
|
+
if field_name in scan_data:
|
|
210
|
+
result[section_name][field_name] = copy.deepcopy(
|
|
211
|
+
scan_data[field_name]
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
# --- Rule 5: Unknown/user-custom sections -> preserve ---
|
|
215
|
+
# Any section in `existing` not covered above is preserved by the deepcopy.
|
|
216
|
+
# We do NOT add new unknown sections from scan_sections.
|
|
217
|
+
|
|
218
|
+
return result
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
# ---------------------------------------------------------------------------
|
|
222
|
+
# ScanOutput dataclass
|
|
223
|
+
# ---------------------------------------------------------------------------
|
|
224
|
+
|
|
38
225
|
@dataclass(frozen=True)
|
|
39
226
|
class ScanOutput:
|
|
40
227
|
"""Aggregated output from all scanners.
|
|
@@ -187,11 +374,9 @@ class ScanOrchestrator:
|
|
|
187
374
|
scan_sections = collect_scanner_sections(scanner_results)
|
|
188
375
|
|
|
189
376
|
# Merge with empty existing context (no JSON persistence)
|
|
190
|
-
|
|
191
|
-
merged_sections = merge_context(
|
|
377
|
+
merged_sections = _merge_sections(
|
|
192
378
|
existing={},
|
|
193
379
|
scan_sections=scan_sections,
|
|
194
|
-
section_owners=section_owners,
|
|
195
380
|
)
|
|
196
381
|
|
|
197
382
|
# Determine which sections were updated vs preserved
|
|
@@ -210,7 +395,6 @@ class ScanOrchestrator:
|
|
|
210
395
|
self._cross_populate_monorepo(merged_sections)
|
|
211
396
|
|
|
212
397
|
# Remove empty {} placeholders for agent-enriched and mixed sections
|
|
213
|
-
from tools.scan.merge import MIXED_SECTION_SCANNER_FIELDS
|
|
214
398
|
remove_if_empty = (
|
|
215
399
|
AGENT_ENRICHED_SECTIONS
|
|
216
400
|
| frozenset(MIXED_SECTION_SCANNER_FIELDS.keys())
|
|
@@ -23,8 +23,7 @@ from unittest.mock import patch
|
|
|
23
23
|
import pytest
|
|
24
24
|
|
|
25
25
|
from tools.scan.config import ScanConfig
|
|
26
|
-
from tools.scan.
|
|
27
|
-
from tools.scan.orchestrator import ScanOrchestrator, ScanOutput
|
|
26
|
+
from tools.scan.orchestrator import AGENT_ENRICHED_SECTIONS, ScanOrchestrator, ScanOutput
|
|
28
27
|
from tools.scan.registry import ScannerRegistry
|
|
29
28
|
from tools.scan.scanners.base import BaseScanner, ScanResult
|
|
30
29
|
from tools.scan.tests.conftest import create_git_dir
|