@jaguilar87/gaia 5.0.8 → 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 +11 -0
- package/bin/README.md +6 -1
- package/bin/cli/approvals.py +341 -238
- 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 +19 -85
- 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/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 +27 -7
- package/dist/gaia-ops/skills/agent-approval-protocol/reference.md +11 -6
- package/dist/gaia-ops/skills/gaia-patterns/reference.md +2 -2
- package/dist/gaia-ops/skills/orchestrator-present-approval/SKILL.md +69 -28
- package/dist/gaia-ops/skills/orchestrator-present-approval/reference.md +16 -3
- package/dist/gaia-ops/skills/orchestrator-present-approval/template.md +10 -5
- package/dist/gaia-ops/skills/pending-approvals/SKILL.md +16 -11
- package/dist/gaia-ops/skills/subagent-request-approval/SKILL.md +20 -6
- package/dist/gaia-ops/skills/subagent-request-approval/reference.md +23 -15
- 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 +19 -85
- 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/user_prompt_submit.py +20 -0
- package/gaia/approvals/store.py +87 -9
- package/gaia/store/schema.sql +38 -1
- package/gaia/store/writer.py +400 -0
- package/hooks/adapters/claude_code.py +19 -85
- package/hooks/elicitation_result.py +20 -75
- 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/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 +27 -7
- package/skills/agent-approval-protocol/reference.md +11 -6
- package/skills/gaia-patterns/reference.md +2 -2
- package/skills/orchestrator-present-approval/SKILL.md +69 -28
- package/skills/orchestrator-present-approval/reference.md +16 -3
- package/skills/orchestrator-present-approval/template.md +10 -5
- package/skills/pending-approvals/SKILL.md +16 -11
- package/skills/subagent-request-approval/SKILL.md +20 -6
- package/skills/subagent-request-approval/reference.md +23 -15
- 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/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
|
@@ -1,269 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Unit tests for context combining logic (T026).
|
|
3
|
-
|
|
4
|
-
Tests scanner-owned section replacement, agent-enriched section preservation,
|
|
5
|
-
mixed section sub-key merge, unknown section preservation, v1-to-v2 upgrade,
|
|
6
|
-
and idempotency.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
import copy
|
|
10
|
-
import json
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
from typing import Any, Dict
|
|
13
|
-
|
|
14
|
-
import pytest
|
|
15
|
-
|
|
16
|
-
from tools.scan.merge import merge_context
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
# ---------------------------------------------------------------------------
|
|
20
|
-
# Helpers
|
|
21
|
-
# ---------------------------------------------------------------------------
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def _section_owners() -> Dict[str, str]:
|
|
25
|
-
"""Return a realistic section_owners map from ScannerRegistry."""
|
|
26
|
-
return {
|
|
27
|
-
"project_identity": "stack",
|
|
28
|
-
"stack": "stack",
|
|
29
|
-
"git": "git",
|
|
30
|
-
"infrastructure": "infrastructure",
|
|
31
|
-
"orchestration": "orchestration",
|
|
32
|
-
"environment.tools": "tools",
|
|
33
|
-
"environment.tool_preferences": "tools",
|
|
34
|
-
"environment.runtimes": "environment",
|
|
35
|
-
"environment.os": "environment",
|
|
36
|
-
"environment.env_files": "environment",
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
# ---------------------------------------------------------------------------
|
|
41
|
-
# Test data helpers
|
|
42
|
-
# ---------------------------------------------------------------------------
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def _make_existing_context() -> Dict[str, Any]:
|
|
46
|
-
"""Create a realistic existing project-context with scanner + agent data."""
|
|
47
|
-
return {
|
|
48
|
-
"metadata": {
|
|
49
|
-
"version": "2.0",
|
|
50
|
-
"last_updated": "2026-01-01T00:00:00Z",
|
|
51
|
-
"project_name": "test-project",
|
|
52
|
-
"scan_config": {
|
|
53
|
-
"staleness_hours": 24,
|
|
54
|
-
"last_scan": "2026-01-01T00:00:00Z",
|
|
55
|
-
"scanner_version": "0.1.0",
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
"project_identity": {
|
|
59
|
-
"_source": "scanner:stack",
|
|
60
|
-
"name": "old-name",
|
|
61
|
-
"type": "application",
|
|
62
|
-
},
|
|
63
|
-
"stack": {
|
|
64
|
-
"_source": "scanner:stack",
|
|
65
|
-
"languages": [{"name": "python", "manifest": "pyproject.toml", "primary": True}],
|
|
66
|
-
"frameworks": [],
|
|
67
|
-
"build_tools": [],
|
|
68
|
-
},
|
|
69
|
-
"git": {
|
|
70
|
-
"_source": "scanner:git",
|
|
71
|
-
"platform": "github",
|
|
72
|
-
"remotes": [],
|
|
73
|
-
"default_branch": "main",
|
|
74
|
-
},
|
|
75
|
-
"environment": {
|
|
76
|
-
"_source": "scanner:environment",
|
|
77
|
-
"os": {"platform": "linux", "architecture": "x64"},
|
|
78
|
-
"runtimes": [{"name": "python3", "version": "3.11.0"}],
|
|
79
|
-
"env_files": [],
|
|
80
|
-
"tools": [{"name": "git", "path": "/usr/bin/git"}],
|
|
81
|
-
"tool_preferences": {"file_viewer": "bat"},
|
|
82
|
-
},
|
|
83
|
-
"operational_guidelines": {
|
|
84
|
-
"_source": "agent:developer",
|
|
85
|
-
"deployment_strategy": "blue-green",
|
|
86
|
-
"rollback_procedure": "manual",
|
|
87
|
-
},
|
|
88
|
-
"my_custom_notes": {
|
|
89
|
-
"author": "user",
|
|
90
|
-
"notes": "User-maintained section",
|
|
91
|
-
},
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
def _make_scan_results() -> Dict[str, Any]:
|
|
96
|
-
"""Create scan results from a new scan run."""
|
|
97
|
-
return {
|
|
98
|
-
"project_identity": {
|
|
99
|
-
"_source": "scanner:stack",
|
|
100
|
-
"name": "new-name",
|
|
101
|
-
"type": "monorepo",
|
|
102
|
-
"description": "Updated description",
|
|
103
|
-
},
|
|
104
|
-
"stack": {
|
|
105
|
-
"_source": "scanner:stack",
|
|
106
|
-
"languages": [
|
|
107
|
-
{"name": "typescript", "manifest": "package.json", "primary": True},
|
|
108
|
-
{"name": "python", "manifest": "pyproject.toml", "primary": False},
|
|
109
|
-
],
|
|
110
|
-
"frameworks": [{"name": "react", "language": "typescript"}],
|
|
111
|
-
"build_tools": [{"name": "npm", "detected_by": "lock_file"}],
|
|
112
|
-
},
|
|
113
|
-
"git": {
|
|
114
|
-
"_source": "scanner:git",
|
|
115
|
-
"platform": "github",
|
|
116
|
-
"remotes": [{"name": "origin", "url": "git@github.com:o/r.git"}],
|
|
117
|
-
"default_branch": "main",
|
|
118
|
-
},
|
|
119
|
-
"environment": {
|
|
120
|
-
"_source": "scanner:environment",
|
|
121
|
-
"os": {"platform": "linux", "architecture": "x64", "wsl": True},
|
|
122
|
-
"runtimes": [{"name": "python3", "version": "3.12.0"}],
|
|
123
|
-
"env_files": [{"name": ".env", "path": ".env"}],
|
|
124
|
-
},
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
# ---------------------------------------------------------------------------
|
|
129
|
-
# Rule 1: Scanner-owned section fully replaced
|
|
130
|
-
# ---------------------------------------------------------------------------
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
class TestScannerOwnedSectionReplacement:
|
|
134
|
-
"""Test that scanner-owned sections are fully replaced with new data."""
|
|
135
|
-
|
|
136
|
-
def test_project_identity_replaced(self) -> None:
|
|
137
|
-
existing = _make_existing_context()
|
|
138
|
-
scan = _make_scan_results()
|
|
139
|
-
result = merge_context(existing, scan, _section_owners())
|
|
140
|
-
assert result["project_identity"]["name"] == "new-name"
|
|
141
|
-
assert result["project_identity"]["type"] == "monorepo"
|
|
142
|
-
|
|
143
|
-
def test_stack_section_replaced(self) -> None:
|
|
144
|
-
existing = _make_existing_context()
|
|
145
|
-
scan = _make_scan_results()
|
|
146
|
-
result = merge_context(existing, scan, _section_owners())
|
|
147
|
-
lang_names = [l["name"] for l in result["stack"]["languages"]]
|
|
148
|
-
assert "typescript" in lang_names
|
|
149
|
-
assert len(result["stack"]["frameworks"]) == 1
|
|
150
|
-
|
|
151
|
-
def test_git_section_replaced(self) -> None:
|
|
152
|
-
existing = _make_existing_context()
|
|
153
|
-
scan = _make_scan_results()
|
|
154
|
-
result = merge_context(existing, scan, _section_owners())
|
|
155
|
-
assert len(result["git"]["remotes"]) == 1
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
# ---------------------------------------------------------------------------
|
|
159
|
-
# Rule 2: Agent-enriched sections preserved
|
|
160
|
-
# ---------------------------------------------------------------------------
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
class TestAgentEnrichedPreservation:
|
|
164
|
-
"""Test that agent-enriched sections are preserved byte-identical."""
|
|
165
|
-
|
|
166
|
-
def test_operational_guidelines_preserved(self) -> None:
|
|
167
|
-
existing = _make_existing_context()
|
|
168
|
-
scan = _make_scan_results()
|
|
169
|
-
result = merge_context(existing, scan, _section_owners())
|
|
170
|
-
assert result["operational_guidelines"]["deployment_strategy"] == "blue-green"
|
|
171
|
-
assert result["operational_guidelines"]["rollback_procedure"] == "manual"
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
# ---------------------------------------------------------------------------
|
|
175
|
-
# Rule 4: Mixed section (environment) sub-key merge
|
|
176
|
-
# ---------------------------------------------------------------------------
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
class TestMixedSectionMerge:
|
|
180
|
-
"""Test that mixed sections merge scanner fields and keep agent fields."""
|
|
181
|
-
|
|
182
|
-
def test_environment_scanner_fields_refreshed(self) -> None:
|
|
183
|
-
existing = _make_existing_context()
|
|
184
|
-
scan = _make_scan_results()
|
|
185
|
-
result = merge_context(existing, scan, _section_owners())
|
|
186
|
-
# Scanner-owned sub-keys should be updated
|
|
187
|
-
assert result["environment"]["os"].get("wsl") is True
|
|
188
|
-
runtimes = result["environment"]["runtimes"]
|
|
189
|
-
py = [r for r in runtimes if r["name"] == "python3"][0]
|
|
190
|
-
assert py["version"] == "3.12.0"
|
|
191
|
-
|
|
192
|
-
def test_environment_agent_fields_kept(self) -> None:
|
|
193
|
-
existing = _make_existing_context()
|
|
194
|
-
scan = _make_scan_results()
|
|
195
|
-
result = merge_context(existing, scan, _section_owners())
|
|
196
|
-
# tools and tool_preferences came from tool scanner (not in this scan)
|
|
197
|
-
# They should be preserved from existing
|
|
198
|
-
assert "tools" in result["environment"] or "tool_preferences" in result["environment"]
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
# ---------------------------------------------------------------------------
|
|
202
|
-
# Rule 5: Unknown/user-custom sections preserved
|
|
203
|
-
# ---------------------------------------------------------------------------
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
class TestUserCustomPreservation:
|
|
207
|
-
"""Test that user-custom sections survive combining."""
|
|
208
|
-
|
|
209
|
-
def test_custom_section_preserved(self) -> None:
|
|
210
|
-
existing = _make_existing_context()
|
|
211
|
-
scan = _make_scan_results()
|
|
212
|
-
result = merge_context(existing, scan, _section_owners())
|
|
213
|
-
assert "my_custom_notes" in result
|
|
214
|
-
assert result["my_custom_notes"]["author"] == "user"
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
# ---------------------------------------------------------------------------
|
|
218
|
-
# v1-to-v2 upgrade
|
|
219
|
-
# ---------------------------------------------------------------------------
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
class TestV1ToV2Upgrade:
|
|
223
|
-
"""Test upgrading from v1 project-context (no scan_config)."""
|
|
224
|
-
|
|
225
|
-
def test_v1_context_upgraded(self, sample_project_context_v1: Dict[str, Any]) -> None:
|
|
226
|
-
scan = _make_scan_results()
|
|
227
|
-
result = merge_context(sample_project_context_v1, scan, _section_owners())
|
|
228
|
-
# Should have scan_config after upgrade
|
|
229
|
-
assert "metadata" in result
|
|
230
|
-
# Agent-enriched data from v1 should be preserved
|
|
231
|
-
if "operational_guidelines" in sample_project_context_v1:
|
|
232
|
-
assert "operational_guidelines" in result
|
|
233
|
-
|
|
234
|
-
def test_v1_agent_data_not_lost(self, sample_project_context_v1: Dict[str, Any]) -> None:
|
|
235
|
-
scan = _make_scan_results()
|
|
236
|
-
result = merge_context(sample_project_context_v1, scan, _section_owners())
|
|
237
|
-
# User-custom sections from v1 are preserved as-is (Rule 4).
|
|
238
|
-
# project_details is no longer produced by the scanner (no backward compat),
|
|
239
|
-
# but if it existed in the v1 context it is preserved as a user-custom section.
|
|
240
|
-
if "project_details" in sample_project_context_v1:
|
|
241
|
-
assert "project_details" in result
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
# ---------------------------------------------------------------------------
|
|
245
|
-
# Idempotency
|
|
246
|
-
# ---------------------------------------------------------------------------
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
class TestIdempotency:
|
|
250
|
-
"""Test that running combine twice produces same result (except timestamps)."""
|
|
251
|
-
|
|
252
|
-
def test_idempotent_combine(self) -> None:
|
|
253
|
-
existing = _make_existing_context()
|
|
254
|
-
scan = _make_scan_results()
|
|
255
|
-
|
|
256
|
-
result1 = merge_context(existing, scan, _section_owners())
|
|
257
|
-
result2 = merge_context(result1, scan, _section_owners())
|
|
258
|
-
|
|
259
|
-
# Strip timestamps for comparison
|
|
260
|
-
def strip_timestamps(d: Dict) -> Dict:
|
|
261
|
-
d = copy.deepcopy(d)
|
|
262
|
-
if "metadata" in d:
|
|
263
|
-
meta = d["metadata"]
|
|
264
|
-
meta.pop("last_updated", None)
|
|
265
|
-
if "scan_config" in meta:
|
|
266
|
-
meta["scan_config"].pop("last_scan", None)
|
|
267
|
-
return d
|
|
268
|
-
|
|
269
|
-
assert strip_timestamps(result1) == strip_timestamps(result2)
|