@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,1068 @@
|
|
|
1
|
+
"""
|
|
2
|
+
gaia metrics -- mirror of gaia-metrics.js
|
|
3
|
+
|
|
4
|
+
Displays system metrics dashboard:
|
|
5
|
+
- Security tier usage distribution
|
|
6
|
+
- Command type breakdown
|
|
7
|
+
- Top commands by frequency
|
|
8
|
+
- Agent invocations
|
|
9
|
+
- Agent outcomes
|
|
10
|
+
- Token usage (approx)
|
|
11
|
+
- Anomaly summary (last 30 days)
|
|
12
|
+
- Activity today
|
|
13
|
+
|
|
14
|
+
With --agent NAME shows a detail view for that agent.
|
|
15
|
+
|
|
16
|
+
Data sources:
|
|
17
|
+
.claude/logs/audit-*.jsonl
|
|
18
|
+
.claude/project-context/episodic-memory/index.json
|
|
19
|
+
.claude/project-context/workflow-episodic-memory/metrics.jsonl (fallback)
|
|
20
|
+
.claude/project-context/workflow-episodic-memory/anomalies.jsonl
|
|
21
|
+
.claude/project-context/workflow-episodic-memory/run-snapshots.jsonl
|
|
22
|
+
.claude/project-context/workflow-episodic-memory/agent-skills.jsonl
|
|
23
|
+
|
|
24
|
+
Flags:
|
|
25
|
+
--agent NAME Show detail view for a specific agent
|
|
26
|
+
--json Machine-readable output
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
import fnmatch
|
|
30
|
+
import json
|
|
31
|
+
import os
|
|
32
|
+
import re
|
|
33
|
+
import sys
|
|
34
|
+
from datetime import datetime, timezone, timedelta
|
|
35
|
+
from pathlib import Path
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
# Project root detection
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
def _find_project_root() -> Path:
|
|
43
|
+
start = Path(os.environ.get("INIT_CWD", "")) if os.environ.get("INIT_CWD") else None
|
|
44
|
+
if start and (start / ".claude").exists():
|
|
45
|
+
return start
|
|
46
|
+
|
|
47
|
+
current = Path.cwd()
|
|
48
|
+
while True:
|
|
49
|
+
if (current / ".claude").exists():
|
|
50
|
+
return current
|
|
51
|
+
parent = current.parent
|
|
52
|
+
if parent == current:
|
|
53
|
+
break
|
|
54
|
+
current = parent
|
|
55
|
+
|
|
56
|
+
return Path(os.environ.get("INIT_CWD", str(Path.cwd())))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# ---------------------------------------------------------------------------
|
|
60
|
+
# Data readers
|
|
61
|
+
# ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
def _read_jsonl(path: Path) -> list:
|
|
64
|
+
if not path.exists():
|
|
65
|
+
return []
|
|
66
|
+
entries = []
|
|
67
|
+
try:
|
|
68
|
+
for line in path.read_text(encoding="utf-8").splitlines():
|
|
69
|
+
line = line.strip()
|
|
70
|
+
if not line:
|
|
71
|
+
continue
|
|
72
|
+
try:
|
|
73
|
+
entries.append(json.loads(line))
|
|
74
|
+
except json.JSONDecodeError:
|
|
75
|
+
pass
|
|
76
|
+
except OSError:
|
|
77
|
+
pass
|
|
78
|
+
return entries
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _read_audit_logs(root: Path) -> list:
|
|
82
|
+
logs_dir = root / ".claude" / "logs"
|
|
83
|
+
if not logs_dir.exists():
|
|
84
|
+
return []
|
|
85
|
+
all_entries = []
|
|
86
|
+
try:
|
|
87
|
+
for f in logs_dir.iterdir():
|
|
88
|
+
if f.name.startswith("audit-") and f.name.endswith(".jsonl"):
|
|
89
|
+
all_entries.extend(_read_jsonl(f))
|
|
90
|
+
except OSError:
|
|
91
|
+
pass
|
|
92
|
+
return all_entries
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _read_workflow_metrics(root: Path) -> list:
|
|
96
|
+
"""Primary: episodic-memory/index.json; fallback: workflow metrics.jsonl."""
|
|
97
|
+
index_path = root / ".claude" / "project-context" / "episodic-memory" / "index.json"
|
|
98
|
+
if index_path.exists():
|
|
99
|
+
try:
|
|
100
|
+
data = json.loads(index_path.read_text(encoding="utf-8"))
|
|
101
|
+
episodes = [e for e in (data.get("episodes") or []) if e.get("agent")]
|
|
102
|
+
if episodes:
|
|
103
|
+
return episodes
|
|
104
|
+
except (json.JSONDecodeError, OSError):
|
|
105
|
+
pass
|
|
106
|
+
|
|
107
|
+
metrics_path = root / ".claude" / "project-context" / "workflow-episodic-memory" / "metrics.jsonl"
|
|
108
|
+
return [e for e in _read_jsonl(metrics_path) if e.get("agent")]
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _read_run_snapshots(root: Path) -> list:
|
|
112
|
+
return _read_jsonl(
|
|
113
|
+
root / ".claude" / "project-context" / "workflow-episodic-memory" / "run-snapshots.jsonl"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _read_agent_skill_snapshots(root: Path) -> list:
|
|
118
|
+
return _read_jsonl(
|
|
119
|
+
root / ".claude" / "project-context" / "workflow-episodic-memory" / "agent-skills.jsonl"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _read_anomaly_entries(root: Path) -> list:
|
|
124
|
+
return _read_jsonl(
|
|
125
|
+
root / ".claude" / "project-context" / "workflow-episodic-memory" / "anomalies.jsonl"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _read_agent_definition(root: Path, agent_name: str) -> dict:
|
|
130
|
+
"""Extract description and skills from agent .md frontmatter."""
|
|
131
|
+
agent_path = root / ".claude" / "agents" / f"{agent_name}.md"
|
|
132
|
+
if not agent_path.exists():
|
|
133
|
+
return {}
|
|
134
|
+
try:
|
|
135
|
+
content = agent_path.read_text(encoding="utf-8")
|
|
136
|
+
if not content.startswith("---"):
|
|
137
|
+
return {}
|
|
138
|
+
end = content.find("---", 3)
|
|
139
|
+
if end == -1:
|
|
140
|
+
return {}
|
|
141
|
+
fm = content[3:end]
|
|
142
|
+
description = ""
|
|
143
|
+
skills = []
|
|
144
|
+
in_skills = False
|
|
145
|
+
for line in fm.splitlines():
|
|
146
|
+
stripped = line.strip()
|
|
147
|
+
if stripped.startswith("description:"):
|
|
148
|
+
description = stripped[len("description:"):].strip().strip("'\"")
|
|
149
|
+
in_skills = False
|
|
150
|
+
elif stripped == "skills:":
|
|
151
|
+
in_skills = True
|
|
152
|
+
elif in_skills and stripped.startswith("- "):
|
|
153
|
+
skills.append(stripped[2:].strip())
|
|
154
|
+
elif in_skills and stripped and not stripped.startswith("-"):
|
|
155
|
+
in_skills = False
|
|
156
|
+
return {"description": description, "skills": skills}
|
|
157
|
+
except OSError:
|
|
158
|
+
return {}
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
# ---------------------------------------------------------------------------
|
|
162
|
+
# Utility functions
|
|
163
|
+
# ---------------------------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
def _classify_command(command: str) -> str:
|
|
166
|
+
if not command:
|
|
167
|
+
return "general"
|
|
168
|
+
cmd = command.strip().lower()
|
|
169
|
+
if cmd.startswith("terragrunt") or cmd.startswith("terraform"):
|
|
170
|
+
return "terraform"
|
|
171
|
+
if cmd.startswith("kubectl"):
|
|
172
|
+
return "kubernetes"
|
|
173
|
+
if cmd.startswith("helm") or cmd.startswith("flux"):
|
|
174
|
+
return "gitops"
|
|
175
|
+
if cmd.startswith("git") or cmd.startswith("glab"):
|
|
176
|
+
return "git"
|
|
177
|
+
if cmd.startswith("gcloud") or cmd.startswith("gsutil"):
|
|
178
|
+
return "gcp"
|
|
179
|
+
if cmd.startswith("aws"):
|
|
180
|
+
return "aws"
|
|
181
|
+
if cmd.startswith("docker"):
|
|
182
|
+
return "docker"
|
|
183
|
+
if cmd.startswith(("npm", "node", "python", "pip")):
|
|
184
|
+
return "dev"
|
|
185
|
+
return "general"
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _extract_command_label(command: str) -> str:
|
|
189
|
+
"""Extract short human-readable label from full command string."""
|
|
190
|
+
if not command:
|
|
191
|
+
return "(unknown)"
|
|
192
|
+
cmd = command.strip()
|
|
193
|
+
# Strip env var assignments
|
|
194
|
+
cmd = re.sub(r'^(?:[A-Z_][A-Z0-9_]*=\S+\s+)+', '', cmd)
|
|
195
|
+
# Strip timeout wrapper
|
|
196
|
+
cmd = re.sub(r'^timeout\s+\S+\s+', '', cmd)
|
|
197
|
+
# Strip cd/pushd navigation
|
|
198
|
+
m = re.match(r'^(?:cd|pushd)\s+\S+\s*(?:&&|;)\s*(.*)', cmd)
|
|
199
|
+
if m:
|
|
200
|
+
cmd = m.group(1).strip()
|
|
201
|
+
# Strip at pipe/semicolon/&&
|
|
202
|
+
cmd = re.split(r'\s*(?:[|;&]|&&|\|\|)\s*', cmd)[0].strip()
|
|
203
|
+
# Strip trailing redirections
|
|
204
|
+
cmd = re.sub(r'\s*\d*>.*$', '', cmd).strip()
|
|
205
|
+
|
|
206
|
+
tokens = cmd.split()
|
|
207
|
+
parts = [tokens[0]] if tokens else ["(unknown)"]
|
|
208
|
+
for t in tokens[1:]:
|
|
209
|
+
if len(parts) >= 3:
|
|
210
|
+
break
|
|
211
|
+
if not t.startswith(("-", "/", '"', "'")):
|
|
212
|
+
parts.append(t)
|
|
213
|
+
return " ".join(parts)[:32]
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _format_tokens(n) -> str:
|
|
217
|
+
if n is None:
|
|
218
|
+
return "n/a"
|
|
219
|
+
if n >= 1_000_000:
|
|
220
|
+
return f"{n / 1_000_000:.1f}M"
|
|
221
|
+
if n >= 1_000:
|
|
222
|
+
return f"{n / 1_000:.1f}k"
|
|
223
|
+
return str(n)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _format_chars(n) -> str:
|
|
227
|
+
if n is None:
|
|
228
|
+
return "n/a"
|
|
229
|
+
if n >= 1000:
|
|
230
|
+
return f"{n / 1000:.1f}k"
|
|
231
|
+
return str(n)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _make_bar(percentage: float, max_width: int = 14) -> str:
|
|
235
|
+
filled = max(0, round((percentage / 100) * max_width))
|
|
236
|
+
return "#" * filled
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _count_values(values: list) -> dict:
|
|
240
|
+
counts = {}
|
|
241
|
+
for v in values:
|
|
242
|
+
if not v:
|
|
243
|
+
continue
|
|
244
|
+
counts[v] = counts.get(v, 0) + 1
|
|
245
|
+
return counts
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _sorted_counts(counts: dict) -> list:
|
|
249
|
+
return sorted(
|
|
250
|
+
[{"name": k, "count": v} for k, v in counts.items()],
|
|
251
|
+
key=lambda x: (-x["count"], x["name"]),
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _top_counts(values: list, limit: int = 5) -> list:
|
|
256
|
+
return _sorted_counts(_count_values(values))[:limit]
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _format_count_summary(entries: list, empty_label: str = "none") -> str:
|
|
260
|
+
if not entries:
|
|
261
|
+
return empty_label
|
|
262
|
+
return ", ".join(f"{e['name']}({e['count']})" for e in entries)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _format_skills(skills: list, limit: int = 4) -> str:
|
|
266
|
+
if not skills:
|
|
267
|
+
return "none"
|
|
268
|
+
if len(skills) <= limit:
|
|
269
|
+
return ", ".join(skills)
|
|
270
|
+
return ", ".join(skills[:limit]) + f", +{len(skills) - limit} more"
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
# ---------------------------------------------------------------------------
|
|
274
|
+
# Metric calculators
|
|
275
|
+
# ---------------------------------------------------------------------------
|
|
276
|
+
|
|
277
|
+
def _calculate_tier_usage(audit_logs: list) -> dict:
|
|
278
|
+
tier_entries = [l for l in audit_logs if l.get("tier")]
|
|
279
|
+
counts = {}
|
|
280
|
+
for e in tier_entries:
|
|
281
|
+
t = e.get("tier", "unknown")
|
|
282
|
+
counts[t] = counts.get(t, 0) + 1
|
|
283
|
+
|
|
284
|
+
total = len(tier_entries)
|
|
285
|
+
distribution = sorted(
|
|
286
|
+
[{"tier": t, "count": c, "percentage": c / total * 100 if total else 0}
|
|
287
|
+
for t, c in counts.items()],
|
|
288
|
+
key=lambda x: x["tier"],
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
today = datetime.now(timezone.utc).date().isoformat()
|
|
292
|
+
today_entries = [l for l in audit_logs if (l.get("timestamp") or "").startswith(today)]
|
|
293
|
+
today_t3 = sum(1 for l in today_entries if l.get("tier") == "T3")
|
|
294
|
+
|
|
295
|
+
hour_counts = {}
|
|
296
|
+
for e in today_entries:
|
|
297
|
+
ts = e.get("timestamp")
|
|
298
|
+
if ts and len(ts) >= 13:
|
|
299
|
+
h = ts[11:13]
|
|
300
|
+
hour_counts[h] = hour_counts.get(h, 0) + 1
|
|
301
|
+
|
|
302
|
+
peak_hour = None
|
|
303
|
+
peak_count = 0
|
|
304
|
+
for h, c in hour_counts.items():
|
|
305
|
+
if c > peak_count:
|
|
306
|
+
peak_count = c
|
|
307
|
+
peak_hour = h
|
|
308
|
+
|
|
309
|
+
return {
|
|
310
|
+
"total": total,
|
|
311
|
+
"distribution": distribution,
|
|
312
|
+
"today_count": len(today_entries),
|
|
313
|
+
"today_t3": today_t3,
|
|
314
|
+
"peak_hour": peak_hour,
|
|
315
|
+
"peak_count": peak_count,
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _calculate_command_type_breakdown(audit_logs: list) -> dict:
|
|
320
|
+
counts = {}
|
|
321
|
+
for e in audit_logs:
|
|
322
|
+
t = _classify_command(e.get("command") or "")
|
|
323
|
+
counts[t] = counts.get(t, 0) + 1
|
|
324
|
+
|
|
325
|
+
total = len(audit_logs)
|
|
326
|
+
breakdown = sorted(
|
|
327
|
+
[{"type": t, "count": c, "percentage": c / total * 100 if total else 0}
|
|
328
|
+
for t, c in counts.items()],
|
|
329
|
+
key=lambda x: -x["count"],
|
|
330
|
+
)
|
|
331
|
+
return {"total": total, "breakdown": breakdown}
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _calculate_top_commands(audit_logs: list) -> list:
|
|
335
|
+
tier_order = {"T3": 3, "T2": 2, "T1": 1, "T0": 0, "unknown": -1}
|
|
336
|
+
label_map = {}
|
|
337
|
+
|
|
338
|
+
for e in audit_logs:
|
|
339
|
+
if not e.get("command"):
|
|
340
|
+
continue
|
|
341
|
+
label = _extract_command_label(e["command"])
|
|
342
|
+
tier = e.get("tier") or "unknown"
|
|
343
|
+
|
|
344
|
+
if label not in label_map:
|
|
345
|
+
label_map[label] = {"count": 0, "tier": tier, "t3count": 0}
|
|
346
|
+
label_map[label]["count"] += 1
|
|
347
|
+
if tier == "T3":
|
|
348
|
+
label_map[label]["t3count"] += 1
|
|
349
|
+
if tier_order.get(tier, -1) > tier_order.get(label_map[label]["tier"], -1):
|
|
350
|
+
label_map[label]["tier"] = tier
|
|
351
|
+
|
|
352
|
+
return sorted(
|
|
353
|
+
[{"label": l, **v} for l, v in label_map.items()],
|
|
354
|
+
key=lambda x: -x["count"],
|
|
355
|
+
)[:10]
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def _calculate_error_rate(audit_logs: list) -> dict:
|
|
359
|
+
with_code = [l for l in audit_logs if "exit_code" in l]
|
|
360
|
+
errors = [l for l in with_code if l["exit_code"] != 0]
|
|
361
|
+
all_zero = bool(with_code) and len(errors) == 0
|
|
362
|
+
total = len(with_code)
|
|
363
|
+
return {
|
|
364
|
+
"total": total,
|
|
365
|
+
"errors": len(errors),
|
|
366
|
+
"error_rate": len(errors) / total * 100 if total else 0,
|
|
367
|
+
"limited_by_api": all_zero,
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def _calculate_agent_invocations(workflow_metrics: list) -> dict:
|
|
372
|
+
today = datetime.now(timezone.utc).date().isoformat()
|
|
373
|
+
today_count = sum(1 for r in workflow_metrics if (r.get("timestamp") or "").startswith(today))
|
|
374
|
+
|
|
375
|
+
agent_map = {}
|
|
376
|
+
for e in workflow_metrics:
|
|
377
|
+
name = e.get("agent") or "unknown"
|
|
378
|
+
if name not in agent_map:
|
|
379
|
+
agent_map[name] = {"count": 0, "total_output": 0, "successes": 0}
|
|
380
|
+
agent_map[name]["count"] += 1
|
|
381
|
+
agent_map[name]["total_output"] += e.get("output_length") or 0
|
|
382
|
+
if e.get("exit_code") == 0:
|
|
383
|
+
agent_map[name]["successes"] += 1
|
|
384
|
+
|
|
385
|
+
total = len(workflow_metrics)
|
|
386
|
+
agents = sorted(
|
|
387
|
+
[
|
|
388
|
+
{
|
|
389
|
+
"name": n,
|
|
390
|
+
"count": v["count"],
|
|
391
|
+
"avg_output": round(v["total_output"] / v["count"]) if v["count"] else 0,
|
|
392
|
+
"success_rate": v["successes"] / v["count"] * 100 if v["count"] else 0,
|
|
393
|
+
"percentage": v["count"] / total * 100 if total else 0,
|
|
394
|
+
}
|
|
395
|
+
for n, v in agent_map.items()
|
|
396
|
+
],
|
|
397
|
+
key=lambda x: -x["count"],
|
|
398
|
+
)
|
|
399
|
+
return {"agents": agents, "total": total, "today_count": today_count}
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def _calculate_agent_outcomes(workflow_metrics: list):
|
|
403
|
+
with_status = [r for r in workflow_metrics if r.get("plan_status")]
|
|
404
|
+
if not with_status:
|
|
405
|
+
return None
|
|
406
|
+
|
|
407
|
+
counts = {}
|
|
408
|
+
for e in with_status:
|
|
409
|
+
s = e["plan_status"].upper()
|
|
410
|
+
counts[s] = counts.get(s, 0) + 1
|
|
411
|
+
|
|
412
|
+
total = len(with_status)
|
|
413
|
+
distribution = sorted(
|
|
414
|
+
[{"status": s, "count": c, "percentage": c / total * 100} for s, c in counts.items()],
|
|
415
|
+
key=lambda x: -x["count"],
|
|
416
|
+
)
|
|
417
|
+
return {"distribution": distribution, "total": total}
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def _calculate_token_usage(workflow_metrics: list):
|
|
421
|
+
with_tokens = [r for r in workflow_metrics if isinstance(r.get("output_tokens_approx"), (int, float))]
|
|
422
|
+
if not with_tokens:
|
|
423
|
+
return None
|
|
424
|
+
|
|
425
|
+
agent_map = {}
|
|
426
|
+
for e in with_tokens:
|
|
427
|
+
name = e.get("agent") or "unknown"
|
|
428
|
+
if name not in agent_map:
|
|
429
|
+
agent_map[name] = {"total": 0, "count": 0}
|
|
430
|
+
agent_map[name]["total"] += e["output_tokens_approx"]
|
|
431
|
+
agent_map[name]["count"] += 1
|
|
432
|
+
|
|
433
|
+
grand_total = sum(e["output_tokens_approx"] for e in with_tokens)
|
|
434
|
+
agents = sorted(
|
|
435
|
+
[
|
|
436
|
+
{
|
|
437
|
+
"name": n,
|
|
438
|
+
"total": v["total"],
|
|
439
|
+
"avg": round(v["total"] / v["count"]) if v["count"] else 0,
|
|
440
|
+
"count": v["count"],
|
|
441
|
+
}
|
|
442
|
+
for n, v in agent_map.items()
|
|
443
|
+
],
|
|
444
|
+
key=lambda x: -x["total"],
|
|
445
|
+
)
|
|
446
|
+
return {"agents": agents, "grand_total": grand_total, "entry_count": len(with_tokens)}
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def _calculate_anomaly_summary(anomaly_entries: list):
|
|
450
|
+
cutoff = (datetime.now(timezone.utc) - timedelta(days=30)).isoformat()
|
|
451
|
+
entries = [e for e in anomaly_entries if e and (e.get("timestamp") or "") >= cutoff]
|
|
452
|
+
if not entries:
|
|
453
|
+
return None
|
|
454
|
+
|
|
455
|
+
type_counts = {}
|
|
456
|
+
agent_counts = {}
|
|
457
|
+
for e in entries:
|
|
458
|
+
agent = (e.get("metrics") or {}).get("agent", "unknown")
|
|
459
|
+
for anomaly in e.get("anomalies") or []:
|
|
460
|
+
t = anomaly.get("type", "unknown")
|
|
461
|
+
type_counts[t] = type_counts.get(t, 0) + 1
|
|
462
|
+
agent_counts[agent] = agent_counts.get(agent, 0) + 1
|
|
463
|
+
|
|
464
|
+
total = sum(type_counts.values())
|
|
465
|
+
by_type = sorted(
|
|
466
|
+
[{"type": t, "count": c, "percentage": c / total * 100 if total else 0}
|
|
467
|
+
for t, c in type_counts.items()],
|
|
468
|
+
key=lambda x: -x["count"],
|
|
469
|
+
)
|
|
470
|
+
by_agent = _sorted_counts(agent_counts)[:5]
|
|
471
|
+
|
|
472
|
+
return {
|
|
473
|
+
"total": total,
|
|
474
|
+
"session_count": len(entries),
|
|
475
|
+
"by_type": by_type,
|
|
476
|
+
"by_agent": by_agent,
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
def _calculate_runtime_skill_summary(skill_snapshots: list, run_snapshots: list) -> dict:
|
|
481
|
+
explicit = [e for e in skill_snapshots if e and e.get("agent")]
|
|
482
|
+
run_defaults = [
|
|
483
|
+
{
|
|
484
|
+
"timestamp": e.get("timestamp", ""),
|
|
485
|
+
"session_id": e.get("session_id", ""),
|
|
486
|
+
"agent": e.get("agent"),
|
|
487
|
+
"model": (e.get("default_skills_snapshot") or {}).get("model", ""),
|
|
488
|
+
"tools": (e.get("default_skills_snapshot") or {}).get("tools", []),
|
|
489
|
+
"skills": (e.get("default_skills_snapshot") or {}).get("skills", []),
|
|
490
|
+
"skills_count": (e.get("default_skills_snapshot") or {}).get("skills_count", 0),
|
|
491
|
+
"source": "run-default",
|
|
492
|
+
}
|
|
493
|
+
for e in run_snapshots
|
|
494
|
+
if e and e.get("agent") and e.get("default_skills_snapshot")
|
|
495
|
+
]
|
|
496
|
+
|
|
497
|
+
latest_by_agent = {}
|
|
498
|
+
for snap in run_defaults + explicit:
|
|
499
|
+
agent = snap.get("agent") or "unknown"
|
|
500
|
+
current = latest_by_agent.get(agent)
|
|
501
|
+
if not current or str(snap.get("timestamp", "")) >= str(current.get("timestamp", "")):
|
|
502
|
+
latest_by_agent[agent] = {
|
|
503
|
+
"agent": agent,
|
|
504
|
+
"timestamp": snap.get("timestamp", ""),
|
|
505
|
+
"model": snap.get("model", ""),
|
|
506
|
+
"tools": snap.get("tools") if isinstance(snap.get("tools"), list) else [],
|
|
507
|
+
"skills": snap.get("skills") if isinstance(snap.get("skills"), list) else [],
|
|
508
|
+
"skills_count": snap.get("skills_count") if isinstance(snap.get("skills_count"), int) else len(snap.get("skills") or []),
|
|
509
|
+
"source": snap.get("source", "explicit"),
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
profiles = sorted(latest_by_agent.values(), key=lambda x: x["agent"])
|
|
513
|
+
all_skills = [s for p in profiles for s in p["skills"]]
|
|
514
|
+
top_skills = _top_counts(all_skills, 6)
|
|
515
|
+
|
|
516
|
+
return {
|
|
517
|
+
"explicit_count": len(explicit),
|
|
518
|
+
"run_default_count": len(run_defaults),
|
|
519
|
+
"agent_count": len(profiles),
|
|
520
|
+
"latest_profiles": profiles,
|
|
521
|
+
"top_skills": top_skills,
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def _calculate_context_snapshot_summary(run_snapshots: list):
|
|
526
|
+
with_ctx = [e for e in run_snapshots if e and e.get("context_snapshot")]
|
|
527
|
+
if not with_ctx:
|
|
528
|
+
return None
|
|
529
|
+
|
|
530
|
+
primary_surfaces = []
|
|
531
|
+
contract_sections = []
|
|
532
|
+
writable_sections = []
|
|
533
|
+
multi_surface_count = 0
|
|
534
|
+
|
|
535
|
+
for e in with_ctx:
|
|
536
|
+
snap = e["context_snapshot"]
|
|
537
|
+
sr = snap.get("surface_routing") or {}
|
|
538
|
+
if sr.get("primary_surface"):
|
|
539
|
+
primary_surfaces.append(sr["primary_surface"])
|
|
540
|
+
if sr.get("multi_surface"):
|
|
541
|
+
multi_surface_count += 1
|
|
542
|
+
contract_sections.extend(snap.get("contract_sections") or [])
|
|
543
|
+
writable_sections.extend((snap.get("context_update_scope") or {}).get("writable_sections") or [])
|
|
544
|
+
|
|
545
|
+
return {
|
|
546
|
+
"total": len(with_ctx),
|
|
547
|
+
"multi_surface_count": multi_surface_count,
|
|
548
|
+
"primary_surfaces": _top_counts(primary_surfaces, 6),
|
|
549
|
+
"contract_sections": _top_counts(contract_sections, 6),
|
|
550
|
+
"writable_sections": _top_counts(writable_sections, 6),
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
def _calculate_context_update_summary(run_snapshots: list):
|
|
555
|
+
if not run_snapshots:
|
|
556
|
+
return None
|
|
557
|
+
|
|
558
|
+
updated = [e for e in run_snapshots if e.get("context_updated")]
|
|
559
|
+
rejected = [e for e in run_snapshots if e.get("context_rejected_sections")]
|
|
560
|
+
|
|
561
|
+
updated_sections = [s for e in updated for s in (e.get("context_sections_updated") or [])]
|
|
562
|
+
rejected_sections = [s for e in run_snapshots for s in (e.get("context_rejected_sections") or [])]
|
|
563
|
+
|
|
564
|
+
return {
|
|
565
|
+
"total_runs": len(run_snapshots),
|
|
566
|
+
"updated_runs": len(updated),
|
|
567
|
+
"rejected_runs": len(rejected),
|
|
568
|
+
"updated_sections": _top_counts(updated_sections, 6),
|
|
569
|
+
"rejected_sections": _top_counts(rejected_sections, 6),
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
# ---------------------------------------------------------------------------
|
|
574
|
+
# Display functions
|
|
575
|
+
# ---------------------------------------------------------------------------
|
|
576
|
+
|
|
577
|
+
def _display_metrics(data: dict):
|
|
578
|
+
SEP = "=" * 52
|
|
579
|
+
|
|
580
|
+
print("\nGaia-Ops System Metrics")
|
|
581
|
+
print(SEP)
|
|
582
|
+
|
|
583
|
+
tiers = data["tiers"]
|
|
584
|
+
cmd_types = data["cmd_types"]
|
|
585
|
+
top_cmds = data["top_cmds"]
|
|
586
|
+
agent_inv = data["agent_invocations"]
|
|
587
|
+
error_stats = data["error_stats"]
|
|
588
|
+
audit_total = data["audit_total"]
|
|
589
|
+
agent_outcomes = data["agent_outcomes"]
|
|
590
|
+
token_usage = data["token_usage"]
|
|
591
|
+
anomaly_summary = data["anomaly_summary"]
|
|
592
|
+
runtime_skills = data["runtime_skills"]
|
|
593
|
+
ctx_snapshots = data["context_snapshots"]
|
|
594
|
+
ctx_updates = data["context_updates"]
|
|
595
|
+
|
|
596
|
+
# Security Tier Usage
|
|
597
|
+
print(f"\nSecurity Tier Usage ({tiers['total']} operations)")
|
|
598
|
+
tier_label = {"T0": "read-only", "T1": "validation", "T2": "simulation", "T3": "realization"}
|
|
599
|
+
if tiers["total"] == 0:
|
|
600
|
+
print(" no tier data")
|
|
601
|
+
else:
|
|
602
|
+
for item in tiers["distribution"]:
|
|
603
|
+
tier = item["tier"]
|
|
604
|
+
count = item["count"]
|
|
605
|
+
pct = item["percentage"]
|
|
606
|
+
bar = _make_bar(pct, 14)
|
|
607
|
+
label = tier_label.get(tier, tier)
|
|
608
|
+
suffix = " realization (!)" if tier == "T3" else f" {label}"
|
|
609
|
+
print(f" {tier:<4} {count:>4} {bar:<14} {pct:>5.1f}%{suffix}")
|
|
610
|
+
|
|
611
|
+
# Command Type Breakdown
|
|
612
|
+
print(f"\nCommand Type Breakdown (derived from {audit_total} audit entries)")
|
|
613
|
+
if not cmd_types["breakdown"]:
|
|
614
|
+
print(" no command data")
|
|
615
|
+
else:
|
|
616
|
+
for item in cmd_types["breakdown"]:
|
|
617
|
+
bar = _make_bar(item["percentage"], 10)
|
|
618
|
+
print(f" {item['type']:<12} {item['count']:>4} {bar:<10} {item['percentage']:>5.1f}%")
|
|
619
|
+
|
|
620
|
+
# Top Commands
|
|
621
|
+
print("\nTop Commands")
|
|
622
|
+
if not top_cmds:
|
|
623
|
+
print(" no command data")
|
|
624
|
+
else:
|
|
625
|
+
for item in top_cmds:
|
|
626
|
+
warn = " (!)" if item["t3count"] > 0 else ""
|
|
627
|
+
print(f" {item['label']:<30} {item['count']:>4} {item['tier']}{warn}")
|
|
628
|
+
|
|
629
|
+
# Agent Invocations
|
|
630
|
+
if agent_inv["today_count"] > 0:
|
|
631
|
+
agent_header = f"({agent_inv['today_count']} sessions today)"
|
|
632
|
+
else:
|
|
633
|
+
agent_header = f"({agent_inv['total']} total)"
|
|
634
|
+
print(f"\nAgent Invocations {agent_header}")
|
|
635
|
+
if not agent_inv["agents"]:
|
|
636
|
+
print(" no invocation data")
|
|
637
|
+
else:
|
|
638
|
+
for item in agent_inv["agents"]:
|
|
639
|
+
bar = _make_bar(item["percentage"], 8)
|
|
640
|
+
avg = f"avg {_format_chars(item['avg_output']):>6} chars"
|
|
641
|
+
ok_pct = item["success_rate"]
|
|
642
|
+
ok_str = f"{ok_pct:.0f}% ok"
|
|
643
|
+
print(f" {item['name']:<24} {item['count']:>3} {bar:<8} {avg} {ok_str}")
|
|
644
|
+
print(" tip: gaia metrics --agent <name> for detail view")
|
|
645
|
+
|
|
646
|
+
# Agent Outcomes
|
|
647
|
+
if agent_outcomes:
|
|
648
|
+
print(f"\nAgent Outcomes ({agent_outcomes['total']} sessions with status)")
|
|
649
|
+
for item in agent_outcomes["distribution"]:
|
|
650
|
+
bar = _make_bar(item["percentage"], 10)
|
|
651
|
+
print(f" {item['status']:<16} {item['count']:>3} {bar:<10} {item['percentage']:>5.1f}%")
|
|
652
|
+
|
|
653
|
+
# Token Usage
|
|
654
|
+
if token_usage:
|
|
655
|
+
print(f"\nToken Usage (approx) total: ~{_format_tokens(token_usage['grand_total'])}")
|
|
656
|
+
for item in token_usage["agents"]:
|
|
657
|
+
print(
|
|
658
|
+
f" {item['name']:<24} {item['count']:>3} sessions"
|
|
659
|
+
f" total {_format_tokens(item['total']):>6}"
|
|
660
|
+
f" avg {_format_tokens(item['avg']):>6}"
|
|
661
|
+
)
|
|
662
|
+
|
|
663
|
+
# Runtime Skill Snapshots
|
|
664
|
+
if runtime_skills and runtime_skills["agent_count"] > 0:
|
|
665
|
+
rs = runtime_skills
|
|
666
|
+
print(
|
|
667
|
+
f"\nRuntime Skill Snapshots ({rs['agent_count']} agents, "
|
|
668
|
+
f"{rs['explicit_count']} explicit, {rs['run_default_count']} run defaults)"
|
|
669
|
+
)
|
|
670
|
+
for profile in rs["latest_profiles"][:6]:
|
|
671
|
+
model = profile.get("model") or "default"
|
|
672
|
+
print(
|
|
673
|
+
f" {profile['agent']:<24} model {model:<8} "
|
|
674
|
+
f"skills {profile['skills_count']:>2} tools {len(profile['tools']):>2} "
|
|
675
|
+
f"{_format_skills(profile['skills'], 3)}"
|
|
676
|
+
)
|
|
677
|
+
if len(rs["latest_profiles"]) > 6:
|
|
678
|
+
print(f" ... {len(rs['latest_profiles']) - 6} more agents with captured snapshots")
|
|
679
|
+
print(f" Common skills: {_format_count_summary(rs['top_skills'])}")
|
|
680
|
+
|
|
681
|
+
# Context Snapshot Summary
|
|
682
|
+
if ctx_snapshots:
|
|
683
|
+
print(f"\nContext Snapshot Summary ({ctx_snapshots['total']} sessions)")
|
|
684
|
+
print(f" Primary surfaces: {_format_count_summary(ctx_snapshots['primary_surfaces'])}")
|
|
685
|
+
print(f" Multi-surface: {ctx_snapshots['multi_surface_count']}/{ctx_snapshots['total']} sessions")
|
|
686
|
+
print(f" Contract sections: {_format_count_summary(ctx_snapshots['contract_sections'])}")
|
|
687
|
+
if ctx_snapshots["writable_sections"]:
|
|
688
|
+
print(f" Writable scope: {_format_count_summary(ctx_snapshots['writable_sections'])}")
|
|
689
|
+
|
|
690
|
+
# Context Updates
|
|
691
|
+
if ctx_updates:
|
|
692
|
+
print(f"\nContext Updates ({ctx_updates['updated_runs']}/{ctx_updates['total_runs']} sessions updated)")
|
|
693
|
+
print(f" Rejected writes: {ctx_updates['rejected_runs']} sessions")
|
|
694
|
+
print(f" Updated sections: {_format_count_summary(ctx_updates['updated_sections'])}")
|
|
695
|
+
if ctx_updates["rejected_sections"]:
|
|
696
|
+
print(f" Rejected sections: {_format_count_summary(ctx_updates['rejected_sections'])}")
|
|
697
|
+
|
|
698
|
+
# Anomaly Summary
|
|
699
|
+
if anomaly_summary and anomaly_summary["total"] > 0:
|
|
700
|
+
a = anomaly_summary
|
|
701
|
+
print(f"\nAnomaly Summary (last 30 days) {a['total']} anomalies across {a['session_count']} sessions")
|
|
702
|
+
for item in a["by_type"]:
|
|
703
|
+
bar = _make_bar(item["percentage"], 10)
|
|
704
|
+
print(f" {item['type']:<28} {item['count']:>3} {bar:<10} {item['percentage']:>5.1f}%")
|
|
705
|
+
if a["by_agent"]:
|
|
706
|
+
print(f" Agents: {_format_count_summary(a['by_agent'])}")
|
|
707
|
+
|
|
708
|
+
# Activity Today
|
|
709
|
+
print("\nActivity Today")
|
|
710
|
+
print(f" Total calls: {tiers['today_count']}")
|
|
711
|
+
print(f" T3 operations: {tiers['today_t3']}" + (" (!)" if tiers["today_t3"] > 0 else ""))
|
|
712
|
+
if tiers["peak_hour"] is not None:
|
|
713
|
+
print(f" Peak hour: {tiers['peak_hour']}:00-{tiers['peak_hour']}:59 ({tiers['peak_count']} calls)")
|
|
714
|
+
else:
|
|
715
|
+
print(" Peak hour: no data for today")
|
|
716
|
+
|
|
717
|
+
if error_stats["limited_by_api"]:
|
|
718
|
+
print(" Error rate: n/a (hook API limitation -- exit_code always 0)")
|
|
719
|
+
elif error_stats["total"] == 0:
|
|
720
|
+
print(" Error rate: no exit_code data")
|
|
721
|
+
else:
|
|
722
|
+
print(f" Error rate: {error_stats['errors']}/{error_stats['total']} ({error_stats['error_rate']:.1f}%)")
|
|
723
|
+
|
|
724
|
+
print("\n" + SEP)
|
|
725
|
+
print("Source: .claude/logs/audit-*.jsonl | episodic-memory/index.json | workflow-episodic-memory/*.jsonl\n")
|
|
726
|
+
|
|
727
|
+
|
|
728
|
+
def _display_agent_detail(root: Path, agent_name: str, data: dict):
|
|
729
|
+
SEP = "=" * 52
|
|
730
|
+
wm = data["workflow_metrics"]
|
|
731
|
+
audit_logs = data["audit_logs"]
|
|
732
|
+
run_snapshots = data["run_snapshots"]
|
|
733
|
+
skill_snapshots = data["skill_snapshots"]
|
|
734
|
+
anomaly_entries = data["anomaly_entries"]
|
|
735
|
+
|
|
736
|
+
print(f"\nAgent: {agent_name}")
|
|
737
|
+
print(SEP)
|
|
738
|
+
|
|
739
|
+
# Profile
|
|
740
|
+
print("\nProfile")
|
|
741
|
+
agent_def = _read_agent_definition(root, agent_name)
|
|
742
|
+
if not agent_def:
|
|
743
|
+
print(" Agent definition not found in .claude/agents/")
|
|
744
|
+
else:
|
|
745
|
+
if agent_def.get("description"):
|
|
746
|
+
print(f" Description: {agent_def['description']}")
|
|
747
|
+
if agent_def.get("skills"):
|
|
748
|
+
skills_str = ", ".join(agent_def["skills"])
|
|
749
|
+
if len(skills_str) <= 60:
|
|
750
|
+
print(f" Skills: {skills_str}")
|
|
751
|
+
else:
|
|
752
|
+
# Wrap skills at ~60 chars
|
|
753
|
+
chunks = []
|
|
754
|
+
current = []
|
|
755
|
+
length = 0
|
|
756
|
+
for s in agent_def["skills"]:
|
|
757
|
+
if length + len(s) + 2 > 56 and current:
|
|
758
|
+
chunks.append(", ".join(current))
|
|
759
|
+
current = [s]
|
|
760
|
+
length = len(s)
|
|
761
|
+
else:
|
|
762
|
+
current.append(s)
|
|
763
|
+
length += len(s) + 2
|
|
764
|
+
if current:
|
|
765
|
+
chunks.append(", ".join(current))
|
|
766
|
+
print(f" Skills: {chunks[0]}")
|
|
767
|
+
for chunk in chunks[1:]:
|
|
768
|
+
print(f" {chunk}")
|
|
769
|
+
|
|
770
|
+
# Runtime Snapshot (latest profile for this agent)
|
|
771
|
+
print("\nRuntime Snapshot")
|
|
772
|
+
# Find latest snapshot
|
|
773
|
+
explicit = [e for e in skill_snapshots if e.get("agent") == agent_name]
|
|
774
|
+
run_defaults = [e for e in run_snapshots if e.get("agent") == agent_name and e.get("default_skills_snapshot")]
|
|
775
|
+
all_snaps = sorted(
|
|
776
|
+
[{"ts": e.get("timestamp", ""), "source": "explicit", **e} for e in explicit]
|
|
777
|
+
+ [{"ts": e.get("timestamp", ""), "source": "run-default", "model": (e.get("default_skills_snapshot") or {}).get("model", ""), "tools": (e.get("default_skills_snapshot") or {}).get("tools", []), "skills": (e.get("default_skills_snapshot") or {}).get("skills", []), "skills_count": (e.get("default_skills_snapshot") or {}).get("skills_count", 0)} for e in run_defaults],
|
|
778
|
+
key=lambda x: x["ts"],
|
|
779
|
+
reverse=True,
|
|
780
|
+
)
|
|
781
|
+
if not all_snaps:
|
|
782
|
+
print(" no runtime skill snapshot data")
|
|
783
|
+
else:
|
|
784
|
+
latest = all_snaps[0]
|
|
785
|
+
print(f" Latest model: {latest.get('model') or 'default'}")
|
|
786
|
+
src_label = "agent-skills.jsonl" if latest.get("source") == "explicit" else "run-snapshots default profile"
|
|
787
|
+
print(f" Snapshot source: {src_label}")
|
|
788
|
+
print(f" Snapshots seen: {len(explicit)} explicit, {len(run_defaults)} run defaults")
|
|
789
|
+
tools = latest.get("tools") or []
|
|
790
|
+
print(f" Tools: {', '.join(tools) if tools else 'none'}")
|
|
791
|
+
skills = latest.get("skills") or []
|
|
792
|
+
print(f" Skills: {_format_skills(skills, 6)}")
|
|
793
|
+
|
|
794
|
+
# Invocation History
|
|
795
|
+
agent_sessions = sorted(
|
|
796
|
+
[r for r in wm if r.get("agent") == agent_name],
|
|
797
|
+
key=lambda r: r.get("timestamp") or "",
|
|
798
|
+
)
|
|
799
|
+
success_count = sum(1 for r in agent_sessions if r.get("exit_code") == 0)
|
|
800
|
+
total_output = sum(r.get("output_length") or 0 for r in agent_sessions)
|
|
801
|
+
avg_output = round(total_output / len(agent_sessions)) if agent_sessions else 0
|
|
802
|
+
|
|
803
|
+
print("\nInvocation History (last 7 days)")
|
|
804
|
+
if not agent_sessions:
|
|
805
|
+
print(" no invocations found in episodic-memory/index.json")
|
|
806
|
+
else:
|
|
807
|
+
print(
|
|
808
|
+
f" Total: {len(agent_sessions)} invocations | "
|
|
809
|
+
f"Success: {success_count}/{len(agent_sessions)} | "
|
|
810
|
+
f"Avg output: {_format_chars(avg_output)} chars"
|
|
811
|
+
)
|
|
812
|
+
print()
|
|
813
|
+
for session in agent_sessions:
|
|
814
|
+
dt = (session.get("timestamp") or "")[:16].replace("T", " ")
|
|
815
|
+
ok = "ok" if session.get("exit_code") == 0 else "!!"
|
|
816
|
+
chars = f"{session.get('output_length') or 0:,}"
|
|
817
|
+
task_short = (session.get("task_id") or "n/a")[:8]
|
|
818
|
+
print(f" {dt} {ok} {chars:>7} chars task: {task_short}")
|
|
819
|
+
|
|
820
|
+
# Context Snapshot Summary
|
|
821
|
+
agent_run_snaps = [e for e in run_snapshots if e.get("agent") == agent_name]
|
|
822
|
+
agent_ctx = _calculate_context_snapshot_summary(agent_run_snaps)
|
|
823
|
+
agent_ctx_updates = _calculate_context_update_summary(agent_run_snaps)
|
|
824
|
+
|
|
825
|
+
print("\nContext Snapshot Summary")
|
|
826
|
+
if not agent_ctx:
|
|
827
|
+
print(" no context snapshot data")
|
|
828
|
+
else:
|
|
829
|
+
print(f" Sessions with context: {agent_ctx['total']}")
|
|
830
|
+
print(f" Primary surfaces: {_format_count_summary(agent_ctx['primary_surfaces'])}")
|
|
831
|
+
print(f" Multi-surface: {agent_ctx['multi_surface_count']}/{agent_ctx['total']}")
|
|
832
|
+
print(f" Contract sections: {_format_count_summary(agent_ctx['contract_sections'])}")
|
|
833
|
+
if agent_ctx["writable_sections"]:
|
|
834
|
+
print(f" Writable scope: {_format_count_summary(agent_ctx['writable_sections'])}")
|
|
835
|
+
|
|
836
|
+
# Context Updates + Anomalies
|
|
837
|
+
agent_anomalies_entries = [e for e in anomaly_entries if (e.get("metrics") or {}).get("agent") == agent_name]
|
|
838
|
+
agent_anomaly_type_counts = {}
|
|
839
|
+
for e in agent_anomalies_entries:
|
|
840
|
+
for anomaly in e.get("anomalies") or []:
|
|
841
|
+
t = anomaly.get("type", "unknown")
|
|
842
|
+
agent_anomaly_type_counts[t] = agent_anomaly_type_counts.get(t, 0) + 1
|
|
843
|
+
agent_anomaly_total = sum(agent_anomaly_type_counts.values())
|
|
844
|
+
agent_anomaly_by_type = _sorted_counts(agent_anomaly_type_counts)[:6]
|
|
845
|
+
|
|
846
|
+
print("\nContext Updates + Anomalies")
|
|
847
|
+
if not agent_ctx_updates and not agent_anomaly_total:
|
|
848
|
+
print(" no context update or anomaly data")
|
|
849
|
+
else:
|
|
850
|
+
if agent_ctx_updates:
|
|
851
|
+
print(f" Context updated: {agent_ctx_updates['updated_runs']}/{agent_ctx_updates['total_runs']} sessions")
|
|
852
|
+
print(f" Updated sections: {_format_count_summary(agent_ctx_updates['updated_sections'])}")
|
|
853
|
+
if agent_ctx_updates["rejected_sections"]:
|
|
854
|
+
print(f" Rejected sections: {_format_count_summary(agent_ctx_updates['rejected_sections'])}")
|
|
855
|
+
if agent_anomaly_total:
|
|
856
|
+
print(f" Anomalies: {agent_anomaly_total} across {len(agent_anomalies_entries)} sessions")
|
|
857
|
+
print(f" Types: {_format_count_summary(agent_anomaly_by_type)}")
|
|
858
|
+
|
|
859
|
+
# Top Commands (correlated from audit log -- approximate)
|
|
860
|
+
print("\nTop Commands (sampled from audit log, approximate time windows)")
|
|
861
|
+
if not agent_sessions or not audit_logs:
|
|
862
|
+
print(" no data to correlate")
|
|
863
|
+
else:
|
|
864
|
+
named_stops = sorted([r for r in wm if r.get("agent")], key=lambda r: r.get("timestamp") or "")
|
|
865
|
+
tier_order = {"T3": 3, "T2": 2, "T1": 1, "T0": 0, "unknown": -1}
|
|
866
|
+
label_map = {}
|
|
867
|
+
|
|
868
|
+
for i, session in enumerate(agent_sessions):
|
|
869
|
+
# Find this session's position in named_stops
|
|
870
|
+
stop_idx = next(
|
|
871
|
+
(j for j, r in enumerate(named_stops) if r.get("task_id") == session.get("task_id")),
|
|
872
|
+
-1,
|
|
873
|
+
)
|
|
874
|
+
prev_stop = named_stops[stop_idx - 1] if stop_idx > 0 else None
|
|
875
|
+
window_start = (prev_stop or {}).get("timestamp")
|
|
876
|
+
window_end = session.get("timestamp")
|
|
877
|
+
|
|
878
|
+
if not window_end:
|
|
879
|
+
continue
|
|
880
|
+
|
|
881
|
+
end_ts = _parse_ts(window_end)
|
|
882
|
+
start_ts = _parse_ts(window_start) if window_start else end_ts - 600
|
|
883
|
+
|
|
884
|
+
for e in audit_logs:
|
|
885
|
+
if not e.get("command") or not e.get("timestamp"):
|
|
886
|
+
continue
|
|
887
|
+
ts = _parse_ts(e["timestamp"])
|
|
888
|
+
if start_ts <= ts <= end_ts:
|
|
889
|
+
label = _extract_command_label(e["command"])
|
|
890
|
+
tier = e.get("tier") or "unknown"
|
|
891
|
+
if label not in label_map:
|
|
892
|
+
label_map[label] = {"count": 0, "tier": tier, "t3count": 0}
|
|
893
|
+
label_map[label]["count"] += 1
|
|
894
|
+
if tier == "T3":
|
|
895
|
+
label_map[label]["t3count"] += 1
|
|
896
|
+
if tier_order.get(tier, -1) > tier_order.get(label_map[label]["tier"], -1):
|
|
897
|
+
label_map[label]["tier"] = tier
|
|
898
|
+
|
|
899
|
+
top = sorted(
|
|
900
|
+
[{"label": l, **v} for l, v in label_map.items()],
|
|
901
|
+
key=lambda x: -x["count"],
|
|
902
|
+
)[:10]
|
|
903
|
+
|
|
904
|
+
if not top:
|
|
905
|
+
print(" no overlapping commands found in audit window")
|
|
906
|
+
else:
|
|
907
|
+
for item in top:
|
|
908
|
+
warn = " (!)" if item["t3count"] > 0 else ""
|
|
909
|
+
print(f" {item['tier']:<3} {item['label']:<28} {item['count']:>4}{warn}")
|
|
910
|
+
print("\n Note: command windows are approximated from SubagentStop timestamps")
|
|
911
|
+
|
|
912
|
+
print("\n" + SEP + "\n")
|
|
913
|
+
|
|
914
|
+
|
|
915
|
+
def _parse_ts(ts_str: str) -> float:
|
|
916
|
+
"""Parse ISO timestamp to Unix seconds."""
|
|
917
|
+
try:
|
|
918
|
+
return datetime.fromisoformat(ts_str.replace("Z", "+00:00")).timestamp()
|
|
919
|
+
except (ValueError, AttributeError):
|
|
920
|
+
return 0.0
|
|
921
|
+
|
|
922
|
+
|
|
923
|
+
# ---------------------------------------------------------------------------
|
|
924
|
+
# Plugin interface
|
|
925
|
+
# ---------------------------------------------------------------------------
|
|
926
|
+
|
|
927
|
+
def register(subparsers):
|
|
928
|
+
"""Register the 'metrics' subcommand."""
|
|
929
|
+
p = subparsers.add_parser(
|
|
930
|
+
"metrics",
|
|
931
|
+
help="Show system metrics dashboard (tiers, commands, agents, anomalies)",
|
|
932
|
+
description=(
|
|
933
|
+
"Display Gaia-Ops system metrics dashboard.\n"
|
|
934
|
+
"\n"
|
|
935
|
+
"Data sources:\n"
|
|
936
|
+
" .claude/logs/audit-*.jsonl\n"
|
|
937
|
+
" .claude/project-context/episodic-memory/index.json\n"
|
|
938
|
+
" .claude/project-context/workflow-episodic-memory/\n"
|
|
939
|
+
),
|
|
940
|
+
)
|
|
941
|
+
p.add_argument(
|
|
942
|
+
"--agent",
|
|
943
|
+
metavar="NAME",
|
|
944
|
+
default=None,
|
|
945
|
+
help="Show detail view for a specific agent",
|
|
946
|
+
)
|
|
947
|
+
p.add_argument(
|
|
948
|
+
"--json",
|
|
949
|
+
action="store_true",
|
|
950
|
+
default=False,
|
|
951
|
+
help="Output results as JSON",
|
|
952
|
+
)
|
|
953
|
+
return p
|
|
954
|
+
|
|
955
|
+
|
|
956
|
+
def cmd_metrics(args) -> int:
|
|
957
|
+
"""Execute the metrics subcommand."""
|
|
958
|
+
root = _find_project_root()
|
|
959
|
+
claude_dir = root / ".claude"
|
|
960
|
+
agent_name = getattr(args, "agent", None)
|
|
961
|
+
as_json = getattr(args, "json", False)
|
|
962
|
+
|
|
963
|
+
if not claude_dir.exists():
|
|
964
|
+
if as_json:
|
|
965
|
+
print(json.dumps({"error": "gaia-ops not installed in this directory"}))
|
|
966
|
+
else:
|
|
967
|
+
print("\nGaia-ops not installed in this directory")
|
|
968
|
+
print("Run: npx gaia-scan\n")
|
|
969
|
+
return 1
|
|
970
|
+
|
|
971
|
+
audit_logs = _read_audit_logs(root)
|
|
972
|
+
workflow_metrics = _read_workflow_metrics(root)
|
|
973
|
+
run_snapshots = _read_run_snapshots(root)
|
|
974
|
+
skill_snapshots = _read_agent_skill_snapshots(root)
|
|
975
|
+
anomaly_entries = _read_anomaly_entries(root)
|
|
976
|
+
|
|
977
|
+
if not audit_logs and not workflow_metrics and not run_snapshots and not skill_snapshots and not anomaly_entries:
|
|
978
|
+
if as_json:
|
|
979
|
+
empty_output = {
|
|
980
|
+
"security_tiers": {"total": 0, "distribution": [], "today_count": 0, "today_t3": 0, "peak_hour": None, "peak_count": 0},
|
|
981
|
+
"cmd_types": {"total": 0, "breakdown": []},
|
|
982
|
+
"top_cmds": [],
|
|
983
|
+
"agent_invocations": {"agents": [], "total": 0, "today_count": 0},
|
|
984
|
+
"error_stats": {"total": 0, "errors": 0, "error_rate": 0, "limited_by_api": False},
|
|
985
|
+
"agent_outcomes": None,
|
|
986
|
+
"token_usage": None,
|
|
987
|
+
"anomaly_summary": None,
|
|
988
|
+
"runtime_skills": {"explicit_count": 0, "run_default_count": 0, "agent_count": 0, "latest_profiles": [], "top_skills": []},
|
|
989
|
+
"context_snapshots": None,
|
|
990
|
+
"context_updates": None,
|
|
991
|
+
}
|
|
992
|
+
print(json.dumps(empty_output))
|
|
993
|
+
else:
|
|
994
|
+
print("\nNo metrics data available yet")
|
|
995
|
+
print("Metrics will be generated as you use the system\n")
|
|
996
|
+
return 0
|
|
997
|
+
|
|
998
|
+
if as_json:
|
|
999
|
+
# Compute all metrics and return as JSON
|
|
1000
|
+
tiers = _calculate_tier_usage(audit_logs)
|
|
1001
|
+
cmd_types = _calculate_command_type_breakdown(audit_logs)
|
|
1002
|
+
top_cmds = _calculate_top_commands(audit_logs)
|
|
1003
|
+
agent_inv = _calculate_agent_invocations(workflow_metrics)
|
|
1004
|
+
error_stats = _calculate_error_rate(audit_logs)
|
|
1005
|
+
agent_outcomes = _calculate_agent_outcomes(workflow_metrics)
|
|
1006
|
+
token_usage = _calculate_token_usage(workflow_metrics)
|
|
1007
|
+
anomaly_summary = _calculate_anomaly_summary(anomaly_entries)
|
|
1008
|
+
runtime_skills = _calculate_runtime_skill_summary(skill_snapshots, run_snapshots)
|
|
1009
|
+
ctx_snapshots = _calculate_context_snapshot_summary(run_snapshots)
|
|
1010
|
+
ctx_updates = _calculate_context_update_summary(run_snapshots)
|
|
1011
|
+
|
|
1012
|
+
output = {
|
|
1013
|
+
"security_tiers": tiers,
|
|
1014
|
+
"cmd_types": cmd_types,
|
|
1015
|
+
"top_cmds": top_cmds,
|
|
1016
|
+
"agent_invocations": agent_inv,
|
|
1017
|
+
"error_stats": error_stats,
|
|
1018
|
+
"agent_outcomes": agent_outcomes,
|
|
1019
|
+
"token_usage": token_usage,
|
|
1020
|
+
"anomaly_summary": anomaly_summary,
|
|
1021
|
+
"runtime_skills": runtime_skills,
|
|
1022
|
+
"context_snapshots": ctx_snapshots,
|
|
1023
|
+
"context_updates": ctx_updates,
|
|
1024
|
+
}
|
|
1025
|
+
if agent_name:
|
|
1026
|
+
output["agent_filter"] = agent_name
|
|
1027
|
+
print(json.dumps(output, indent=2))
|
|
1028
|
+
return 0
|
|
1029
|
+
|
|
1030
|
+
data = {
|
|
1031
|
+
"workflow_metrics": workflow_metrics,
|
|
1032
|
+
"audit_logs": audit_logs,
|
|
1033
|
+
"run_snapshots": run_snapshots,
|
|
1034
|
+
"skill_snapshots": skill_snapshots,
|
|
1035
|
+
"anomaly_entries": anomaly_entries,
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
if agent_name:
|
|
1039
|
+
_display_agent_detail(root, agent_name, data)
|
|
1040
|
+
else:
|
|
1041
|
+
tiers = _calculate_tier_usage(audit_logs)
|
|
1042
|
+
cmd_types = _calculate_command_type_breakdown(audit_logs)
|
|
1043
|
+
top_cmds = _calculate_top_commands(audit_logs)
|
|
1044
|
+
agent_inv = _calculate_agent_invocations(workflow_metrics)
|
|
1045
|
+
error_stats = _calculate_error_rate(audit_logs)
|
|
1046
|
+
agent_outcomes = _calculate_agent_outcomes(workflow_metrics)
|
|
1047
|
+
token_usage = _calculate_token_usage(workflow_metrics)
|
|
1048
|
+
anomaly_summary = _calculate_anomaly_summary(anomaly_entries)
|
|
1049
|
+
runtime_skills = _calculate_runtime_skill_summary(skill_snapshots, run_snapshots)
|
|
1050
|
+
ctx_snapshots = _calculate_context_snapshot_summary(run_snapshots)
|
|
1051
|
+
ctx_updates = _calculate_context_update_summary(run_snapshots)
|
|
1052
|
+
|
|
1053
|
+
_display_metrics({
|
|
1054
|
+
"tiers": tiers,
|
|
1055
|
+
"cmd_types": cmd_types,
|
|
1056
|
+
"top_cmds": top_cmds,
|
|
1057
|
+
"agent_invocations": agent_inv,
|
|
1058
|
+
"error_stats": error_stats,
|
|
1059
|
+
"audit_total": len(audit_logs),
|
|
1060
|
+
"agent_outcomes": agent_outcomes,
|
|
1061
|
+
"token_usage": token_usage,
|
|
1062
|
+
"anomaly_summary": anomaly_summary,
|
|
1063
|
+
"runtime_skills": runtime_skills,
|
|
1064
|
+
"context_snapshots": ctx_snapshots,
|
|
1065
|
+
"context_updates": ctx_updates,
|
|
1066
|
+
})
|
|
1067
|
+
|
|
1068
|
+
return 0
|