@jaguilar87/gaia 5.0.0-rc1
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 +1212 -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 +237 -0
- package/agents/gaia-planner.md +53 -0
- package/agents/gaia-system.md +70 -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 +628 -0
- package/bin/cli/history.py +305 -0
- package/bin/cli/memory.py +464 -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 +816 -0
- package/bin/pre-publish-validate.js +610 -0
- package/bin/python-detect.js +60 -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 +421 -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 +237 -0
- package/dist/gaia-ops/agents/gaia-planner.md +53 -0
- package/dist/gaia-ops/agents/gaia-system.md +70 -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 +421 -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 +163 -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 +232 -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_start.py +81 -0
- package/dist/gaia-ops/hooks/stop_hook.py +82 -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 +154 -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 +182 -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 +82 -0
- package/dist/gaia-ops/skills/gaia-release/reference.md +102 -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/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 +360 -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 +84 -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 +232 -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_start.py +81 -0
- package/dist/gaia-security/hooks/stop_hook.py +82 -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 +232 -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_start.py +81 -0
- package/hooks/stop_hook.py +82 -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 +99 -0
- package/pyproject.toml +32 -0
- package/skills/README.md +154 -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 +182 -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 +82 -0
- package/skills/gaia-release/reference.md +102 -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/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 +360 -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,264 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Skills mapper for gaia-ops agent/skill/surface/contract analysis.
|
|
3
|
+
|
|
4
|
+
Builds a complete map of agents, their skills, the surfaces that route
|
|
5
|
+
to them, and the contract permissions they hold. Flags orphan skills.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import re
|
|
12
|
+
import sys
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any, Optional
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
_TOOLS_DIR = Path(__file__).resolve().parent.parent
|
|
19
|
+
if str(_TOOLS_DIR) not in sys.path:
|
|
20
|
+
sys.path.insert(0, str(_TOOLS_DIR))
|
|
21
|
+
|
|
22
|
+
from gaia_simulator.routing_simulator import _parse_frontmatter
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
# Data classes
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class SkillMapping:
|
|
32
|
+
"""Maps a skill to the agents that use it."""
|
|
33
|
+
|
|
34
|
+
skill_name: str
|
|
35
|
+
skill_path: str
|
|
36
|
+
used_by_agents: list[str]
|
|
37
|
+
is_orphan: bool # no agent uses it
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class AgentProfile:
|
|
42
|
+
"""Full profile for an agent."""
|
|
43
|
+
|
|
44
|
+
agent_name: str
|
|
45
|
+
skills: list[str]
|
|
46
|
+
invocation_count: int # from metrics/logs
|
|
47
|
+
surfaces: list[str] # which surfaces route to this agent
|
|
48
|
+
read_sections: list[str]
|
|
49
|
+
write_sections: list[str]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# ---------------------------------------------------------------------------
|
|
53
|
+
# SkillsMapper
|
|
54
|
+
# ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class SkillsMapper:
|
|
58
|
+
"""Builds the complete map of agents, skills, surfaces, and contracts."""
|
|
59
|
+
|
|
60
|
+
def __init__(self, agents_dir: Path, skills_dir: Path, config_dir: Path):
|
|
61
|
+
"""Initialize the mapper.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
agents_dir: Path to agents/ directory with agent .md files.
|
|
65
|
+
skills_dir: Path to skills/ directory with skill subdirectories.
|
|
66
|
+
config_dir: Path to config/ directory with routing and contracts.
|
|
67
|
+
"""
|
|
68
|
+
self._agents_dir = agents_dir
|
|
69
|
+
self._skills_dir = skills_dir
|
|
70
|
+
self._config_dir = config_dir
|
|
71
|
+
|
|
72
|
+
# Load agent frontmatter -- skip README.md (not an agent definition)
|
|
73
|
+
self._agent_frontmatter: dict[str, dict[str, Any]] = {}
|
|
74
|
+
if agents_dir.is_dir():
|
|
75
|
+
for md_file in sorted(agents_dir.glob("*.md")):
|
|
76
|
+
if md_file.name.upper() == "README.MD":
|
|
77
|
+
continue
|
|
78
|
+
content = md_file.read_text(encoding="utf-8", errors="replace")
|
|
79
|
+
fm = _parse_frontmatter(content)
|
|
80
|
+
name = fm.get("name", md_file.stem)
|
|
81
|
+
self._agent_frontmatter[name] = fm
|
|
82
|
+
|
|
83
|
+
# Load surface routing config
|
|
84
|
+
routing_file = config_dir / "surface-routing.json"
|
|
85
|
+
if routing_file.is_file():
|
|
86
|
+
self._routing_config = json.loads(
|
|
87
|
+
routing_file.read_text(encoding="utf-8")
|
|
88
|
+
)
|
|
89
|
+
else:
|
|
90
|
+
self._routing_config = {"surfaces": {}}
|
|
91
|
+
|
|
92
|
+
# Load contracts
|
|
93
|
+
contracts_file = config_dir / "context-contracts.json"
|
|
94
|
+
if contracts_file.is_file():
|
|
95
|
+
self._contracts = json.loads(
|
|
96
|
+
contracts_file.read_text(encoding="utf-8")
|
|
97
|
+
)
|
|
98
|
+
else:
|
|
99
|
+
self._contracts = {"agents": {}}
|
|
100
|
+
|
|
101
|
+
# Discover all skill directories
|
|
102
|
+
self._all_skills: list[str] = []
|
|
103
|
+
if skills_dir.is_dir():
|
|
104
|
+
for item in sorted(skills_dir.iterdir()):
|
|
105
|
+
if item.is_dir() and not item.name.startswith("."):
|
|
106
|
+
self._all_skills.append(item.name)
|
|
107
|
+
|
|
108
|
+
def _get_surfaces_for_agent(self, agent_name: str) -> list[str]:
|
|
109
|
+
"""Find which surfaces route to a given agent."""
|
|
110
|
+
surfaces: list[str] = []
|
|
111
|
+
for surface_name, surface_cfg in self._routing_config.get(
|
|
112
|
+
"surfaces", {}
|
|
113
|
+
).items():
|
|
114
|
+
if surface_cfg.get("primary_agent") == agent_name:
|
|
115
|
+
surfaces.append(surface_name)
|
|
116
|
+
return surfaces
|
|
117
|
+
|
|
118
|
+
def get_agent_profiles(self) -> list[AgentProfile]:
|
|
119
|
+
"""Full profile for each agent.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
List of AgentProfile instances, one per agent.
|
|
123
|
+
"""
|
|
124
|
+
profiles: list[AgentProfile] = []
|
|
125
|
+
|
|
126
|
+
for agent_name, fm in self._agent_frontmatter.items():
|
|
127
|
+
skills = fm.get("skills", [])
|
|
128
|
+
surfaces = self._get_surfaces_for_agent(agent_name)
|
|
129
|
+
contract = self._contracts.get("agents", {}).get(agent_name, {})
|
|
130
|
+
read_sections = contract.get("read", [])
|
|
131
|
+
write_sections = contract.get("write", [])
|
|
132
|
+
|
|
133
|
+
profiles.append(
|
|
134
|
+
AgentProfile(
|
|
135
|
+
agent_name=agent_name,
|
|
136
|
+
skills=skills,
|
|
137
|
+
invocation_count=0,
|
|
138
|
+
surfaces=surfaces,
|
|
139
|
+
read_sections=read_sections,
|
|
140
|
+
write_sections=write_sections,
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
return profiles
|
|
145
|
+
|
|
146
|
+
def get_skill_mappings(self) -> list[SkillMapping]:
|
|
147
|
+
"""Which skills are used by which agents. Flag orphans.
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
List of SkillMapping instances, one per discovered skill.
|
|
151
|
+
"""
|
|
152
|
+
# Build reverse map: skill -> list of agents
|
|
153
|
+
skill_to_agents: dict[str, list[str]] = {}
|
|
154
|
+
for agent_name, fm in self._agent_frontmatter.items():
|
|
155
|
+
for skill in fm.get("skills", []):
|
|
156
|
+
skill_to_agents.setdefault(skill, []).append(agent_name)
|
|
157
|
+
|
|
158
|
+
mappings: list[SkillMapping] = []
|
|
159
|
+
for skill_name in self._all_skills:
|
|
160
|
+
skill_path = str(self._skills_dir / skill_name)
|
|
161
|
+
used_by = skill_to_agents.get(skill_name, [])
|
|
162
|
+
mappings.append(
|
|
163
|
+
SkillMapping(
|
|
164
|
+
skill_name=skill_name,
|
|
165
|
+
skill_path=skill_path,
|
|
166
|
+
used_by_agents=used_by,
|
|
167
|
+
is_orphan=len(used_by) == 0,
|
|
168
|
+
)
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
return mappings
|
|
172
|
+
|
|
173
|
+
def get_unused_skills(self) -> list[str]:
|
|
174
|
+
"""Skills that no agent references.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
List of orphan skill names.
|
|
178
|
+
"""
|
|
179
|
+
return [m.skill_name for m in self.get_skill_mappings() if m.is_orphan]
|
|
180
|
+
|
|
181
|
+
def enrich_from_logs(self, metrics_path: Path) -> dict[str, Any]:
|
|
182
|
+
"""Cross-reference with production metrics.
|
|
183
|
+
|
|
184
|
+
Reads audit-*.jsonl files to count agent invocations.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
metrics_path: Path to directory containing audit JSONL files.
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
Dict with agent_invocations and skill_loads counts.
|
|
191
|
+
"""
|
|
192
|
+
agent_counts: dict[str, int] = {}
|
|
193
|
+
skill_counts: dict[str, int] = {}
|
|
194
|
+
|
|
195
|
+
if not metrics_path.is_dir():
|
|
196
|
+
return {"agent_invocations": agent_counts, "skill_loads": skill_counts}
|
|
197
|
+
|
|
198
|
+
for jsonl_file in sorted(metrics_path.glob("audit-*.jsonl")):
|
|
199
|
+
for line in jsonl_file.read_text(
|
|
200
|
+
encoding="utf-8", errors="replace"
|
|
201
|
+
).splitlines():
|
|
202
|
+
line = line.strip()
|
|
203
|
+
if not line:
|
|
204
|
+
continue
|
|
205
|
+
try:
|
|
206
|
+
record = json.loads(line)
|
|
207
|
+
except (json.JSONDecodeError, ValueError):
|
|
208
|
+
continue
|
|
209
|
+
agent = record.get("agent", record.get("subagent_type", ""))
|
|
210
|
+
if agent:
|
|
211
|
+
agent_counts[agent] = agent_counts.get(agent, 0) + 1
|
|
212
|
+
|
|
213
|
+
# Map agent counts to skill counts
|
|
214
|
+
for agent_name, count in agent_counts.items():
|
|
215
|
+
fm = self._agent_frontmatter.get(agent_name, {})
|
|
216
|
+
for skill in fm.get("skills", []):
|
|
217
|
+
skill_counts[skill] = skill_counts.get(skill, 0) + count
|
|
218
|
+
|
|
219
|
+
return {"agent_invocations": agent_counts, "skill_loads": skill_counts}
|
|
220
|
+
|
|
221
|
+
def format_report(self) -> str:
|
|
222
|
+
"""Human-readable report of the agent/skill/surface map.
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
Formatted multi-line string.
|
|
226
|
+
"""
|
|
227
|
+
lines = []
|
|
228
|
+
lines.append("=" * 60)
|
|
229
|
+
lines.append("SKILLS MAPPER REPORT")
|
|
230
|
+
lines.append("=" * 60)
|
|
231
|
+
|
|
232
|
+
# Agent -> Skills table
|
|
233
|
+
lines.append("")
|
|
234
|
+
lines.append("AGENT -> SKILLS:")
|
|
235
|
+
lines.append("-" * 40)
|
|
236
|
+
for profile in self.get_agent_profiles():
|
|
237
|
+
skills_str = ", ".join(profile.skills) or "(none)"
|
|
238
|
+
surfaces_str = ", ".join(profile.surfaces) or "(none)"
|
|
239
|
+
lines.append(" " + profile.agent_name + ":")
|
|
240
|
+
lines.append(" Skills: " + skills_str)
|
|
241
|
+
lines.append(" Surfaces: " + surfaces_str)
|
|
242
|
+
lines.append(" Read: " + str(len(profile.read_sections)) + " sections")
|
|
243
|
+
lines.append(" Write: " + str(len(profile.write_sections)) + " sections")
|
|
244
|
+
|
|
245
|
+
# Skill -> Agents table (reverse)
|
|
246
|
+
lines.append("")
|
|
247
|
+
lines.append("SKILL -> AGENTS:")
|
|
248
|
+
lines.append("-" * 40)
|
|
249
|
+
for mapping in self.get_skill_mappings():
|
|
250
|
+
agents_str = ", ".join(mapping.used_by_agents) or "ORPHAN"
|
|
251
|
+
orphan_tag = " [ORPHAN]" if mapping.is_orphan else ""
|
|
252
|
+
lines.append(" " + mapping.skill_name + ": " + agents_str + orphan_tag)
|
|
253
|
+
|
|
254
|
+
# Orphan skills
|
|
255
|
+
unused = self.get_unused_skills()
|
|
256
|
+
if unused:
|
|
257
|
+
lines.append("")
|
|
258
|
+
lines.append("ORPHAN SKILLS (" + str(len(unused)) + "):")
|
|
259
|
+
lines.append("-" * 40)
|
|
260
|
+
for skill in unused:
|
|
261
|
+
lines.append(" - " + skill)
|
|
262
|
+
|
|
263
|
+
lines.append("=" * 60)
|
|
264
|
+
return chr(10).join(lines)
|
|
Binary file
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Episodic Memory Module for GAIA-OPS
|
|
3
|
+
|
|
4
|
+
This module provides episodic memory functionality for storing and retrieving
|
|
5
|
+
historical context from user interactions.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .episodic import (
|
|
9
|
+
EpisodicMemory,
|
|
10
|
+
Episode,
|
|
11
|
+
search_episodic_memory
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
'EpisodicMemory',
|
|
16
|
+
'Episode',
|
|
17
|
+
'search_episodic_memory'
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
__version__ = '1.0.0'
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Backfill FTS5 search index from episodes.jsonl.
|
|
4
|
+
|
|
5
|
+
Reads all episodes from the episodic memory JSONL file and indexes them
|
|
6
|
+
into the FTS5 search store. Idempotent -- safe to run multiple times.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
python3 tools/memory/backfill_fts5.py
|
|
10
|
+
python3 -m tools.memory.backfill_fts5
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import sys
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _find_project_root() -> Path:
|
|
19
|
+
"""Walk up from cwd to find the directory containing .claude/."""
|
|
20
|
+
current = Path.cwd()
|
|
21
|
+
for candidate in [current, *current.parents]:
|
|
22
|
+
if (candidate / ".claude").is_dir():
|
|
23
|
+
return candidate
|
|
24
|
+
return current
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def main() -> int:
|
|
28
|
+
project_root = _find_project_root()
|
|
29
|
+
episodes_path = project_root / ".claude" / "project-context" / "episodic-memory" / "episodes.jsonl"
|
|
30
|
+
|
|
31
|
+
if not episodes_path.exists():
|
|
32
|
+
print(f"ERROR: episodes.jsonl not found at {episodes_path}", file=sys.stderr)
|
|
33
|
+
return 1
|
|
34
|
+
|
|
35
|
+
# Import here so the script works when run from the project root
|
|
36
|
+
# (tools/memory is on the path via the module run, or cwd is project root)
|
|
37
|
+
try:
|
|
38
|
+
from tools.memory import search_store
|
|
39
|
+
except ImportError:
|
|
40
|
+
# Fallback: add project root to sys.path and retry
|
|
41
|
+
sys.path.insert(0, str(project_root))
|
|
42
|
+
from tools.memory import search_store
|
|
43
|
+
|
|
44
|
+
indexed = 0
|
|
45
|
+
skipped_event = 0
|
|
46
|
+
skipped_malformed = 0
|
|
47
|
+
|
|
48
|
+
with open(episodes_path, encoding="utf-8") as fh:
|
|
49
|
+
for lineno, raw_line in enumerate(fh, start=1):
|
|
50
|
+
line = raw_line.strip()
|
|
51
|
+
if not line:
|
|
52
|
+
continue
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
record = json.loads(line)
|
|
56
|
+
except json.JSONDecodeError as exc:
|
|
57
|
+
print(f"WARNING: line {lineno} is malformed JSON, skipping ({exc})", file=sys.stderr)
|
|
58
|
+
skipped_malformed += 1
|
|
59
|
+
continue
|
|
60
|
+
|
|
61
|
+
# Skip outcome_update / relationship_added events
|
|
62
|
+
if "event_type" in record:
|
|
63
|
+
skipped_event += 1
|
|
64
|
+
continue
|
|
65
|
+
|
|
66
|
+
episode_id = record.get("episode_id") or record.get("id", "")
|
|
67
|
+
if not episode_id:
|
|
68
|
+
print(f"WARNING: line {lineno} has no episode_id/id, skipping", file=sys.stderr)
|
|
69
|
+
skipped_malformed += 1
|
|
70
|
+
continue
|
|
71
|
+
|
|
72
|
+
prompt = record.get("prompt", "")
|
|
73
|
+
enriched_prompt = record.get("enriched_prompt", "")
|
|
74
|
+
title = record.get("title", "")
|
|
75
|
+
|
|
76
|
+
raw_tags = record.get("tags", [])
|
|
77
|
+
if isinstance(raw_tags, list):
|
|
78
|
+
tags = " ".join(str(t) for t in raw_tags)
|
|
79
|
+
else:
|
|
80
|
+
tags = str(raw_tags)
|
|
81
|
+
|
|
82
|
+
search_store.index_episode(
|
|
83
|
+
episode_id=episode_id,
|
|
84
|
+
prompt=prompt,
|
|
85
|
+
enriched_prompt=enriched_prompt,
|
|
86
|
+
tags=tags,
|
|
87
|
+
title=title,
|
|
88
|
+
)
|
|
89
|
+
indexed += 1
|
|
90
|
+
|
|
91
|
+
if indexed % 1000 == 0:
|
|
92
|
+
print(f"Progress: {indexed} episodes indexed...")
|
|
93
|
+
|
|
94
|
+
if indexed == 0 and skipped_event == 0 and skipped_malformed == 0:
|
|
95
|
+
print("0 episodes indexed (file is empty)")
|
|
96
|
+
else:
|
|
97
|
+
print(
|
|
98
|
+
f"Done: {indexed} episodes indexed"
|
|
99
|
+
+ (f", {skipped_event} event records skipped" if skipped_event else "")
|
|
100
|
+
+ (f", {skipped_malformed} malformed lines skipped" if skipped_malformed else "")
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
return 0
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
if __name__ == "__main__":
|
|
107
|
+
sys.exit(main())
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Conflict Detection Module for GAIA-OPS Memory Files
|
|
4
|
+
|
|
5
|
+
Scans memory .md files for contradictions by:
|
|
6
|
+
1. Loading all .md files from the memory directory
|
|
7
|
+
2. Computing Jaccard similarity on word sets (after stopword removal)
|
|
8
|
+
3. For similar file pairs (Jaccard > threshold), checking for polarity contradictions
|
|
9
|
+
4. Returning structured conflict reports
|
|
10
|
+
|
|
11
|
+
Inspired by hippo-memory's conflict detection heuristics.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
import re
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Optional
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# 50-word stopword list
|
|
21
|
+
STOPWORDS = frozenset([
|
|
22
|
+
"a", "an", "the", "and", "or", "but", "in", "on", "at", "to", "for",
|
|
23
|
+
"of", "with", "by", "from", "is", "are", "was", "were", "be", "been",
|
|
24
|
+
"being", "have", "has", "had", "do", "does", "did", "will", "would",
|
|
25
|
+
"could", "should", "may", "might", "shall", "that", "this", "these",
|
|
26
|
+
"those", "it", "its", "as", "if", "than", "then", "so", "not", "no",
|
|
27
|
+
"all", "any", "each", "both", "more",
|
|
28
|
+
])
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _tokenize(text: str) -> set:
|
|
32
|
+
"""Tokenize text into lowercase words, removing stopwords."""
|
|
33
|
+
words = re.findall(r"[a-zA-Z0-9]+", text.lower())
|
|
34
|
+
return {w for w in words if w not in STOPWORDS and len(w) > 1}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _jaccard(set_a: set, set_b: set) -> float:
|
|
38
|
+
"""Compute Jaccard similarity between two word sets."""
|
|
39
|
+
if not set_a and not set_b:
|
|
40
|
+
return 0.0
|
|
41
|
+
intersection = len(set_a & set_b)
|
|
42
|
+
union = len(set_a | set_b)
|
|
43
|
+
return intersection / union if union > 0 else 0.0
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _extract_lines(text: str) -> list:
|
|
47
|
+
"""Return non-empty, non-heading stripped lines from text."""
|
|
48
|
+
return [line.strip() for line in text.splitlines() if line.strip()]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _check_polarity_contradictions(lines_a: list, lines_b: list) -> list:
|
|
52
|
+
"""
|
|
53
|
+
Check for polarity contradictions between two sets of lines.
|
|
54
|
+
|
|
55
|
+
Patterns checked:
|
|
56
|
+
- "use X" vs "do not use X" / "don't use X"
|
|
57
|
+
- "enabled" vs "disabled"
|
|
58
|
+
- "always" vs "never"
|
|
59
|
+
- "Windows" vs "WSL" / "Linux" in same context
|
|
60
|
+
- Version mismatches (e.g., "v4" vs "v5")
|
|
61
|
+
"""
|
|
62
|
+
conflicts = []
|
|
63
|
+
|
|
64
|
+
# Pattern 1: "use X" vs "do not use X" / "don't use X"
|
|
65
|
+
use_pattern = re.compile(r"\buse\s+(\w+)", re.IGNORECASE)
|
|
66
|
+
no_use_pattern = re.compile(r"\b(?:do\s+not|don\'t|never\s+use)\s+(\w+)", re.IGNORECASE)
|
|
67
|
+
|
|
68
|
+
uses_a = {m.group(1).lower(): line for line in lines_a for m in [use_pattern.search(line)] if m}
|
|
69
|
+
uses_b = {m.group(1).lower(): line for line in lines_b for m in [use_pattern.search(line)] if m}
|
|
70
|
+
no_uses_a = {m.group(1).lower(): line for line in lines_a for m in [no_use_pattern.search(line)] if m}
|
|
71
|
+
no_uses_b = {m.group(1).lower(): line for line in lines_b for m in [no_use_pattern.search(line)] if m}
|
|
72
|
+
|
|
73
|
+
for term, line_a in uses_a.items():
|
|
74
|
+
if term in no_uses_b:
|
|
75
|
+
conflicts.append({
|
|
76
|
+
"line_a": line_a,
|
|
77
|
+
"line_b": no_uses_b[term],
|
|
78
|
+
"reason": f"'use {term}' contradicts 'do not use {term}'",
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
for term, line_b in uses_b.items():
|
|
82
|
+
if term in no_uses_a:
|
|
83
|
+
conflicts.append({
|
|
84
|
+
"line_a": no_uses_a[term],
|
|
85
|
+
"line_b": line_b,
|
|
86
|
+
"reason": f"'use {term}' contradicts 'do not use {term}'",
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
# Pattern 2: "enabled" vs "disabled"
|
|
90
|
+
enabled_pattern = re.compile(r"\benabled\b", re.IGNORECASE)
|
|
91
|
+
disabled_pattern = re.compile(r"\bdisabled\b", re.IGNORECASE)
|
|
92
|
+
|
|
93
|
+
enabled_lines_a = [l for l in lines_a if enabled_pattern.search(l)]
|
|
94
|
+
disabled_lines_a = [l for l in lines_a if disabled_pattern.search(l)]
|
|
95
|
+
enabled_lines_b = [l for l in lines_b if enabled_pattern.search(l)]
|
|
96
|
+
disabled_lines_b = [l for l in lines_b if disabled_pattern.search(l)]
|
|
97
|
+
|
|
98
|
+
for line_a in enabled_lines_a:
|
|
99
|
+
for line_b in disabled_lines_b:
|
|
100
|
+
# Only flag if they share a common significant keyword
|
|
101
|
+
shared = _tokenize(line_a) & _tokenize(line_b)
|
|
102
|
+
if shared:
|
|
103
|
+
conflicts.append({
|
|
104
|
+
"line_a": line_a,
|
|
105
|
+
"line_b": line_b,
|
|
106
|
+
"reason": "enabled vs disabled on shared topic",
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
for line_a in disabled_lines_a:
|
|
110
|
+
for line_b in enabled_lines_b:
|
|
111
|
+
shared = _tokenize(line_a) & _tokenize(line_b)
|
|
112
|
+
if shared:
|
|
113
|
+
conflicts.append({
|
|
114
|
+
"line_a": line_a,
|
|
115
|
+
"line_b": line_b,
|
|
116
|
+
"reason": "disabled vs enabled on shared topic",
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
# Pattern 3: "always" vs "never"
|
|
120
|
+
always_pattern = re.compile(r"\balways\b", re.IGNORECASE)
|
|
121
|
+
never_pattern = re.compile(r"\bnever\b", re.IGNORECASE)
|
|
122
|
+
|
|
123
|
+
always_lines_a = [l for l in lines_a if always_pattern.search(l)]
|
|
124
|
+
never_lines_a = [l for l in lines_a if never_pattern.search(l)]
|
|
125
|
+
always_lines_b = [l for l in lines_b if always_pattern.search(l)]
|
|
126
|
+
never_lines_b = [l for l in lines_b if never_pattern.search(l)]
|
|
127
|
+
|
|
128
|
+
for line_a in always_lines_a:
|
|
129
|
+
for line_b in never_lines_b:
|
|
130
|
+
shared = _tokenize(line_a) & _tokenize(line_b)
|
|
131
|
+
if shared:
|
|
132
|
+
conflicts.append({
|
|
133
|
+
"line_a": line_a,
|
|
134
|
+
"line_b": line_b,
|
|
135
|
+
"reason": "always vs never on shared topic",
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
for line_a in never_lines_a:
|
|
139
|
+
for line_b in always_lines_b:
|
|
140
|
+
shared = _tokenize(line_a) & _tokenize(line_b)
|
|
141
|
+
if shared:
|
|
142
|
+
conflicts.append({
|
|
143
|
+
"line_a": line_a,
|
|
144
|
+
"line_b": line_b,
|
|
145
|
+
"reason": "never vs always on shared topic",
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
# Pattern 4: "Windows" vs "WSL/Linux" in same context
|
|
149
|
+
windows_pattern = re.compile(r"\bWindows\b")
|
|
150
|
+
wsl_pattern = re.compile(r"\b(?:WSL|Linux)\b")
|
|
151
|
+
|
|
152
|
+
windows_lines_a = [l for l in lines_a if windows_pattern.search(l)]
|
|
153
|
+
wsl_lines_a = [l for l in lines_a if wsl_pattern.search(l)]
|
|
154
|
+
windows_lines_b = [l for l in lines_b if windows_pattern.search(l)]
|
|
155
|
+
wsl_lines_b = [l for l in lines_b if wsl_pattern.search(l)]
|
|
156
|
+
|
|
157
|
+
# Cross-file: file_a says Windows, file_b says WSL for same context
|
|
158
|
+
for line_a in windows_lines_a:
|
|
159
|
+
for line_b in wsl_lines_b:
|
|
160
|
+
shared = _tokenize(line_a) & _tokenize(line_b)
|
|
161
|
+
if shared:
|
|
162
|
+
conflicts.append({
|
|
163
|
+
"line_a": line_a,
|
|
164
|
+
"line_b": line_b,
|
|
165
|
+
"reason": "Windows vs WSL/Linux on shared topic",
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
for line_a in wsl_lines_a:
|
|
169
|
+
for line_b in windows_lines_b:
|
|
170
|
+
shared = _tokenize(line_a) & _tokenize(line_b)
|
|
171
|
+
if shared:
|
|
172
|
+
conflicts.append({
|
|
173
|
+
"line_a": line_a,
|
|
174
|
+
"line_b": line_b,
|
|
175
|
+
"reason": "WSL/Linux vs Windows on shared topic",
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
# Pattern 5: Version mismatches (e.g., "v4" vs "v5")
|
|
179
|
+
version_pattern = re.compile(r"\bv(\d+)\b", re.IGNORECASE)
|
|
180
|
+
|
|
181
|
+
def extract_versions(lines: list) -> dict:
|
|
182
|
+
"""Map version number -> line for lines containing a version."""
|
|
183
|
+
result = {}
|
|
184
|
+
for line in lines:
|
|
185
|
+
for m in version_pattern.finditer(line):
|
|
186
|
+
v = int(m.group(1))
|
|
187
|
+
result[v] = line
|
|
188
|
+
return result
|
|
189
|
+
|
|
190
|
+
versions_a = extract_versions(lines_a)
|
|
191
|
+
versions_b = extract_versions(lines_b)
|
|
192
|
+
|
|
193
|
+
for va, line_a in versions_a.items():
|
|
194
|
+
for vb, line_b in versions_b.items():
|
|
195
|
+
if va != vb:
|
|
196
|
+
# Only flag if the context words (excluding the version itself) overlap
|
|
197
|
+
tokens_a = _tokenize(re.sub(r"\bv\d+\b", "", line_a, flags=re.IGNORECASE))
|
|
198
|
+
tokens_b = _tokenize(re.sub(r"\bv\d+\b", "", line_b, flags=re.IGNORECASE))
|
|
199
|
+
shared = tokens_a & tokens_b
|
|
200
|
+
if shared:
|
|
201
|
+
conflicts.append({
|
|
202
|
+
"line_a": line_a,
|
|
203
|
+
"line_b": line_b,
|
|
204
|
+
"reason": f"version mismatch: v{va} vs v{vb} on shared topic",
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
# Deduplicate: remove identical (line_a, line_b, reason) triples
|
|
208
|
+
seen = set()
|
|
209
|
+
deduped = []
|
|
210
|
+
for c in conflicts:
|
|
211
|
+
key = (c["line_a"], c["line_b"], c["reason"])
|
|
212
|
+
if key not in seen:
|
|
213
|
+
seen.add(key)
|
|
214
|
+
deduped.append(c)
|
|
215
|
+
|
|
216
|
+
return deduped
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _load_memory_files(memory_dir: Path) -> dict:
|
|
220
|
+
"""
|
|
221
|
+
Load all .md files from memory_dir.
|
|
222
|
+
Returns dict mapping filename -> file content string.
|
|
223
|
+
"""
|
|
224
|
+
files = {}
|
|
225
|
+
try:
|
|
226
|
+
for entry in memory_dir.iterdir():
|
|
227
|
+
if entry.is_file() and entry.suffix == ".md":
|
|
228
|
+
try:
|
|
229
|
+
content = entry.read_text(encoding="utf-8", errors="replace")
|
|
230
|
+
files[str(entry)] = content
|
|
231
|
+
except (PermissionError, OSError):
|
|
232
|
+
pass
|
|
233
|
+
except (PermissionError, OSError, FileNotFoundError):
|
|
234
|
+
pass
|
|
235
|
+
return files
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def detect_conflicts(
|
|
239
|
+
memory_dir: Optional[Path] = None,
|
|
240
|
+
threshold: float = 0.3,
|
|
241
|
+
) -> list:
|
|
242
|
+
"""
|
|
243
|
+
Scan memory .md files for contradictions.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
memory_dir: Path to directory containing memory .md files.
|
|
247
|
+
Defaults to ~/.claude/projects/-home-jorge-ws-me/memory/
|
|
248
|
+
threshold: Jaccard similarity threshold above which pairs are checked
|
|
249
|
+
for polarity contradictions. Default 0.3.
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
List of dicts with keys:
|
|
253
|
+
file_a - absolute path of first file
|
|
254
|
+
file_b - absolute path of second file
|
|
255
|
+
similarity - Jaccard similarity score (float)
|
|
256
|
+
conflicts - list of {"line_a", "line_b", "reason"} dicts
|
|
257
|
+
"""
|
|
258
|
+
if memory_dir is None:
|
|
259
|
+
default = Path.home() / ".claude" / "projects" / "-home-jorge-ws-me" / "memory"
|
|
260
|
+
memory_dir = default
|
|
261
|
+
|
|
262
|
+
memory_dir = Path(memory_dir)
|
|
263
|
+
|
|
264
|
+
file_contents = _load_memory_files(memory_dir)
|
|
265
|
+
if not file_contents:
|
|
266
|
+
return []
|
|
267
|
+
|
|
268
|
+
file_paths = sorted(file_contents.keys())
|
|
269
|
+
word_sets = {fp: _tokenize(file_contents[fp]) for fp in file_paths}
|
|
270
|
+
file_lines = {fp: _extract_lines(file_contents[fp]) for fp in file_paths}
|
|
271
|
+
|
|
272
|
+
results = []
|
|
273
|
+
|
|
274
|
+
for i in range(len(file_paths)):
|
|
275
|
+
for j in range(i + 1, len(file_paths)):
|
|
276
|
+
fp_a = file_paths[i]
|
|
277
|
+
fp_b = file_paths[j]
|
|
278
|
+
|
|
279
|
+
sim = _jaccard(word_sets[fp_a], word_sets[fp_b])
|
|
280
|
+
if sim <= threshold:
|
|
281
|
+
continue
|
|
282
|
+
|
|
283
|
+
conflicts = _check_polarity_contradictions(
|
|
284
|
+
file_lines[fp_a],
|
|
285
|
+
file_lines[fp_b],
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
results.append({
|
|
289
|
+
"file_a": fp_a,
|
|
290
|
+
"file_b": fp_b,
|
|
291
|
+
"similarity": round(sim, 4),
|
|
292
|
+
"conflicts": conflicts,
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
return results
|