@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,1278 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @jaguilar87/gaia - Metrics viewer
|
|
5
|
+
*
|
|
6
|
+
* Displays system metrics using real fields from audit/metrics logs.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* npx gaia-metrics # full dashboard
|
|
10
|
+
* npx gaia-metrics --agent <name> # agent detail view
|
|
11
|
+
*
|
|
12
|
+
* Metrics shown:
|
|
13
|
+
* - Security tier usage distribution
|
|
14
|
+
* - Command type breakdown (terraform, git, kubernetes, etc.)
|
|
15
|
+
* - Top commands by frequency
|
|
16
|
+
* - Agent invocations (from episodic-memory/index.json)
|
|
17
|
+
* - Anomaly summary (last 30 days, from anomalies.jsonl)
|
|
18
|
+
* - Activity summary for today
|
|
19
|
+
*
|
|
20
|
+
* Data sources:
|
|
21
|
+
* - .claude/logs/audit-*.jsonl (SSOT for command metrics)
|
|
22
|
+
* - .claude/project-context/episodic-memory/index.json (primary, agent metrics)
|
|
23
|
+
* - .claude/project-context/workflow-episodic-memory/metrics.jsonl (fallback)
|
|
24
|
+
* - .claude/project-context/workflow-episodic-memory/anomalies.jsonl
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { join, dirname, resolve } from 'path';
|
|
28
|
+
import fs from 'fs/promises';
|
|
29
|
+
import { existsSync } from 'fs';
|
|
30
|
+
import chalk from 'chalk';
|
|
31
|
+
import ora from 'ora';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Find the project root directory by looking for .claude/ directory
|
|
35
|
+
*/
|
|
36
|
+
function findProjectRoot() {
|
|
37
|
+
if (process.env.INIT_CWD) {
|
|
38
|
+
const claudeDir = join(process.env.INIT_CWD, '.claude');
|
|
39
|
+
if (existsSync(claudeDir)) {
|
|
40
|
+
return process.env.INIT_CWD;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let currentDir = process.cwd();
|
|
45
|
+
const root = resolve('/');
|
|
46
|
+
|
|
47
|
+
while (currentDir !== root) {
|
|
48
|
+
const claudeDir = join(currentDir, '.claude');
|
|
49
|
+
if (existsSync(claudeDir)) {
|
|
50
|
+
return currentDir;
|
|
51
|
+
}
|
|
52
|
+
currentDir = dirname(currentDir);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return process.env.INIT_CWD || process.cwd();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const CWD = findProjectRoot();
|
|
59
|
+
|
|
60
|
+
async function readJsonLines(path) {
|
|
61
|
+
if (!existsSync(path)) return [];
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
const content = await fs.readFile(path, 'utf-8');
|
|
65
|
+
return content.split('\n')
|
|
66
|
+
.filter(line => line.trim())
|
|
67
|
+
.map(line => {
|
|
68
|
+
try {
|
|
69
|
+
return JSON.parse(line);
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
.filter(Boolean);
|
|
75
|
+
} catch {
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ─────────────────────────────────────────────────────────────
|
|
81
|
+
// DATA READERS
|
|
82
|
+
// ─────────────────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Read audit logs from .claude/logs/audit-*.jsonl
|
|
86
|
+
* Fields: timestamp, tool_name, command, tier, exit_code, session_id
|
|
87
|
+
*/
|
|
88
|
+
async function readAuditLogs() {
|
|
89
|
+
try {
|
|
90
|
+
const logsDir = join(CWD, '.claude', 'logs');
|
|
91
|
+
if (!existsSync(logsDir)) return [];
|
|
92
|
+
|
|
93
|
+
const files = await fs.readdir(logsDir);
|
|
94
|
+
const auditFiles = files.filter(f => f.startsWith('audit-') && f.endsWith('.jsonl'));
|
|
95
|
+
|
|
96
|
+
let all = [];
|
|
97
|
+
for (const file of auditFiles) {
|
|
98
|
+
try {
|
|
99
|
+
const content = await fs.readFile(join(logsDir, file), 'utf-8');
|
|
100
|
+
const parsed = content.split('\n')
|
|
101
|
+
.filter(l => l.trim())
|
|
102
|
+
.map(l => { try { return JSON.parse(l); } catch { return null; } })
|
|
103
|
+
.filter(Boolean);
|
|
104
|
+
all = all.concat(parsed);
|
|
105
|
+
} catch { /* skip */ }
|
|
106
|
+
}
|
|
107
|
+
return all;
|
|
108
|
+
} catch {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// metrics-*.jsonl removed: audit logs are the SSOT for command metrics.
|
|
114
|
+
// The command_type field is now derived via classifyCommand() from the
|
|
115
|
+
// audit log's command field. Set GAIA_WRITE_METRICS=1 to re-enable writing.
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Read workflow metrics from the episodic memory index (primary source).
|
|
119
|
+
* Falls back to workflow-episodic-memory/metrics.jsonl for backward compatibility.
|
|
120
|
+
* Fields: timestamp, agent, exit_code, output_length, task_id, session_id,
|
|
121
|
+
* plan_status, output_tokens_approx, prompt
|
|
122
|
+
*/
|
|
123
|
+
async function readWorkflowMetrics() {
|
|
124
|
+
// Primary: episodic-memory/index.json
|
|
125
|
+
try {
|
|
126
|
+
const indexPath = join(CWD, '.claude', 'project-context', 'episodic-memory', 'index.json');
|
|
127
|
+
if (existsSync(indexPath)) {
|
|
128
|
+
const data = JSON.parse(await fs.readFile(indexPath, 'utf-8'));
|
|
129
|
+
const episodes = (data.episodes || []).filter(e => e.agent);
|
|
130
|
+
if (episodes.length > 0) return episodes;
|
|
131
|
+
}
|
|
132
|
+
} catch { /* fall through to legacy */ }
|
|
133
|
+
|
|
134
|
+
// Fallback: workflow-episodic-memory/metrics.jsonl
|
|
135
|
+
try {
|
|
136
|
+
const metricsPath = join(CWD, '.claude', 'project-context', 'workflow-episodic-memory', 'metrics.jsonl');
|
|
137
|
+
if (!existsSync(metricsPath)) return [];
|
|
138
|
+
|
|
139
|
+
const content = await fs.readFile(metricsPath, 'utf-8');
|
|
140
|
+
return content.split('\n')
|
|
141
|
+
.filter(l => l.trim())
|
|
142
|
+
.map(l => { try { return JSON.parse(l); } catch { return null; } })
|
|
143
|
+
.filter(r => r !== null && r.agent); // exclude empty-agent entries
|
|
144
|
+
} catch {
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Read structured run telemetry snapshots from workflow memory.
|
|
151
|
+
* Fields: timestamp, session_id, task_id, agent, tier, plan_status,
|
|
152
|
+
* context_snapshot, context_updated, context_sections_updated,
|
|
153
|
+
* context_rejected_sections, default_skills_snapshot
|
|
154
|
+
*/
|
|
155
|
+
async function readRunSnapshots() {
|
|
156
|
+
const path = join(CWD, '.claude', 'project-context', 'workflow-episodic-memory', 'run-snapshots.jsonl');
|
|
157
|
+
return readJsonLines(path);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Read persisted runtime skill snapshots from workflow memory.
|
|
162
|
+
* Fields: timestamp, session_id, agent, task_description, model, tools,
|
|
163
|
+
* skills, skills_count
|
|
164
|
+
*/
|
|
165
|
+
async function readAgentSkillSnapshots() {
|
|
166
|
+
const path = join(CWD, '.claude', 'project-context', 'workflow-episodic-memory', 'agent-skills.jsonl');
|
|
167
|
+
return readJsonLines(path);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Read anomaly records from workflow memory.
|
|
172
|
+
* Fields: timestamp, anomalies, metrics
|
|
173
|
+
*/
|
|
174
|
+
async function readAnomalyEntries() {
|
|
175
|
+
const path = join(CWD, '.claude', 'project-context', 'workflow-episodic-memory', 'anomalies.jsonl');
|
|
176
|
+
return readJsonLines(path);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Read and parse agent definition from .claude/agents/<name>.md
|
|
181
|
+
* Extracts description and skills list from YAML frontmatter.
|
|
182
|
+
* Returns { description, skills } or null if not found.
|
|
183
|
+
*/
|
|
184
|
+
async function readAgentDefinition(agentName) {
|
|
185
|
+
const agentPath = join(CWD, '.claude', 'agents', `${agentName}.md`);
|
|
186
|
+
if (!existsSync(agentPath)) return null;
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
const content = await fs.readFile(agentPath, 'utf-8');
|
|
190
|
+
if (!content.startsWith('---')) return null;
|
|
191
|
+
|
|
192
|
+
const endIdx = content.indexOf('---', 3);
|
|
193
|
+
if (endIdx === -1) return null;
|
|
194
|
+
|
|
195
|
+
const frontmatter = content.slice(3, endIdx);
|
|
196
|
+
let description = '';
|
|
197
|
+
const skills = [];
|
|
198
|
+
let inSkills = false;
|
|
199
|
+
|
|
200
|
+
for (const line of frontmatter.split('\n')) {
|
|
201
|
+
const trimmed = line.trim();
|
|
202
|
+
if (trimmed.startsWith('description:')) {
|
|
203
|
+
description = trimmed.replace(/^description:\s*/, '').replace(/^['"]|['"]$/g, '');
|
|
204
|
+
inSkills = false;
|
|
205
|
+
} else if (trimmed === 'skills:') {
|
|
206
|
+
inSkills = true;
|
|
207
|
+
} else if (inSkills && trimmed.startsWith('- ')) {
|
|
208
|
+
skills.push(trimmed.slice(2).trim());
|
|
209
|
+
} else if (inSkills && trimmed && !trimmed.startsWith('-')) {
|
|
210
|
+
inSkills = false;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return { description, skills };
|
|
215
|
+
} catch {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ─────────────────────────────────────────────────────────────
|
|
221
|
+
// UTILITY FUNCTIONS
|
|
222
|
+
// ─────────────────────────────────────────────────────────────
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Classify a raw command string into a technology category.
|
|
226
|
+
* Used as fallback when metrics log command_type is unavailable.
|
|
227
|
+
*/
|
|
228
|
+
function classifyCommand(command) {
|
|
229
|
+
if (!command) return 'general';
|
|
230
|
+
const cmd = command.trim().toLowerCase();
|
|
231
|
+
|
|
232
|
+
if (cmd.startsWith('terragrunt') || cmd.startsWith('terraform')) return 'terraform';
|
|
233
|
+
if (cmd.startsWith('kubectl')) return 'kubernetes';
|
|
234
|
+
if (cmd.startsWith('helm') || cmd.startsWith('flux')) return 'gitops';
|
|
235
|
+
if (cmd.startsWith('git') || cmd.startsWith('glab')) return 'git';
|
|
236
|
+
if (cmd.startsWith('gcloud') || cmd.startsWith('gsutil')) return 'gcp';
|
|
237
|
+
if (cmd.startsWith('aws')) return 'aws';
|
|
238
|
+
if (cmd.startsWith('docker')) return 'docker';
|
|
239
|
+
if (cmd.startsWith('npm') || cmd.startsWith('node') || cmd.startsWith('python') || cmd.startsWith('pip')) return 'dev';
|
|
240
|
+
return 'general';
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Extract a short human-readable label from a full command string.
|
|
245
|
+
* Strips wrappers like `timeout 30s`, env vars, and path prefixes.
|
|
246
|
+
* Takes the tool name + first subcommand, truncated to 32 chars.
|
|
247
|
+
*/
|
|
248
|
+
function extractCommandLabel(command) {
|
|
249
|
+
if (!command) return '(unknown)';
|
|
250
|
+
let cmd = command.trim();
|
|
251
|
+
|
|
252
|
+
// Strip leading env var assignments (FOO=bar cmd ...)
|
|
253
|
+
cmd = cmd.replace(/^(?:[A-Z_][A-Z0-9_]*=\S+\s+)+/, '');
|
|
254
|
+
|
|
255
|
+
// Strip timeout wrapper: "timeout 30s <real cmd>"
|
|
256
|
+
cmd = cmd.replace(/^timeout\s+\S+\s+/, '');
|
|
257
|
+
|
|
258
|
+
// Strip leading cd/pushd navigation: "cd /some/path && <real cmd>"
|
|
259
|
+
const cdMatch = cmd.match(/^(?:cd|pushd)\s+\S+\s*(?:&&|;)\s*(.*)/);
|
|
260
|
+
if (cdMatch) cmd = cdMatch[1].trim();
|
|
261
|
+
|
|
262
|
+
// Strip shell redirections and anything after pipe/semicolon/&&
|
|
263
|
+
cmd = cmd.split(/\s*(?:[|;&]|&&|\|\|)\s*/)[0].trim();
|
|
264
|
+
// Strip trailing redirections like 2>&1 or > /tmp/file
|
|
265
|
+
cmd = cmd.replace(/\s*\d*>.*$/, '').trim();
|
|
266
|
+
|
|
267
|
+
const tokens = cmd.split(/\s+/);
|
|
268
|
+
// Take tool (token 0) + first non-flag, non-path, non-quote argument
|
|
269
|
+
const parts = [tokens[0]];
|
|
270
|
+
for (let i = 1; i < tokens.length && parts.length < 3; i++) {
|
|
271
|
+
const t = tokens[i];
|
|
272
|
+
if (!t.startsWith('-') && !t.startsWith('/') && !t.startsWith('"') && !t.startsWith("'")) {
|
|
273
|
+
parts.push(t);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return parts.join(' ').slice(0, 32);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Format output_length as human-readable string (e.g. "23.3k chars").
|
|
281
|
+
*/
|
|
282
|
+
function formatChars(n) {
|
|
283
|
+
if (n === null || n === undefined) return 'n/a';
|
|
284
|
+
if (n >= 1000) return `${(n / 1000).toFixed(1)}k`;
|
|
285
|
+
return `${n}`;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Build a fixed-width bar string using block characters.
|
|
290
|
+
*/
|
|
291
|
+
function makeBar(percentage, maxWidth = 14) {
|
|
292
|
+
const filled = Math.max(0, Math.round((percentage / 100) * maxWidth));
|
|
293
|
+
return '█'.repeat(filled);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// ─────────────────────────────────────────────────────────────
|
|
297
|
+
// METRIC CALCULATORS
|
|
298
|
+
// ─────────────────────────────────────────────────────────────
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Security tier usage distribution.
|
|
302
|
+
* Also extracts today's activity stats and peak hour.
|
|
303
|
+
*/
|
|
304
|
+
function calculateTierUsage(auditLogs) {
|
|
305
|
+
const tierEntries = auditLogs.filter(l => l.tier);
|
|
306
|
+
|
|
307
|
+
const counts = {};
|
|
308
|
+
for (const entry of tierEntries) {
|
|
309
|
+
const t = entry.tier || 'unknown';
|
|
310
|
+
counts[t] = (counts[t] || 0) + 1;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const total = tierEntries.length;
|
|
314
|
+
const distribution = Object.entries(counts)
|
|
315
|
+
.map(([tier, count]) => ({
|
|
316
|
+
tier,
|
|
317
|
+
count,
|
|
318
|
+
percentage: total > 0 ? (count / total * 100) : 0,
|
|
319
|
+
}))
|
|
320
|
+
.sort((a, b) => a.tier.localeCompare(b.tier));
|
|
321
|
+
|
|
322
|
+
// Today's stats
|
|
323
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
324
|
+
const todayEntries = auditLogs.filter(l => l.timestamp && l.timestamp.startsWith(today));
|
|
325
|
+
const todayT3 = todayEntries.filter(l => l.tier === 'T3').length;
|
|
326
|
+
|
|
327
|
+
// Peak hour from today's entries
|
|
328
|
+
const hourCounts = {};
|
|
329
|
+
for (const entry of todayEntries) {
|
|
330
|
+
if (entry.timestamp) {
|
|
331
|
+
const hour = entry.timestamp.slice(11, 13);
|
|
332
|
+
hourCounts[hour] = (hourCounts[hour] || 0) + 1;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
let peakHour = null;
|
|
336
|
+
let peakCount = 0;
|
|
337
|
+
for (const [hour, count] of Object.entries(hourCounts)) {
|
|
338
|
+
if (count > peakCount) { peakCount = count; peakHour = hour; }
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return { total, distribution, todayCount: todayEntries.length, todayT3, peakHour, peakCount };
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Command type breakdown.
|
|
346
|
+
* Derives command_type via classifyCommand() from the audit log command field.
|
|
347
|
+
* Audit logs are the SSOT; metrics-*.jsonl is no longer written.
|
|
348
|
+
*/
|
|
349
|
+
function calculateCommandTypeBreakdown(auditLogs) {
|
|
350
|
+
const counts = {};
|
|
351
|
+
for (const entry of auditLogs) {
|
|
352
|
+
const type = classifyCommand(entry.command);
|
|
353
|
+
counts[type] = (counts[type] || 0) + 1;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const total = auditLogs.length;
|
|
357
|
+
const breakdown = Object.entries(counts)
|
|
358
|
+
.map(([type, count]) => ({
|
|
359
|
+
type,
|
|
360
|
+
count,
|
|
361
|
+
percentage: total > 0 ? (count / total * 100) : 0,
|
|
362
|
+
}))
|
|
363
|
+
.sort((a, b) => b.count - a.count);
|
|
364
|
+
|
|
365
|
+
return { total, breakdown };
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Top 10 commands by frequency from audit logs.
|
|
370
|
+
* Labels are the first 2-3 non-flag tokens of the command string.
|
|
371
|
+
* Tracks the highest tier seen per label and whether any T3 was used.
|
|
372
|
+
*/
|
|
373
|
+
function calculateTopCommands(auditLogs) {
|
|
374
|
+
const tierOrder = { T3: 3, T2: 2, T1: 1, T0: 0, unknown: -1 };
|
|
375
|
+
const labelMap = {};
|
|
376
|
+
|
|
377
|
+
for (const entry of auditLogs) {
|
|
378
|
+
if (!entry.command) continue;
|
|
379
|
+
const label = extractCommandLabel(entry.command);
|
|
380
|
+
const tier = entry.tier || 'unknown';
|
|
381
|
+
|
|
382
|
+
if (!labelMap[label]) {
|
|
383
|
+
labelMap[label] = { count: 0, tier, t3count: 0 };
|
|
384
|
+
}
|
|
385
|
+
labelMap[label].count++;
|
|
386
|
+
if (tier === 'T3') labelMap[label].t3count++;
|
|
387
|
+
if ((tierOrder[tier] ?? -1) > (tierOrder[labelMap[label].tier] ?? -1)) {
|
|
388
|
+
labelMap[label].tier = tier;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return Object.entries(labelMap)
|
|
393
|
+
.map(([label, { count, tier, t3count }]) => ({ label, count, tier, t3count }))
|
|
394
|
+
.sort((a, b) => b.count - a.count)
|
|
395
|
+
.slice(0, 10);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Error rate analysis from audit logs.
|
|
400
|
+
* Detects whether the known hook API bug (exit_code always 0) is present.
|
|
401
|
+
*/
|
|
402
|
+
function calculateErrorRate(auditLogs) {
|
|
403
|
+
const withExitCode = auditLogs.filter(l => 'exit_code' in l);
|
|
404
|
+
const errors = withExitCode.filter(l => l.exit_code !== 0);
|
|
405
|
+
const allZero = withExitCode.length > 0 && errors.length === 0;
|
|
406
|
+
|
|
407
|
+
return {
|
|
408
|
+
total: withExitCode.length,
|
|
409
|
+
errors: errors.length,
|
|
410
|
+
errorRate: withExitCode.length > 0 ? (errors.length / withExitCode.length * 100) : 0,
|
|
411
|
+
limitedByApi: allZero,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Agent invocations summary from workflow episodic metrics.
|
|
417
|
+
* Groups by agent name, computes count, avg output_length, success rate.
|
|
418
|
+
* Also returns todayCount for the header.
|
|
419
|
+
*/
|
|
420
|
+
function calculateAgentInvocations(workflowMetrics) {
|
|
421
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
422
|
+
const todayCount = workflowMetrics.filter(
|
|
423
|
+
r => r.timestamp && r.timestamp.startsWith(today)
|
|
424
|
+
).length;
|
|
425
|
+
|
|
426
|
+
const agentMap = {};
|
|
427
|
+
for (const entry of workflowMetrics) {
|
|
428
|
+
const name = entry.agent;
|
|
429
|
+
if (!agentMap[name]) {
|
|
430
|
+
agentMap[name] = { count: 0, totalOutput: 0, successes: 0 };
|
|
431
|
+
}
|
|
432
|
+
agentMap[name].count++;
|
|
433
|
+
agentMap[name].totalOutput += (entry.output_length || 0);
|
|
434
|
+
if (entry.exit_code === 0) agentMap[name].successes++;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const total = workflowMetrics.length;
|
|
438
|
+
const agents = Object.entries(agentMap)
|
|
439
|
+
.map(([name, { count, totalOutput, successes }]) => ({
|
|
440
|
+
name,
|
|
441
|
+
count,
|
|
442
|
+
avgOutput: count > 0 ? Math.round(totalOutput / count) : 0,
|
|
443
|
+
successRate: count > 0 ? (successes / count * 100) : 0,
|
|
444
|
+
percentage: total > 0 ? (count / total * 100) : 0,
|
|
445
|
+
}))
|
|
446
|
+
.sort((a, b) => b.count - a.count);
|
|
447
|
+
|
|
448
|
+
return { agents, total, todayCount };
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Agent outcome distribution from plan_status field.
|
|
453
|
+
* Counts COMPLETE, BLOCKED, NEEDS_INPUT, IN_PROGRESS, REVIEW, and others.
|
|
454
|
+
* Returns null if no entries have the plan_status field (older data).
|
|
455
|
+
*/
|
|
456
|
+
function calculateAgentOutcomes(workflowMetrics) {
|
|
457
|
+
const withStatus = workflowMetrics.filter(r => r.plan_status && r.plan_status !== '');
|
|
458
|
+
if (withStatus.length === 0) return null;
|
|
459
|
+
|
|
460
|
+
const counts = {};
|
|
461
|
+
for (const entry of withStatus) {
|
|
462
|
+
const status = entry.plan_status.toUpperCase();
|
|
463
|
+
counts[status] = (counts[status] || 0) + 1;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const total = withStatus.length;
|
|
467
|
+
const distribution = Object.entries(counts)
|
|
468
|
+
.map(([status, count]) => ({ status, count, percentage: (count / total) * 100 }))
|
|
469
|
+
.sort((a, b) => b.count - a.count);
|
|
470
|
+
|
|
471
|
+
return { distribution, total };
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Token usage approximation from output_tokens_approx field.
|
|
476
|
+
* Groups by agent, computes total and average.
|
|
477
|
+
* Returns null if no entries have the field (older data).
|
|
478
|
+
*/
|
|
479
|
+
function calculateTokenUsage(workflowMetrics) {
|
|
480
|
+
const withTokens = workflowMetrics.filter(r => typeof r.output_tokens_approx === 'number');
|
|
481
|
+
if (withTokens.length === 0) return null;
|
|
482
|
+
|
|
483
|
+
const agentMap = {};
|
|
484
|
+
for (const entry of withTokens) {
|
|
485
|
+
const name = entry.agent || 'unknown';
|
|
486
|
+
if (!agentMap[name]) agentMap[name] = { total: 0, count: 0 };
|
|
487
|
+
agentMap[name].total += entry.output_tokens_approx;
|
|
488
|
+
agentMap[name].count++;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const grandTotal = withTokens.reduce((s, r) => s + r.output_tokens_approx, 0);
|
|
492
|
+
const agents = Object.entries(agentMap)
|
|
493
|
+
.map(([name, { total, count }]) => ({
|
|
494
|
+
name,
|
|
495
|
+
total,
|
|
496
|
+
avg: count > 0 ? Math.round(total / count) : 0,
|
|
497
|
+
count,
|
|
498
|
+
}))
|
|
499
|
+
.sort((a, b) => b.total - a.total);
|
|
500
|
+
|
|
501
|
+
return { agents, grandTotal, entryCount: withTokens.length };
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Runtime skill snapshot summary from agent-skills.jsonl and run snapshots.
|
|
506
|
+
*/
|
|
507
|
+
function calculateRuntimeSkillSummary(skillSnapshots, runSnapshots) {
|
|
508
|
+
const explicitSnapshots = skillSnapshots.filter(entry => entry && entry.agent);
|
|
509
|
+
const runtimeDefaults = runSnapshots
|
|
510
|
+
.filter(entry => entry && entry.agent && entry.default_skills_snapshot)
|
|
511
|
+
.map(entry => ({
|
|
512
|
+
timestamp: entry.timestamp,
|
|
513
|
+
session_id: entry.session_id,
|
|
514
|
+
agent: entry.agent,
|
|
515
|
+
model: entry.default_skills_snapshot.model || '',
|
|
516
|
+
tools: entry.default_skills_snapshot.tools || [],
|
|
517
|
+
skills: entry.default_skills_snapshot.skills || [],
|
|
518
|
+
skills_count: entry.default_skills_snapshot.skills_count || 0,
|
|
519
|
+
source: 'run-default',
|
|
520
|
+
}));
|
|
521
|
+
|
|
522
|
+
const latestByAgent = new Map();
|
|
523
|
+
for (const snapshot of [...runtimeDefaults, ...explicitSnapshots]) {
|
|
524
|
+
const agent = snapshot.agent || 'unknown';
|
|
525
|
+
const current = latestByAgent.get(agent);
|
|
526
|
+
if (!current || String(snapshot.timestamp || '') >= String(current.timestamp || '')) {
|
|
527
|
+
latestByAgent.set(agent, {
|
|
528
|
+
agent,
|
|
529
|
+
timestamp: snapshot.timestamp || '',
|
|
530
|
+
model: snapshot.model || '',
|
|
531
|
+
tools: Array.isArray(snapshot.tools) ? snapshot.tools : [],
|
|
532
|
+
skills: Array.isArray(snapshot.skills) ? snapshot.skills : [],
|
|
533
|
+
skillsCount: typeof snapshot.skills_count === 'number'
|
|
534
|
+
? snapshot.skills_count
|
|
535
|
+
: Array.isArray(snapshot.skills) ? snapshot.skills.length : 0,
|
|
536
|
+
source: snapshot.source || 'explicit',
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
const latestProfiles = [...latestByAgent.values()]
|
|
542
|
+
.sort((a, b) => a.agent.localeCompare(b.agent));
|
|
543
|
+
const topSkillsSummary = topCounts(latestProfiles.flatMap(profile => profile.skills), 6);
|
|
544
|
+
|
|
545
|
+
return {
|
|
546
|
+
explicitCount: explicitSnapshots.length,
|
|
547
|
+
runDefaultCount: runtimeDefaults.length,
|
|
548
|
+
agentCount: latestProfiles.length,
|
|
549
|
+
latestProfiles,
|
|
550
|
+
topSkills: topSkillsSummary,
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Context snapshot summary from run-snapshots.jsonl.
|
|
556
|
+
*/
|
|
557
|
+
function calculateContextSnapshotSummary(runSnapshots) {
|
|
558
|
+
const withContext = runSnapshots.filter(entry => {
|
|
559
|
+
const snapshot = entry.context_snapshot || {};
|
|
560
|
+
return Object.keys(snapshot).length > 0;
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
if (withContext.length === 0) return null;
|
|
564
|
+
|
|
565
|
+
const primarySurfaces = [];
|
|
566
|
+
const contractSections = [];
|
|
567
|
+
const writableSections = [];
|
|
568
|
+
let multiSurfaceCount = 0;
|
|
569
|
+
|
|
570
|
+
for (const entry of withContext) {
|
|
571
|
+
const snapshot = entry.context_snapshot || {};
|
|
572
|
+
if (snapshot.surface_routing?.primary_surface) {
|
|
573
|
+
primarySurfaces.push(snapshot.surface_routing.primary_surface);
|
|
574
|
+
}
|
|
575
|
+
if (snapshot.surface_routing?.multi_surface) {
|
|
576
|
+
multiSurfaceCount++;
|
|
577
|
+
}
|
|
578
|
+
contractSections.push(...(snapshot.contract_sections || []));
|
|
579
|
+
writableSections.push(...(snapshot.context_update_scope?.writable_sections || []));
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
return {
|
|
583
|
+
total: withContext.length,
|
|
584
|
+
multiSurfaceCount,
|
|
585
|
+
primarySurfaces: topCounts(primarySurfaces, 6),
|
|
586
|
+
contractSections: topCounts(contractSections, 6),
|
|
587
|
+
writableSections: topCounts(writableSections, 6),
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Context update summary from run-snapshots.jsonl.
|
|
593
|
+
*/
|
|
594
|
+
function calculateContextUpdateSummary(runSnapshots) {
|
|
595
|
+
if (runSnapshots.length === 0) return null;
|
|
596
|
+
|
|
597
|
+
const updatedRuns = runSnapshots.filter(entry => entry.context_updated);
|
|
598
|
+
const rejectedRuns = runSnapshots.filter(
|
|
599
|
+
entry => Array.isArray(entry.context_rejected_sections) && entry.context_rejected_sections.length > 0
|
|
600
|
+
);
|
|
601
|
+
|
|
602
|
+
return {
|
|
603
|
+
totalRuns: runSnapshots.length,
|
|
604
|
+
updatedRuns: updatedRuns.length,
|
|
605
|
+
rejectedRuns: rejectedRuns.length,
|
|
606
|
+
updatedSections: topCounts(
|
|
607
|
+
updatedRuns.flatMap(entry => entry.context_sections_updated || []),
|
|
608
|
+
6
|
|
609
|
+
),
|
|
610
|
+
rejectedSections: topCounts(
|
|
611
|
+
runSnapshots.flatMap(entry => entry.context_rejected_sections || []),
|
|
612
|
+
6
|
|
613
|
+
),
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Anomaly summary from workflow-episodic-memory/anomalies.jsonl.
|
|
619
|
+
* Groups anomalies by type for the last 30 days.
|
|
620
|
+
*/
|
|
621
|
+
function calculateAnomalySummary(anomalyEntries) {
|
|
622
|
+
const cutoff = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString();
|
|
623
|
+
const entries = anomalyEntries.filter(
|
|
624
|
+
entry => entry && entry.timestamp && entry.timestamp >= cutoff
|
|
625
|
+
);
|
|
626
|
+
|
|
627
|
+
if (entries.length === 0) return null;
|
|
628
|
+
|
|
629
|
+
const typeCounts = {};
|
|
630
|
+
const agentCounts = {};
|
|
631
|
+
for (const entry of entries) {
|
|
632
|
+
const anomalies = entry.anomalies || [];
|
|
633
|
+
const agent = entry.metrics?.agent || 'unknown';
|
|
634
|
+
for (const anomaly of anomalies) {
|
|
635
|
+
const type = anomaly.type || 'unknown';
|
|
636
|
+
typeCounts[type] = (typeCounts[type] || 0) + 1;
|
|
637
|
+
agentCounts[agent] = (agentCounts[agent] || 0) + 1;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
const total = Object.values(typeCounts).reduce((sum, count) => sum + count, 0);
|
|
642
|
+
const byType = Object.entries(typeCounts)
|
|
643
|
+
.map(([type, count]) => ({
|
|
644
|
+
type,
|
|
645
|
+
count,
|
|
646
|
+
percentage: total > 0 ? (count / total * 100) : 0,
|
|
647
|
+
}))
|
|
648
|
+
.sort((a, b) => b.count - a.count);
|
|
649
|
+
|
|
650
|
+
return {
|
|
651
|
+
total,
|
|
652
|
+
sessionCount: entries.length,
|
|
653
|
+
byType,
|
|
654
|
+
byAgent: sortedCounts(agentCounts).slice(0, 5),
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Extract audit log entries that fall within the time window of a given
|
|
660
|
+
* agent session. Uses the session timestamp as the end boundary, and
|
|
661
|
+
* the previous named-agent session as the start boundary (approximation).
|
|
662
|
+
*
|
|
663
|
+
* This is a best-effort time-window correlation, documented as approximate.
|
|
664
|
+
*/
|
|
665
|
+
function correlateAuditLogsToSession(auditLogs, sessionEnd, sessionStart) {
|
|
666
|
+
const endTs = new Date(sessionEnd).getTime();
|
|
667
|
+
const startTs = sessionStart ? new Date(sessionStart).getTime() : endTs - 10 * 60 * 1000; // 10 min fallback
|
|
668
|
+
|
|
669
|
+
return auditLogs.filter(entry => {
|
|
670
|
+
if (!entry.timestamp) return false;
|
|
671
|
+
const ts = new Date(entry.timestamp).getTime();
|
|
672
|
+
return ts >= startTs && ts <= endTs;
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function getLatestRuntimeProfile(agentName, skillSnapshots, runSnapshots) {
|
|
677
|
+
const explicit = skillSnapshots
|
|
678
|
+
.filter(entry => entry.agent === agentName)
|
|
679
|
+
.map(entry => ({
|
|
680
|
+
timestamp: entry.timestamp || '',
|
|
681
|
+
model: entry.model || '',
|
|
682
|
+
tools: Array.isArray(entry.tools) ? entry.tools : [],
|
|
683
|
+
skills: Array.isArray(entry.skills) ? entry.skills : [],
|
|
684
|
+
skillsCount: typeof entry.skills_count === 'number'
|
|
685
|
+
? entry.skills_count
|
|
686
|
+
: Array.isArray(entry.skills) ? entry.skills.length : 0,
|
|
687
|
+
source: 'explicit',
|
|
688
|
+
}));
|
|
689
|
+
|
|
690
|
+
const defaults = runSnapshots
|
|
691
|
+
.filter(entry => entry.agent === agentName && entry.default_skills_snapshot)
|
|
692
|
+
.map(entry => ({
|
|
693
|
+
timestamp: entry.timestamp || '',
|
|
694
|
+
model: entry.default_skills_snapshot.model || '',
|
|
695
|
+
tools: entry.default_skills_snapshot.tools || [],
|
|
696
|
+
skills: entry.default_skills_snapshot.skills || [],
|
|
697
|
+
skillsCount: typeof entry.default_skills_snapshot.skills_count === 'number'
|
|
698
|
+
? entry.default_skills_snapshot.skills_count
|
|
699
|
+
: Array.isArray(entry.default_skills_snapshot.skills)
|
|
700
|
+
? entry.default_skills_snapshot.skills.length
|
|
701
|
+
: 0,
|
|
702
|
+
source: 'run-default',
|
|
703
|
+
}));
|
|
704
|
+
|
|
705
|
+
const snapshots = [...defaults, ...explicit].sort(
|
|
706
|
+
(a, b) => String(b.timestamp).localeCompare(String(a.timestamp))
|
|
707
|
+
);
|
|
708
|
+
|
|
709
|
+
return {
|
|
710
|
+
latest: snapshots[0] || null,
|
|
711
|
+
explicitCount: explicit.length,
|
|
712
|
+
runDefaultCount: defaults.length,
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function calculateAgentAnomalySummary(agentName, anomalyEntries) {
|
|
717
|
+
const entries = anomalyEntries.filter(entry => entry.metrics?.agent === agentName);
|
|
718
|
+
if (entries.length === 0) return null;
|
|
719
|
+
|
|
720
|
+
const typeCounts = {};
|
|
721
|
+
for (const entry of entries) {
|
|
722
|
+
for (const anomaly of (entry.anomalies || [])) {
|
|
723
|
+
const type = anomaly.type || 'unknown';
|
|
724
|
+
typeCounts[type] = (typeCounts[type] || 0) + 1;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
return {
|
|
729
|
+
total: Object.values(typeCounts).reduce((sum, count) => sum + count, 0),
|
|
730
|
+
sessionCount: entries.length,
|
|
731
|
+
byType: sortedCounts(typeCounts).slice(0, 6),
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// ─────────────────────────────────────────────────────────────
|
|
736
|
+
// DISPLAY FUNCTIONS
|
|
737
|
+
// ─────────────────────────────────────────────────────────────
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* Format token count as human-readable (e.g. "6.9k", "1.2M").
|
|
741
|
+
*/
|
|
742
|
+
function formatTokens(n) {
|
|
743
|
+
if (n === null || n === undefined) return 'n/a';
|
|
744
|
+
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
|
|
745
|
+
if (n >= 1_000) return `${(n / 1_000).toFixed(1)}k`;
|
|
746
|
+
return `${n}`;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
function countValues(values) {
|
|
750
|
+
const counts = {};
|
|
751
|
+
for (const value of values) {
|
|
752
|
+
if (!value) continue;
|
|
753
|
+
counts[value] = (counts[value] || 0) + 1;
|
|
754
|
+
}
|
|
755
|
+
return counts;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
function sortedCounts(counts) {
|
|
759
|
+
return Object.entries(counts)
|
|
760
|
+
.map(([name, count]) => ({ name, count }))
|
|
761
|
+
.sort((a, b) => b.count - a.count || a.name.localeCompare(b.name));
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
function topCounts(values, limit = 5) {
|
|
765
|
+
return sortedCounts(countValues(values)).slice(0, limit);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
function formatCountSummary(entries, emptyLabel = 'none') {
|
|
769
|
+
if (!entries || entries.length === 0) return emptyLabel;
|
|
770
|
+
return entries.map(({ name, count }) => `${name}(${count})`).join(', ');
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
function formatSkills(skills, limit = 4) {
|
|
774
|
+
if (!Array.isArray(skills) || skills.length === 0) return 'none';
|
|
775
|
+
if (skills.length <= limit) return skills.join(', ');
|
|
776
|
+
return `${skills.slice(0, limit).join(', ')}, +${skills.length - limit} more`;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
/**
|
|
780
|
+
* Display the main dashboard metrics.
|
|
781
|
+
*/
|
|
782
|
+
function displayMetrics(
|
|
783
|
+
tiers,
|
|
784
|
+
cmdTypes,
|
|
785
|
+
topCmds,
|
|
786
|
+
agentInvocations,
|
|
787
|
+
errorStats,
|
|
788
|
+
auditTotal,
|
|
789
|
+
agentOutcomes,
|
|
790
|
+
tokenUsage,
|
|
791
|
+
anomalySummary,
|
|
792
|
+
runtimeSkills,
|
|
793
|
+
contextSnapshots,
|
|
794
|
+
contextUpdates
|
|
795
|
+
) {
|
|
796
|
+
const SEP = chalk.gray('═'.repeat(52));
|
|
797
|
+
|
|
798
|
+
console.log(chalk.cyan('\n📊 Gaia-Ops System Metrics'));
|
|
799
|
+
console.log(SEP);
|
|
800
|
+
|
|
801
|
+
// ── Security Tier Usage ──────────────────────────────
|
|
802
|
+
console.log(chalk.bold(`\n🔒 Security Tier Usage (${tiers.total} operations)`));
|
|
803
|
+
|
|
804
|
+
if (tiers.total === 0) {
|
|
805
|
+
console.log(chalk.gray(' no tier data'));
|
|
806
|
+
} else {
|
|
807
|
+
const tierLabel = { T0: 'read-only', T1: 'validation', T2: 'simulation', T3: 'realization' };
|
|
808
|
+
for (const { tier, count, percentage } of tiers.distribution) {
|
|
809
|
+
const color = tier === 'T3' ? chalk.red
|
|
810
|
+
: tier === 'T2' ? chalk.yellow
|
|
811
|
+
: chalk.green;
|
|
812
|
+
const bar = makeBar(percentage, 14);
|
|
813
|
+
const pct = percentage.toFixed(1).padStart(5);
|
|
814
|
+
const label = tierLabel[tier] || tier;
|
|
815
|
+
const suffix = tier === 'T3' ? chalk.red(' ⚠️ realization') : ` ${label}`;
|
|
816
|
+
console.log(color(
|
|
817
|
+
` ${tier.padEnd(4)} ${count.toString().padStart(4)} ${bar.padEnd(14)} ${pct}%${suffix}`
|
|
818
|
+
));
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// ── Command Type Breakdown ───────────────────────────
|
|
823
|
+
console.log(chalk.bold(`\n🛠 Command Type Breakdown (derived from ${auditTotal} audit entries)`));
|
|
824
|
+
|
|
825
|
+
if (cmdTypes.breakdown.length === 0) {
|
|
826
|
+
console.log(chalk.gray(' no command data'));
|
|
827
|
+
} else {
|
|
828
|
+
for (const { type, count, percentage } of cmdTypes.breakdown) {
|
|
829
|
+
const bar = makeBar(percentage, 10);
|
|
830
|
+
const pct = percentage.toFixed(1).padStart(5);
|
|
831
|
+
console.log(` ${type.padEnd(12)} ${count.toString().padStart(4)} ${bar.padEnd(10)} ${pct}%`);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// ── Top Commands ─────────────────────────────────────
|
|
836
|
+
console.log(chalk.bold('\n🔝 Top Commands'));
|
|
837
|
+
|
|
838
|
+
if (topCmds.length === 0) {
|
|
839
|
+
console.log(chalk.gray(' no command data'));
|
|
840
|
+
} else {
|
|
841
|
+
for (const { label, count, tier, t3count } of topCmds) {
|
|
842
|
+
const tierColor = tier === 'T3' ? chalk.red
|
|
843
|
+
: tier === 'T2' ? chalk.yellow
|
|
844
|
+
: chalk.gray;
|
|
845
|
+
const warn = t3count > 0 ? chalk.red(' ⚠️') : '';
|
|
846
|
+
console.log(
|
|
847
|
+
` ${label.padEnd(30)} ${count.toString().padStart(4)} ${tierColor(tier)}${warn}`
|
|
848
|
+
);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// ── Agent Invocations ────────────────────────────────
|
|
853
|
+
const agentHeader = agentInvocations.todayCount > 0
|
|
854
|
+
? `(${agentInvocations.todayCount} sessions today)`
|
|
855
|
+
: `(${agentInvocations.total} total)`;
|
|
856
|
+
console.log(chalk.bold(`\n🤖 Agent Invocations ${agentHeader}`));
|
|
857
|
+
|
|
858
|
+
if (agentInvocations.agents.length === 0) {
|
|
859
|
+
console.log(chalk.gray(' no invocation data'));
|
|
860
|
+
} else {
|
|
861
|
+
for (const { name, count, avgOutput, successRate, percentage } of agentInvocations.agents) {
|
|
862
|
+
const bar = makeBar(percentage, 8);
|
|
863
|
+
const avg = `avg ${formatChars(avgOutput).padStart(6)} chars`;
|
|
864
|
+
const ok = successRate === 100
|
|
865
|
+
? chalk.green('100% ok')
|
|
866
|
+
: chalk.yellow(`${successRate.toFixed(0)}% ok`);
|
|
867
|
+
console.log(
|
|
868
|
+
` ${name.padEnd(24)} ${count.toString().padStart(3)} ${bar.padEnd(8)} ${avg} ${ok}`
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
console.log(chalk.gray(` 💡 tip: npx gaia-metrics --agent <name> for detail view`));
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// ── Agent Outcomes ───────────────────────────────────
|
|
875
|
+
if (agentOutcomes) {
|
|
876
|
+
console.log(chalk.bold(`\n📋 Agent Outcomes (${agentOutcomes.total} sessions with status)`));
|
|
877
|
+
const outcomeColor = { COMPLETE: chalk.green, BLOCKED: chalk.red, NEEDS_INPUT: chalk.yellow, IN_PROGRESS: chalk.cyan, REVIEW: chalk.magenta };
|
|
878
|
+
for (const { status, count, percentage } of agentOutcomes.distribution) {
|
|
879
|
+
const bar = makeBar(percentage, 10);
|
|
880
|
+
const pct = percentage.toFixed(1).padStart(5);
|
|
881
|
+
const color = outcomeColor[status] || chalk.gray;
|
|
882
|
+
console.log(color(` ${status.padEnd(16)} ${count.toString().padStart(3)} ${bar.padEnd(10)} ${pct}%`));
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
// ── Token Usage (approx) ─────────────────────────────
|
|
887
|
+
if (tokenUsage) {
|
|
888
|
+
console.log(chalk.bold(`\n🪙 Token Usage (approx) total: ~${formatTokens(tokenUsage.grandTotal)}`));
|
|
889
|
+
for (const { name, total, avg, count } of tokenUsage.agents) {
|
|
890
|
+
const totalFmt = formatTokens(total).padStart(6);
|
|
891
|
+
const avgFmt = formatTokens(avg).padStart(6);
|
|
892
|
+
console.log(` ${name.padEnd(24)} ${count.toString().padStart(3)} sessions total ${totalFmt} avg ${avgFmt}`);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// ── Runtime Skill Snapshots ───────────────────────────
|
|
897
|
+
if (runtimeSkills && runtimeSkills.agentCount > 0) {
|
|
898
|
+
console.log(chalk.bold(
|
|
899
|
+
`\n🧠 Runtime Skill Snapshots (${runtimeSkills.agentCount} agents, ${runtimeSkills.explicitCount} explicit, ${runtimeSkills.runDefaultCount} run defaults)`
|
|
900
|
+
));
|
|
901
|
+
for (const profile of runtimeSkills.latestProfiles.slice(0, 6)) {
|
|
902
|
+
const model = profile.model || 'default';
|
|
903
|
+
console.log(
|
|
904
|
+
` ${profile.agent.padEnd(24)} model ${model.padEnd(8)} ` +
|
|
905
|
+
`skills ${String(profile.skillsCount).padStart(2)} tools ${String(profile.tools.length).padStart(2)} ` +
|
|
906
|
+
`${formatSkills(profile.skills, 3)}`
|
|
907
|
+
);
|
|
908
|
+
}
|
|
909
|
+
if (runtimeSkills.latestProfiles.length > 6) {
|
|
910
|
+
console.log(chalk.gray(` ... ${runtimeSkills.latestProfiles.length - 6} more agents with captured snapshots`));
|
|
911
|
+
}
|
|
912
|
+
console.log(` Common skills: ${formatCountSummary(runtimeSkills.topSkills)}`);
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
// ── Context Snapshot Summary ──────────────────────────
|
|
916
|
+
if (contextSnapshots) {
|
|
917
|
+
console.log(chalk.bold(`\n🗺 Context Snapshot Summary (${contextSnapshots.total} sessions)`));
|
|
918
|
+
console.log(` Primary surfaces: ${formatCountSummary(contextSnapshots.primarySurfaces)}`);
|
|
919
|
+
console.log(` Multi-surface: ${contextSnapshots.multiSurfaceCount}/${contextSnapshots.total} sessions`);
|
|
920
|
+
console.log(` Contract sections: ${formatCountSummary(contextSnapshots.contractSections)}`);
|
|
921
|
+
if (contextSnapshots.writableSections.length > 0) {
|
|
922
|
+
console.log(` Writable scope: ${formatCountSummary(contextSnapshots.writableSections)}`);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
// ── Context Updates ───────────────────────────────────
|
|
927
|
+
if (contextUpdates) {
|
|
928
|
+
console.log(chalk.bold(`\n📝 Context Updates (${contextUpdates.updatedRuns}/${contextUpdates.totalRuns} sessions updated)`));
|
|
929
|
+
console.log(` Rejected writes: ${contextUpdates.rejectedRuns} sessions`);
|
|
930
|
+
console.log(` Updated sections: ${formatCountSummary(contextUpdates.updatedSections)}`);
|
|
931
|
+
if (contextUpdates.rejectedSections.length > 0) {
|
|
932
|
+
console.log(chalk.yellow(` Rejected sections: ${formatCountSummary(contextUpdates.rejectedSections)}`));
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
// ── Anomaly Summary (last 30 days) ─────────────────
|
|
937
|
+
if (anomalySummary && anomalySummary.total > 0) {
|
|
938
|
+
console.log(chalk.bold(
|
|
939
|
+
`\n⚠️ Anomaly Summary (last 30 days) ${anomalySummary.total} anomalies across ${anomalySummary.sessionCount} sessions`
|
|
940
|
+
));
|
|
941
|
+
for (const { type, count, percentage } of anomalySummary.byType) {
|
|
942
|
+
const bar = makeBar(percentage, 10);
|
|
943
|
+
const pct = percentage.toFixed(1).padStart(5);
|
|
944
|
+
const color = type.includes('contract') ? chalk.red : chalk.yellow;
|
|
945
|
+
console.log(color(` ${type.padEnd(28)} ${count.toString().padStart(3)} ${bar.padEnd(10)} ${pct}%`));
|
|
946
|
+
}
|
|
947
|
+
if (anomalySummary.byAgent.length > 0) {
|
|
948
|
+
console.log(` Agents: ${formatCountSummary(anomalySummary.byAgent)}`);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
// ── Activity Today ───────────────────────────────────
|
|
953
|
+
console.log(chalk.bold('\n⚡ Activity Today'));
|
|
954
|
+
console.log(` Total calls: ${tiers.todayCount}`);
|
|
955
|
+
|
|
956
|
+
if (tiers.todayT3 > 0) {
|
|
957
|
+
console.log(chalk.red(` T3 operations: ${tiers.todayT3} ⚠️`));
|
|
958
|
+
} else {
|
|
959
|
+
console.log(chalk.green(` T3 operations: ${tiers.todayT3}`));
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
if (tiers.peakHour !== null) {
|
|
963
|
+
console.log(` Peak hour: ${tiers.peakHour}:00-${tiers.peakHour}:59 (${tiers.peakCount} calls)`);
|
|
964
|
+
} else {
|
|
965
|
+
console.log(chalk.gray(' Peak hour: no data for today'));
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
if (errorStats.limitedByApi) {
|
|
969
|
+
console.log(chalk.gray(' Error rate: n/a (hook API limitation — exit_code always 0)'));
|
|
970
|
+
} else if (errorStats.total === 0) {
|
|
971
|
+
console.log(chalk.gray(' Error rate: no exit_code data'));
|
|
972
|
+
} else {
|
|
973
|
+
const errColor = errorStats.errors > 0 ? chalk.red : chalk.green;
|
|
974
|
+
console.log(
|
|
975
|
+
` Error rate: ${errColor(`${errorStats.errors}/${errorStats.total} (${errorStats.errorRate.toFixed(1)}%)`)}`
|
|
976
|
+
);
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
console.log('\n' + SEP);
|
|
980
|
+
console.log(
|
|
981
|
+
chalk.gray(
|
|
982
|
+
'💡 Source: .claude/logs/audit-*.jsonl | episodic-memory/index.json | workflow-episodic-memory/*.jsonl\n'
|
|
983
|
+
)
|
|
984
|
+
);
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
/**
|
|
988
|
+
* Display the agent detail view (--agent <name>).
|
|
989
|
+
*/
|
|
990
|
+
async function displayAgentDetail(agentName, workflowMetrics, auditLogs, runSnapshots, skillSnapshots, anomalyEntries) {
|
|
991
|
+
const SEP = chalk.gray('═'.repeat(52));
|
|
992
|
+
|
|
993
|
+
console.log(chalk.cyan(`\n🤖 Agent: ${agentName}`));
|
|
994
|
+
console.log(SEP);
|
|
995
|
+
|
|
996
|
+
// ── Profile ──────────────────────────────────────────
|
|
997
|
+
console.log(chalk.bold('\n📋 Profile'));
|
|
998
|
+
const agentDef = await readAgentDefinition(agentName);
|
|
999
|
+
|
|
1000
|
+
if (!agentDef) {
|
|
1001
|
+
console.log(chalk.yellow(' Agent definition not found in .claude/agents/'));
|
|
1002
|
+
} else {
|
|
1003
|
+
if (agentDef.description) {
|
|
1004
|
+
console.log(` Description: ${agentDef.description}`);
|
|
1005
|
+
}
|
|
1006
|
+
if (agentDef.skills.length > 0) {
|
|
1007
|
+
// Wrap skills at ~60 chars
|
|
1008
|
+
const skillLine = agentDef.skills.join(', ');
|
|
1009
|
+
if (skillLine.length <= 56) {
|
|
1010
|
+
console.log(` Skills: ${skillLine}`);
|
|
1011
|
+
} else {
|
|
1012
|
+
const chunks = [];
|
|
1013
|
+
let current = [];
|
|
1014
|
+
let len = 0;
|
|
1015
|
+
for (const s of agentDef.skills) {
|
|
1016
|
+
if (len + s.length + 2 > 56 && current.length > 0) {
|
|
1017
|
+
chunks.push(current.join(', '));
|
|
1018
|
+
current = [s];
|
|
1019
|
+
len = s.length;
|
|
1020
|
+
} else {
|
|
1021
|
+
current.push(s);
|
|
1022
|
+
len += s.length + 2;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
if (current.length) chunks.push(current.join(', '));
|
|
1026
|
+
console.log(` Skills: ${chunks[0]}`);
|
|
1027
|
+
for (let i = 1; i < chunks.length; i++) {
|
|
1028
|
+
console.log(` ${chunks[i]}`);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
// ── Runtime Snapshot ─────────────────────────────────
|
|
1035
|
+
console.log(chalk.bold('\n🧠 Runtime Snapshot'));
|
|
1036
|
+
const runtimeProfile = getLatestRuntimeProfile(agentName, skillSnapshots, runSnapshots);
|
|
1037
|
+
if (!runtimeProfile.latest) {
|
|
1038
|
+
console.log(chalk.gray(' no runtime skill snapshot data'));
|
|
1039
|
+
} else {
|
|
1040
|
+
const latest = runtimeProfile.latest;
|
|
1041
|
+
console.log(` Latest model: ${latest.model || 'default'}`);
|
|
1042
|
+
console.log(
|
|
1043
|
+
` Snapshot source: ${latest.source === 'explicit'
|
|
1044
|
+
? 'agent-skills.jsonl'
|
|
1045
|
+
: 'run-snapshots default profile'}`
|
|
1046
|
+
);
|
|
1047
|
+
console.log(` Snapshots seen: ${runtimeProfile.explicitCount} explicit, ${runtimeProfile.runDefaultCount} run defaults`);
|
|
1048
|
+
console.log(` Tools: ${latest.tools.length > 0 ? latest.tools.join(', ') : 'none'}`);
|
|
1049
|
+
console.log(` Skills: ${formatSkills(latest.skills, 6)}`);
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
// ── Invocation History ───────────────────────────────
|
|
1053
|
+
const agentSessions = workflowMetrics
|
|
1054
|
+
.filter(r => r.agent === agentName)
|
|
1055
|
+
.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
|
|
1056
|
+
|
|
1057
|
+
const successCount = agentSessions.filter(r => r.exit_code === 0).length;
|
|
1058
|
+
const totalOutput = agentSessions.reduce((s, r) => s + (r.output_length || 0), 0);
|
|
1059
|
+
const avgOutput = agentSessions.length > 0 ? Math.round(totalOutput / agentSessions.length) : 0;
|
|
1060
|
+
|
|
1061
|
+
console.log(chalk.bold(`\n📊 Invocation History (last 7 days)`));
|
|
1062
|
+
|
|
1063
|
+
if (agentSessions.length === 0) {
|
|
1064
|
+
console.log(chalk.gray(' no invocations found in episodic-memory/index.json'));
|
|
1065
|
+
} else {
|
|
1066
|
+
console.log(
|
|
1067
|
+
` Total: ${agentSessions.length} invocations | ` +
|
|
1068
|
+
`Success: ${successCount}/${agentSessions.length} | ` +
|
|
1069
|
+
`Avg output: ${formatChars(avgOutput)} chars`
|
|
1070
|
+
);
|
|
1071
|
+
console.log('');
|
|
1072
|
+
|
|
1073
|
+
for (const session of agentSessions) {
|
|
1074
|
+
const dt = session.timestamp.slice(0, 16).replace('T', ' ');
|
|
1075
|
+
const ok = session.exit_code === 0
|
|
1076
|
+
? chalk.green('✓')
|
|
1077
|
+
: chalk.red('✗');
|
|
1078
|
+
const chars = (session.output_length || 0).toLocaleString();
|
|
1079
|
+
const taskShort = session.task_id ? session.task_id.slice(0, 8) : 'n/a';
|
|
1080
|
+
console.log(
|
|
1081
|
+
` ${dt} ${ok} ${chars.padStart(7)} chars task: ${taskShort}`
|
|
1082
|
+
);
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
// ── Context Snapshot Summary ─────────────────────────
|
|
1087
|
+
const agentRunSnapshots = runSnapshots.filter(entry => entry.agent === agentName);
|
|
1088
|
+
const agentContextSummary = calculateContextSnapshotSummary(agentRunSnapshots);
|
|
1089
|
+
const agentContextUpdates = calculateContextUpdateSummary(agentRunSnapshots);
|
|
1090
|
+
const agentAnomalies = calculateAgentAnomalySummary(agentName, anomalyEntries);
|
|
1091
|
+
|
|
1092
|
+
console.log(chalk.bold('\n🗺 Context Snapshot Summary'));
|
|
1093
|
+
if (!agentContextSummary) {
|
|
1094
|
+
console.log(chalk.gray(' no context snapshot data'));
|
|
1095
|
+
} else {
|
|
1096
|
+
console.log(` Sessions with context: ${agentContextSummary.total}`);
|
|
1097
|
+
console.log(` Primary surfaces: ${formatCountSummary(agentContextSummary.primarySurfaces)}`);
|
|
1098
|
+
console.log(` Multi-surface: ${agentContextSummary.multiSurfaceCount}/${agentContextSummary.total}`);
|
|
1099
|
+
console.log(` Contract sections: ${formatCountSummary(agentContextSummary.contractSections)}`);
|
|
1100
|
+
if (agentContextSummary.writableSections.length > 0) {
|
|
1101
|
+
console.log(` Writable scope: ${formatCountSummary(agentContextSummary.writableSections)}`);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
console.log(chalk.bold('\n📝 Context Updates + Anomalies'));
|
|
1106
|
+
if (!agentContextUpdates && !agentAnomalies) {
|
|
1107
|
+
console.log(chalk.gray(' no context update or anomaly data'));
|
|
1108
|
+
} else {
|
|
1109
|
+
if (agentContextUpdates) {
|
|
1110
|
+
console.log(` Context updated: ${agentContextUpdates.updatedRuns}/${agentContextUpdates.totalRuns} sessions`);
|
|
1111
|
+
console.log(` Updated sections: ${formatCountSummary(agentContextUpdates.updatedSections)}`);
|
|
1112
|
+
if (agentContextUpdates.rejectedSections.length > 0) {
|
|
1113
|
+
console.log(chalk.yellow(` Rejected sections: ${formatCountSummary(agentContextUpdates.rejectedSections)}`));
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
if (agentAnomalies) {
|
|
1117
|
+
console.log(` Anomalies: ${agentAnomalies.total} across ${agentAnomalies.sessionCount} sessions`);
|
|
1118
|
+
console.log(` Types: ${formatCountSummary(agentAnomalies.byType)}`);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
// ── Top Commands (correlated from audit log) ─────────
|
|
1123
|
+
console.log(chalk.bold('\n🔝 Top Commands (sampled from audit log, approximate time windows)'));
|
|
1124
|
+
|
|
1125
|
+
if (agentSessions.length === 0 || auditLogs.length === 0) {
|
|
1126
|
+
console.log(chalk.gray(' no data to correlate'));
|
|
1127
|
+
} else {
|
|
1128
|
+
// Build session windows: for each named-agent stop, find the previous named-agent stop
|
|
1129
|
+
// to define the start of the window. Falls back to 10 minutes before the stop.
|
|
1130
|
+
const namedStops = workflowMetrics
|
|
1131
|
+
.filter(r => r.agent)
|
|
1132
|
+
.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
|
|
1133
|
+
|
|
1134
|
+
const agentStopIdxs = agentSessions.map(s =>
|
|
1135
|
+
namedStops.findIndex(r => r.task_id === s.task_id)
|
|
1136
|
+
);
|
|
1137
|
+
|
|
1138
|
+
const tierOrder = { T3: 3, T2: 2, T1: 1, T0: 0, unknown: -1 };
|
|
1139
|
+
const labelMap = {};
|
|
1140
|
+
|
|
1141
|
+
for (let i = 0; i < agentSessions.length; i++) {
|
|
1142
|
+
const session = agentSessions[i];
|
|
1143
|
+
const stopIdx = agentStopIdxs[i];
|
|
1144
|
+
const prevStop = stopIdx > 0 ? namedStops[stopIdx - 1] : null;
|
|
1145
|
+
const windowStart = prevStop ? prevStop.timestamp : null;
|
|
1146
|
+
|
|
1147
|
+
const windowCmds = correlateAuditLogsToSession(
|
|
1148
|
+
auditLogs,
|
|
1149
|
+
session.timestamp,
|
|
1150
|
+
windowStart
|
|
1151
|
+
);
|
|
1152
|
+
|
|
1153
|
+
for (const entry of windowCmds) {
|
|
1154
|
+
if (!entry.command) continue;
|
|
1155
|
+
const label = extractCommandLabel(entry.command);
|
|
1156
|
+
const tier = entry.tier || 'unknown';
|
|
1157
|
+
if (!labelMap[label]) labelMap[label] = { count: 0, tier, t3count: 0 };
|
|
1158
|
+
labelMap[label].count++;
|
|
1159
|
+
if (tier === 'T3') labelMap[label].t3count++;
|
|
1160
|
+
if ((tierOrder[tier] ?? -1) > (tierOrder[labelMap[label].tier] ?? -1)) {
|
|
1161
|
+
labelMap[label].tier = tier;
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
const topCmds = Object.entries(labelMap)
|
|
1167
|
+
.map(([label, { count, tier, t3count }]) => ({ label, count, tier, t3count }))
|
|
1168
|
+
.sort((a, b) => b.count - a.count)
|
|
1169
|
+
.slice(0, 10);
|
|
1170
|
+
|
|
1171
|
+
if (topCmds.length === 0) {
|
|
1172
|
+
console.log(chalk.gray(' no overlapping commands found in audit window'));
|
|
1173
|
+
} else {
|
|
1174
|
+
for (const { label, count, tier, t3count } of topCmds) {
|
|
1175
|
+
const tierColor = tier === 'T3' ? chalk.red
|
|
1176
|
+
: tier === 'T2' ? chalk.yellow
|
|
1177
|
+
: chalk.gray;
|
|
1178
|
+
const warn = t3count > 0 ? chalk.red(' ⚠️') : '';
|
|
1179
|
+
console.log(
|
|
1180
|
+
` ${tierColor(tier.padEnd(3))} ${label.padEnd(28)} ${count.toString().padStart(4)}${warn}`
|
|
1181
|
+
);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
console.log(chalk.gray('\n Note: command windows are approximated from SubagentStop timestamps'));
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
console.log('\n' + SEP + '\n');
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
// ─────────────────────────────────────────────────────────────
|
|
1192
|
+
// MAIN
|
|
1193
|
+
// ─────────────────────────────────────────────────────────────
|
|
1194
|
+
|
|
1195
|
+
async function main() {
|
|
1196
|
+
process.stderr.write('[DEPRECATED] gaia-metrics.js is deprecated. Use: python3 bin/gaia metrics\n[DEPRECATED] Migration guide: see CHANGELOG.md\n');
|
|
1197
|
+
|
|
1198
|
+
// Parse --agent flag
|
|
1199
|
+
const agentFlagIdx = process.argv.indexOf('--agent');
|
|
1200
|
+
const agentName = agentFlagIdx !== -1 ? process.argv[agentFlagIdx + 1] : null;
|
|
1201
|
+
|
|
1202
|
+
const spinner = ora('Loading metrics...').start();
|
|
1203
|
+
|
|
1204
|
+
try {
|
|
1205
|
+
const claudeDir = join(CWD, '.claude');
|
|
1206
|
+
if (!existsSync(claudeDir)) {
|
|
1207
|
+
spinner.fail('.claude/ directory not found');
|
|
1208
|
+
console.log(chalk.yellow('\n⚠️ Gaia-ops not installed in this directory'));
|
|
1209
|
+
console.log(chalk.gray(' Run: npx gaia-scan\n'));
|
|
1210
|
+
process.exit(1);
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
const [auditLogs, workflowMetrics, runSnapshots, skillSnapshots, anomalyEntries] = await Promise.all([
|
|
1214
|
+
readAuditLogs(),
|
|
1215
|
+
readWorkflowMetrics(),
|
|
1216
|
+
readRunSnapshots(),
|
|
1217
|
+
readAgentSkillSnapshots(),
|
|
1218
|
+
readAnomalyEntries(),
|
|
1219
|
+
]);
|
|
1220
|
+
|
|
1221
|
+
if (
|
|
1222
|
+
auditLogs.length === 0 &&
|
|
1223
|
+
workflowMetrics.length === 0 &&
|
|
1224
|
+
runSnapshots.length === 0 &&
|
|
1225
|
+
skillSnapshots.length === 0 &&
|
|
1226
|
+
anomalyEntries.length === 0
|
|
1227
|
+
) {
|
|
1228
|
+
spinner.info('No log data found');
|
|
1229
|
+
console.log(chalk.yellow('\n⚠️ No metrics data available yet'));
|
|
1230
|
+
console.log(chalk.gray(' Metrics will be generated as you use the system\n'));
|
|
1231
|
+
process.exit(0);
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
spinner.succeed(
|
|
1235
|
+
`Loaded ${auditLogs.length} audit + ${workflowMetrics.length} workflow + ${runSnapshots.length} telemetry entries`
|
|
1236
|
+
);
|
|
1237
|
+
|
|
1238
|
+
if (agentName) {
|
|
1239
|
+
// Agent detail view
|
|
1240
|
+
await displayAgentDetail(agentName, workflowMetrics, auditLogs, runSnapshots, skillSnapshots, anomalyEntries);
|
|
1241
|
+
} else {
|
|
1242
|
+
// Full dashboard
|
|
1243
|
+
const tiers = calculateTierUsage(auditLogs);
|
|
1244
|
+
const cmdTypes = calculateCommandTypeBreakdown(auditLogs);
|
|
1245
|
+
const topCmds = calculateTopCommands(auditLogs);
|
|
1246
|
+
const agentInvocations = calculateAgentInvocations(workflowMetrics);
|
|
1247
|
+
const errorStats = calculateErrorRate(auditLogs);
|
|
1248
|
+
const agentOutcomes = calculateAgentOutcomes(workflowMetrics);
|
|
1249
|
+
const tokenUsage = calculateTokenUsage(workflowMetrics);
|
|
1250
|
+
const anomalySummary = calculateAnomalySummary(anomalyEntries);
|
|
1251
|
+
const runtimeSkills = calculateRuntimeSkillSummary(skillSnapshots, runSnapshots);
|
|
1252
|
+
const contextSnapshots = calculateContextSnapshotSummary(runSnapshots);
|
|
1253
|
+
const contextUpdates = calculateContextUpdateSummary(runSnapshots);
|
|
1254
|
+
|
|
1255
|
+
displayMetrics(
|
|
1256
|
+
tiers,
|
|
1257
|
+
cmdTypes,
|
|
1258
|
+
topCmds,
|
|
1259
|
+
agentInvocations,
|
|
1260
|
+
errorStats,
|
|
1261
|
+
auditLogs.length,
|
|
1262
|
+
agentOutcomes,
|
|
1263
|
+
tokenUsage,
|
|
1264
|
+
anomalySummary,
|
|
1265
|
+
runtimeSkills,
|
|
1266
|
+
contextSnapshots,
|
|
1267
|
+
contextUpdates
|
|
1268
|
+
);
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
} catch (error) {
|
|
1272
|
+
spinner.fail(`Failed to load metrics: ${error.message}`);
|
|
1273
|
+
console.error(chalk.red(`\n❌ Error: ${error.stack}\n`));
|
|
1274
|
+
process.exit(1);
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
main();
|