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,180 @@
|
|
|
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 'rubygems/package'
|
|
7
|
+
require 'zlib'
|
|
8
|
+
require 'fileutils'
|
|
9
|
+
|
|
10
|
+
module RosettAi
|
|
11
|
+
module Backup
|
|
12
|
+
# Factory and base for compression handlers
|
|
13
|
+
class Compressor
|
|
14
|
+
ALGORITHMS = ['zip', 'tar.gz', 'tar.xz'].freeze
|
|
15
|
+
|
|
16
|
+
# Returns a compressor instance for the given algorithm.
|
|
17
|
+
#
|
|
18
|
+
# @param algorithm [String] one of 'zip', 'tar.gz', 'tar.xz'
|
|
19
|
+
# @return [Compressor]
|
|
20
|
+
# @raise [RosettAi::BackupError] if algorithm is not supported
|
|
21
|
+
def self.for(algorithm)
|
|
22
|
+
case algorithm
|
|
23
|
+
when 'zip' then ZipCompressor.new
|
|
24
|
+
when 'tar.gz' then TarGzCompressor.new
|
|
25
|
+
when 'tar.xz' then TarXzCompressor.new
|
|
26
|
+
else
|
|
27
|
+
raise RosettAi::BackupError,
|
|
28
|
+
"Unknown compression algorithm: #{algorithm}. Supported: #{ALGORITHMS.join(', ')}"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Whether the compression tool is available on this system.
|
|
33
|
+
#
|
|
34
|
+
# @return [Boolean]
|
|
35
|
+
# @abstract Subclasses must implement this method.
|
|
36
|
+
def available?
|
|
37
|
+
raise NotImplementedError
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# File extension for archives produced by this compressor.
|
|
41
|
+
#
|
|
42
|
+
# @return [String] file extension including the leading dot
|
|
43
|
+
def extension
|
|
44
|
+
raise NotImplementedError
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Compresses files into an archive at +output_path+.
|
|
48
|
+
#
|
|
49
|
+
# @param files [Array<Hash>] entries with :full_path and :archive_path keys
|
|
50
|
+
# @param base_dirs [Array<String>] base directories for relative path resolution
|
|
51
|
+
# @param output_path [String] destination path for the archive
|
|
52
|
+
# @param level [Integer, nil] compression level (algorithm-specific; nil for default)
|
|
53
|
+
# @return [String] the output path
|
|
54
|
+
# @abstract Subclasses must implement this method.
|
|
55
|
+
def compress(files, base_dirs, output_path, level: nil)
|
|
56
|
+
raise NotImplementedError
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# ZIP compression using rubyzip gem
|
|
61
|
+
class ZipCompressor < Compressor
|
|
62
|
+
# @return [Boolean] true if the rubyzip gem is loadable
|
|
63
|
+
def available?
|
|
64
|
+
require 'zip'
|
|
65
|
+
true
|
|
66
|
+
rescue LoadError
|
|
67
|
+
false
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# @return [String] ".zip"
|
|
71
|
+
def extension
|
|
72
|
+
'.zip'
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# @param files [Array<Hash>] entries with :full_path and :archive_path keys
|
|
76
|
+
# @param _base_dirs [Array<String>] unused
|
|
77
|
+
# @param output_path [String] destination path for the archive
|
|
78
|
+
# @param level [Integer, nil] Zlib compression level (nil for default)
|
|
79
|
+
# @return [String] the output path
|
|
80
|
+
def compress(files, _base_dirs, output_path, level: nil)
|
|
81
|
+
require 'zip'
|
|
82
|
+
compression_level = level || Zip.default_compression
|
|
83
|
+
|
|
84
|
+
::Zip::OutputStream.open(output_path) do |zipfile|
|
|
85
|
+
files.each do |entry|
|
|
86
|
+
zipfile.put_next_entry(entry[:archive_path], nil, nil, ::Zip::Entry::DEFLATED, compression_level)
|
|
87
|
+
zipfile.write(File.read(entry[:full_path]))
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
output_path
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# tar.gz compression using Ruby stdlib
|
|
96
|
+
class TarGzCompressor < Compressor
|
|
97
|
+
# @return [Boolean] always true (uses Ruby stdlib)
|
|
98
|
+
def available?
|
|
99
|
+
true
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# @return [String] ".tar.gz"
|
|
103
|
+
def extension
|
|
104
|
+
'.tar.gz'
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# @param files [Array<Hash>] entries with :full_path and :archive_path keys
|
|
108
|
+
# @param _base_dirs [Array<String>] unused
|
|
109
|
+
# @param output_path [String] destination path for the archive
|
|
110
|
+
# @param level [Integer, nil] Zlib compression level (nil for default)
|
|
111
|
+
# @return [String] the output path
|
|
112
|
+
def compress(files, _base_dirs, output_path, level: nil)
|
|
113
|
+
gz_level = level || Zlib::DEFAULT_COMPRESSION
|
|
114
|
+
|
|
115
|
+
File.open(output_path, 'wb', 0o644) do |file|
|
|
116
|
+
Zlib::GzipWriter.wrap(file, gz_level) do |gz|
|
|
117
|
+
Gem::Package::TarWriter.new(gz) do |tar|
|
|
118
|
+
files.each do |entry|
|
|
119
|
+
content = File.read(entry[:full_path])
|
|
120
|
+
tar.add_file_simple(entry[:archive_path], 0o644, content.bytesize) do |io|
|
|
121
|
+
io.write(content)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
output_path
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# tar.xz compression using system xz command
|
|
133
|
+
class TarXzCompressor < Compressor
|
|
134
|
+
# @return [Boolean] true if the +xz+ binary is found on PATH
|
|
135
|
+
def available?
|
|
136
|
+
ENV.fetch('PATH', '').split(File::PATH_SEPARATOR).any? do |dir|
|
|
137
|
+
File.executable?(File.join(dir, 'xz'))
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# @return [String] ".tar.xz"
|
|
142
|
+
def extension
|
|
143
|
+
'.tar.xz'
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# @param files [Array<Hash>] entries with :full_path and :archive_path keys
|
|
147
|
+
# @param _base_dirs [Array<String>] unused
|
|
148
|
+
# @param output_path [String] destination path for the archive
|
|
149
|
+
# @param level [Integer, nil] xz preset level (1-9; nil for xz default)
|
|
150
|
+
# @return [String] the output path
|
|
151
|
+
# @raise [RosettAi::BackupError] if the xz command fails
|
|
152
|
+
def compress(files, _base_dirs, output_path, level: nil)
|
|
153
|
+
tar_path = "#{output_path}.tar"
|
|
154
|
+
|
|
155
|
+
File.open(tar_path, 'wb', 0o644) do |file|
|
|
156
|
+
Gem::Package::TarWriter.new(file) do |tar|
|
|
157
|
+
files.each do |entry|
|
|
158
|
+
content = File.read(entry[:full_path])
|
|
159
|
+
tar.add_file_simple(entry[:archive_path], 0o644, content.bytesize) do |io|
|
|
160
|
+
io.write(content)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
xz_args = ['xz']
|
|
167
|
+
xz_args.push("-#{level}") if level
|
|
168
|
+
xz_args.push('--stdout', tar_path)
|
|
169
|
+
success = File.open(output_path, 'wb', 0o644) do |out|
|
|
170
|
+
system(*xz_args, out: out)
|
|
171
|
+
end
|
|
172
|
+
FileUtils.rm_f(tar_path)
|
|
173
|
+
|
|
174
|
+
raise RosettAi::BackupError, 'xz compression failed' unless success
|
|
175
|
+
|
|
176
|
+
output_path
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
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 Backup
|
|
8
|
+
# Base class for backup destinations
|
|
9
|
+
class Destination
|
|
10
|
+
SUPPORTED_SCHEMES = ['file', 's3', 'ssh', 'bup', 'https'].freeze
|
|
11
|
+
|
|
12
|
+
attr_reader :uri
|
|
13
|
+
|
|
14
|
+
def initialize(uri)
|
|
15
|
+
@uri = uri
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Writes the archive to this destination.
|
|
19
|
+
#
|
|
20
|
+
# @param archive_path [String] path to the archive file to write
|
|
21
|
+
# @return [String] final destination path
|
|
22
|
+
# @abstract Subclasses must implement this method.
|
|
23
|
+
def write(archive_path)
|
|
24
|
+
raise NotImplementedError, "#{self.class}#write not implemented"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Returns a destination instance for the given URI string.
|
|
28
|
+
#
|
|
29
|
+
# @param destination_string [String] URI (e.g. "file:///tmp/backup.zip")
|
|
30
|
+
# @return [Destination] a FileDestination or UnsupportedDestination
|
|
31
|
+
def self.for(destination_string)
|
|
32
|
+
scheme, path = parse_scheme(destination_string)
|
|
33
|
+
|
|
34
|
+
case scheme
|
|
35
|
+
when 'file'
|
|
36
|
+
FileDestination.new(path)
|
|
37
|
+
else
|
|
38
|
+
UnsupportedDestination.new(scheme, path)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Splits a destination string into scheme and path components.
|
|
43
|
+
#
|
|
44
|
+
# @param destination_string [String] URI or bare path
|
|
45
|
+
# @return [Array(String, String)] scheme and path (defaults to "file" scheme)
|
|
46
|
+
def self.parse_scheme(destination_string)
|
|
47
|
+
if destination_string.match?(%r{^[a-z][a-z0-9+\-.]*://})
|
|
48
|
+
scheme, path = destination_string.split('://', 2)
|
|
49
|
+
[scheme, path]
|
|
50
|
+
else
|
|
51
|
+
['file', destination_string]
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Writes archive to local filesystem
|
|
57
|
+
class FileDestination < Destination
|
|
58
|
+
attr_reader :path
|
|
59
|
+
|
|
60
|
+
def initialize(path)
|
|
61
|
+
super("file://#{path}")
|
|
62
|
+
@path = File.expand_path(path)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Copies the archive to the local filesystem destination.
|
|
66
|
+
#
|
|
67
|
+
# @param archive_path [String] path to the archive file to copy
|
|
68
|
+
# @return [String] expanded destination path
|
|
69
|
+
def write(archive_path)
|
|
70
|
+
FileUtils.mkdir_p(File.dirname(@path))
|
|
71
|
+
FileUtils.cp(archive_path, @path)
|
|
72
|
+
@path
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Placeholder for unsupported destination schemes
|
|
77
|
+
class UnsupportedDestination < Destination
|
|
78
|
+
attr_reader :scheme
|
|
79
|
+
|
|
80
|
+
def initialize(scheme, path)
|
|
81
|
+
super("#{scheme}://#{path}")
|
|
82
|
+
@scheme = scheme
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def write(_archive_path)
|
|
86
|
+
raise RosettAi::BackupError,
|
|
87
|
+
"Destination scheme '#{@scheme}' is not yet supported. Supported: file://"
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,156 @@
|
|
|
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 'yaml'
|
|
7
|
+
require 'fileutils'
|
|
8
|
+
require_relative '../yaml_loader'
|
|
9
|
+
require_relative '../validators/schema_validator'
|
|
10
|
+
require_relative '../validators/behaviour_validator'
|
|
11
|
+
|
|
12
|
+
module RosettAi
|
|
13
|
+
module Behaviour
|
|
14
|
+
# MCP-facing facade for behaviour file management.
|
|
15
|
+
#
|
|
16
|
+
# Provides add/modify/delete operations for behaviour YAML files,
|
|
17
|
+
# matching the interface expected by {Mcp::Tools::BehaviourManageTool}.
|
|
18
|
+
#
|
|
19
|
+
# @author hugo
|
|
20
|
+
# @author claude
|
|
21
|
+
class Manager
|
|
22
|
+
# @param behaviour_dir [Pathname, String, nil] override for testing;
|
|
23
|
+
# defaults to XDG user path (~/.config/rosett-ai/conf/behaviour/)
|
|
24
|
+
def initialize(behaviour_dir: nil)
|
|
25
|
+
@behaviour_dir = Pathname.new(behaviour_dir || RosettAi.paths.rai_conf_dir.join('behaviour'))
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Creates a new behaviour file.
|
|
29
|
+
#
|
|
30
|
+
# @param name [String] behaviour name
|
|
31
|
+
# @param description [String, nil] behaviour description
|
|
32
|
+
# @return [Pathname] path to created file
|
|
33
|
+
def add(name, description: nil)
|
|
34
|
+
path = behaviour_path(name)
|
|
35
|
+
raise RosettAi::BehaviourError, "Behaviour '#{name}' already exists" if path.exist?
|
|
36
|
+
|
|
37
|
+
data = build_template(name, description || "#{name} behaviour")
|
|
38
|
+
FileUtils.mkdir_p(path.dirname)
|
|
39
|
+
path.write(YAML.dump(data))
|
|
40
|
+
validate_behaviour!(path)
|
|
41
|
+
validate_enforcement!(data)
|
|
42
|
+
path
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Modifies an existing behaviour file.
|
|
46
|
+
#
|
|
47
|
+
# @param name [String] behaviour name
|
|
48
|
+
# @param description [String, nil] new description
|
|
49
|
+
# @return [Pathname] path to modified file
|
|
50
|
+
def modify(name, description: nil)
|
|
51
|
+
path = behaviour_path(name)
|
|
52
|
+
raise RosettAi::BehaviourError, "Behaviour '#{name}' not found" unless path.exist?
|
|
53
|
+
|
|
54
|
+
update_behaviour_file(path, description)
|
|
55
|
+
validate_behaviour!(path)
|
|
56
|
+
data = RosettAi::YamlLoader.load_file(path.to_s)
|
|
57
|
+
validate_enforcement!(data)
|
|
58
|
+
path
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Deletes a behaviour file.
|
|
62
|
+
#
|
|
63
|
+
# @param name [String] behaviour name
|
|
64
|
+
# @param options [Hash] :force_delete to skip existence check
|
|
65
|
+
# @return [Boolean] true on success
|
|
66
|
+
def delete(name, options: {})
|
|
67
|
+
path = behaviour_path(name)
|
|
68
|
+
force_delete = options.fetch(:force_delete, false)
|
|
69
|
+
raise RosettAi::BehaviourError, "Behaviour '#{name}' not found" unless force_delete || path.exist?
|
|
70
|
+
|
|
71
|
+
path.delete if path.exist?
|
|
72
|
+
cleanup_compiled_rules(name)
|
|
73
|
+
path
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def behaviour_path(name)
|
|
79
|
+
@behaviour_dir.join("#{name}.yml")
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def update_behaviour_file(path, description)
|
|
83
|
+
data = RosettAi::YamlLoader.load_file(path.to_s)
|
|
84
|
+
data['description'] = description if description
|
|
85
|
+
data['modified_at'] = Time.now.utc.strftime('%Y-%m-%d')
|
|
86
|
+
path.write(YAML.dump(data))
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def cleanup_compiled_rules(name)
|
|
90
|
+
rules_dir = RosettAi.paths.rules_dir
|
|
91
|
+
return unless rules_dir.exist?
|
|
92
|
+
|
|
93
|
+
Dir.glob(rules_dir.join("behaviour-#{name}.*")).each do |compiled|
|
|
94
|
+
next unless managed_compiled_file?(compiled)
|
|
95
|
+
|
|
96
|
+
File.delete(compiled)
|
|
97
|
+
end
|
|
98
|
+
rescue Errno::ENOENT
|
|
99
|
+
# Race condition: file deleted between glob and delete
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def managed_compiled_file?(path)
|
|
103
|
+
first_line = File.open(path, &:readline)
|
|
104
|
+
first_line.include?('rosett-ai-') && first_line.include?('managed')
|
|
105
|
+
rescue EOFError, Errno::ENOENT
|
|
106
|
+
false
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def build_template(name, description)
|
|
110
|
+
{
|
|
111
|
+
'name' => name,
|
|
112
|
+
'description' => description,
|
|
113
|
+
'version' => '1.0.0',
|
|
114
|
+
'author' => 'rosett-ai',
|
|
115
|
+
'created_at' => Time.now.utc.strftime('%Y-%m-%d'),
|
|
116
|
+
'modified_at' => Time.now.utc.strftime('%Y-%m-%d'),
|
|
117
|
+
'modified_by' => 'rosett-ai',
|
|
118
|
+
'sensitive' => false,
|
|
119
|
+
'used_in' => [],
|
|
120
|
+
'rules' => [
|
|
121
|
+
{
|
|
122
|
+
'id' => 'rule_001',
|
|
123
|
+
'description' => 'Example rule - replace with actual rule',
|
|
124
|
+
'priority' => 50,
|
|
125
|
+
'enabled' => true
|
|
126
|
+
}
|
|
127
|
+
]
|
|
128
|
+
}
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def validate_behaviour!(path)
|
|
132
|
+
validator = RosettAi::Validators::BehaviourValidator.new
|
|
133
|
+
return if validator.valid?(path.to_s)
|
|
134
|
+
|
|
135
|
+
# Remove invalid file to prevent leaving bad state
|
|
136
|
+
path.delete if path.exist?
|
|
137
|
+
raise RosettAi::BehaviourError, "Validation failed: #{validator.errors.join(', ')}"
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Runs EnforcementValidator on behaviour data. Logs warnings for
|
|
141
|
+
# downgraded rules but does not block the operation (enforcement
|
|
142
|
+
# is optional and downgrades are informational).
|
|
143
|
+
#
|
|
144
|
+
# @param data [Hash] parsed behaviour YAML
|
|
145
|
+
# @return [Hash] validation result
|
|
146
|
+
def validate_enforcement!(data)
|
|
147
|
+
result = RosettAi::Mcp::Enforcement::Validator.new.validate(data)
|
|
148
|
+
result[:downgraded].each do |entry|
|
|
149
|
+
warnings = entry[:warnings].join('; ')
|
|
150
|
+
warn "[rai-enforce] #{entry[:rule_id]} downgraded to advisory: #{warnings}"
|
|
151
|
+
end
|
|
152
|
+
result
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
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 Compiler
|
|
8
|
+
# Abstract base class for compiler backends.
|
|
9
|
+
#
|
|
10
|
+
# Backends transform compiled category data into target-specific output
|
|
11
|
+
# formats. Each backend is paired with a TargetProfile loaded from
|
|
12
|
+
# conf/engines/<name>/target.yml.
|
|
13
|
+
#
|
|
14
|
+
# Factory method .for(target_name) loads the profile and resolves the
|
|
15
|
+
# correct backend class from the Plugins::Registry.
|
|
16
|
+
class Backend
|
|
17
|
+
# Built-in backends that don't require an external engine gem.
|
|
18
|
+
# Lazy-loaded via lambdas to avoid circular requires.
|
|
19
|
+
BUILTIN_BACKENDS = {
|
|
20
|
+
'agents_md' => -> { RosettAi::Compiler::Backends::AgentsMdBackend },
|
|
21
|
+
'claude' => -> { RosettAi::Compiler::Backends::ClaudeBackend }
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
24
|
+
attr_reader :profile
|
|
25
|
+
|
|
26
|
+
def initialize(profile)
|
|
27
|
+
@profile = profile
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Factory: load a target profile and instantiate the appropriate backend.
|
|
31
|
+
#
|
|
32
|
+
# @param target_name [String, Symbol] engine/target identifier
|
|
33
|
+
# @return [Backend] the engine-specific backend instance
|
|
34
|
+
# @raise [RosettAi::CompileError] if the target or backend is unknown
|
|
35
|
+
def self.for(target_name)
|
|
36
|
+
name = target_name.to_s
|
|
37
|
+
profile = TargetProfile.load(name)
|
|
38
|
+
resolve_backend_class(profile.backend).new(profile)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.resolve_backend_class(backend_name)
|
|
42
|
+
return BUILTIN_BACKENDS[backend_name].call if BUILTIN_BACKENDS.key?(backend_name)
|
|
43
|
+
|
|
44
|
+
unless Plugins::Registry.registered?(:engine, backend_name)
|
|
45
|
+
available = Plugins::Registry.available(:engine)
|
|
46
|
+
hint = if available.empty?
|
|
47
|
+
"No engine plugins installed. Install with: apt install rosett-ai-engine-#{backend_name.tr('_', '-')}"
|
|
48
|
+
else
|
|
49
|
+
"Available engines: #{available.join(', ')}"
|
|
50
|
+
end
|
|
51
|
+
raise CompileError,
|
|
52
|
+
"Unknown backend: #{backend_name}. #{hint}"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
plugin = Plugins::Registry.plugin_module(:engine, backend_name)
|
|
56
|
+
plugin.backend_class
|
|
57
|
+
end
|
|
58
|
+
private_class_method :resolve_backend_class
|
|
59
|
+
|
|
60
|
+
def render(_data)
|
|
61
|
+
raise NotImplementedError, "#{self.class}#render must be implemented"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def generated_marker
|
|
65
|
+
raise NotImplementedError, "#{self.class}#generated_marker must be implemented"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def managed_file?(path)
|
|
69
|
+
return false unless File.exist?(path)
|
|
70
|
+
|
|
71
|
+
first_line = File.open(path, &:readline)
|
|
72
|
+
first_line.start_with?(generated_marker)
|
|
73
|
+
rescue EOFError
|
|
74
|
+
false
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def file_extension
|
|
78
|
+
case profile.output_format
|
|
79
|
+
when 'json' then '.json'
|
|
80
|
+
when 'yaml' then '.yml'
|
|
81
|
+
else '.md'
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
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 Compiler
|
|
8
|
+
module Backends
|
|
9
|
+
# Compiler backend for AGENTS.md (AAIF standard format).
|
|
10
|
+
#
|
|
11
|
+
# Renders behaviour configuration into idiomatic AGENTS.md files
|
|
12
|
+
# compatible with Claude Code, Goose, Cursor, and other AAIF-aligned tools.
|
|
13
|
+
# Output is valid Markdown without YAML frontmatter, following the
|
|
14
|
+
# AGENTS.md v0.1.0 specification.
|
|
15
|
+
#
|
|
16
|
+
# @author hugo
|
|
17
|
+
# @author claude
|
|
18
|
+
class AgentsMdBackend < Backend
|
|
19
|
+
MARKER_PREFIX = '<!-- rosett-ai-agents-md-managed'
|
|
20
|
+
|
|
21
|
+
# Renders behaviour data into AGENTS.md format.
|
|
22
|
+
#
|
|
23
|
+
# @param data [Hash] parsed YAML behaviour data
|
|
24
|
+
# @return [String] rendered Markdown content
|
|
25
|
+
def render(data)
|
|
26
|
+
lines = build_header(data)
|
|
27
|
+
lines.concat(build_description(data))
|
|
28
|
+
lines.concat(build_rules(data))
|
|
29
|
+
lines << ''
|
|
30
|
+
lines.join("\n")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @return [String] marker prefix for managed file detection
|
|
34
|
+
def generated_marker
|
|
35
|
+
MARKER_PREFIX
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def build_header(data)
|
|
41
|
+
lines = ["#{MARKER_PREFIX} -->"]
|
|
42
|
+
if profile.rendering.fetch('include_source_metadata', false)
|
|
43
|
+
lines << "<!-- Source: conf/#{data['category']}/#{data['name']}.yml -->"
|
|
44
|
+
end
|
|
45
|
+
lines << ''
|
|
46
|
+
lines << "# #{format_title(data['name'])}"
|
|
47
|
+
lines
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def build_description(data)
|
|
51
|
+
desc = data['description'].to_s.strip
|
|
52
|
+
return [] if desc.empty?
|
|
53
|
+
|
|
54
|
+
['', desc]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def build_rules(data)
|
|
58
|
+
rules = enabled_rules(data)
|
|
59
|
+
return [] if rules.empty?
|
|
60
|
+
|
|
61
|
+
lines = ['', '## Rules', '']
|
|
62
|
+
rules.each do |rule|
|
|
63
|
+
lines << "- #{rule['description'].to_s.strip}"
|
|
64
|
+
end
|
|
65
|
+
lines
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def enabled_rules(data)
|
|
69
|
+
(data['rules'] || [])
|
|
70
|
+
.select { |r| r.fetch('enabled', true) }
|
|
71
|
+
.sort_by { |r| -(r['priority'] || 50) }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def format_title(name)
|
|
75
|
+
name.to_s.tr('_', ' ').gsub(/\b\w/, &:upcase)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
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 Compiler
|
|
8
|
+
module Backends
|
|
9
|
+
# Compiler backend for Claude Code rule files.
|
|
10
|
+
#
|
|
11
|
+
# Renders behaviour configuration into Markdown files for Claude Code's
|
|
12
|
+
# ~/.claude/rules/ directory. Output includes priority annotations,
|
|
13
|
+
# source metadata, and the rosett-ai-claude-managed marker for idempotent
|
|
14
|
+
# updates.
|
|
15
|
+
class ClaudeBackend < Backend
|
|
16
|
+
MARKER_PREFIX = '<!-- rosett-ai-claude-managed'
|
|
17
|
+
|
|
18
|
+
# Renders behaviour data into Claude Code rule format.
|
|
19
|
+
#
|
|
20
|
+
# @param data [Hash] parsed YAML behaviour data
|
|
21
|
+
# @return [String] rendered Markdown content
|
|
22
|
+
def render(data)
|
|
23
|
+
sections = [
|
|
24
|
+
build_header(data),
|
|
25
|
+
build_description(data),
|
|
26
|
+
build_rules(data),
|
|
27
|
+
['']
|
|
28
|
+
]
|
|
29
|
+
sections.flatten.join("\n")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @return [String] marker prefix for managed file detection
|
|
33
|
+
def generated_marker
|
|
34
|
+
MARKER_PREFIX
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def build_header(data)
|
|
40
|
+
header = ["#{MARKER_PREFIX} -->"]
|
|
41
|
+
append_source_metadata(header, data)
|
|
42
|
+
header << ''
|
|
43
|
+
header << "# #{data['name']}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def append_source_metadata(header, data)
|
|
47
|
+
return unless profile.rendering.fetch('include_source_metadata', false)
|
|
48
|
+
|
|
49
|
+
header << "<!-- Source: conf/#{data['category']}/#{data['name']}.yml -->"
|
|
50
|
+
header << "<!-- Category: #{data['category']} | Version: #{data['version']} -->"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def build_description(data)
|
|
54
|
+
desc = data['description'].to_s.strip
|
|
55
|
+
return [] if desc.empty?
|
|
56
|
+
|
|
57
|
+
['', desc]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def build_rules(data)
|
|
61
|
+
rules = enabled_rules(data)
|
|
62
|
+
return [] if rules.empty?
|
|
63
|
+
|
|
64
|
+
['', '## Rules'] + rules.flat_map { |rule| format_rule(rule) }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def format_rule(rule)
|
|
68
|
+
heading = if priority_annotations?
|
|
69
|
+
"### #{rule['id']} (priority: #{rule['priority'] || 50})"
|
|
70
|
+
else
|
|
71
|
+
"### #{rule['id']}"
|
|
72
|
+
end
|
|
73
|
+
['', heading, '', rule['description'].to_s.strip]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def priority_annotations?
|
|
77
|
+
profile.rendering.fetch('include_priority_annotations', false)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def enabled_rules(data)
|
|
81
|
+
(data['rules'] || [])
|
|
82
|
+
.select { |r| r.fetch('enabled', true) }
|
|
83
|
+
.sort_by { |r| -(r['priority'] || 50) }
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
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 Compiler
|
|
8
|
+
# Compiler backend adapters.
|
|
9
|
+
module Backends
|
|
10
|
+
# Deprecated — use RosettAiEngine::Generic::Backend. Will be removed in v1.1.0.
|
|
11
|
+
# Only defined when rosett-ai-engine-generic gem is installed.
|
|
12
|
+
GenericBackend = RosettAiEngine::Generic::Backend if defined?(RosettAiEngine::Generic::Backend)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|