rosett-ai 1.3.3
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.
- checksums.yaml +7 -0
- data/.ai-provenance.yml +119 -0
- data/.debride_whitelist +186 -0
- data/.fasterer.yml +29 -0
- data/.mdl_style.rb +10 -0
- data/.mdlrc +3 -0
- data/.mutant.yml +49 -0
- data/.namespace-allowlist +42 -0
- data/.reek.yml +1040 -0
- data/.rosett-ai/config.yml +3 -0
- data/.rspec +5 -0
- data/.rubocop.yml +380 -0
- data/.ruby-version +1 -0
- data/.yamllint +51 -0
- data/.yardopts +12 -0
- data/AI-DISCLOSURE.md +48 -0
- data/CHANGELOG.md +519 -0
- data/CLAUDE.md +141 -0
- data/CONTRIBUTING.md +734 -0
- data/INSTALL.md +154 -0
- data/LICENSE +674 -0
- data/LICENSE.md +675 -0
- data/QUICKSTART.md +73 -0
- data/README.md +366 -0
- data/Rakefile +200 -0
- data/SECURITY.md +114 -0
- data/bin/rai +1 -0
- data/cliff.toml +52 -0
- data/conf/adopt_redactions.yml +8 -0
- data/conf/behaviour/.gitkeep +0 -0
- data/conf/compliance/cra_rules.yml +25 -0
- data/conf/compliance/license_rules.yml +20 -0
- data/conf/design/aaif_alignment.yml +181 -0
- data/conf/design/ab_testing.yml +172 -0
- data/conf/design/accessibility.yml +84 -0
- data/conf/design/ai_authorship.yml +210 -0
- data/conf/design/ai_provenance.yml +224 -0
- data/conf/design/ai_tool_configuration.yml +207 -0
- data/conf/design/architecture.yml +139 -0
- data/conf/design/autocompletion.yml +115 -0
- data/conf/design/backward_compatibility.yml +112 -0
- data/conf/design/behaviour_composition.yml +246 -0
- data/conf/design/build_rake_extraction.yml +57 -0
- data/conf/design/ci_pipeline.yml +100 -0
- data/conf/design/claude_code_configuration.yml +157 -0
- data/conf/design/compiler.yml +128 -0
- data/conf/design/comply.yml +153 -0
- data/conf/design/content_packs.yml +84 -0
- data/conf/design/desktop_integration.yml +289 -0
- data/conf/design/distribution.yml +216 -0
- data/conf/design/doctor.yml +184 -0
- data/conf/design/documentation.yml +152 -0
- data/conf/design/engine_architecture.yml +257 -0
- data/conf/design/error_handling.yml +103 -0
- data/conf/design/feature_flags.yml +142 -0
- data/conf/design/git_hooks.yml +165 -0
- data/conf/design/gui_plugins.yml +475 -0
- data/conf/design/i18n.yml +84 -0
- data/conf/design/integration_testing.yml +56 -0
- data/conf/design/licensing_system.yml +88 -0
- data/conf/design/lifecycle_management.yml +208 -0
- data/conf/design/mcp_integration.yml +207 -0
- data/conf/design/mcp_settings.yml +126 -0
- data/conf/design/migration.yml +56 -0
- data/conf/design/monitoring_observability.yml +194 -0
- data/conf/design/namespace_cleanup.yml +145 -0
- data/conf/design/plugin_test_segregation.yml +145 -0
- data/conf/design/policy_management.yml +229 -0
- data/conf/design/project_management.yml +183 -0
- data/conf/design/rai_mcp_asset_discovery.yml +164 -0
- data/conf/design/rai_mcp_server.yml +605 -0
- data/conf/design/release_management.yml +117 -0
- data/conf/design/retrofit.yml +199 -0
- data/conf/design/retrospective_analyzer.yml +79 -0
- data/conf/design/scope_hierarchy.yml +352 -0
- data/conf/design/security.yml +115 -0
- data/conf/design/session_retrospective.yml +85 -0
- data/conf/design/smart_ui_feedback.yml +89 -0
- data/conf/design/structured_logging.yml +148 -0
- data/conf/design/styles.yml +123 -0
- data/conf/design/test_peer_review.yml +89 -0
- data/conf/design/testing.yml +136 -0
- data/conf/design/threat_model.yml +108 -0
- data/conf/design/ui_framework.yml +111 -0
- data/conf/design/usage_optimization.yml +122 -0
- data/conf/design/version_management.yml +60 -0
- data/conf/design/workflow.yml +227 -0
- data/conf/mcp/server_defaults.yml +42 -0
- data/conf/mcp/trust.yml +21 -0
- data/conf/packaging/core.yml +12 -0
- data/conf/packaging/gtk4.yml +11 -0
- data/conf/packaging/qt6.yml +11 -0
- data/conf/policy/default_deny_list.yml +197 -0
- data/conf/review/cli-command-audit.yml +857 -0
- data/conf/review/design-docs.yml +1064 -0
- data/conf/review/design-questionnaire.yml +153 -0
- data/conf/review/questionnaire.yml +146 -0
- data/conf/review/rosett-ai-core.yml +2919 -0
- data/conf/schemas/ai_config_schema.json +73 -0
- data/conf/schemas/behaviour_schema.json +132 -0
- data/conf/schemas/compliance_rule_schema.json +63 -0
- data/conf/schemas/content_pack_manifest_schema.json +51 -0
- data/conf/schemas/design_schema.json +210 -0
- data/conf/schemas/engine_manifest_schema.json +144 -0
- data/conf/schemas/lockfile_schema.json +74 -0
- data/conf/schemas/mcp_server_schema.json +48 -0
- data/conf/schemas/packaging_schema.json +70 -0
- data/conf/schemas/policy_schema.json +85 -0
- data/conf/schemas/provenance_schema.json +84 -0
- data/conf/schemas/rai_config_schema.json +56 -0
- data/conf/schemas/rai_project_schema.json +20 -0
- data/conf/schemas/scope_hierarchy_schema.json +49 -0
- data/conf/schemas/target_schema.json +67 -0
- data/conf/schemas/tooling_schema.json +65 -0
- data/conf/schemas/workflow_schema.json +112 -0
- data/conf/targets/agents_md.yml +17 -0
- data/conf/targets/claude.yml +12 -0
- data/conf/tooling/tools.yml +58 -0
- data/dist/rosett-ai-mcp.service +48 -0
- data/dist/rosett-ai-mcp.yml.default +45 -0
- data/doc/AAIF_POSITIONING.md +58 -0
- data/doc/ADOPT.md +224 -0
- data/doc/AI_PROVENANCE.md +139 -0
- data/doc/ARCHITECTURE.md +920 -0
- data/doc/BEHAVIOUR.md +409 -0
- data/doc/BUILD.md +138 -0
- data/doc/CI_CD_RECIPES.md +171 -0
- data/doc/CLAUDE_SESSIONS_MOVED.md +16 -0
- data/doc/COMMAND_ANALYSIS.md +229 -0
- data/doc/CONFIGURATION.md +281 -0
- data/doc/DESIGN_AUDIT.md +235 -0
- data/doc/DESIGN_PEER_REVIEW.md +771 -0
- data/doc/DESKTOP.md +447 -0
- data/doc/ENGINES.md +567 -0
- data/doc/ENGINE_DEVELOPMENT_GUIDE.md +417 -0
- data/doc/FEATURE_AUDIT.md +218 -0
- data/doc/IMPLEMENTATION_PLAN.md +669 -0
- data/doc/INCIDENT_REPORT_2026-02-02.md +251 -0
- data/doc/MIGRATION_GUIDE.md +88 -0
- data/doc/PACKAGING.md +232 -0
- data/doc/PROJECT_DASHBOARD.md +153 -0
- data/doc/PULP_DEPLOYMENT.md +164 -0
- data/doc/QUALITY_FIX_SUMMARY.md +110 -0
- data/doc/QUICK_START.md +162 -0
- data/doc/REEK_CONFIGURATION.md +166 -0
- data/doc/REFERENCE.md +253 -0
- data/doc/REFERENCES.md +324 -0
- data/doc/SECURITY_REVIEW_CHECKLIST.md +72 -0
- data/doc/SESSION_2026-02-28_GTK4_HARDENING.md +359 -0
- data/doc/SETUP.md +202 -0
- data/doc/TEST_PEER_REVIEW.md +152 -0
- data/doc/THREAT_MODEL.md +230 -0
- data/doc/USAGE.md +545 -0
- data/doc/USER_MANUAL.md +585 -0
- data/doc/ai_test_review_checklist.md +110 -0
- data/doc/changes/2026-02-18-packaging-fpm.md +155 -0
- data/doc/changes/2026-02-19-testing-infrastructure.md +221 -0
- data/doc/changes/2026-02-20-security-implementation.md +281 -0
- data/doc/changes/2026-02-20-styles-implementation.md +220 -0
- data/doc/changes/2026-02-21-architecture-completion.md +95 -0
- data/doc/changes/2026-02-21-architecture-ui-layer.md +253 -0
- data/doc/changes/2026-02-21-cc-config-implementation.md +108 -0
- data/doc/changes/2026-02-21-ci-pipeline-implementation.md +214 -0
- data/doc/changes/2026-02-21-compiler-multi-target-pipeline.md +241 -0
- data/doc/changes/2026-02-21-config-design-show-commands.md +61 -0
- data/doc/changes/2026-02-21-design-implementation-overview.md +455 -0
- data/doc/changes/2026-02-21-lifecycle-management.md +196 -0
- data/doc/changes/2026-02-21-path-resolver.md +128 -0
- data/doc/changes/2026-02-24-ci-tmpdir-mutant-fetch.md +45 -0
- data/doc/changes/2026-03-01-ci-bundler-strategy.md +120 -0
- data/doc/changes/2026-03-20-security-hardening-phase2.md +163 -0
- data/doc/context/SESSION-HANDOFF.md +69 -0
- data/doc/context/ai-engine-usage-trends-2026.md +80 -0
- data/doc/context/plan-pluggable-engines.md +590 -0
- data/doc/decisions/001-flog-deferred.md +32 -0
- data/doc/decisions/002-path-resolution-strategy.md +158 -0
- data/doc/decisions/003-ui-adapter-selection.md +193 -0
- data/doc/decisions/004-design-document-validation.md +179 -0
- data/doc/decisions/005-package-splitting-strategy.md +200 -0
- data/doc/decisions/006-multi-engine-architecture.md +147 -0
- data/doc/decisions/007-engine-agnostic-pivot.md +219 -0
- data/doc/decisions/008-ci-bundler-strategy.md +129 -0
- data/doc/decisions/009-core-only-v1-release.md +60 -0
- data/doc/decisions/010-engine-debian-packaging.md +66 -0
- data/doc/decisions/011-context-aware-cli.md +71 -0
- data/doc/dependency_decisions.yml +247 -0
- data/doc/issues/001-wrapper-missing-environment-variables.md +197 -0
- data/doc/issues/002-embedded-ruby-wrong-prefix.md +217 -0
- data/doc/issues/003-smoke-test-false-positive.md +127 -0
- data/doc/issues/004-market-research-design-updates.md +109 -0
- data/doc/issues/005-compile-scope-coexistence.md +161 -0
- data/doc/locales/.gitkeep +0 -0
- data/doc/man/rai.1.ronn +505 -0
- data/doc/operations/packaging.md +133 -0
- data/doc/operations/rosett-ai-release.md +65 -0
- data/doc/reference/error-catalog.md +107 -0
- data/doc/reference/rosett-ai-technical-reference.pdf +0 -0
- data/doc/reference/src/Pictures/cover.jpg +0 -0
- data/doc/reference/src/Pictures/head1.jpg +0 -0
- data/doc/reference/src/Pictures/head2.jpg +0 -0
- data/doc/reference/src/Pictures/head3.jpg +0 -0
- data/doc/reference/src/Pictures/head4.jpg +0 -0
- data/doc/reference/src/Pictures/head5.jpg +0 -0
- data/doc/reference/src/Pictures/head6.jpg +0 -0
- data/doc/reference/src/Pictures/head7.jpg +0 -0
- data/doc/reference/src/Pictures/head8.jpg +0 -0
- data/doc/reference/src/StyleInd.ist +4 -0
- data/doc/reference/src/bibliography.bib +79 -0
- data/doc/reference/src/main.tex +1288 -0
- data/doc/reference/src/structure.tex +303 -0
- data/doc/rosett-ai-bookmarks.html +301 -0
- data/kitchen.yml +46 -0
- data/lib/rosett_ai/adopter/executor_resolver.rb +77 -0
- data/lib/rosett_ai/adopter/local_analysis_collector.rb +154 -0
- data/lib/rosett_ai/adopter/rule_adopter.rb +254 -0
- data/lib/rosett_ai/ai_config/config_compiler.rb +111 -0
- data/lib/rosett_ai/ai_config/context_window.rb +55 -0
- data/lib/rosett_ai/ai_config/cost_controls.rb +44 -0
- data/lib/rosett_ai/ai_config/fallback_chain.rb +64 -0
- data/lib/rosett_ai/ai_config/model_router.rb +121 -0
- data/lib/rosett_ai/ai_config/validator.rb +45 -0
- data/lib/rosett_ai/authorship/attribution_compiler.rb +99 -0
- data/lib/rosett_ai/authorship/disclosure_policy.rb +81 -0
- data/lib/rosett_ai/authorship/review_validator.rb +39 -0
- data/lib/rosett_ai/authorship/trailer_generator.rb +88 -0
- data/lib/rosett_ai/backup/compressor.rb +180 -0
- data/lib/rosett_ai/backup/destination.rb +91 -0
- data/lib/rosett_ai/behaviour/manager.rb +156 -0
- data/lib/rosett_ai/compiler/backend.rb +86 -0
- data/lib/rosett_ai/compiler/backends/agents_md_backend.rb +80 -0
- data/lib/rosett_ai/compiler/backends/claude_backend.rb +88 -0
- data/lib/rosett_ai/compiler/backends/generic_backend.rb +15 -0
- data/lib/rosett_ai/compiler/behaviour_compiler.rb +40 -0
- data/lib/rosett_ai/compiler/capability_checker.rb +104 -0
- data/lib/rosett_ai/compiler/compilation_pipeline.rb +361 -0
- data/lib/rosett_ai/compiler/compiled_output.rb +39 -0
- data/lib/rosett_ai/compiler/locale_compiler.rb +250 -0
- data/lib/rosett_ai/compiler/target_profile.rb +112 -0
- data/lib/rosett_ai/completion/generator.rb +101 -0
- data/lib/rosett_ai/completion/shells/bash_generator.rb +126 -0
- data/lib/rosett_ai/completion/shells/fish_generator.rb +78 -0
- data/lib/rosett_ai/completion/shells/zsh_generator.rb +126 -0
- data/lib/rosett_ai/comply/checkers/cra_checker.rb +102 -0
- data/lib/rosett_ai/comply/checkers/license_checker.rb +85 -0
- data/lib/rosett_ai/comply/checkers/spdx_header_checker.rb +98 -0
- data/lib/rosett_ai/comply/reporter.rb +113 -0
- data/lib/rosett_ai/comply/runner.rb +50 -0
- data/lib/rosett_ai/composition/circular_dependency_detector.rb +56 -0
- data/lib/rosett_ai/composition/composer.rb +158 -0
- data/lib/rosett_ai/composition/composition_result.rb +64 -0
- data/lib/rosett_ai/composition/conflict_detector.rb +53 -0
- data/lib/rosett_ai/composition/lockfile.rb +103 -0
- data/lib/rosett_ai/composition/merge_strategy.rb +131 -0
- data/lib/rosett_ai/composition/priority_sorter.rb +29 -0
- data/lib/rosett_ai/composition/scope_resolver.rb +55 -0
- data/lib/rosett_ai/config/compile_result.rb +37 -0
- data/lib/rosett_ai/config/compiler.rb +13 -0
- data/lib/rosett_ai/config/domain_transformer.rb +13 -0
- data/lib/rosett_ai/config/key_map.rb +13 -0
- data/lib/rosett_ai/config/masking_secret_resolver.rb +40 -0
- data/lib/rosett_ai/config/scope_router.rb +13 -0
- data/lib/rosett_ai/config/secret_resolver.rb +125 -0
- data/lib/rosett_ai/configuration.rb +119 -0
- data/lib/rosett_ai/content/content_client.rb +60 -0
- data/lib/rosett_ai/content/pack_installer.rb +117 -0
- data/lib/rosett_ai/content/pack_manifest.rb +50 -0
- data/lib/rosett_ai/content/pack_registry.rb +68 -0
- data/lib/rosett_ai/content_packs/manager.rb +50 -0
- data/lib/rosett_ai/dbus/compositor_detector.rb +77 -0
- data/lib/rosett_ai/dbus/focus_adapters/base.rb +59 -0
- data/lib/rosett_ai/dbus/focus_adapters/gnome_adapter.rb +172 -0
- data/lib/rosett_ai/dbus/focus_adapters/hyprland_adapter.rb +77 -0
- data/lib/rosett_ai/dbus/focus_adapters/i3_adapter.rb +65 -0
- data/lib/rosett_ai/dbus/focus_adapters/kwin_adapter.rb +103 -0
- data/lib/rosett_ai/dbus/focus_adapters/x11_adapter.rb +105 -0
- data/lib/rosett_ai/dbus/focus_monitor_interface.rb +103 -0
- data/lib/rosett_ai/dbus/manager_interface.rb +213 -0
- data/lib/rosett_ai/dbus/plugin_manager_interface.rb +169 -0
- data/lib/rosett_ai/dbus/rate_limiter.rb +89 -0
- data/lib/rosett_ai/dbus/service.rb +121 -0
- data/lib/rosett_ai/dbus/status_notifier_interface.rb +79 -0
- data/lib/rosett_ai/deprecation.rb +79 -0
- data/lib/rosett_ai/desktop/dbus_client.rb +259 -0
- data/lib/rosett_ai/desktop/gtk4_app.rb +371 -0
- data/lib/rosett_ai/desktop/gtk4_preferences.rb +331 -0
- data/lib/rosett_ai/desktop/gui_logger.rb +236 -0
- data/lib/rosett_ai/doctor/check.rb +92 -0
- data/lib/rosett_ai/doctor/checks/cache_health_check.rb +50 -0
- data/lib/rosett_ai/doctor/checks/dbus_availability_check.rb +39 -0
- data/lib/rosett_ai/doctor/checks/engine_detection_check.rb +46 -0
- data/lib/rosett_ai/doctor/checks/file_permission_check.rb +44 -0
- data/lib/rosett_ai/doctor/checks/gem_dependency_check.rb +55 -0
- data/lib/rosett_ai/doctor/checks/ruby_version_check.rb +50 -0
- data/lib/rosett_ai/doctor/checks/stale_config_nncc_check.rb +57 -0
- data/lib/rosett_ai/doctor/checks/stale_home_nncc_check.rb +59 -0
- data/lib/rosett_ai/doctor.rb +81 -0
- data/lib/rosett_ai/documentation/reference_compiler.rb +122 -0
- data/lib/rosett_ai/documentation/translator.rb +62 -0
- data/lib/rosett_ai/engines/base_config_compiler.rb +203 -0
- data/lib/rosett_ai/engines/detector.rb +63 -0
- data/lib/rosett_ai/engines/registry.rb +50 -0
- data/lib/rosett_ai/error_handler.rb +139 -0
- data/lib/rosett_ai/exit_codes.rb +76 -0
- data/lib/rosett_ai/feature_flags.rb +102 -0
- data/lib/rosett_ai/formatting.rb +33 -0
- data/lib/rosett_ai/gem_consistency_checker.rb +199 -0
- data/lib/rosett_ai/git_hooks/chain_detector.rb +86 -0
- data/lib/rosett_ai/git_hooks/installer.rb +175 -0
- data/lib/rosett_ai/git_hooks/script_generator.rb +125 -0
- data/lib/rosett_ai/gitlab/validators/supplementary_gitlab_ci_yaml_validator.rb +79 -0
- data/lib/rosett_ai/i18n/locale_resolver.rb +46 -0
- data/lib/rosett_ai/i18n/utf8_checker.rb +32 -0
- data/lib/rosett_ai/init/config_file_writer.rb +24 -0
- data/lib/rosett_ai/init/directory_builder.rb +38 -0
- data/lib/rosett_ai/init/file_copier.rb +95 -0
- data/lib/rosett_ai/init/global_initializer.rb +28 -0
- data/lib/rosett_ai/init/local_initializer.rb +27 -0
- data/lib/rosett_ai/init/mcp_registrar.rb +109 -0
- data/lib/rosett_ai/init/project_initializer.rb +38 -0
- data/lib/rosett_ai/licensing/license_key.rb +139 -0
- data/lib/rosett_ai/licensing/license_store.rb +64 -0
- data/lib/rosett_ai/licensing/license_validator.rb +60 -0
- data/lib/rosett_ai/licensing/tier.rb +42 -0
- data/lib/rosett_ai/mcp/admin/auditor.rb +88 -0
- data/lib/rosett_ai/mcp/admin/health_checker.rb +81 -0
- data/lib/rosett_ai/mcp/admin/registry.rb +100 -0
- data/lib/rosett_ai/mcp/admin/schema_validator.rb +63 -0
- data/lib/rosett_ai/mcp/enforcement/.gitkeep +0 -0
- data/lib/rosett_ai/mcp/enforcement/hook_generator.rb +197 -0
- data/lib/rosett_ai/mcp/enforcement/validator.rb +215 -0
- data/lib/rosett_ai/mcp/governance.rb +160 -0
- data/lib/rosett_ai/mcp/http_security_config.rb +158 -0
- data/lib/rosett_ai/mcp/instructions.rb +266 -0
- data/lib/rosett_ai/mcp/key_hasher.rb +66 -0
- data/lib/rosett_ai/mcp/keyfile.rb +221 -0
- data/lib/rosett_ai/mcp/middleware/authentication.rb +146 -0
- data/lib/rosett_ai/mcp/middleware/content_type.rb +56 -0
- data/lib/rosett_ai/mcp/middleware/cors.rb +83 -0
- data/lib/rosett_ai/mcp/middleware/origin_validation.rb +73 -0
- data/lib/rosett_ai/mcp/middleware/rate_limit.rb +106 -0
- data/lib/rosett_ai/mcp/middleware/request_size.rb +51 -0
- data/lib/rosett_ai/mcp/plugins.rb +143 -0
- data/lib/rosett_ai/mcp/prompts/compilation_prompt.rb +40 -0
- data/lib/rosett_ai/mcp/prompts/compliance_prompt.rb +41 -0
- data/lib/rosett_ai/mcp/prompts/diagnostics_prompt.rb +41 -0
- data/lib/rosett_ai/mcp/prompts/validation_prompt.rb +41 -0
- data/lib/rosett_ai/mcp/resources/behaviour_resource.rb +127 -0
- data/lib/rosett_ai/mcp/resources/config_resource.rb +72 -0
- data/lib/rosett_ai/mcp/resources/design_resource.rb +58 -0
- data/lib/rosett_ai/mcp/resources/hooks_resource.rb +74 -0
- data/lib/rosett_ai/mcp/resources/provenance_resource.rb +51 -0
- data/lib/rosett_ai/mcp/resources/rules_resource.rb +60 -0
- data/lib/rosett_ai/mcp/resources/schema_resource.rb +72 -0
- data/lib/rosett_ai/mcp/response_helper.rb +46 -0
- data/lib/rosett_ai/mcp/security_logger.rb +60 -0
- data/lib/rosett_ai/mcp/server.rb +212 -0
- data/lib/rosett_ai/mcp/settings/server_installer.rb +112 -0
- data/lib/rosett_ai/mcp/settings/trust_manager.rb +142 -0
- data/lib/rosett_ai/mcp/tools/adopt_tool.rb +70 -0
- data/lib/rosett_ai/mcp/tools/backup_tool.rb +64 -0
- data/lib/rosett_ai/mcp/tools/behaviour_display_tool.rb +72 -0
- data/lib/rosett_ai/mcp/tools/behaviour_list_tool.rb +56 -0
- data/lib/rosett_ai/mcp/tools/behaviour_manage_tool.rb +114 -0
- data/lib/rosett_ai/mcp/tools/behaviour_show_tool.rb +62 -0
- data/lib/rosett_ai/mcp/tools/compile_status_tool.rb +122 -0
- data/lib/rosett_ai/mcp/tools/compile_tool.rb +191 -0
- data/lib/rosett_ai/mcp/tools/comply_tool.rb +79 -0
- data/lib/rosett_ai/mcp/tools/config_compile_tool.rb +71 -0
- data/lib/rosett_ai/mcp/tools/config_status_tool.rb +79 -0
- data/lib/rosett_ai/mcp/tools/content_tool.rb +78 -0
- data/lib/rosett_ai/mcp/tools/context_query_tool.rb +156 -0
- data/lib/rosett_ai/mcp/tools/design_list_tool.rb +57 -0
- data/lib/rosett_ai/mcp/tools/design_show_tool.rb +69 -0
- data/lib/rosett_ai/mcp/tools/doctor_tool.rb +62 -0
- data/lib/rosett_ai/mcp/tools/documentation_status_tool.rb +45 -0
- data/lib/rosett_ai/mcp/tools/engines_tool.rb +84 -0
- data/lib/rosett_ai/mcp/tools/hook_install_tool.rb +190 -0
- data/lib/rosett_ai/mcp/tools/hook_preview_tool.rb +173 -0
- data/lib/rosett_ai/mcp/tools/hooks_status_tool.rb +84 -0
- data/lib/rosett_ai/mcp/tools/init_tool.rb +87 -0
- data/lib/rosett_ai/mcp/tools/license_status_tool.rb +44 -0
- data/lib/rosett_ai/mcp/tools/project_tool.rb +117 -0
- data/lib/rosett_ai/mcp/tools/provenance_tool.rb +97 -0
- data/lib/rosett_ai/mcp/tools/provenance_write_tool.rb +40 -0
- data/lib/rosett_ai/mcp/tools/retrofit_tool.rb +81 -0
- data/lib/rosett_ai/mcp/tools/rule_search_tool.rb +163 -0
- data/lib/rosett_ai/mcp/tools/schema_get_tool.rb +94 -0
- data/lib/rosett_ai/mcp/tools/tooling_tool.rb +86 -0
- data/lib/rosett_ai/mcp/tools/validate_tool.rb +105 -0
- data/lib/rosett_ai/mcp/tools/workflow_execute_tool.rb +74 -0
- data/lib/rosett_ai/mcp/tools/workflow_tool.rb +78 -0
- data/lib/rosett_ai/migration/detector.rb +117 -0
- data/lib/rosett_ai/migration/nncc_config_migrator.rb +94 -0
- data/lib/rosett_ai/migration/nncc_project_migrator.rb +90 -0
- data/lib/rosett_ai/migration/xdg_migrator.rb +123 -0
- data/lib/rosett_ai/package_manager/apt.rb +108 -0
- data/lib/rosett_ai/package_manager/base.rb +68 -0
- data/lib/rosett_ai/package_manager/gem_backend.rb +90 -0
- data/lib/rosett_ai/packaging/variant_config.rb +92 -0
- data/lib/rosett_ai/path_resolver.rb +115 -0
- data/lib/rosett_ai/plugins/contract.rb +43 -0
- data/lib/rosett_ai/plugins/engine_contract.rb +60 -0
- data/lib/rosett_ai/plugins/gui_contract.rb +74 -0
- data/lib/rosett_ai/plugins/mcp_contract.rb +48 -0
- data/lib/rosett_ai/plugins/registry.rb +150 -0
- data/lib/rosett_ai/policy/auditor.rb +41 -0
- data/lib/rosett_ai/policy/deny_list.rb +71 -0
- data/lib/rosett_ai/policy/opt_out_scanner.rb +37 -0
- data/lib/rosett_ai/policy/policy_compiler.rb +84 -0
- data/lib/rosett_ai/policy/protected_files.rb +47 -0
- data/lib/rosett_ai/policy/tier_hierarchy.rb +48 -0
- data/lib/rosett_ai/policy/validator.rb +35 -0
- data/lib/rosett_ai/profiler.rb +79 -0
- data/lib/rosett_ai/project/drift_detector.rb +126 -0
- data/lib/rosett_ai/project/manager.rb +115 -0
- data/lib/rosett_ai/project/sync_manager.rb +138 -0
- data/lib/rosett_ai/project/template_applier.rb +105 -0
- data/lib/rosett_ai/project_context.rb +82 -0
- data/lib/rosett_ai/provenance/entry.rb +63 -0
- data/lib/rosett_ai/provenance/file_source.rb +32 -0
- data/lib/rosett_ai/provenance/source.rb +62 -0
- data/lib/rosett_ai/provenance/store.rb +153 -0
- data/lib/rosett_ai/provenance/tracker.rb +62 -0
- data/lib/rosett_ai/provenance/trailer_generator.rb +43 -0
- data/lib/rosett_ai/provenance/validator.rb +45 -0
- data/lib/rosett_ai/quorum/collector.rb +59 -0
- data/lib/rosett_ai/quorum/comparator.rb +81 -0
- data/lib/rosett_ai/quorum/dispatcher.rb +57 -0
- data/lib/rosett_ai/quorum/strategies/adopt.rb +56 -0
- data/lib/rosett_ai/rai_config.rb +107 -0
- data/lib/rosett_ai/retrofit/base_parser.rb +66 -0
- data/lib/rosett_ai/retrofit/engine.rb +171 -0
- data/lib/rosett_ai/retrofit/parsers/agents_md_parser.rb +50 -0
- data/lib/rosett_ai/retrofit/parsers/claude_parser.rb +69 -0
- data/lib/rosett_ai/retrofit/parsers/cursor_parser.rb +82 -0
- data/lib/rosett_ai/retrofit/round_trip_validator.rb +65 -0
- data/lib/rosett_ai/retrofit/scanner.rb +47 -0
- data/lib/rosett_ai/retrofit/secret_detector.rb +87 -0
- data/lib/rosett_ai/secrets_resolver.rb +71 -0
- data/lib/rosett_ai/smart_feedback/suggester.rb +83 -0
- data/lib/rosett_ai/smart_feedback/thor_middleware.rb +84 -0
- data/lib/rosett_ai/structured_logger.rb +110 -0
- data/lib/rosett_ai/telemetry/json_lines_writer.rb +50 -0
- data/lib/rosett_ai/telemetry/log_rotator.rb +67 -0
- data/lib/rosett_ai/telemetry/provider.rb +26 -0
- data/lib/rosett_ai/telemetry/reporter.rb +144 -0
- data/lib/rosett_ai/telemetry.rb +47 -0
- data/lib/rosett_ai/text_sanitizer.rb +62 -0
- data/lib/rosett_ai/thor/cli.rb +269 -0
- data/lib/rosett_ai/thor/tasks/adopt.rb +250 -0
- data/lib/rosett_ai/thor/tasks/backup.rb +420 -0
- data/lib/rosett_ai/thor/tasks/behaviour.rb +474 -0
- data/lib/rosett_ai/thor/tasks/build.rb +1162 -0
- data/lib/rosett_ai/thor/tasks/compile.rb +415 -0
- data/lib/rosett_ai/thor/tasks/completion.rb +123 -0
- data/lib/rosett_ai/thor/tasks/comply.rb +82 -0
- data/lib/rosett_ai/thor/tasks/config.rb +265 -0
- data/lib/rosett_ai/thor/tasks/content.rb +193 -0
- data/lib/rosett_ai/thor/tasks/dbus.rb +321 -0
- data/lib/rosett_ai/thor/tasks/design.rb +258 -0
- data/lib/rosett_ai/thor/tasks/desktop.rb +129 -0
- data/lib/rosett_ai/thor/tasks/doctor.rb +127 -0
- data/lib/rosett_ai/thor/tasks/documentation.rb +321 -0
- data/lib/rosett_ai/thor/tasks/engines.rb +167 -0
- data/lib/rosett_ai/thor/tasks/hooks.rb +219 -0
- data/lib/rosett_ai/thor/tasks/init.rb +259 -0
- data/lib/rosett_ai/thor/tasks/license.rb +120 -0
- data/lib/rosett_ai/thor/tasks/mcp.rb +535 -0
- data/lib/rosett_ai/thor/tasks/migrate.rb +121 -0
- data/lib/rosett_ai/thor/tasks/plugins.rb +157 -0
- data/lib/rosett_ai/thor/tasks/project.rb +260 -0
- data/lib/rosett_ai/thor/tasks/provenance.rb +195 -0
- data/lib/rosett_ai/thor/tasks/release.rb +314 -0
- data/lib/rosett_ai/thor/tasks/retrofit.rb +90 -0
- data/lib/rosett_ai/thor/tasks/tooling.rb +308 -0
- data/lib/rosett_ai/thor/tasks/validate.rb +108 -0
- data/lib/rosett_ai/thor/tasks/workflow.rb +196 -0
- data/lib/rosett_ai/tooling/ci_yaml_validator.rb +37 -0
- data/lib/rosett_ai/tooling/version_checker.rb +35 -0
- data/lib/rosett_ai/ui/accessible_tui.rb +61 -0
- data/lib/rosett_ai/ui/base.rb +46 -0
- data/lib/rosett_ai/ui/gtk4.rb +98 -0
- data/lib/rosett_ai/ui/kde.rb +40 -0
- data/lib/rosett_ai/ui/qt6.rb +40 -0
- data/lib/rosett_ai/ui/registry.rb +60 -0
- data/lib/rosett_ai/ui/tty_helper.rb +74 -0
- data/lib/rosett_ai/ui/tui.rb +59 -0
- data/lib/rosett_ai/validators/behaviour_validator.rb +20 -0
- data/lib/rosett_ai/validators/design_validator.rb +17 -0
- data/lib/rosett_ai/validators/schema_validator.rb +84 -0
- data/lib/rosett_ai/validators/tooling_validator.rb +17 -0
- data/lib/rosett_ai/version.rb +8 -0
- data/lib/rosett_ai/version_consistency_checker.rb +129 -0
- data/lib/rosett_ai/workflow/audit_log.rb +86 -0
- data/lib/rosett_ai/workflow/engine.rb +142 -0
- data/lib/rosett_ai/workflow/manager.rb +82 -0
- data/lib/rosett_ai/workflow/schema_validator.rb +71 -0
- data/lib/rosett_ai/workflow/step_runner.rb +61 -0
- data/lib/rosett_ai/workflow/steps/prompt_step.rb +62 -0
- data/lib/rosett_ai/workflow/steps/rai_step.rb +74 -0
- data/lib/rosett_ai/workflow/steps/shell_step.rb +53 -0
- data/lib/rosett_ai/yaml_loader.rb +78 -0
- data/lib/rosett_ai.rb +221 -0
- data/lib/rubocop/cop/rosett_ai/shell_interpolation.rb +54 -0
- data/lib/rubocop/cop/rosett_ai/unsafe_const_get.rb +60 -0
- data/lib/rubocop/cop/rosett_ai/unsafe_send.rb +50 -0
- data/lib/rubocop/cop/rosett_ai/unsafe_yaml_load.rb +40 -0
- data/lib/rubocop/rosett_ai.rb +9 -0
- data/lib/scripts/generated/docker_hub_tags.rb +126 -0
- data/locales/.gitkeep +0 -0
- data/locales/ar.yml +579 -0
- data/locales/en.yml +571 -0
- data/locales/fr.yml +567 -0
- data/packaging/build-engine-deb.sh +81 -0
- data/packaging/scripts/postinst +17 -0
- data/packaging/scripts/postrm +19 -0
- data/packaging/scripts/prerm +10 -0
- data/packaging/wrapper.sh.template +38 -0
- data/rosett-ai.gemspec +63 -0
- data/rules/.gitkeep +0 -0
- data/scripts/publish/pulp_upload.sh +123 -0
- data/settings.json +29 -0
- data/share/applications/be.neatnerds.rosettai.desktop +29 -0
- data/share/dbus-1/interfaces/be.neatnerds.rosettai.xml +103 -0
- data/share/dbus-1/services/be.neatnerds.rosettai.service +3 -0
- data/share/templates/behaviour/criticalthinking.yml +69 -0
- metadata +810 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-only
|
|
4
|
+
# Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
|
|
5
|
+
|
|
6
|
+
require 'logger'
|
|
7
|
+
require 'json'
|
|
8
|
+
require 'securerandom'
|
|
9
|
+
|
|
10
|
+
module RosettAi
|
|
11
|
+
# Structured logger with per-invocation correlation IDs and JSON/text output.
|
|
12
|
+
#
|
|
13
|
+
# Drop-in replacement for Ruby's +Logger+. Every log entry includes a
|
|
14
|
+
# correlation ID (UUID v4) generated once per CLI invocation so that all
|
|
15
|
+
# entries from one run can be correlated.
|
|
16
|
+
#
|
|
17
|
+
# Log format is controlled by +RAI_LOG_FORMAT+:
|
|
18
|
+
# - +"text"+ (default): <tt>[LEVEL] [correlation_id] message</tt>
|
|
19
|
+
# - +"json"+: JSON Lines (one JSON object per line)
|
|
20
|
+
#
|
|
21
|
+
# @example
|
|
22
|
+
# logger = RosettAi::StructuredLogger.new
|
|
23
|
+
# logger.info("Loading behaviour files")
|
|
24
|
+
# # Text: [INFO] [a1b2c3d4-...] Loading behaviour files
|
|
25
|
+
# # JSON: {"timestamp":"...","level":"info","correlation_id":"a1b2c3d4-...","pid":12345,"message":"Loading behaviour files"}
|
|
26
|
+
#
|
|
27
|
+
# @author hugo
|
|
28
|
+
# @author claude
|
|
29
|
+
class StructuredLogger < ::Logger
|
|
30
|
+
VALID_FORMATS = ['text', 'json'].freeze
|
|
31
|
+
|
|
32
|
+
# @return [String] correlation ID for the current invocation (UUID v4)
|
|
33
|
+
attr_reader :correlation_id
|
|
34
|
+
|
|
35
|
+
# @return [String, nil] current command name (set via {#command=})
|
|
36
|
+
attr_reader :command
|
|
37
|
+
|
|
38
|
+
# Creates a new structured logger writing to stderr.
|
|
39
|
+
#
|
|
40
|
+
# @param output [IO] output stream (default: $stderr)
|
|
41
|
+
# @param correlation_id [String, nil] explicit correlation ID (default: generates new UUID)
|
|
42
|
+
def initialize(output = $stderr, correlation_id: nil)
|
|
43
|
+
super(output)
|
|
44
|
+
@correlation_id = correlation_id || SecureRandom.uuid
|
|
45
|
+
@command = nil
|
|
46
|
+
@mutex = Mutex.new
|
|
47
|
+
|
|
48
|
+
self.level = resolve_level
|
|
49
|
+
self.formatter = build_formatter
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Sets the current command name for inclusion in log entries.
|
|
53
|
+
#
|
|
54
|
+
# @param name [String] command name
|
|
55
|
+
# @return [void]
|
|
56
|
+
def command=(name)
|
|
57
|
+
@mutex.synchronize { @command = name }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Reset logger state. Generates a new correlation ID.
|
|
61
|
+
# Intended for test isolation.
|
|
62
|
+
#
|
|
63
|
+
# @return [void]
|
|
64
|
+
def reset!
|
|
65
|
+
@mutex.synchronize do
|
|
66
|
+
@correlation_id = SecureRandom.uuid
|
|
67
|
+
@command = nil
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def resolve_level
|
|
74
|
+
raw = ENV.fetch('RAI_LOG_LEVEL', 'WARN').upcase
|
|
75
|
+
::Logger.const_get(raw)
|
|
76
|
+
rescue NameError
|
|
77
|
+
::Logger::WARN
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def log_format
|
|
81
|
+
raw = ENV.fetch('RAI_LOG_FORMAT', 'text').downcase
|
|
82
|
+
VALID_FORMATS.include?(raw) ? raw : 'text'
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def build_formatter
|
|
86
|
+
case log_format
|
|
87
|
+
when 'json' then method(:json_formatter)
|
|
88
|
+
else method(:text_formatter)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def text_formatter(severity, _timestamp, _progname, message)
|
|
93
|
+
"[#{severity}] [#{@correlation_id[0, 8]}] #{message}\n"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def json_formatter(severity, timestamp, _progname, message)
|
|
97
|
+
entry = {
|
|
98
|
+
timestamp: timestamp.utc.iso8601(3),
|
|
99
|
+
level: severity.downcase,
|
|
100
|
+
correlation_id: @correlation_id,
|
|
101
|
+
pid: Process.pid,
|
|
102
|
+
message: message.to_s
|
|
103
|
+
}
|
|
104
|
+
cmd = @mutex.synchronize { @command }
|
|
105
|
+
entry[:command] = cmd if cmd
|
|
106
|
+
|
|
107
|
+
"#{JSON.generate(entry)}\n"
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-only
|
|
4
|
+
# Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
|
|
5
|
+
|
|
6
|
+
require 'json'
|
|
7
|
+
|
|
8
|
+
module RosettAi
|
|
9
|
+
module Telemetry
|
|
10
|
+
# Writes telemetry events as JSON Lines (one JSON object per line).
|
|
11
|
+
#
|
|
12
|
+
# @author hugo
|
|
13
|
+
# @author claude
|
|
14
|
+
class JsonLinesWriter
|
|
15
|
+
# @param io [IO] output stream
|
|
16
|
+
# @param rotator [LogRotator, nil] optional log rotator for file output
|
|
17
|
+
def initialize(io:, rotator: nil)
|
|
18
|
+
@io = io
|
|
19
|
+
@rotator = rotator
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Write a single event as a JSON line.
|
|
23
|
+
#
|
|
24
|
+
# @param event_hash [Hash] event data (must be JSON-serializable)
|
|
25
|
+
# @return [void]
|
|
26
|
+
def write(event_hash)
|
|
27
|
+
rotate_if_needed
|
|
28
|
+
@io.puts(JSON.generate(event_hash))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Close the underlying IO.
|
|
32
|
+
# @return [void]
|
|
33
|
+
def close
|
|
34
|
+
@io.close unless @io == $stderr || @io == $stdout
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def rotate_if_needed
|
|
40
|
+
return unless @rotator
|
|
41
|
+
|
|
42
|
+
return unless @rotator.rotate_if_needed!
|
|
43
|
+
|
|
44
|
+
@io.close
|
|
45
|
+
@io = File.open(@rotator.path, 'a') # rubocop:disable Style/FileOpen -- lifecycle managed by #close
|
|
46
|
+
@io.sync = true
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-only
|
|
4
|
+
# Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
|
|
5
|
+
|
|
6
|
+
module RosettAi
|
|
7
|
+
module Telemetry
|
|
8
|
+
# Rotates telemetry log files when they exceed a size threshold.
|
|
9
|
+
#
|
|
10
|
+
# Rotated files are numbered .1 through .N, where .1 is the most
|
|
11
|
+
# recent. The oldest file is deleted when max_files is exceeded.
|
|
12
|
+
#
|
|
13
|
+
# @author hugo
|
|
14
|
+
# @author claude
|
|
15
|
+
class LogRotator
|
|
16
|
+
DEFAULT_MAX_SIZE = 10 * 1024 * 1024 # 10 MB
|
|
17
|
+
DEFAULT_MAX_FILES = 5
|
|
18
|
+
|
|
19
|
+
# @return [String] path to the log file
|
|
20
|
+
attr_reader :path
|
|
21
|
+
|
|
22
|
+
# @param path [String] log file path
|
|
23
|
+
# @param max_size [Integer] rotation threshold in bytes (default: 10 MB)
|
|
24
|
+
# @param max_files [Integer] number of rotated files to keep (default: 5)
|
|
25
|
+
def initialize(path:, max_size: DEFAULT_MAX_SIZE, max_files: DEFAULT_MAX_FILES)
|
|
26
|
+
@path = path
|
|
27
|
+
@max_size = max_size
|
|
28
|
+
@max_files = max_files
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Rotate the log file if it exceeds the size threshold.
|
|
32
|
+
#
|
|
33
|
+
# @return [Boolean] true if rotation occurred
|
|
34
|
+
def rotate_if_needed! # rubocop:disable Naming/PredicateMethod -- mutates state, not a pure predicate
|
|
35
|
+
return false unless File.exist?(@path)
|
|
36
|
+
return false unless File.size(@path) >= @max_size
|
|
37
|
+
|
|
38
|
+
rotate!
|
|
39
|
+
true
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def rotate!
|
|
45
|
+
delete_oldest
|
|
46
|
+
shift_rotated_files
|
|
47
|
+
File.rename(@path, rotated_path(1))
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def delete_oldest
|
|
51
|
+
oldest = rotated_path(@max_files)
|
|
52
|
+
FileUtils.rm_f(oldest)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def shift_rotated_files
|
|
56
|
+
(@max_files - 1).downto(1) do |index|
|
|
57
|
+
current = rotated_path(index)
|
|
58
|
+
File.rename(current, rotated_path(index + 1)) if File.exist?(current)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def rotated_path(index)
|
|
63
|
+
"#{@path}.#{index}"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-only
|
|
4
|
+
# Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
|
|
5
|
+
|
|
6
|
+
module RosettAi
|
|
7
|
+
module Telemetry
|
|
8
|
+
# Interface module for telemetry providers.
|
|
9
|
+
#
|
|
10
|
+
# Providers receive frozen event hashes via +#report+ and must
|
|
11
|
+
# not mutate them. Implement this interface to add custom
|
|
12
|
+
# telemetry consumers (e.g., D-Bus health, metrics aggregation).
|
|
13
|
+
#
|
|
14
|
+
# @author hugo
|
|
15
|
+
# @author claude
|
|
16
|
+
module Provider
|
|
17
|
+
# Receive a telemetry event.
|
|
18
|
+
#
|
|
19
|
+
# @param event_hash [Hash] frozen event data
|
|
20
|
+
# @return [void]
|
|
21
|
+
def report(event_hash)
|
|
22
|
+
raise NotImplementedError, "#{self.class}#report must be implemented"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-only
|
|
4
|
+
# Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
|
|
5
|
+
|
|
6
|
+
require 'securerandom'
|
|
7
|
+
|
|
8
|
+
module RosettAi
|
|
9
|
+
module Telemetry
|
|
10
|
+
# Singleton reporter that writes structured telemetry events.
|
|
11
|
+
#
|
|
12
|
+
# Events are written as JSON Lines to stderr (default) or a file
|
|
13
|
+
# specified by +RAI_TELEMETRY_FILE+. Providers can be registered
|
|
14
|
+
# to receive event notifications.
|
|
15
|
+
#
|
|
16
|
+
# @author hugo
|
|
17
|
+
# @author claude
|
|
18
|
+
class Reporter
|
|
19
|
+
@instance = nil
|
|
20
|
+
@mutex = Mutex.new
|
|
21
|
+
|
|
22
|
+
# @return [Reporter] singleton instance
|
|
23
|
+
def self.instance
|
|
24
|
+
@mutex.synchronize { @instance ||= new }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Reset singleton. Intended for test isolation.
|
|
28
|
+
# @return [void]
|
|
29
|
+
def self.reset!
|
|
30
|
+
@mutex.synchronize do
|
|
31
|
+
@instance&.close
|
|
32
|
+
@instance = nil
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @return [Boolean] telemetry enabled?
|
|
37
|
+
def self.enabled?
|
|
38
|
+
RosettAi::Telemetry.enabled?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def initialize
|
|
42
|
+
@providers = []
|
|
43
|
+
@mutex = Mutex.new
|
|
44
|
+
@correlation_id = SecureRandom.uuid
|
|
45
|
+
@writer = resolve_writer
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Write a telemetry event.
|
|
49
|
+
#
|
|
50
|
+
# @param level [Symbol, String] event severity (:debug, :info, :warn, :error)
|
|
51
|
+
# @param event [Symbol, String] event name (e.g. :command_start)
|
|
52
|
+
# @param data [Hash] additional event data
|
|
53
|
+
# @return [void]
|
|
54
|
+
def write_event(level:, event:, **data)
|
|
55
|
+
return unless self.class.enabled?
|
|
56
|
+
return unless RosettAi::Telemetry.level_enabled?(level)
|
|
57
|
+
|
|
58
|
+
event_hash = build_event(level: level, event: event, **data).freeze
|
|
59
|
+
@mutex.synchronize do
|
|
60
|
+
safe_write(event_hash)
|
|
61
|
+
@providers.each { |provider| provider.report(event_hash) }
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Emit a command_start event.
|
|
66
|
+
#
|
|
67
|
+
# @param command [String] command name
|
|
68
|
+
# @param data [Hash] additional data
|
|
69
|
+
# @return [void]
|
|
70
|
+
def command_start(command:, **data)
|
|
71
|
+
write_event(level: :info, event: :command_start, command: command, **data)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Emit a command_end event with timing.
|
|
75
|
+
#
|
|
76
|
+
# @param command [String] command name
|
|
77
|
+
# @param duration_ms [Float] execution time in milliseconds
|
|
78
|
+
# @param status [String] outcome ('success' or 'error')
|
|
79
|
+
# @param data [Hash] additional data
|
|
80
|
+
# @return [void]
|
|
81
|
+
def command_end(command:, duration_ms:, status:, **data)
|
|
82
|
+
write_event(level: :info, event: :command_end, command: command,
|
|
83
|
+
duration_ms: duration_ms, status: status, **data)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Register a telemetry provider.
|
|
87
|
+
#
|
|
88
|
+
# @param provider [#report] object implementing the Provider interface
|
|
89
|
+
# @return [void]
|
|
90
|
+
def register_provider(provider)
|
|
91
|
+
@mutex.synchronize { @providers << provider }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# @return [String] correlation ID for this session
|
|
95
|
+
attr_reader :correlation_id
|
|
96
|
+
|
|
97
|
+
# Close any open file handles.
|
|
98
|
+
# @return [void]
|
|
99
|
+
def close
|
|
100
|
+
@mutex.synchronize do
|
|
101
|
+
@writer&.close
|
|
102
|
+
@writer = nil
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
private
|
|
107
|
+
|
|
108
|
+
def build_event(level:, event:, **data)
|
|
109
|
+
{
|
|
110
|
+
timestamp: Time.now.utc.iso8601(3),
|
|
111
|
+
level: level.to_s,
|
|
112
|
+
event: event.to_s,
|
|
113
|
+
pid: Process.pid,
|
|
114
|
+
correlation_id: @correlation_id
|
|
115
|
+
}.merge(data)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def safe_write(event_hash)
|
|
119
|
+
@writer&.write(event_hash)
|
|
120
|
+
rescue IOError, Errno::ENOSPC => e
|
|
121
|
+
warn "rai: telemetry write failed — #{e.message}"
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def resolve_writer
|
|
125
|
+
return nil unless self.class.enabled?
|
|
126
|
+
|
|
127
|
+
file_path = ENV.fetch('RAI_TELEMETRY_FILE', nil)
|
|
128
|
+
if file_path
|
|
129
|
+
rotator = LogRotator.new(path: file_path)
|
|
130
|
+
rotator.rotate_if_needed!
|
|
131
|
+
io = File.open(file_path, 'a') # rubocop:disable Style/FileOpen -- lifecycle managed by #close
|
|
132
|
+
io.sync = true
|
|
133
|
+
JsonLinesWriter.new(io: io, rotator: rotator)
|
|
134
|
+
else
|
|
135
|
+
JsonLinesWriter.new(io: $stderr)
|
|
136
|
+
end
|
|
137
|
+
rescue Errno::EACCES, Errno::ENOENT => e
|
|
138
|
+
warn "rai: telemetry file #{file_path} unwritable " \
|
|
139
|
+
"— telemetry disabled for this invocation (#{e.message})"
|
|
140
|
+
nil
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-only
|
|
4
|
+
# Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
|
|
5
|
+
|
|
6
|
+
module RosettAi
|
|
7
|
+
# Opt-in structured telemetry for CLI observability.
|
|
8
|
+
#
|
|
9
|
+
# Enabled via +RAI_TELEMETRY=1+. Captures JSON Lines events for
|
|
10
|
+
# command timing, errors, and diagnostics. All data stays local.
|
|
11
|
+
#
|
|
12
|
+
# @author hugo
|
|
13
|
+
# @author claude
|
|
14
|
+
# @see conf/design/monitoring_observability.yml
|
|
15
|
+
module Telemetry
|
|
16
|
+
LEVELS = ['debug', 'info', 'warn', 'error'].freeze
|
|
17
|
+
|
|
18
|
+
# @return [Boolean] true if telemetry is enabled via environment variable
|
|
19
|
+
def self.enabled?
|
|
20
|
+
ENV['RAI_TELEMETRY'] == '1'
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @return [RosettAi::Telemetry::Reporter] singleton reporter instance
|
|
24
|
+
def self.reporter
|
|
25
|
+
Reporter.instance
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @return [String] configured telemetry level (default: info)
|
|
29
|
+
def self.level
|
|
30
|
+
raw = ENV.fetch('RAI_TELEMETRY_LEVEL', 'info').downcase
|
|
31
|
+
LEVELS.include?(raw) ? raw : 'info'
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @param level [String] event level to check
|
|
35
|
+
# @return [Boolean] true if the level meets the configured threshold
|
|
36
|
+
def self.level_enabled?(level)
|
|
37
|
+
LEVELS.index(level.to_s) >= LEVELS.index(self.level)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Reset singleton state. Intended for test isolation.
|
|
41
|
+
#
|
|
42
|
+
# @return [void]
|
|
43
|
+
def self.reset!
|
|
44
|
+
Reporter.reset!
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-only
|
|
4
|
+
# Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
|
|
5
|
+
|
|
6
|
+
module RosettAi
|
|
7
|
+
# Sanitizes text for safe TUI display and normalizes Unicode input.
|
|
8
|
+
#
|
|
9
|
+
# strip_ansi — removes ANSI escape sequences and control characters
|
|
10
|
+
# at display boundaries (criterion 10).
|
|
11
|
+
# normalize_nfc — applies Unicode NFC normalization at input boundaries
|
|
12
|
+
# (criterion 11).
|
|
13
|
+
module TextSanitizer
|
|
14
|
+
# Matches ANSI CSI sequences (\e[...X), OSC sequences (\e]...\a),
|
|
15
|
+
# and non-printable control characters except tab, newline, and CR.
|
|
16
|
+
ANSI_PATTERN = /\e\[\d*(?:;\d*)*[A-Za-z]|\e\][^\a]*\a|[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/
|
|
17
|
+
|
|
18
|
+
# Removes ANSI escape sequences and non-printable control characters.
|
|
19
|
+
#
|
|
20
|
+
# @param string [String] the input string to sanitize
|
|
21
|
+
# @return [String] the string with ANSI sequences removed
|
|
22
|
+
def self.strip_ansi(string)
|
|
23
|
+
String(string).gsub(ANSI_PATTERN, '')
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Applies Unicode NFC normalization at input boundaries.
|
|
27
|
+
#
|
|
28
|
+
# @param string [String] the input string to normalize
|
|
29
|
+
# @return [String] the NFC-normalized UTF-8 string
|
|
30
|
+
def self.normalize_nfc(string)
|
|
31
|
+
String(string).encode('UTF-8').unicode_normalize(:nfc)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Characters that have special meaning in Markdown syntax.
|
|
35
|
+
MARKDOWN_META = /([\\`*_{}\[\]()#+\-.!|])/
|
|
36
|
+
|
|
37
|
+
# Escapes Markdown metacharacters so text renders as literal content.
|
|
38
|
+
#
|
|
39
|
+
# @param text [String, nil] the input text
|
|
40
|
+
# @return [String] the escaped text safe for Markdown embedding
|
|
41
|
+
def self.escape_markdown(text)
|
|
42
|
+
text.to_s.gsub(MARKDOWN_META, '\\\\\1')
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Recursively strips ANSI escape sequences from strings in nested structures.
|
|
46
|
+
#
|
|
47
|
+
# @param data [String, Hash, Array, Object] the data to sanitize
|
|
48
|
+
# @return [String, Hash, Array, Object] the sanitized data in its original structure
|
|
49
|
+
def self.sanitize_for_display(data)
|
|
50
|
+
case data
|
|
51
|
+
when String
|
|
52
|
+
strip_ansi(data)
|
|
53
|
+
when Hash
|
|
54
|
+
data.transform_values { |v| sanitize_for_display(v) }
|
|
55
|
+
when Array
|
|
56
|
+
data.map { |v| sanitize_for_display(v) }
|
|
57
|
+
else
|
|
58
|
+
data
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|