@jaguilar87/gaia 5.0.0-rc.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/.claude-plugin/marketplace.json +33 -0
- package/.claude-plugin/plugin.json +26 -0
- package/ARCHITECTURE.md +335 -0
- package/CHANGELOG.md +1298 -0
- package/CODE_OF_CONDUCT.md +11 -0
- package/CONTRIBUTING.md +146 -0
- package/INSTALL.md +436 -0
- package/LICENSE +21 -0
- package/README.md +222 -0
- package/SECURITY.md +47 -0
- package/agents/README.md +78 -0
- package/agents/cloud-troubleshooter.md +73 -0
- package/agents/developer.md +65 -0
- package/agents/gaia-operator.md +64 -0
- package/agents/gaia-orchestrator.md +111 -0
- package/agents/gaia-planner.md +53 -0
- package/agents/gaia-system.md +71 -0
- package/agents/gitops-operator.md +61 -0
- package/agents/terraform-architect.md +63 -0
- package/bin/README.md +106 -0
- package/bin/cli/__init__.py +1 -0
- package/bin/cli/approvals.py +740 -0
- package/bin/cli/cleanup.py +562 -0
- package/bin/cli/context.py +283 -0
- package/bin/cli/doctor.py +651 -0
- package/bin/cli/history.py +305 -0
- package/bin/cli/memory.py +483 -0
- package/bin/cli/metrics.py +1068 -0
- package/bin/cli/plans.py +515 -0
- package/bin/cli/status.py +302 -0
- package/bin/cli/update.py +382 -0
- package/bin/gaia +112 -0
- package/bin/gaia-cleanup.js +531 -0
- package/bin/gaia-doctor.js +635 -0
- package/bin/gaia-evidence +126 -0
- package/bin/gaia-history.js +251 -0
- package/bin/gaia-metrics.js +1278 -0
- package/bin/gaia-review.js +269 -0
- package/bin/gaia-scan +44 -0
- package/bin/gaia-scan.py +589 -0
- package/bin/gaia-skills-diagnose.js +929 -0
- package/bin/gaia-status.js +278 -0
- package/bin/gaia-uninstall.js +111 -0
- package/bin/gaia-update.js +919 -0
- package/bin/pre-publish-validate.js +610 -0
- package/bin/python-detect.js +60 -0
- package/bin/validate-sandbox.sh +601 -0
- package/commands/README.md +64 -0
- package/commands/gaia.md +37 -0
- package/commands/scan-project.md +67 -0
- package/config/README.md +71 -0
- package/config/cloud/aws.json +134 -0
- package/config/cloud/gcp.json +139 -0
- package/config/context-contracts.json +158 -0
- package/config/crons-schema.md +81 -0
- package/config/git_standards.json +72 -0
- package/config/surface-routing.json +417 -0
- package/config/universal-rules.json +102 -0
- package/dist/gaia-ops/.claude-plugin/plugin.json +24 -0
- package/dist/gaia-ops/README.md +80 -0
- package/dist/gaia-ops/agents/cloud-troubleshooter.md +73 -0
- package/dist/gaia-ops/agents/developer.md +65 -0
- package/dist/gaia-ops/agents/gaia-operator.md +64 -0
- package/dist/gaia-ops/agents/gaia-orchestrator.md +111 -0
- package/dist/gaia-ops/agents/gaia-planner.md +53 -0
- package/dist/gaia-ops/agents/gaia-system.md +71 -0
- package/dist/gaia-ops/agents/gitops-operator.md +61 -0
- package/dist/gaia-ops/agents/terraform-architect.md +63 -0
- package/dist/gaia-ops/commands/gaia.md +37 -0
- package/dist/gaia-ops/config/README.md +71 -0
- package/dist/gaia-ops/config/cloud/aws.json +134 -0
- package/dist/gaia-ops/config/cloud/gcp.json +139 -0
- package/dist/gaia-ops/config/context-contracts.json +158 -0
- package/dist/gaia-ops/config/crons-schema.md +81 -0
- package/dist/gaia-ops/config/git_standards.json +72 -0
- package/dist/gaia-ops/config/surface-routing.json +417 -0
- package/dist/gaia-ops/config/universal-rules.json +102 -0
- package/dist/gaia-ops/hooks/adapters/__init__.py +52 -0
- package/dist/gaia-ops/hooks/adapters/base.py +219 -0
- package/dist/gaia-ops/hooks/adapters/channel.py +17 -0
- package/dist/gaia-ops/hooks/adapters/claude_code.py +1890 -0
- package/dist/gaia-ops/hooks/adapters/types.py +194 -0
- package/dist/gaia-ops/hooks/adapters/utils.py +25 -0
- package/dist/gaia-ops/hooks/hooks.json +192 -0
- package/dist/gaia-ops/hooks/modules/__init__.py +15 -0
- package/dist/gaia-ops/hooks/modules/agents/__init__.py +29 -0
- package/dist/gaia-ops/hooks/modules/agents/contract_validator.py +647 -0
- package/dist/gaia-ops/hooks/modules/agents/response_contract.py +496 -0
- package/dist/gaia-ops/hooks/modules/agents/skill_injection_verifier.py +120 -0
- package/dist/gaia-ops/hooks/modules/agents/state_tracker.py +267 -0
- package/dist/gaia-ops/hooks/modules/agents/task_info_builder.py +74 -0
- package/dist/gaia-ops/hooks/modules/agents/transcript_analyzer.py +458 -0
- package/dist/gaia-ops/hooks/modules/agents/transcript_reader.py +152 -0
- package/dist/gaia-ops/hooks/modules/audit/__init__.py +28 -0
- package/dist/gaia-ops/hooks/modules/audit/event_detector.py +168 -0
- package/dist/gaia-ops/hooks/modules/audit/logger.py +131 -0
- package/dist/gaia-ops/hooks/modules/audit/metrics.py +134 -0
- package/dist/gaia-ops/hooks/modules/audit/workflow_auditor.py +611 -0
- package/dist/gaia-ops/hooks/modules/audit/workflow_recorder.py +296 -0
- package/dist/gaia-ops/hooks/modules/context/__init__.py +11 -0
- package/dist/gaia-ops/hooks/modules/context/agentic_loop_detector.py +165 -0
- package/dist/gaia-ops/hooks/modules/context/anchor_tracker.py +317 -0
- package/dist/gaia-ops/hooks/modules/context/compact_context_builder.py +218 -0
- package/dist/gaia-ops/hooks/modules/context/context_freshness.py +145 -0
- package/dist/gaia-ops/hooks/modules/context/context_injector.py +558 -0
- package/dist/gaia-ops/hooks/modules/context/context_writer.py +530 -0
- package/dist/gaia-ops/hooks/modules/context/contracts_loader.py +161 -0
- package/dist/gaia-ops/hooks/modules/core/__init__.py +40 -0
- package/dist/gaia-ops/hooks/modules/core/hook_entry.py +78 -0
- package/dist/gaia-ops/hooks/modules/core/paths.py +160 -0
- package/dist/gaia-ops/hooks/modules/core/plugin_mode.py +149 -0
- package/dist/gaia-ops/hooks/modules/core/plugin_setup.py +577 -0
- package/dist/gaia-ops/hooks/modules/core/state.py +179 -0
- package/dist/gaia-ops/hooks/modules/core/stdin.py +24 -0
- package/dist/gaia-ops/hooks/modules/events/__init__.py +1 -0
- package/dist/gaia-ops/hooks/modules/events/event_writer.py +210 -0
- package/dist/gaia-ops/hooks/modules/memory/__init__.py +8 -0
- package/dist/gaia-ops/hooks/modules/memory/episode_writer.py +216 -0
- package/dist/gaia-ops/hooks/modules/orchestrator/__init__.py +1 -0
- package/dist/gaia-ops/hooks/modules/orchestrator/delegate_mode.py +122 -0
- package/dist/gaia-ops/hooks/modules/scanning/__init__.py +8 -0
- package/dist/gaia-ops/hooks/modules/scanning/scan_trigger.py +84 -0
- package/dist/gaia-ops/hooks/modules/security/__init__.py +120 -0
- package/dist/gaia-ops/hooks/modules/security/approval_cleanup.py +87 -0
- package/dist/gaia-ops/hooks/modules/security/approval_constants.py +23 -0
- package/dist/gaia-ops/hooks/modules/security/approval_grants.py +1638 -0
- package/dist/gaia-ops/hooks/modules/security/approval_messages.py +71 -0
- package/dist/gaia-ops/hooks/modules/security/approval_scopes.py +222 -0
- package/dist/gaia-ops/hooks/modules/security/blocked_commands.py +595 -0
- package/dist/gaia-ops/hooks/modules/security/blocked_message_formatter.py +87 -0
- package/dist/gaia-ops/hooks/modules/security/command_semantics.py +181 -0
- package/dist/gaia-ops/hooks/modules/security/composition_rules.py +547 -0
- package/dist/gaia-ops/hooks/modules/security/flag_classifiers.py +873 -0
- package/dist/gaia-ops/hooks/modules/security/gitops_validator.py +179 -0
- package/dist/gaia-ops/hooks/modules/security/mutative_verbs.py +1131 -0
- package/dist/gaia-ops/hooks/modules/security/network_hosts.py +481 -0
- package/dist/gaia-ops/hooks/modules/security/prompt_validator.py +40 -0
- package/dist/gaia-ops/hooks/modules/security/shell_unwrapper.py +165 -0
- package/dist/gaia-ops/hooks/modules/security/tiers.py +196 -0
- package/dist/gaia-ops/hooks/modules/session/__init__.py +10 -0
- package/dist/gaia-ops/hooks/modules/session/pending_scanner.py +174 -0
- package/dist/gaia-ops/hooks/modules/session/session_context_writer.py +100 -0
- package/dist/gaia-ops/hooks/modules/session/session_event_injector.py +160 -0
- package/dist/gaia-ops/hooks/modules/session/session_manager.py +31 -0
- package/dist/gaia-ops/hooks/modules/session/session_registry.py +333 -0
- package/dist/gaia-ops/hooks/modules/tools/__init__.py +29 -0
- package/dist/gaia-ops/hooks/modules/tools/bash_validator.py +1008 -0
- package/dist/gaia-ops/hooks/modules/tools/cloud_pipe_validator.py +231 -0
- package/dist/gaia-ops/hooks/modules/tools/hook_response.py +55 -0
- package/dist/gaia-ops/hooks/modules/tools/shell_parser.py +227 -0
- package/dist/gaia-ops/hooks/modules/tools/stage_decomposer.py +315 -0
- package/dist/gaia-ops/hooks/modules/tools/task_validator.py +294 -0
- package/dist/gaia-ops/hooks/modules/validation/__init__.py +23 -0
- package/dist/gaia-ops/hooks/modules/validation/commit_validator.py +380 -0
- package/dist/gaia-ops/hooks/post_compact.py +43 -0
- package/dist/gaia-ops/hooks/post_tool_use.py +54 -0
- package/dist/gaia-ops/hooks/pre_compact.py +60 -0
- package/dist/gaia-ops/hooks/pre_tool_use.py +413 -0
- package/dist/gaia-ops/hooks/session_end_hook.py +77 -0
- package/dist/gaia-ops/hooks/session_start.py +81 -0
- package/dist/gaia-ops/hooks/stop_hook.py +70 -0
- package/dist/gaia-ops/hooks/subagent_start.py +71 -0
- package/dist/gaia-ops/hooks/subagent_stop.py +295 -0
- package/dist/gaia-ops/hooks/task_completed.py +70 -0
- package/dist/gaia-ops/hooks/user_prompt_submit.py +246 -0
- package/dist/gaia-ops/settings.json +72 -0
- package/dist/gaia-ops/skills/README.md +158 -0
- package/dist/gaia-ops/skills/agent-creation/SKILL.md +87 -0
- package/dist/gaia-ops/skills/agent-creation/examples.md +170 -0
- package/dist/gaia-ops/skills/agent-creation/reference.md +191 -0
- package/dist/gaia-ops/skills/agent-protocol/SKILL.md +93 -0
- package/dist/gaia-ops/skills/agent-protocol/examples.md +223 -0
- package/dist/gaia-ops/skills/agent-response/SKILL.md +69 -0
- package/dist/gaia-ops/skills/agentic-loop/SKILL.md +80 -0
- package/dist/gaia-ops/skills/agentic-loop/reference.md +378 -0
- package/dist/gaia-ops/skills/blog-writing/SKILL.md +98 -0
- package/dist/gaia-ops/skills/blog-writing/reference.md +130 -0
- package/dist/gaia-ops/skills/brief-spec/SKILL.md +185 -0
- package/dist/gaia-ops/skills/command-execution/SKILL.md +64 -0
- package/dist/gaia-ops/skills/command-execution/reference.md +83 -0
- package/dist/gaia-ops/skills/context-updater/SKILL.md +87 -0
- package/dist/gaia-ops/skills/context-updater/examples.md +71 -0
- package/dist/gaia-ops/skills/developer-patterns/SKILL.md +50 -0
- package/dist/gaia-ops/skills/developer-patterns/reference.md +112 -0
- package/dist/gaia-ops/skills/execution/SKILL.md +99 -0
- package/dist/gaia-ops/skills/fast-queries/SKILL.md +43 -0
- package/dist/gaia-ops/skills/gaia-compact/SKILL.md +74 -0
- package/dist/gaia-ops/skills/gaia-patterns/SKILL.md +108 -0
- package/dist/gaia-ops/skills/gaia-patterns/reference.md +395 -0
- package/dist/gaia-ops/skills/gaia-planner/SKILL.md +37 -0
- package/dist/gaia-ops/skills/gaia-planner/reference.md +107 -0
- package/dist/gaia-ops/skills/gaia-release/SKILL.md +85 -0
- package/dist/gaia-ops/skills/gaia-release/reference.md +92 -0
- package/dist/gaia-ops/skills/gaia-self-check/SKILL.md +114 -0
- package/dist/gaia-ops/skills/gaia-self-check/reference.md +453 -0
- package/dist/gaia-ops/skills/gaia-verify/SKILL.md +77 -0
- package/dist/gaia-ops/skills/gaia-verify/reference.md +80 -0
- package/dist/gaia-ops/skills/git-conventions/SKILL.md +47 -0
- package/dist/gaia-ops/skills/gitops-patterns/SKILL.md +60 -0
- package/dist/gaia-ops/skills/gitops-patterns/reference.md +183 -0
- package/dist/gaia-ops/skills/gmail-policy/SKILL.md +200 -0
- package/dist/gaia-ops/skills/gmail-policy/reference.md +150 -0
- package/dist/gaia-ops/skills/gmail-triage/SKILL.md +100 -0
- package/dist/gaia-ops/skills/gws-setup/SKILL.md +99 -0
- package/dist/gaia-ops/skills/gws-setup/reference.md +73 -0
- package/dist/gaia-ops/skills/investigation/SKILL.md +100 -0
- package/dist/gaia-ops/skills/memory-curation/SKILL.md +83 -0
- package/dist/gaia-ops/skills/memory-search/SKILL.md +88 -0
- package/dist/gaia-ops/skills/orchestrator-approval/SKILL.md +160 -0
- package/dist/gaia-ops/skills/orchestrator-approval/reference.md +174 -0
- package/dist/gaia-ops/skills/pending-approvals/SKILL.md +72 -0
- package/dist/gaia-ops/skills/pending-approvals/reference.md +214 -0
- package/dist/gaia-ops/skills/readme-writing/SKILL.md +71 -0
- package/dist/gaia-ops/skills/readme-writing/reference.md +188 -0
- package/dist/gaia-ops/skills/reference.md +135 -0
- package/dist/gaia-ops/skills/request-approval/SKILL.md +140 -0
- package/dist/gaia-ops/skills/request-approval/examples.md +140 -0
- package/dist/gaia-ops/skills/request-approval/reference.md +57 -0
- package/dist/gaia-ops/skills/schedule-task/SKILL.md +64 -0
- package/dist/gaia-ops/skills/schedule-task/reference.md +233 -0
- package/dist/gaia-ops/skills/security-tiers/SKILL.md +141 -0
- package/dist/gaia-ops/skills/security-tiers/destructive-commands-reference.md +623 -0
- package/dist/gaia-ops/skills/security-tiers/reference.md +39 -0
- package/dist/gaia-ops/skills/session-reflection/SKILL.md +69 -0
- package/dist/gaia-ops/skills/skill-creation/SKILL.md +92 -0
- package/dist/gaia-ops/skills/skill-creation/reference.md +29 -0
- package/dist/gaia-ops/skills/terraform-patterns/SKILL.md +89 -0
- package/dist/gaia-ops/skills/terraform-patterns/reference.md +93 -0
- package/dist/gaia-ops/tools/__init__.py +9 -0
- package/dist/gaia-ops/tools/agentic-loop/decide-status.py +210 -0
- package/dist/gaia-ops/tools/agentic-loop/parse-metric.py +106 -0
- package/dist/gaia-ops/tools/agentic-loop/record-iteration.py +221 -0
- package/dist/gaia-ops/tools/context/README.md +132 -0
- package/dist/gaia-ops/tools/context/__init__.py +42 -0
- package/dist/gaia-ops/tools/context/_paths.py +20 -0
- package/dist/gaia-ops/tools/context/context_provider.py +721 -0
- package/dist/gaia-ops/tools/context/context_section_reader.py +342 -0
- package/dist/gaia-ops/tools/context/deep_merge.py +159 -0
- package/dist/gaia-ops/tools/context/pending_updates.py +760 -0
- package/dist/gaia-ops/tools/context/surface_router.py +278 -0
- package/dist/gaia-ops/tools/fast-queries/README.md +65 -0
- package/dist/gaia-ops/tools/fast-queries/__init__.py +30 -0
- package/dist/gaia-ops/tools/fast-queries/appservices/quicktriage_devops_developer.sh +75 -0
- package/dist/gaia-ops/tools/fast-queries/cloud/aws/quicktriage_aws_troubleshooter.sh +32 -0
- package/dist/gaia-ops/tools/fast-queries/cloud/gcp/quicktriage_gcp_troubleshooter.sh +88 -0
- package/dist/gaia-ops/tools/fast-queries/gitops/quicktriage_gitops_operator.sh +48 -0
- package/dist/gaia-ops/tools/fast-queries/run_triage.sh +59 -0
- package/dist/gaia-ops/tools/fast-queries/terraform/quicktriage_terraform_architect.sh +80 -0
- package/dist/gaia-ops/tools/gaia_simulator/__init__.py +33 -0
- package/dist/gaia-ops/tools/gaia_simulator/cli.py +354 -0
- package/dist/gaia-ops/tools/gaia_simulator/extractor.py +457 -0
- package/dist/gaia-ops/tools/gaia_simulator/reporter.py +258 -0
- package/dist/gaia-ops/tools/gaia_simulator/routing_simulator.py +334 -0
- package/dist/gaia-ops/tools/gaia_simulator/runner.py +539 -0
- package/dist/gaia-ops/tools/gaia_simulator/skills_mapper.py +264 -0
- package/dist/gaia-ops/tools/memory/README.md +0 -0
- package/dist/gaia-ops/tools/memory/__init__.py +20 -0
- package/dist/gaia-ops/tools/memory/backfill_fts5.py +107 -0
- package/dist/gaia-ops/tools/memory/conflict_detector.py +295 -0
- package/dist/gaia-ops/tools/memory/episodic.py +1210 -0
- package/dist/gaia-ops/tools/memory/git_invalidator.py +262 -0
- package/dist/gaia-ops/tools/memory/paths.py +102 -0
- package/dist/gaia-ops/tools/memory/scoring.py +193 -0
- package/dist/gaia-ops/tools/memory/search_store.py +375 -0
- package/dist/gaia-ops/tools/persist_transcript_analysis.py +85 -0
- package/dist/gaia-ops/tools/review/__init__.py +1 -0
- package/dist/gaia-ops/tools/review/review_engine.py +157 -0
- package/dist/gaia-ops/tools/scan/__init__.py +35 -0
- package/dist/gaia-ops/tools/scan/config.py +247 -0
- package/dist/gaia-ops/tools/scan/merge.py +212 -0
- package/dist/gaia-ops/tools/scan/orchestrator.py +549 -0
- package/dist/gaia-ops/tools/scan/registry.py +127 -0
- package/dist/gaia-ops/tools/scan/scanners/__init__.py +18 -0
- package/dist/gaia-ops/tools/scan/scanners/base.py +137 -0
- package/dist/gaia-ops/tools/scan/scanners/environment.py +349 -0
- package/dist/gaia-ops/tools/scan/scanners/git.py +570 -0
- package/dist/gaia-ops/tools/scan/scanners/infrastructure.py +875 -0
- package/dist/gaia-ops/tools/scan/scanners/orchestration.py +600 -0
- package/dist/gaia-ops/tools/scan/scanners/stack.py +1085 -0
- package/dist/gaia-ops/tools/scan/scanners/tools.py +260 -0
- package/dist/gaia-ops/tools/scan/setup.py +686 -0
- package/dist/gaia-ops/tools/scan/tests/__init__.py +1 -0
- package/dist/gaia-ops/tools/scan/tests/conftest.py +796 -0
- package/dist/gaia-ops/tools/scan/tests/test_environment.py +323 -0
- package/dist/gaia-ops/tools/scan/tests/test_git.py +419 -0
- package/dist/gaia-ops/tools/scan/tests/test_infrastructure.py +382 -0
- package/dist/gaia-ops/tools/scan/tests/test_integration.py +920 -0
- package/dist/gaia-ops/tools/scan/tests/test_merge.py +269 -0
- package/dist/gaia-ops/tools/scan/tests/test_orchestration.py +304 -0
- package/dist/gaia-ops/tools/scan/tests/test_stack.py +604 -0
- package/dist/gaia-ops/tools/scan/tests/test_tools.py +349 -0
- package/dist/gaia-ops/tools/scan/ui.py +624 -0
- package/dist/gaia-ops/tools/scan/verify.py +270 -0
- package/dist/gaia-ops/tools/scan/walk.py +118 -0
- package/dist/gaia-ops/tools/scan/workspace.py +85 -0
- package/dist/gaia-ops/tools/validation/README.md +244 -0
- package/dist/gaia-ops/tools/validation/__init__.py +17 -0
- package/dist/gaia-ops/tools/validation/approval_gate.py +321 -0
- package/dist/gaia-ops/tools/validation/validate_skills.py +189 -0
- package/dist/gaia-security/.claude-plugin/plugin.json +24 -0
- package/dist/gaia-security/README.md +90 -0
- package/dist/gaia-security/config/universal-rules.json +102 -0
- package/dist/gaia-security/hooks/adapters/__init__.py +52 -0
- package/dist/gaia-security/hooks/adapters/base.py +219 -0
- package/dist/gaia-security/hooks/adapters/channel.py +17 -0
- package/dist/gaia-security/hooks/adapters/claude_code.py +1890 -0
- package/dist/gaia-security/hooks/adapters/types.py +194 -0
- package/dist/gaia-security/hooks/adapters/utils.py +25 -0
- package/dist/gaia-security/hooks/hooks.json +113 -0
- package/dist/gaia-security/hooks/modules/__init__.py +15 -0
- package/dist/gaia-security/hooks/modules/agents/__init__.py +29 -0
- package/dist/gaia-security/hooks/modules/agents/contract_validator.py +647 -0
- package/dist/gaia-security/hooks/modules/agents/response_contract.py +496 -0
- package/dist/gaia-security/hooks/modules/agents/skill_injection_verifier.py +120 -0
- package/dist/gaia-security/hooks/modules/agents/state_tracker.py +267 -0
- package/dist/gaia-security/hooks/modules/agents/task_info_builder.py +74 -0
- package/dist/gaia-security/hooks/modules/agents/transcript_analyzer.py +458 -0
- package/dist/gaia-security/hooks/modules/agents/transcript_reader.py +152 -0
- package/dist/gaia-security/hooks/modules/audit/__init__.py +28 -0
- package/dist/gaia-security/hooks/modules/audit/event_detector.py +168 -0
- package/dist/gaia-security/hooks/modules/audit/logger.py +131 -0
- package/dist/gaia-security/hooks/modules/audit/metrics.py +134 -0
- package/dist/gaia-security/hooks/modules/audit/workflow_auditor.py +611 -0
- package/dist/gaia-security/hooks/modules/audit/workflow_recorder.py +296 -0
- package/dist/gaia-security/hooks/modules/context/__init__.py +11 -0
- package/dist/gaia-security/hooks/modules/context/agentic_loop_detector.py +165 -0
- package/dist/gaia-security/hooks/modules/context/anchor_tracker.py +317 -0
- package/dist/gaia-security/hooks/modules/context/compact_context_builder.py +218 -0
- package/dist/gaia-security/hooks/modules/context/context_freshness.py +145 -0
- package/dist/gaia-security/hooks/modules/context/context_injector.py +558 -0
- package/dist/gaia-security/hooks/modules/context/context_writer.py +530 -0
- package/dist/gaia-security/hooks/modules/context/contracts_loader.py +161 -0
- package/dist/gaia-security/hooks/modules/core/__init__.py +40 -0
- package/dist/gaia-security/hooks/modules/core/hook_entry.py +78 -0
- package/dist/gaia-security/hooks/modules/core/paths.py +160 -0
- package/dist/gaia-security/hooks/modules/core/plugin_mode.py +149 -0
- package/dist/gaia-security/hooks/modules/core/plugin_setup.py +577 -0
- package/dist/gaia-security/hooks/modules/core/state.py +179 -0
- package/dist/gaia-security/hooks/modules/core/stdin.py +24 -0
- package/dist/gaia-security/hooks/modules/events/__init__.py +1 -0
- package/dist/gaia-security/hooks/modules/events/event_writer.py +210 -0
- package/dist/gaia-security/hooks/modules/memory/__init__.py +8 -0
- package/dist/gaia-security/hooks/modules/memory/episode_writer.py +216 -0
- package/dist/gaia-security/hooks/modules/orchestrator/__init__.py +1 -0
- package/dist/gaia-security/hooks/modules/orchestrator/delegate_mode.py +122 -0
- package/dist/gaia-security/hooks/modules/scanning/__init__.py +8 -0
- package/dist/gaia-security/hooks/modules/scanning/scan_trigger.py +84 -0
- package/dist/gaia-security/hooks/modules/security/__init__.py +120 -0
- package/dist/gaia-security/hooks/modules/security/approval_cleanup.py +87 -0
- package/dist/gaia-security/hooks/modules/security/approval_constants.py +23 -0
- package/dist/gaia-security/hooks/modules/security/approval_grants.py +1638 -0
- package/dist/gaia-security/hooks/modules/security/approval_messages.py +71 -0
- package/dist/gaia-security/hooks/modules/security/approval_scopes.py +222 -0
- package/dist/gaia-security/hooks/modules/security/blocked_commands.py +595 -0
- package/dist/gaia-security/hooks/modules/security/blocked_message_formatter.py +87 -0
- package/dist/gaia-security/hooks/modules/security/command_semantics.py +181 -0
- package/dist/gaia-security/hooks/modules/security/composition_rules.py +547 -0
- package/dist/gaia-security/hooks/modules/security/flag_classifiers.py +873 -0
- package/dist/gaia-security/hooks/modules/security/gitops_validator.py +179 -0
- package/dist/gaia-security/hooks/modules/security/mutative_verbs.py +1131 -0
- package/dist/gaia-security/hooks/modules/security/network_hosts.py +481 -0
- package/dist/gaia-security/hooks/modules/security/prompt_validator.py +40 -0
- package/dist/gaia-security/hooks/modules/security/shell_unwrapper.py +165 -0
- package/dist/gaia-security/hooks/modules/security/tiers.py +196 -0
- package/dist/gaia-security/hooks/modules/session/__init__.py +10 -0
- package/dist/gaia-security/hooks/modules/session/pending_scanner.py +174 -0
- package/dist/gaia-security/hooks/modules/session/session_context_writer.py +100 -0
- package/dist/gaia-security/hooks/modules/session/session_event_injector.py +160 -0
- package/dist/gaia-security/hooks/modules/session/session_manager.py +31 -0
- package/dist/gaia-security/hooks/modules/session/session_registry.py +333 -0
- package/dist/gaia-security/hooks/modules/tools/__init__.py +29 -0
- package/dist/gaia-security/hooks/modules/tools/bash_validator.py +1008 -0
- package/dist/gaia-security/hooks/modules/tools/cloud_pipe_validator.py +231 -0
- package/dist/gaia-security/hooks/modules/tools/hook_response.py +55 -0
- package/dist/gaia-security/hooks/modules/tools/shell_parser.py +227 -0
- package/dist/gaia-security/hooks/modules/tools/stage_decomposer.py +315 -0
- package/dist/gaia-security/hooks/modules/tools/task_validator.py +294 -0
- package/dist/gaia-security/hooks/modules/validation/__init__.py +23 -0
- package/dist/gaia-security/hooks/modules/validation/commit_validator.py +380 -0
- package/dist/gaia-security/hooks/post_tool_use.py +54 -0
- package/dist/gaia-security/hooks/pre_tool_use.py +413 -0
- package/dist/gaia-security/hooks/session_end_hook.py +77 -0
- package/dist/gaia-security/hooks/session_start.py +81 -0
- package/dist/gaia-security/hooks/stop_hook.py +70 -0
- package/dist/gaia-security/hooks/user_prompt_submit.py +246 -0
- package/dist/gaia-security/settings.json +58 -0
- package/git-hooks/commit-msg +41 -0
- package/hooks/README.md +100 -0
- package/hooks/adapters/__init__.py +52 -0
- package/hooks/adapters/base.py +219 -0
- package/hooks/adapters/channel.py +17 -0
- package/hooks/adapters/claude_code.py +1890 -0
- package/hooks/adapters/types.py +194 -0
- package/hooks/adapters/utils.py +25 -0
- package/hooks/elicitation_result.py +179 -0
- package/hooks/hooks.json +84 -0
- package/hooks/modules/README.md +189 -0
- package/hooks/modules/__init__.py +15 -0
- package/hooks/modules/agents/__init__.py +29 -0
- package/hooks/modules/agents/contract_validator.py +647 -0
- package/hooks/modules/agents/response_contract.py +496 -0
- package/hooks/modules/agents/skill_injection_verifier.py +120 -0
- package/hooks/modules/agents/state_tracker.py +267 -0
- package/hooks/modules/agents/task_info_builder.py +74 -0
- package/hooks/modules/agents/transcript_analyzer.py +458 -0
- package/hooks/modules/agents/transcript_reader.py +152 -0
- package/hooks/modules/audit/__init__.py +28 -0
- package/hooks/modules/audit/event_detector.py +168 -0
- package/hooks/modules/audit/logger.py +131 -0
- package/hooks/modules/audit/metrics.py +134 -0
- package/hooks/modules/audit/workflow_auditor.py +611 -0
- package/hooks/modules/audit/workflow_recorder.py +296 -0
- package/hooks/modules/context/__init__.py +11 -0
- package/hooks/modules/context/agentic_loop_detector.py +165 -0
- package/hooks/modules/context/anchor_tracker.py +317 -0
- package/hooks/modules/context/compact_context_builder.py +218 -0
- package/hooks/modules/context/context_freshness.py +145 -0
- package/hooks/modules/context/context_injector.py +558 -0
- package/hooks/modules/context/context_writer.py +530 -0
- package/hooks/modules/context/contracts_loader.py +161 -0
- package/hooks/modules/core/__init__.py +40 -0
- package/hooks/modules/core/hook_entry.py +78 -0
- package/hooks/modules/core/paths.py +160 -0
- package/hooks/modules/core/plugin_mode.py +149 -0
- package/hooks/modules/core/plugin_setup.py +577 -0
- package/hooks/modules/core/state.py +179 -0
- package/hooks/modules/core/stdin.py +24 -0
- package/hooks/modules/events/__init__.py +1 -0
- package/hooks/modules/events/event_writer.py +210 -0
- package/hooks/modules/evidence/__init__.py +34 -0
- package/hooks/modules/evidence/assertions.py +137 -0
- package/hooks/modules/evidence/index_writer.py +57 -0
- package/hooks/modules/evidence/loader.py +126 -0
- package/hooks/modules/evidence/runner.py +241 -0
- package/hooks/modules/memory/__init__.py +8 -0
- package/hooks/modules/memory/episode_writer.py +216 -0
- package/hooks/modules/orchestrator/__init__.py +1 -0
- package/hooks/modules/orchestrator/delegate_mode.py +122 -0
- package/hooks/modules/scanning/__init__.py +8 -0
- package/hooks/modules/scanning/scan_trigger.py +84 -0
- package/hooks/modules/security/__init__.py +120 -0
- package/hooks/modules/security/approval_cleanup.py +87 -0
- package/hooks/modules/security/approval_constants.py +23 -0
- package/hooks/modules/security/approval_grants.py +1638 -0
- package/hooks/modules/security/approval_messages.py +71 -0
- package/hooks/modules/security/approval_scopes.py +222 -0
- package/hooks/modules/security/blocked_commands.py +595 -0
- package/hooks/modules/security/blocked_message_formatter.py +87 -0
- package/hooks/modules/security/command_semantics.py +181 -0
- package/hooks/modules/security/composition_rules.py +547 -0
- package/hooks/modules/security/flag_classifiers.py +873 -0
- package/hooks/modules/security/gitops_validator.py +179 -0
- package/hooks/modules/security/mutative_verbs.py +1131 -0
- package/hooks/modules/security/network_hosts.py +481 -0
- package/hooks/modules/security/prompt_validator.py +40 -0
- package/hooks/modules/security/shell_unwrapper.py +165 -0
- package/hooks/modules/security/tiers.py +196 -0
- package/hooks/modules/session/__init__.py +10 -0
- package/hooks/modules/session/pending_scanner.py +174 -0
- package/hooks/modules/session/session_context_writer.py +100 -0
- package/hooks/modules/session/session_event_injector.py +160 -0
- package/hooks/modules/session/session_manager.py +31 -0
- package/hooks/modules/session/session_registry.py +333 -0
- package/hooks/modules/tools/__init__.py +29 -0
- package/hooks/modules/tools/bash_validator.py +1008 -0
- package/hooks/modules/tools/cloud_pipe_validator.py +231 -0
- package/hooks/modules/tools/hook_response.py +55 -0
- package/hooks/modules/tools/shell_parser.py +227 -0
- package/hooks/modules/tools/stage_decomposer.py +315 -0
- package/hooks/modules/tools/task_validator.py +294 -0
- package/hooks/modules/validation/__init__.py +23 -0
- package/hooks/modules/validation/commit_validator.py +380 -0
- package/hooks/post_compact.py +43 -0
- package/hooks/post_tool_use.py +54 -0
- package/hooks/pre_compact.py +60 -0
- package/hooks/pre_tool_use.py +413 -0
- package/hooks/session_end_hook.py +77 -0
- package/hooks/session_start.py +81 -0
- package/hooks/stop_hook.py +70 -0
- package/hooks/subagent_start.py +71 -0
- package/hooks/subagent_stop.py +295 -0
- package/hooks/task_completed.py +70 -0
- package/hooks/user_prompt_submit.py +246 -0
- package/index.js +83 -0
- package/package.json +103 -0
- package/pyproject.toml +32 -0
- package/skills/README.md +158 -0
- package/skills/agent-creation/SKILL.md +87 -0
- package/skills/agent-creation/examples.md +170 -0
- package/skills/agent-creation/reference.md +191 -0
- package/skills/agent-protocol/SKILL.md +93 -0
- package/skills/agent-protocol/examples.md +223 -0
- package/skills/agent-response/SKILL.md +69 -0
- package/skills/agentic-loop/SKILL.md +80 -0
- package/skills/agentic-loop/reference.md +378 -0
- package/skills/blog-writing/SKILL.md +98 -0
- package/skills/blog-writing/reference.md +130 -0
- package/skills/brief-spec/SKILL.md +185 -0
- package/skills/command-execution/SKILL.md +64 -0
- package/skills/command-execution/reference.md +83 -0
- package/skills/context-updater/SKILL.md +87 -0
- package/skills/context-updater/examples.md +71 -0
- package/skills/developer-patterns/SKILL.md +50 -0
- package/skills/developer-patterns/reference.md +112 -0
- package/skills/execution/SKILL.md +99 -0
- package/skills/fast-queries/SKILL.md +43 -0
- package/skills/gaia-compact/SKILL.md +74 -0
- package/skills/gaia-patterns/SKILL.md +108 -0
- package/skills/gaia-patterns/reference.md +395 -0
- package/skills/gaia-planner/SKILL.md +37 -0
- package/skills/gaia-planner/reference.md +107 -0
- package/skills/gaia-release/SKILL.md +85 -0
- package/skills/gaia-release/reference.md +92 -0
- package/skills/gaia-self-check/SKILL.md +114 -0
- package/skills/gaia-self-check/reference.md +453 -0
- package/skills/gaia-verify/SKILL.md +77 -0
- package/skills/gaia-verify/reference.md +80 -0
- package/skills/git-conventions/SKILL.md +47 -0
- package/skills/gitops-patterns/SKILL.md +60 -0
- package/skills/gitops-patterns/reference.md +183 -0
- package/skills/gmail-policy/SKILL.md +200 -0
- package/skills/gmail-policy/reference.md +150 -0
- package/skills/gmail-triage/SKILL.md +100 -0
- package/skills/gws-setup/SKILL.md +99 -0
- package/skills/gws-setup/reference.md +73 -0
- package/skills/investigation/SKILL.md +100 -0
- package/skills/memory-curation/SKILL.md +83 -0
- package/skills/memory-search/SKILL.md +88 -0
- package/skills/orchestrator-approval/SKILL.md +160 -0
- package/skills/orchestrator-approval/reference.md +174 -0
- package/skills/pending-approvals/SKILL.md +72 -0
- package/skills/pending-approvals/reference.md +214 -0
- package/skills/readme-writing/SKILL.md +71 -0
- package/skills/readme-writing/reference.md +188 -0
- package/skills/reference.md +135 -0
- package/skills/request-approval/SKILL.md +140 -0
- package/skills/request-approval/examples.md +140 -0
- package/skills/request-approval/reference.md +57 -0
- package/skills/schedule-task/SKILL.md +64 -0
- package/skills/schedule-task/reference.md +233 -0
- package/skills/security-tiers/SKILL.md +141 -0
- package/skills/security-tiers/destructive-commands-reference.md +623 -0
- package/skills/security-tiers/reference.md +39 -0
- package/skills/session-reflection/SKILL.md +69 -0
- package/skills/skill-creation/SKILL.md +92 -0
- package/skills/skill-creation/reference.md +29 -0
- package/skills/terraform-patterns/SKILL.md +89 -0
- package/skills/terraform-patterns/reference.md +93 -0
- package/templates/README.md +69 -0
- package/templates/managed-settings.template.json +43 -0
- package/tools/__init__.py +9 -0
- package/tools/agentic-loop/decide-status.py +210 -0
- package/tools/agentic-loop/parse-metric.py +106 -0
- package/tools/agentic-loop/record-iteration.py +221 -0
- package/tools/context/README.md +132 -0
- package/tools/context/__init__.py +42 -0
- package/tools/context/_paths.py +20 -0
- package/tools/context/context_provider.py +721 -0
- package/tools/context/context_section_reader.py +342 -0
- package/tools/context/deep_merge.py +159 -0
- package/tools/context/pending_updates.py +760 -0
- package/tools/context/surface_router.py +278 -0
- package/tools/fast-queries/README.md +65 -0
- package/tools/fast-queries/__init__.py +30 -0
- package/tools/fast-queries/appservices/quicktriage_devops_developer.sh +75 -0
- package/tools/fast-queries/cloud/aws/quicktriage_aws_troubleshooter.sh +32 -0
- package/tools/fast-queries/cloud/gcp/quicktriage_gcp_troubleshooter.sh +88 -0
- package/tools/fast-queries/gitops/quicktriage_gitops_operator.sh +48 -0
- package/tools/fast-queries/run_triage.sh +59 -0
- package/tools/fast-queries/terraform/quicktriage_terraform_architect.sh +80 -0
- package/tools/gaia_simulator/__init__.py +33 -0
- package/tools/gaia_simulator/cli.py +354 -0
- package/tools/gaia_simulator/extractor.py +457 -0
- package/tools/gaia_simulator/reporter.py +258 -0
- package/tools/gaia_simulator/routing_simulator.py +334 -0
- package/tools/gaia_simulator/runner.py +539 -0
- package/tools/gaia_simulator/skills_mapper.py +264 -0
- package/tools/memory/README.md +0 -0
- package/tools/memory/__init__.py +20 -0
- package/tools/memory/backfill_fts5.py +107 -0
- package/tools/memory/conflict_detector.py +295 -0
- package/tools/memory/episodic.py +1210 -0
- package/tools/memory/git_invalidator.py +262 -0
- package/tools/memory/paths.py +102 -0
- package/tools/memory/scoring.py +193 -0
- package/tools/memory/search_store.py +375 -0
- package/tools/persist_transcript_analysis.py +85 -0
- package/tools/review/__init__.py +1 -0
- package/tools/review/review_engine.py +157 -0
- package/tools/scan/__init__.py +35 -0
- package/tools/scan/config.py +247 -0
- package/tools/scan/merge.py +212 -0
- package/tools/scan/orchestrator.py +549 -0
- package/tools/scan/registry.py +127 -0
- package/tools/scan/scanners/__init__.py +18 -0
- package/tools/scan/scanners/base.py +137 -0
- package/tools/scan/scanners/environment.py +349 -0
- package/tools/scan/scanners/git.py +570 -0
- package/tools/scan/scanners/infrastructure.py +875 -0
- package/tools/scan/scanners/orchestration.py +600 -0
- package/tools/scan/scanners/stack.py +1085 -0
- package/tools/scan/scanners/tools.py +260 -0
- package/tools/scan/setup.py +686 -0
- package/tools/scan/tests/__init__.py +1 -0
- package/tools/scan/tests/conftest.py +796 -0
- package/tools/scan/tests/test_environment.py +323 -0
- package/tools/scan/tests/test_git.py +419 -0
- package/tools/scan/tests/test_infrastructure.py +382 -0
- package/tools/scan/tests/test_integration.py +920 -0
- package/tools/scan/tests/test_merge.py +269 -0
- package/tools/scan/tests/test_orchestration.py +304 -0
- package/tools/scan/tests/test_stack.py +604 -0
- package/tools/scan/tests/test_tools.py +349 -0
- package/tools/scan/ui.py +624 -0
- package/tools/scan/verify.py +270 -0
- package/tools/scan/walk.py +118 -0
- package/tools/scan/workspace.py +85 -0
- package/tools/validation/README.md +244 -0
- package/tools/validation/__init__.py +17 -0
- package/tools/validation/approval_gate.py +321 -0
- package/tools/validation/validate_skills.py +189 -0
|
@@ -0,0 +1,721 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Context Provider for Claude Agent System
|
|
4
|
+
|
|
5
|
+
Generates structured context payloads for agents based on:
|
|
6
|
+
1. Agent contracts (context-contracts.json + cloud overlays)
|
|
7
|
+
2. Universal rules (universal-rules.json)
|
|
8
|
+
3. Historical episodes (episodic memory)
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
python3 context_provider.py <agent_name> [user_task] [--context-file PATH]
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import argparse
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Dict, List, Any, Optional
|
|
19
|
+
|
|
20
|
+
# Ensure the package root (gaia-ops-dev/) is on sys.path so that
|
|
21
|
+
# `tools.memory.scoring` and `tools.memory.search_store` resolve when
|
|
22
|
+
# this file runs as a subprocess with cwd=workspace-root.
|
|
23
|
+
# Pattern: same as hooks/pre_tool_use.py line 22.
|
|
24
|
+
_PACKAGE_ROOT = str(Path(__file__).resolve().parent.parent.parent)
|
|
25
|
+
if _PACKAGE_ROOT not in sys.path:
|
|
26
|
+
sys.path.insert(0, _PACKAGE_ROOT)
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
from ._paths import resolve_config_dir
|
|
30
|
+
from .surface_router import (
|
|
31
|
+
build_investigation_brief,
|
|
32
|
+
classify_surfaces,
|
|
33
|
+
load_surface_routing_config,
|
|
34
|
+
)
|
|
35
|
+
except ImportError:
|
|
36
|
+
from _paths import resolve_config_dir
|
|
37
|
+
from surface_router import (
|
|
38
|
+
build_investigation_brief,
|
|
39
|
+
classify_surfaces,
|
|
40
|
+
load_surface_routing_config,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# Default paths
|
|
44
|
+
DEFAULT_CONTEXT_PATH = Path(".claude/project-context/project-context.json")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# ============================================================================
|
|
48
|
+
# CONTRACTS DIRECTORY RESOLUTION
|
|
49
|
+
# ============================================================================
|
|
50
|
+
|
|
51
|
+
def get_contracts_dir():
|
|
52
|
+
"""Determines the correct contracts directory based on execution context."""
|
|
53
|
+
return resolve_config_dir()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
DEFAULT_CONTRACTS_DIR = get_contracts_dir()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# ============================================================================
|
|
60
|
+
# UNIVERSAL RULES SYSTEM
|
|
61
|
+
# ============================================================================
|
|
62
|
+
|
|
63
|
+
DEFAULT_RULES_FILE = "universal-rules.json"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def load_universal_rules(agent_name: str, rules_file: Optional[Path] = None) -> Dict[str, Any]:
|
|
67
|
+
"""Load universal rules and agent-specific rules from JSON file."""
|
|
68
|
+
if rules_file is None:
|
|
69
|
+
rules_file = get_contracts_dir() / DEFAULT_RULES_FILE
|
|
70
|
+
|
|
71
|
+
if not rules_file.is_file():
|
|
72
|
+
print(f"Warning: Rules file not found: {rules_file}", file=sys.stderr)
|
|
73
|
+
return {"universal": [], "agent_specific": []}
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
with open(rules_file, 'r', encoding='utf-8') as f:
|
|
77
|
+
rules_data = json.load(f)
|
|
78
|
+
|
|
79
|
+
universal = [r["rule"] for r in rules_data.get("rules", {}).get("universal", [])]
|
|
80
|
+
# agent_specific values may be a flat list [{rule:...}] or a nested
|
|
81
|
+
# dict {"rules": [{rule:...}]} -- handle both formats.
|
|
82
|
+
agent_raw = rules_data.get("rules", {}).get("agent_specific", {}).get(agent_name, [])
|
|
83
|
+
if isinstance(agent_raw, dict):
|
|
84
|
+
agent_raw = agent_raw.get("rules", [])
|
|
85
|
+
agent_specific = [r["rule"] for r in agent_raw]
|
|
86
|
+
|
|
87
|
+
total_rules = len(universal) + len(agent_specific)
|
|
88
|
+
if total_rules > 0:
|
|
89
|
+
print(f"Loaded {len(universal)} universal rules, {len(agent_specific)} agent-specific", file=sys.stderr)
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
"universal": universal,
|
|
93
|
+
"agent_specific": agent_specific
|
|
94
|
+
}
|
|
95
|
+
except Exception as e:
|
|
96
|
+
print(f"Warning: Could not load rules: {e}", file=sys.stderr)
|
|
97
|
+
return {"universal": [], "agent_specific": []}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# ============================================================================
|
|
101
|
+
# CLOUD PROVIDER DETECTION
|
|
102
|
+
# ============================================================================
|
|
103
|
+
|
|
104
|
+
def detect_cloud_provider(project_context: Dict[str, Any]) -> str:
|
|
105
|
+
"""Detects the cloud provider from project-context.json.
|
|
106
|
+
|
|
107
|
+
Detection priority:
|
|
108
|
+
1. metadata.cloud_provider (explicit user/scanner setting)
|
|
109
|
+
2. infrastructure.cloud_providers[0].name (v2 scanner section)
|
|
110
|
+
3. metadata.project_id presence -> gcp
|
|
111
|
+
4. Fallback -> gcp
|
|
112
|
+
"""
|
|
113
|
+
metadata = project_context.get("metadata", {})
|
|
114
|
+
if "cloud_provider" in metadata:
|
|
115
|
+
provider = metadata["cloud_provider"].lower()
|
|
116
|
+
if provider == "multi-cloud":
|
|
117
|
+
print("Multi-cloud detected, using GCP contracts as primary", file=sys.stderr)
|
|
118
|
+
return "gcp"
|
|
119
|
+
return provider
|
|
120
|
+
|
|
121
|
+
sections = project_context.get("sections", {})
|
|
122
|
+
|
|
123
|
+
# v2: read from infrastructure.cloud_providers
|
|
124
|
+
infra = sections.get("infrastructure", {})
|
|
125
|
+
if isinstance(infra, dict):
|
|
126
|
+
cloud_providers = infra.get("cloud_providers", [])
|
|
127
|
+
if isinstance(cloud_providers, list) and cloud_providers:
|
|
128
|
+
primary = cloud_providers[0]
|
|
129
|
+
if isinstance(primary, dict):
|
|
130
|
+
name = primary.get("name", "")
|
|
131
|
+
if name:
|
|
132
|
+
provider = name.lower()
|
|
133
|
+
if provider == "multi-cloud":
|
|
134
|
+
return "gcp"
|
|
135
|
+
return provider
|
|
136
|
+
|
|
137
|
+
if "project_id" in metadata:
|
|
138
|
+
return "gcp"
|
|
139
|
+
|
|
140
|
+
print("Could not detect cloud provider, defaulting to GCP", file=sys.stderr)
|
|
141
|
+
return "gcp"
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def load_provider_contracts(cloud_provider: str, contracts_dir: Path = DEFAULT_CONTRACTS_DIR) -> Dict[str, Any]:
|
|
145
|
+
"""
|
|
146
|
+
Loads context contracts using the base+cloud merge strategy.
|
|
147
|
+
|
|
148
|
+
Strategy:
|
|
149
|
+
1. Load base contracts from context-contracts.json (cloud-agnostic)
|
|
150
|
+
2. Load cloud overrides from cloud/{provider}.json and merge (extend) read/write lists
|
|
151
|
+
3. If base contracts missing → error (contracts are the single source of truth)
|
|
152
|
+
"""
|
|
153
|
+
base_file = contracts_dir / "context-contracts.json"
|
|
154
|
+
cloud_file = contracts_dir / "cloud" / f"{cloud_provider}.json"
|
|
155
|
+
|
|
156
|
+
# --- Step 1: Load base contracts ---
|
|
157
|
+
if not base_file.is_file():
|
|
158
|
+
print(f"Error: Contract file not found at {base_file}", file=sys.stderr)
|
|
159
|
+
sys.exit(1)
|
|
160
|
+
|
|
161
|
+
try:
|
|
162
|
+
with open(base_file, 'r', encoding='utf-8') as f:
|
|
163
|
+
base_contracts = json.load(f)
|
|
164
|
+
print(f"Loaded base contracts from {base_file}", file=sys.stderr)
|
|
165
|
+
except json.JSONDecodeError as e:
|
|
166
|
+
print(f"Error: Invalid JSON in {base_file}: {e}", file=sys.stderr)
|
|
167
|
+
sys.exit(1)
|
|
168
|
+
|
|
169
|
+
# --- Step 2: Merge cloud-specific overrides ---
|
|
170
|
+
if cloud_file.is_file():
|
|
171
|
+
try:
|
|
172
|
+
with open(cloud_file, 'r', encoding='utf-8') as f:
|
|
173
|
+
cloud_overrides = json.load(f)
|
|
174
|
+
print(f"Loaded {cloud_provider.upper()} cloud overrides from {cloud_file}", file=sys.stderr)
|
|
175
|
+
|
|
176
|
+
for agent_name, agent_overrides in cloud_overrides.get("agents", {}).items():
|
|
177
|
+
if agent_name in base_contracts.get("agents", {}):
|
|
178
|
+
existing_read = base_contracts["agents"][agent_name].get("read", [])
|
|
179
|
+
existing_write = base_contracts["agents"][agent_name].get("write", [])
|
|
180
|
+
extra_read = [s for s in agent_overrides.get("read", []) if s not in existing_read]
|
|
181
|
+
extra_write = [s for s in agent_overrides.get("write", []) if s not in existing_write]
|
|
182
|
+
base_contracts["agents"][agent_name]["read"] = existing_read + extra_read
|
|
183
|
+
base_contracts["agents"][agent_name]["write"] = existing_write + extra_write
|
|
184
|
+
else:
|
|
185
|
+
base_contracts["agents"][agent_name] = agent_overrides
|
|
186
|
+
|
|
187
|
+
except json.JSONDecodeError as e:
|
|
188
|
+
print(f"Warning: Invalid JSON in {cloud_file}: {e} — skipping cloud overrides", file=sys.stderr)
|
|
189
|
+
else:
|
|
190
|
+
print(f"No cloud overrides found at {cloud_file}, using base contracts only", file=sys.stderr)
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
"version": base_contracts.get("version", "unknown"),
|
|
194
|
+
"provider": cloud_provider,
|
|
195
|
+
"agents": base_contracts.get("agents", {})
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def load_project_context(context_path: Path) -> Dict[str, Any]:
|
|
200
|
+
"""Loads the project context from the specified JSON file."""
|
|
201
|
+
if not context_path.is_file():
|
|
202
|
+
print(f"Error: Context file not found at {context_path}", file=sys.stderr)
|
|
203
|
+
sys.exit(1)
|
|
204
|
+
with open(context_path, 'r', encoding='utf-8') as f:
|
|
205
|
+
return json.load(f)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
# ============================================================================
|
|
209
|
+
# CONTEXT EXTRACTION
|
|
210
|
+
# ============================================================================
|
|
211
|
+
|
|
212
|
+
def get_relevant_sections(
|
|
213
|
+
sections: Dict[str, Any],
|
|
214
|
+
contract_keys: List[str],
|
|
215
|
+
surface_routing: Optional[Dict[str, Any]] = None,
|
|
216
|
+
routing_config: Optional[Dict[str, Any]] = None,
|
|
217
|
+
) -> Dict[str, Any]:
|
|
218
|
+
"""Filter sections by surface relevance, with fallback to all readable sections.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
sections: All available sections from project-context.json.
|
|
222
|
+
contract_keys: The agent's permitted read keys (from context-contracts).
|
|
223
|
+
surface_routing: The routing result from classify_surfaces().
|
|
224
|
+
routing_config: The full surface-routing.json config (has contract_sections per surface).
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
Filtered dict of sections. Falls back to all readable sections when:
|
|
228
|
+
- No surface_routing or routing_config provided
|
|
229
|
+
- No active surfaces detected
|
|
230
|
+
- Surface has no contract_sections defined
|
|
231
|
+
- Intersection of surface sections and agent permissions is empty
|
|
232
|
+
"""
|
|
233
|
+
all_readable = {k: sections[k] for k in contract_keys if k in sections}
|
|
234
|
+
|
|
235
|
+
if not surface_routing or not routing_config:
|
|
236
|
+
return all_readable
|
|
237
|
+
|
|
238
|
+
active_surfaces = surface_routing.get("active_surfaces", [])
|
|
239
|
+
if not active_surfaces:
|
|
240
|
+
return all_readable
|
|
241
|
+
|
|
242
|
+
surfaces_cfg = routing_config.get("surfaces", {})
|
|
243
|
+
|
|
244
|
+
# Collect relevant sections from all active surfaces
|
|
245
|
+
relevant: set = set()
|
|
246
|
+
for surface in active_surfaces:
|
|
247
|
+
surface_config = surfaces_cfg.get(surface, {})
|
|
248
|
+
surface_sections = surface_config.get("contract_sections", [])
|
|
249
|
+
relevant.update(surface_sections)
|
|
250
|
+
|
|
251
|
+
if not relevant:
|
|
252
|
+
# Surfaces have no contract_sections defined -- inject all (fallback)
|
|
253
|
+
return all_readable
|
|
254
|
+
|
|
255
|
+
# Filter: agent permissions AND surface relevance
|
|
256
|
+
filtered = {k: sections[k] for k in contract_keys if k in sections and k in relevant}
|
|
257
|
+
|
|
258
|
+
if not filtered:
|
|
259
|
+
# Nothing matched -- inject all (fallback)
|
|
260
|
+
return all_readable
|
|
261
|
+
|
|
262
|
+
omitted = set(all_readable.keys()) - set(filtered.keys())
|
|
263
|
+
if omitted:
|
|
264
|
+
print(
|
|
265
|
+
f"Surface gating: {len(filtered)} sections injected, "
|
|
266
|
+
f"{len(omitted)} omitted ({', '.join(sorted(omitted))})",
|
|
267
|
+
file=sys.stderr,
|
|
268
|
+
)
|
|
269
|
+
else:
|
|
270
|
+
print(
|
|
271
|
+
f"Surface gating: all {len(filtered)} readable sections match active surfaces",
|
|
272
|
+
file=sys.stderr,
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
return filtered
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def get_contract_context(
|
|
279
|
+
project_context: Dict[str, Any],
|
|
280
|
+
agent_name: str,
|
|
281
|
+
provider_contracts: Dict[str, Any],
|
|
282
|
+
surface_routing: Optional[Dict[str, Any]] = None,
|
|
283
|
+
routing_config: Optional[Dict[str, Any]] = None,
|
|
284
|
+
) -> Dict[str, Any]:
|
|
285
|
+
"""Extracts the contract-defined context sections for a given agent.
|
|
286
|
+
|
|
287
|
+
When surface_routing and routing_config are provided, sections are filtered
|
|
288
|
+
to only those relevant to the active surface(s). Falls back to returning
|
|
289
|
+
all readable sections when routing is unavailable or yields an empty set.
|
|
290
|
+
"""
|
|
291
|
+
agent_contract = provider_contracts.get("agents", {}).get(agent_name)
|
|
292
|
+
if not agent_contract:
|
|
293
|
+
print(f"ERROR: Invalid agent '{agent_name}'. Available: {list(provider_contracts.get('agents', {}).keys())}", file=sys.stderr)
|
|
294
|
+
sys.exit(1)
|
|
295
|
+
|
|
296
|
+
contract_keys = agent_contract.get("read", [])
|
|
297
|
+
|
|
298
|
+
sections = project_context.get("sections", {})
|
|
299
|
+
if not sections:
|
|
300
|
+
raise KeyError("project-context.json must contain a 'sections' object.")
|
|
301
|
+
|
|
302
|
+
return get_relevant_sections(
|
|
303
|
+
sections, contract_keys,
|
|
304
|
+
surface_routing=surface_routing,
|
|
305
|
+
routing_config=routing_config,
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def get_context_update_contract(
|
|
310
|
+
agent_name: str,
|
|
311
|
+
provider_contracts: Dict[str, Any]
|
|
312
|
+
) -> Dict[str, Any]:
|
|
313
|
+
"""Return the SSOT contract agents should use for CONTEXT_UPDATE decisions."""
|
|
314
|
+
agent_contract = provider_contracts.get("agents", {}).get(agent_name)
|
|
315
|
+
if not agent_contract:
|
|
316
|
+
print(f"ERROR: Invalid agent '{agent_name}'. Available: {list(provider_contracts.get('agents', {}).keys())}", file=sys.stderr)
|
|
317
|
+
sys.exit(1)
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
"readable_sections": agent_contract.get("read", []),
|
|
321
|
+
"writable_sections": agent_contract.get("write", []),
|
|
322
|
+
"source": "config/context-contracts.json + config/cloud/{provider}.json",
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
# ============================================================================
|
|
327
|
+
# EPISODIC MEMORY
|
|
328
|
+
# ============================================================================
|
|
329
|
+
|
|
330
|
+
try:
|
|
331
|
+
from tools.memory.scoring import rank_episodes as _rank_episodes
|
|
332
|
+
_HAS_SCORING = True
|
|
333
|
+
except ImportError:
|
|
334
|
+
try:
|
|
335
|
+
import importlib, sys as _sys
|
|
336
|
+
_scoring = importlib.import_module("tools.memory.scoring")
|
|
337
|
+
_rank_episodes = _scoring.rank_episodes
|
|
338
|
+
_HAS_SCORING = True
|
|
339
|
+
except ImportError:
|
|
340
|
+
_rank_episodes = None
|
|
341
|
+
_HAS_SCORING = False
|
|
342
|
+
|
|
343
|
+
try:
|
|
344
|
+
from tools.memory.search_store import search as fts5_search
|
|
345
|
+
except ImportError:
|
|
346
|
+
fts5_search = None
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def _estimate_tokens(text: str) -> int:
|
|
350
|
+
"""Rough token estimate: 1 token ≈ 4 characters."""
|
|
351
|
+
return len(text) // 4
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def _build_memory_index_table(index_episodes: List[Dict[str, Any]]) -> str:
|
|
355
|
+
"""Build a compact markdown table of all memory sources for Layer 1."""
|
|
356
|
+
from datetime import datetime, timezone
|
|
357
|
+
lines = ["## Memory Index", "", "| # | Title | Type | Score | Age |", "|----|-------|------|-------|-----|"]
|
|
358
|
+
for i, ep in enumerate(index_episodes, 1):
|
|
359
|
+
title = ep.get("title", "")[:40]
|
|
360
|
+
ep_type = ep.get("type", "unknown")
|
|
361
|
+
score = ep.get("relevance_score", ep.get("_score", 0.0))
|
|
362
|
+
# Calculate age from timestamp field
|
|
363
|
+
ts = ep.get("timestamp", "")
|
|
364
|
+
try:
|
|
365
|
+
if ts:
|
|
366
|
+
ep_time = datetime.fromisoformat(ts.replace("Z", "+00:00"))
|
|
367
|
+
age = (datetime.now(timezone.utc) - ep_time).days
|
|
368
|
+
age_str = f"{age}d"
|
|
369
|
+
else:
|
|
370
|
+
age_str = "?d"
|
|
371
|
+
except Exception:
|
|
372
|
+
age_str = "?d"
|
|
373
|
+
lines.append(f"| {i} | {title} | {ep_type} | {score:.2f} | {age_str} |")
|
|
374
|
+
return "\n".join(lines)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def _fallback_keyword_score(episode: Dict[str, Any], user_task: str) -> float:
|
|
378
|
+
"""Keyword-based relevance scoring fallback when scoring module is unavailable."""
|
|
379
|
+
task_lower = user_task.lower()
|
|
380
|
+
task_words = set(task_lower.split())
|
|
381
|
+
score = 0.0
|
|
382
|
+
for tag in episode.get("tags", []):
|
|
383
|
+
if tag.lower() in task_lower:
|
|
384
|
+
score += 0.4
|
|
385
|
+
title_words = set(episode.get("title", "").lower().split())
|
|
386
|
+
common_words = task_words & title_words
|
|
387
|
+
if common_words:
|
|
388
|
+
score += 0.3 * (len(common_words) / max(len(title_words), 1))
|
|
389
|
+
return score * episode.get("relevance_score", 0.5)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def load_relevant_episodes(
|
|
393
|
+
user_task: str,
|
|
394
|
+
max_episodes: int = 2,
|
|
395
|
+
max_tokens: Optional[int] = None,
|
|
396
|
+
) -> Dict[str, Any]:
|
|
397
|
+
"""Load relevant historical episodes using 2-layer progressive disclosure.
|
|
398
|
+
|
|
399
|
+
Layer 1 (always): A compact markdown table of all scored memory sources
|
|
400
|
+
(~200 tokens), returned under the ``memory_index`` key.
|
|
401
|
+
|
|
402
|
+
Layer 2 (selective): Full content of top-N episodes ranked by
|
|
403
|
+
``tools.memory.scoring.rank_episodes()``, loaded only within the
|
|
404
|
+
remaining token budget.
|
|
405
|
+
|
|
406
|
+
Parameters
|
|
407
|
+
----------
|
|
408
|
+
user_task:
|
|
409
|
+
Free-text description of the user's current task.
|
|
410
|
+
max_episodes:
|
|
411
|
+
Legacy cap on the number of full episodes to include (Layer 2).
|
|
412
|
+
max_tokens:
|
|
413
|
+
Total token budget for the episodic memory block. Reads from
|
|
414
|
+
``GAIA_MEMORY_TOKEN_BUDGET`` env var when not supplied explicitly.
|
|
415
|
+
Defaults to 2000.
|
|
416
|
+
"""
|
|
417
|
+
import os as _os
|
|
418
|
+
|
|
419
|
+
if max_tokens is None:
|
|
420
|
+
env_budget = _os.environ.get("GAIA_MEMORY_TOKEN_BUDGET")
|
|
421
|
+
if env_budget:
|
|
422
|
+
try:
|
|
423
|
+
max_tokens = int(env_budget)
|
|
424
|
+
except ValueError:
|
|
425
|
+
max_tokens = 2000
|
|
426
|
+
else:
|
|
427
|
+
max_tokens = 2000
|
|
428
|
+
|
|
429
|
+
try:
|
|
430
|
+
index_file = Path(".claude/project-context/episodic-memory/index.json")
|
|
431
|
+
if not index_file.exists():
|
|
432
|
+
return {}
|
|
433
|
+
|
|
434
|
+
with open(index_file) as f:
|
|
435
|
+
index = json.load(f)
|
|
436
|
+
|
|
437
|
+
all_index_episodes = index.get("episodes", [])
|
|
438
|
+
if not all_index_episodes:
|
|
439
|
+
return {}
|
|
440
|
+
|
|
441
|
+
# --- Layer 1: Memory Index -- compact markdown table (~200 tokens, always included) ---
|
|
442
|
+
layer1_text = _build_memory_index_table(all_index_episodes)
|
|
443
|
+
layer1_tokens = _estimate_tokens(layer1_text)
|
|
444
|
+
remaining_budget = max_tokens - layer1_tokens
|
|
445
|
+
|
|
446
|
+
# --- Score and rank episodes: hybrid FTS5 + keyword fallback ---
|
|
447
|
+
# Build a lookup map from episode id to index entry for fast access
|
|
448
|
+
ep_by_id = {ep["id"]: ep for ep in all_index_episodes if "id" in ep}
|
|
449
|
+
|
|
450
|
+
# Try FTS5 first; results are BM25-ranked (better quality)
|
|
451
|
+
fts5_ids: List[str] = []
|
|
452
|
+
if fts5_search is not None:
|
|
453
|
+
try:
|
|
454
|
+
fts5_results = fts5_search(user_task, max_results=max_episodes * 3)
|
|
455
|
+
fts5_ids = [r["episode_id"] for r in fts5_results if "episode_id" in r]
|
|
456
|
+
print(
|
|
457
|
+
f"FTS5 search returned {len(fts5_ids)} candidates for retrieval",
|
|
458
|
+
file=sys.stderr,
|
|
459
|
+
)
|
|
460
|
+
except Exception as _fts_err:
|
|
461
|
+
print(
|
|
462
|
+
f"Warning: FTS5 search failed (non-fatal): {_fts_err}",
|
|
463
|
+
file=sys.stderr,
|
|
464
|
+
)
|
|
465
|
+
fts5_ids = []
|
|
466
|
+
|
|
467
|
+
# Build ranked list: FTS5 hits first, then fill with keyword/scoring results
|
|
468
|
+
fts5_id_set = set(fts5_ids)
|
|
469
|
+
|
|
470
|
+
# Keyword/scoring baseline (used to fill gaps and as full fallback)
|
|
471
|
+
if _HAS_SCORING and _rank_episodes is not None:
|
|
472
|
+
keyword_ranked = _rank_episodes(all_index_episodes, user_task)
|
|
473
|
+
else:
|
|
474
|
+
keyword_ranked = sorted(
|
|
475
|
+
[dict(ep, _score=_fallback_keyword_score(ep, user_task)) for ep in all_index_episodes],
|
|
476
|
+
key=lambda x: x["_score"],
|
|
477
|
+
reverse=True,
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
# If FTS5 found candidates, prepend them (with decay scoring if available)
|
|
481
|
+
if fts5_ids:
|
|
482
|
+
fts5_episodes = [ep_by_id[eid] for eid in fts5_ids if eid in ep_by_id]
|
|
483
|
+
if _HAS_SCORING and _rank_episodes is not None:
|
|
484
|
+
fts5_episodes = _rank_episodes(fts5_episodes, user_task)
|
|
485
|
+
else:
|
|
486
|
+
# Assign a generous score so they sort above keyword results
|
|
487
|
+
fts5_episodes = [dict(ep, _score=max(ep.get("_score", 0.0), 0.5)) for ep in fts5_episodes]
|
|
488
|
+
# Fill remaining slots with keyword results not already in FTS5 set
|
|
489
|
+
keyword_fill = [ep for ep in keyword_ranked if ep.get("id") not in fts5_id_set]
|
|
490
|
+
ranked = fts5_episodes + keyword_fill
|
|
491
|
+
else:
|
|
492
|
+
# FTS5 not available or returned nothing — fall back entirely to keyword
|
|
493
|
+
ranked = keyword_ranked
|
|
494
|
+
|
|
495
|
+
# --- Layer 2: full content of top episodes within remaining budget ---
|
|
496
|
+
full_episodes = []
|
|
497
|
+
tokens_used = 0
|
|
498
|
+
for ep in ranked:
|
|
499
|
+
if len(full_episodes) >= max_episodes:
|
|
500
|
+
break
|
|
501
|
+
score = ep.get("_score", 0.0)
|
|
502
|
+
if score <= 0.05:
|
|
503
|
+
continue
|
|
504
|
+
if remaining_budget <= 0:
|
|
505
|
+
break
|
|
506
|
+
|
|
507
|
+
full_ep = load_full_episode(ep["id"], index_file.parent)
|
|
508
|
+
if not full_ep:
|
|
509
|
+
continue
|
|
510
|
+
|
|
511
|
+
episode_entry = {
|
|
512
|
+
"id": full_ep["id"],
|
|
513
|
+
"title": full_ep["title"],
|
|
514
|
+
"type": full_ep["type"],
|
|
515
|
+
"relevance": round(score, 4),
|
|
516
|
+
"lessons_learned": full_ep.get("lessons_learned", [])[:2],
|
|
517
|
+
"resolution": full_ep.get("resolution", "")[:200],
|
|
518
|
+
}
|
|
519
|
+
entry_text = json.dumps(episode_entry)
|
|
520
|
+
entry_tokens = _estimate_tokens(entry_text)
|
|
521
|
+
|
|
522
|
+
if tokens_used + entry_tokens > remaining_budget:
|
|
523
|
+
break
|
|
524
|
+
|
|
525
|
+
full_episodes.append(episode_entry)
|
|
526
|
+
tokens_used += entry_tokens
|
|
527
|
+
|
|
528
|
+
result: Dict[str, Any] = {
|
|
529
|
+
"memory_index": layer1_text,
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if full_episodes:
|
|
533
|
+
result["episodes"] = full_episodes
|
|
534
|
+
result["summary"] = f"Found {len(full_episodes)} relevant historical episodes"
|
|
535
|
+
print(
|
|
536
|
+
f"Added {len(full_episodes)} historical episodes to context "
|
|
537
|
+
f"(budget={max_tokens}, used≈{layer1_tokens + tokens_used})",
|
|
538
|
+
file=sys.stderr,
|
|
539
|
+
)
|
|
540
|
+
else:
|
|
541
|
+
print(
|
|
542
|
+
f"Memory index built ({len(all_index_episodes)} entries, "
|
|
543
|
+
f"no full episodes within score/budget threshold)",
|
|
544
|
+
file=sys.stderr,
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
# --- Retrieval strengthening: update retrieval_count + last_retrieved ---
|
|
548
|
+
# Failure here must never block context injection.
|
|
549
|
+
try:
|
|
550
|
+
if full_episodes:
|
|
551
|
+
import tempfile as _tempfile
|
|
552
|
+
from datetime import datetime as _datetime
|
|
553
|
+
|
|
554
|
+
selected_ids = {ep["id"] for ep in full_episodes}
|
|
555
|
+
now_iso = _datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
556
|
+
|
|
557
|
+
updated = False
|
|
558
|
+
for entry in index.get("episodes", []):
|
|
559
|
+
if entry.get("id") in selected_ids:
|
|
560
|
+
entry["retrieval_count"] = entry.get("retrieval_count", 0) + 1
|
|
561
|
+
entry["last_retrieved"] = now_iso
|
|
562
|
+
updated = True
|
|
563
|
+
|
|
564
|
+
if updated:
|
|
565
|
+
index_path = index_file.resolve()
|
|
566
|
+
index_dir = index_path.parent
|
|
567
|
+
fd, tmp_path = _tempfile.mkstemp(
|
|
568
|
+
dir=str(index_dir), suffix=".tmp", prefix="index_"
|
|
569
|
+
)
|
|
570
|
+
try:
|
|
571
|
+
with _os.fdopen(fd, "w", encoding="utf-8") as tf:
|
|
572
|
+
json.dump(index, tf, indent=2)
|
|
573
|
+
_os.rename(tmp_path, str(index_path))
|
|
574
|
+
print(
|
|
575
|
+
f"Retrieval strengthening: updated {len(selected_ids)} episode(s)",
|
|
576
|
+
file=sys.stderr,
|
|
577
|
+
)
|
|
578
|
+
except Exception:
|
|
579
|
+
try:
|
|
580
|
+
_os.unlink(tmp_path)
|
|
581
|
+
except Exception:
|
|
582
|
+
pass
|
|
583
|
+
raise
|
|
584
|
+
except Exception as _rs_err:
|
|
585
|
+
print(
|
|
586
|
+
f"Warning: retrieval_count update failed (non-fatal): {_rs_err}",
|
|
587
|
+
file=sys.stderr,
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
return result
|
|
591
|
+
|
|
592
|
+
except Exception as e:
|
|
593
|
+
print(f"Warning: Could not load episodic memory: {e}", file=sys.stderr)
|
|
594
|
+
return {}
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
def load_full_episode(episode_id: str, memory_dir: Path) -> Optional[Dict[str, Any]]:
|
|
598
|
+
"""Load full episode details from JSONL file."""
|
|
599
|
+
try:
|
|
600
|
+
episodes_file = memory_dir / "episodes.jsonl"
|
|
601
|
+
if episodes_file.exists():
|
|
602
|
+
with open(episodes_file) as f:
|
|
603
|
+
for line in f:
|
|
604
|
+
try:
|
|
605
|
+
episode = json.loads(line)
|
|
606
|
+
if episode.get("id") == episode_id:
|
|
607
|
+
return episode
|
|
608
|
+
except Exception:
|
|
609
|
+
continue
|
|
610
|
+
except Exception:
|
|
611
|
+
pass
|
|
612
|
+
return None
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
# ============================================================================
|
|
616
|
+
# MAIN FUNCTION
|
|
617
|
+
# ============================================================================
|
|
618
|
+
|
|
619
|
+
def main():
|
|
620
|
+
"""Main function to generate and print the context payload."""
|
|
621
|
+
import os as _os
|
|
622
|
+
|
|
623
|
+
parser = argparse.ArgumentParser(
|
|
624
|
+
description="Generates a structured context payload for a Claude agent."
|
|
625
|
+
)
|
|
626
|
+
parser.add_argument("agent_name", help="The name of the agent being invoked.")
|
|
627
|
+
parser.add_argument("user_task", nargs="?", default="General inquiry",
|
|
628
|
+
help="The user's task or query for the agent.")
|
|
629
|
+
parser.add_argument(
|
|
630
|
+
"--context-file",
|
|
631
|
+
type=Path,
|
|
632
|
+
default=DEFAULT_CONTEXT_PATH,
|
|
633
|
+
help=f"Path to the project-context.json file. Defaults to '{DEFAULT_CONTEXT_PATH}'"
|
|
634
|
+
)
|
|
635
|
+
parser.add_argument(
|
|
636
|
+
"--memory-token-budget",
|
|
637
|
+
type=int,
|
|
638
|
+
default=None,
|
|
639
|
+
help=(
|
|
640
|
+
"Token budget for episodic memory injection. "
|
|
641
|
+
"Overrides GAIA_MEMORY_TOKEN_BUDGET env var. Default: 2000."
|
|
642
|
+
),
|
|
643
|
+
)
|
|
644
|
+
|
|
645
|
+
args = parser.parse_args()
|
|
646
|
+
|
|
647
|
+
# Resolve memory token budget: CLI arg > env var > default
|
|
648
|
+
memory_token_budget: Optional[int] = args.memory_token_budget
|
|
649
|
+
if memory_token_budget is None:
|
|
650
|
+
env_budget = _os.environ.get("GAIA_MEMORY_TOKEN_BUDGET")
|
|
651
|
+
if env_budget:
|
|
652
|
+
try:
|
|
653
|
+
memory_token_budget = int(env_budget)
|
|
654
|
+
except ValueError:
|
|
655
|
+
memory_token_budget = 2000
|
|
656
|
+
else:
|
|
657
|
+
memory_token_budget = 2000
|
|
658
|
+
|
|
659
|
+
# Load project context
|
|
660
|
+
project_context = load_project_context(args.context_file)
|
|
661
|
+
|
|
662
|
+
# Detect cloud provider and load contracts
|
|
663
|
+
cloud_provider = detect_cloud_provider(project_context)
|
|
664
|
+
provider_contracts = load_provider_contracts(cloud_provider)
|
|
665
|
+
|
|
666
|
+
# Compute surface routing BEFORE extracting sections so we can gate by surface
|
|
667
|
+
surface_routing_config = load_surface_routing_config()
|
|
668
|
+
surface_routing = classify_surfaces(
|
|
669
|
+
args.user_task,
|
|
670
|
+
current_agent=args.agent_name,
|
|
671
|
+
routing_config=surface_routing_config,
|
|
672
|
+
)
|
|
673
|
+
|
|
674
|
+
# Extract contracted sections (surface-gated when routing is available)
|
|
675
|
+
contract_context = get_contract_context(
|
|
676
|
+
project_context, args.agent_name, provider_contracts,
|
|
677
|
+
surface_routing=surface_routing,
|
|
678
|
+
routing_config=surface_routing_config,
|
|
679
|
+
)
|
|
680
|
+
context_update_contract = get_context_update_contract(args.agent_name, provider_contracts)
|
|
681
|
+
|
|
682
|
+
# Load historical episodes (2-layer progressive disclosure)
|
|
683
|
+
historical_context = load_relevant_episodes(args.user_task, max_tokens=memory_token_budget)
|
|
684
|
+
|
|
685
|
+
# Load universal rules
|
|
686
|
+
rules_context = load_universal_rules(args.agent_name)
|
|
687
|
+
investigation_brief = build_investigation_brief(
|
|
688
|
+
args.user_task,
|
|
689
|
+
args.agent_name,
|
|
690
|
+
contract_context,
|
|
691
|
+
routing_config=surface_routing_config,
|
|
692
|
+
routing=surface_routing,
|
|
693
|
+
)
|
|
694
|
+
|
|
695
|
+
# Build final payload
|
|
696
|
+
final_payload = {
|
|
697
|
+
"project_knowledge": contract_context,
|
|
698
|
+
"write_permissions": context_update_contract,
|
|
699
|
+
"rules": rules_context,
|
|
700
|
+
"surface_routing": surface_routing,
|
|
701
|
+
"investigation_brief": investigation_brief,
|
|
702
|
+
"metadata": {
|
|
703
|
+
"cloud_provider": cloud_provider,
|
|
704
|
+
"contract_version": provider_contracts.get("version", "unknown"),
|
|
705
|
+
"historical_episodes_count": len(historical_context.get("episodes", [])),
|
|
706
|
+
"rules_count": len(rules_context.get("universal", [])) + len(rules_context.get("agent_specific", [])),
|
|
707
|
+
"surface_routing_version": surface_routing_config.get("version", "unknown"),
|
|
708
|
+
"active_surfaces_count": len(surface_routing.get("active_surfaces", [])),
|
|
709
|
+
"surface_routing_confidence": surface_routing.get("confidence", 0.0),
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
# Add historical context if episodes found
|
|
714
|
+
if historical_context:
|
|
715
|
+
final_payload["historical_context"] = historical_context
|
|
716
|
+
|
|
717
|
+
print(json.dumps(final_payload, indent=2))
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
if __name__ == "__main__":
|
|
721
|
+
main()
|