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,250 @@
|
|
|
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 'fileutils'
|
|
7
|
+
require 'time'
|
|
8
|
+
|
|
9
|
+
module RosettAi
|
|
10
|
+
module Compiler
|
|
11
|
+
# Compiles ruby-i18n YAML locale files into gettext (.pot/.po) and
|
|
12
|
+
# Qt Linguist (.ts) formats.
|
|
13
|
+
#
|
|
14
|
+
# Reads all *.yml files from the source directory, flattens nested
|
|
15
|
+
# keys into dotted paths, and generates platform-native locale files
|
|
16
|
+
# for distribution.
|
|
17
|
+
class LocaleCompiler
|
|
18
|
+
PLURAL_FORMS = ['zero', 'one', 'two', 'few', 'many', 'other'].freeze
|
|
19
|
+
|
|
20
|
+
PLURAL_HEADERS = {
|
|
21
|
+
'en' => 'nplurals=2; plural=(n != 1);',
|
|
22
|
+
'fr' => 'nplurals=2; plural=(n > 1);',
|
|
23
|
+
'ar' => 'nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5);'
|
|
24
|
+
}.freeze
|
|
25
|
+
|
|
26
|
+
attr_reader :source_dir, :gettext_dir, :qt_dir
|
|
27
|
+
|
|
28
|
+
# Initialize the locale compiler with source and output directories.
|
|
29
|
+
#
|
|
30
|
+
# @param source_dir [String, Pathname] locale YAML directory
|
|
31
|
+
# @param gettext_dir [String, Pathname] gettext output directory
|
|
32
|
+
# @param qt_dir [String, Pathname] Qt .ts output directory
|
|
33
|
+
def initialize(source_dir:, gettext_dir:, qt_dir:)
|
|
34
|
+
@source_dir = Pathname.new(source_dir)
|
|
35
|
+
@gettext_dir = Pathname.new(gettext_dir)
|
|
36
|
+
@qt_dir = Pathname.new(qt_dir)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Compiles all locale YAML files to gettext and Qt Linguist formats.
|
|
40
|
+
#
|
|
41
|
+
# @return [Hash{Symbol => Array<String>}] output paths keyed by :gettext and :qt
|
|
42
|
+
def compile
|
|
43
|
+
locale_files = discover_locale_files
|
|
44
|
+
results = { gettext: [], qt: [] }
|
|
45
|
+
|
|
46
|
+
locale_files.each do |file|
|
|
47
|
+
data = RosettAi::YamlLoader.load_file(file)
|
|
48
|
+
locale = data.keys.first
|
|
49
|
+
entries = flatten_keys(data[locale])
|
|
50
|
+
|
|
51
|
+
results[:gettext] << compile_gettext(locale, entries)
|
|
52
|
+
results[:qt] << compile_qt(locale, entries)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
results
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def discover_locale_files
|
|
61
|
+
Dir.glob(source_dir.join('*.yml'))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def flatten_keys(data, prefix = nil)
|
|
65
|
+
entries = []
|
|
66
|
+
data.each do |key, value|
|
|
67
|
+
full_key = prefix ? "#{prefix}.#{key}" : key
|
|
68
|
+
case value
|
|
69
|
+
when Hash
|
|
70
|
+
if plural_hash?(value)
|
|
71
|
+
entries << { key: full_key, value: value, plural: true }
|
|
72
|
+
else
|
|
73
|
+
entries.concat(flatten_keys(value, full_key))
|
|
74
|
+
end
|
|
75
|
+
when String
|
|
76
|
+
entries << { key: full_key, value: value }
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
entries
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def plural_hash?(hash)
|
|
83
|
+
hash.keys.any? { |k| PLURAL_FORMS.include?(k) }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def compile_gettext(locale, entries)
|
|
87
|
+
FileUtils.mkdir_p(gettext_dir)
|
|
88
|
+
|
|
89
|
+
pot_path = gettext_dir.join('rosett_ai.pot')
|
|
90
|
+
write_pot(pot_path, entries)
|
|
91
|
+
|
|
92
|
+
locale_dir = gettext_dir.join(locale, 'LC_MESSAGES')
|
|
93
|
+
FileUtils.mkdir_p(locale_dir)
|
|
94
|
+
po_path = locale_dir.join('rosett_ai.po')
|
|
95
|
+
write_po(po_path, locale, entries)
|
|
96
|
+
|
|
97
|
+
po_path.to_s
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def compile_qt(locale, entries)
|
|
101
|
+
FileUtils.mkdir_p(qt_dir)
|
|
102
|
+
|
|
103
|
+
ts_path = qt_dir.join("rosett_ai_#{locale}.ts")
|
|
104
|
+
write_ts(ts_path, locale, entries)
|
|
105
|
+
|
|
106
|
+
ts_path.to_s
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def write_pot(path, entries)
|
|
110
|
+
File.open(path, 'w', 0o644) do |file|
|
|
111
|
+
file.puts pot_header
|
|
112
|
+
entries.each do |entry|
|
|
113
|
+
file.puts
|
|
114
|
+
file.puts "#: #{entry[:key]}"
|
|
115
|
+
if entry[:plural]
|
|
116
|
+
singular = entry[:value]['one'] || entry[:value].values.first
|
|
117
|
+
plural = entry[:value]['other'] || singular
|
|
118
|
+
file.puts "msgid #{quote_po(singular)}"
|
|
119
|
+
file.puts "msgid_plural #{quote_po(plural)}"
|
|
120
|
+
file.puts 'msgstr[0] ""'
|
|
121
|
+
file.puts 'msgstr[1] ""'
|
|
122
|
+
else
|
|
123
|
+
file.puts "msgid #{quote_po(entry[:value])}"
|
|
124
|
+
file.puts 'msgstr ""'
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def write_po(path, locale, entries)
|
|
131
|
+
File.open(path, 'w', 0o644) do |file|
|
|
132
|
+
file.puts po_header(locale)
|
|
133
|
+
entries.each do |entry|
|
|
134
|
+
file.puts
|
|
135
|
+
file.puts "#: #{entry[:key]}"
|
|
136
|
+
if entry[:plural]
|
|
137
|
+
write_po_plural(file, entry)
|
|
138
|
+
else
|
|
139
|
+
file.puts "msgid #{quote_po(entry[:value])}"
|
|
140
|
+
file.puts "msgstr #{quote_po(entry[:value])}"
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def write_po_plural(file, entry)
|
|
147
|
+
forms = entry[:value]
|
|
148
|
+
singular = forms['one'] || forms.values.first
|
|
149
|
+
plural = forms['other'] || singular
|
|
150
|
+
file.puts "msgid #{quote_po(singular)}"
|
|
151
|
+
file.puts "msgid_plural #{quote_po(plural)}"
|
|
152
|
+
forms.each_with_index do |(_, form_text), idx|
|
|
153
|
+
file.puts "msgstr[#{idx}] #{quote_po(form_text)}"
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def write_ts(path, locale, entries)
|
|
158
|
+
contexts = group_by_context(entries)
|
|
159
|
+
|
|
160
|
+
File.open(path, 'w', 0o644) do |file|
|
|
161
|
+
file.puts '<?xml version="1.0" encoding="utf-8"?>'
|
|
162
|
+
file.puts '<!DOCTYPE TS>'
|
|
163
|
+
file.puts "<TS version=\"2.1\" language=\"#{escape_xml(locale)}\">"
|
|
164
|
+
|
|
165
|
+
contexts.each do |context_name, messages|
|
|
166
|
+
file.puts ' <context>'
|
|
167
|
+
file.puts " <name>#{escape_xml(context_name)}</name>"
|
|
168
|
+
messages.each { |msg| write_ts_message(file, msg) }
|
|
169
|
+
file.puts ' </context>'
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
file.puts '</TS>'
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def write_ts_message(file, msg)
|
|
177
|
+
if msg[:plural]
|
|
178
|
+
file.puts ' <message numerus="yes">'
|
|
179
|
+
file.puts " <source>#{escape_xml(msg[:key])}</source>"
|
|
180
|
+
file.puts ' <translation>'
|
|
181
|
+
msg[:value].each_value { |text| file.puts " <numerusform>#{escape_xml(text)}</numerusform>" }
|
|
182
|
+
file.puts ' </translation>'
|
|
183
|
+
else
|
|
184
|
+
file.puts ' <message>'
|
|
185
|
+
file.puts " <source>#{escape_xml(msg[:key])}</source>"
|
|
186
|
+
file.puts " <translation>#{escape_xml(msg[:value])}</translation>"
|
|
187
|
+
end
|
|
188
|
+
file.puts ' </message>'
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def group_by_context(entries)
|
|
192
|
+
groups = {}
|
|
193
|
+
entries.each do |entry|
|
|
194
|
+
parts = entry[:key].split('.')
|
|
195
|
+
context = parts.length > 1 ? parts[0...-1].join('.') : 'default'
|
|
196
|
+
groups[context] ||= []
|
|
197
|
+
groups[context] << entry
|
|
198
|
+
end
|
|
199
|
+
groups
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def pot_header
|
|
203
|
+
<<~HEADER.chomp
|
|
204
|
+
# SOME DESCRIPTIVE TITLE.
|
|
205
|
+
# Copyright (C) 2026 NeatNerds
|
|
206
|
+
# This file is distributed under the GPL-3.0-only license.
|
|
207
|
+
#
|
|
208
|
+
msgid ""
|
|
209
|
+
msgstr ""
|
|
210
|
+
"Project-Id-Version: rosett-ai #{RosettAi::VERSION}\\n"
|
|
211
|
+
"POT-Creation-Date: #{Time.now.utc.strftime('%Y-%m-%d %H:%M%z')}\\n"
|
|
212
|
+
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n"
|
|
213
|
+
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n"
|
|
214
|
+
"Language-Team: LANGUAGE <LL@li.org>\\n"
|
|
215
|
+
"MIME-Version: 1.0\\n"
|
|
216
|
+
"Content-Type: text/plain; charset=UTF-8\\n"
|
|
217
|
+
"Content-Transfer-Encoding: 8bit\\n"
|
|
218
|
+
HEADER
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def po_header(locale)
|
|
222
|
+
plural_forms = PLURAL_HEADERS.fetch(locale, 'nplurals=2; plural=(n != 1);')
|
|
223
|
+
<<~HEADER.chomp
|
|
224
|
+
# #{locale.upcase} translations for Rosett-AI.
|
|
225
|
+
# Copyright (C) 2026 NeatNerds
|
|
226
|
+
# This file is distributed under the GPL-3.0-only license.
|
|
227
|
+
#
|
|
228
|
+
msgid ""
|
|
229
|
+
msgstr ""
|
|
230
|
+
"Project-Id-Version: rosett-ai #{RosettAi::VERSION}\\n"
|
|
231
|
+
"PO-Revision-Date: #{Time.now.utc.strftime('%Y-%m-%d %H:%M%z')}\\n"
|
|
232
|
+
"Last-Translator: rosett-ai\\n"
|
|
233
|
+
"Language: #{locale}\\n"
|
|
234
|
+
"MIME-Version: 1.0\\n"
|
|
235
|
+
"Content-Type: text/plain; charset=UTF-8\\n"
|
|
236
|
+
"Content-Transfer-Encoding: 8bit\\n"
|
|
237
|
+
"Plural-Forms: #{plural_forms}\\n"
|
|
238
|
+
HEADER
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def quote_po(string)
|
|
242
|
+
"\"#{string.gsub('\\', '\\\\\\\\').gsub('"', '\\"').gsub("\n", '\\n')}\""
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def escape_xml(string)
|
|
246
|
+
string.gsub('&', '&').gsub('<', '<').gsub('>', '>').gsub('"', '"')
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
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
|
+
require 'json_schemer'
|
|
8
|
+
|
|
9
|
+
module RosettAi
|
|
10
|
+
module Compiler
|
|
11
|
+
# Loads and validates target profiles from conf/engines/<name>/target.yml,
|
|
12
|
+
# with fallback to conf/targets/<name>.yml for backward compatibility.
|
|
13
|
+
#
|
|
14
|
+
# Target profiles declare which backend to use, the output format, and
|
|
15
|
+
# rendering options for a specific AI assistant target. Adding a new
|
|
16
|
+
# target requires only a new YAML file — no code changes.
|
|
17
|
+
class TargetProfile
|
|
18
|
+
FIELDS = [
|
|
19
|
+
:name, :description, :backend, :engine_version, :output_format,
|
|
20
|
+
:output_dir, :max_context_window, :rendering
|
|
21
|
+
].freeze
|
|
22
|
+
|
|
23
|
+
attr_reader(*FIELDS)
|
|
24
|
+
|
|
25
|
+
def initialize(data)
|
|
26
|
+
FIELDS.each do |field|
|
|
27
|
+
instance_variable_set(:"@#{field}", data[field.to_s])
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Load and validate a target profile by name.
|
|
32
|
+
#
|
|
33
|
+
# Resolution order:
|
|
34
|
+
# 1. Plugin registry (engine gem's `target_profile_path`)
|
|
35
|
+
# 2. `conf/engines/<name>/target.yml` (core tree)
|
|
36
|
+
# 3. `conf/targets/<name>.yml` (legacy)
|
|
37
|
+
#
|
|
38
|
+
# @param target_name [String] engine/target identifier
|
|
39
|
+
# @return [TargetProfile]
|
|
40
|
+
# @raise [RosettAi::CompileError] if target not found or invalid
|
|
41
|
+
def self.load(target_name)
|
|
42
|
+
# 1. Check plugin registry for engine-provided target profile
|
|
43
|
+
if RosettAi::Plugins::Registry.registered?(:engine, target_name)
|
|
44
|
+
plugin = RosettAi::Plugins::Registry.plugin_module(:engine, target_name)
|
|
45
|
+
if plugin.respond_to?(:target_profile_path) && plugin.target_profile_path.exist?
|
|
46
|
+
data = RosettAi::YamlLoader.load_file(plugin.target_profile_path)
|
|
47
|
+
validate!(data, plugin.target_profile_path)
|
|
48
|
+
return new(data)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# 2. Fallback: file-based lookup (core conf/ tree)
|
|
53
|
+
path = engine_target_path(target_name)
|
|
54
|
+
path = legacy_target_path(target_name) unless path.exist?
|
|
55
|
+
unless path.exist?
|
|
56
|
+
available_list = available
|
|
57
|
+
hint = if available_list.empty?
|
|
58
|
+
"Install an engine: apt install rosett-ai-engine-#{target_name.tr('_', '-')}"
|
|
59
|
+
else
|
|
60
|
+
available_list.join(', ')
|
|
61
|
+
end
|
|
62
|
+
raise RosettAi::CompileError,
|
|
63
|
+
"Unknown target: #{target_name}. Available: #{hint}"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
data = RosettAi::YamlLoader.load_file(path)
|
|
67
|
+
validate!(data, path)
|
|
68
|
+
new(data)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @return [Array<String>] sorted list of all available target names
|
|
72
|
+
# (from plugins, engine dir, and legacy targets dir)
|
|
73
|
+
def self.available
|
|
74
|
+
plugin_targets = RosettAi::Plugins::Registry.available(:engine)
|
|
75
|
+
engine_targets = Dir.glob(RosettAi.root.join('conf', 'engines', '*', 'target.yml')).map do |file|
|
|
76
|
+
File.basename(File.dirname(file))
|
|
77
|
+
end
|
|
78
|
+
legacy_targets = Dir.glob(legacy_targets_dir.join('*.yml')).map do |file|
|
|
79
|
+
File.basename(file, '.yml')
|
|
80
|
+
end
|
|
81
|
+
(plugin_targets + engine_targets + legacy_targets).uniq.sort
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def self.engine_target_path(name)
|
|
85
|
+
RosettAi.root.join('conf', 'engines', name, 'target.yml')
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def self.legacy_target_path(name)
|
|
89
|
+
legacy_targets_dir.join("#{name}.yml")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def self.legacy_targets_dir
|
|
93
|
+
RosettAi.root.join('conf', 'targets')
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def self.validate!(data, path)
|
|
97
|
+
schema_path = RosettAi.root.join('conf', 'schemas', 'target_schema.json')
|
|
98
|
+
schema = JSON.parse(schema_path.read)
|
|
99
|
+
schemer = JSONSchemer.schema(schema)
|
|
100
|
+
errors = schemer.validate(data).to_a
|
|
101
|
+
return if errors.empty?
|
|
102
|
+
|
|
103
|
+
messages = errors.map do |err|
|
|
104
|
+
pointer = err['data_pointer'].empty? ? 'root' : err['data_pointer']
|
|
105
|
+
"#{pointer}: #{err['type']}"
|
|
106
|
+
end
|
|
107
|
+
raise RosettAi::CompileError, "Invalid target profile #{path}: #{messages.join(', ')}"
|
|
108
|
+
end
|
|
109
|
+
private_class_method :validate!
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
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 Completion
|
|
8
|
+
# Extracts command metadata from the Thor CLI registry for completion
|
|
9
|
+
# script generation. This is the single source of truth for all
|
|
10
|
+
# shell completion generators.
|
|
11
|
+
#
|
|
12
|
+
# @author hugo
|
|
13
|
+
# @author claude
|
|
14
|
+
class Generator
|
|
15
|
+
SUPPORTED_SHELLS = ['bash', 'zsh', 'fish'].freeze
|
|
16
|
+
|
|
17
|
+
# @return [Hash] command tree extracted from Thor CLI
|
|
18
|
+
attr_reader :command_tree
|
|
19
|
+
|
|
20
|
+
def initialize
|
|
21
|
+
@command_tree = extract_command_tree
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Generate a completion script for the given shell.
|
|
25
|
+
#
|
|
26
|
+
# @param shell [String] one of 'bash', 'zsh', 'fish'
|
|
27
|
+
# @return [String] the completion script content
|
|
28
|
+
# @raise [RosettAi::Error] if shell is unsupported
|
|
29
|
+
def generate(shell)
|
|
30
|
+
shell = shell.to_s.downcase
|
|
31
|
+
unless SUPPORTED_SHELLS.include?(shell)
|
|
32
|
+
raise RosettAi::Error,
|
|
33
|
+
"Shell #{shell} is not supported — available: #{SUPPORTED_SHELLS.join(', ')}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
shell_generator_for(shell).generate
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @param shell [String] shell name
|
|
40
|
+
# @return [Boolean] whether the shell is supported
|
|
41
|
+
def supported?(shell)
|
|
42
|
+
SUPPORTED_SHELLS.include?(shell.to_s.downcase)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def shell_generator_for(shell)
|
|
48
|
+
case shell
|
|
49
|
+
when 'bash' then Shells::BashGenerator.new(command_tree)
|
|
50
|
+
when 'zsh' then Shells::ZshGenerator.new(command_tree)
|
|
51
|
+
when 'fish' then Shells::FishGenerator.new(command_tree)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def extract_command_tree
|
|
56
|
+
cli_class = RosettAi::Thor::CLI
|
|
57
|
+
tree = {}
|
|
58
|
+
|
|
59
|
+
cli_class.all_commands.each_value do |cmd|
|
|
60
|
+
next if cmd.name == 'help'
|
|
61
|
+
|
|
62
|
+
tree[cmd.name] = extract_command_info(cli_class, cmd)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
tree
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def extract_command_info(cli_class, cmd)
|
|
69
|
+
info = { description: cmd.description, options: extract_options(cmd) }
|
|
70
|
+
sub_class = find_subcommand_class(cli_class, cmd.name)
|
|
71
|
+
info[:subcommands] = extract_subcommands(sub_class) if sub_class
|
|
72
|
+
info
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def extract_options(cmd)
|
|
76
|
+
return {} unless cmd.respond_to?(:options) && cmd.options
|
|
77
|
+
|
|
78
|
+
cmd.options.transform_values do |opt|
|
|
79
|
+
{ type: opt.type.to_s, description: opt.description, enum: opt.enum }
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def find_subcommand_class(cli_class, name)
|
|
84
|
+
cli_class.subcommand_classes[name]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def extract_subcommands(sub_class)
|
|
88
|
+
subs = {}
|
|
89
|
+
sub_class.all_commands.each_value do |cmd|
|
|
90
|
+
next if cmd.name == 'help'
|
|
91
|
+
|
|
92
|
+
subs[cmd.name] = {
|
|
93
|
+
description: cmd.description,
|
|
94
|
+
options: extract_options(cmd)
|
|
95
|
+
}
|
|
96
|
+
end
|
|
97
|
+
subs
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
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 Completion
|
|
8
|
+
module Shells
|
|
9
|
+
# Generates bash completion scripts from the Thor command tree.
|
|
10
|
+
#
|
|
11
|
+
# Uses bash-completion framework conventions with _raictl() function.
|
|
12
|
+
# Dynamic completions for behaviour names and engine names use
|
|
13
|
+
# rai subcommands at tab-completion time.
|
|
14
|
+
#
|
|
15
|
+
# @author hugo
|
|
16
|
+
# @author claude
|
|
17
|
+
class BashGenerator
|
|
18
|
+
# @param command_tree [Hash] command tree from Generator
|
|
19
|
+
def initialize(command_tree)
|
|
20
|
+
@command_tree = command_tree
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @return [String] bash completion script
|
|
24
|
+
def generate
|
|
25
|
+
lines = []
|
|
26
|
+
lines << header
|
|
27
|
+
lines << main_function
|
|
28
|
+
lines << subcommand_functions
|
|
29
|
+
lines << dynamic_completions
|
|
30
|
+
lines << registration
|
|
31
|
+
lines.join("\n")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def header
|
|
37
|
+
<<~BASH
|
|
38
|
+
# bash completion for Rosett-AI — generated by rai completion bash
|
|
39
|
+
# Do not edit manually; regenerate with: rai completion bash
|
|
40
|
+
BASH
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def main_function
|
|
44
|
+
commands = @command_tree.keys.sort
|
|
45
|
+
<<~BASH
|
|
46
|
+
_raictl() {
|
|
47
|
+
local cur prev words cword
|
|
48
|
+
_init_completion || return
|
|
49
|
+
|
|
50
|
+
local commands="#{shell_escape(commands.join(' '))}"
|
|
51
|
+
|
|
52
|
+
if [[ ${cword} -eq 1 ]]; then
|
|
53
|
+
COMPREPLY=( $(compgen -W "${commands}" -- "${cur}") )
|
|
54
|
+
return
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
local subcmd="${words[1]}"
|
|
58
|
+
case "${subcmd}" in
|
|
59
|
+
#{subcommand_cases.chomp}
|
|
60
|
+
*)
|
|
61
|
+
COMPREPLY=()
|
|
62
|
+
;;
|
|
63
|
+
esac
|
|
64
|
+
}
|
|
65
|
+
BASH
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def subcommand_cases
|
|
69
|
+
lines = []
|
|
70
|
+
@command_tree.each do |name, info|
|
|
71
|
+
subs = info[:subcommands]
|
|
72
|
+
next unless subs
|
|
73
|
+
|
|
74
|
+
sub_names = subs.keys.sort.join(' ')
|
|
75
|
+
options = collect_options(subs)
|
|
76
|
+
words = [sub_names, options].reject(&:empty?).join(' ')
|
|
77
|
+
lines << " #{shell_escape(name)})\n"
|
|
78
|
+
lines << " COMPREPLY=( $(compgen -W \"#{shell_escape(words)}\" -- \"${cur}\") )\n"
|
|
79
|
+
lines << " ;;\n"
|
|
80
|
+
end
|
|
81
|
+
lines.join
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def subcommand_functions
|
|
85
|
+
'' # Inline in case statement for simplicity
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def dynamic_completions
|
|
89
|
+
<<~BASH
|
|
90
|
+
|
|
91
|
+
_rai_behaviour_names() {
|
|
92
|
+
local names
|
|
93
|
+
names=$(rai behaviour list --format=names 2>/dev/null)
|
|
94
|
+
COMPREPLY=( $(compgen -W "${names}" -- "${cur}") )
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
_rosett_ai_engine_names() {
|
|
98
|
+
local names
|
|
99
|
+
names=$(rai engines list --format=names 2>/dev/null)
|
|
100
|
+
COMPREPLY=( $(compgen -W "${names}" -- "${cur}") )
|
|
101
|
+
}
|
|
102
|
+
BASH
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def registration
|
|
106
|
+
<<~BASH
|
|
107
|
+
complete -F _raictl rosett-ai
|
|
108
|
+
BASH
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def collect_options(subcommands)
|
|
112
|
+
subcommands.each_value
|
|
113
|
+
.flat_map { |s| (s[:options] || {}).keys }
|
|
114
|
+
.uniq
|
|
115
|
+
.sort
|
|
116
|
+
.map { |o| "--#{o.to_s.tr('_', '-')}" }
|
|
117
|
+
.join(' ')
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def shell_escape(str)
|
|
121
|
+
str.gsub(/[^a-zA-Z0-9_ .,-]/, '')
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
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 Completion
|
|
8
|
+
module Shells
|
|
9
|
+
# Generates fish completion scripts from the Thor command tree.
|
|
10
|
+
#
|
|
11
|
+
# Uses fish's `complete` builtin with condition-based subcommand
|
|
12
|
+
# completion. Supports subcommand descriptions and dynamic
|
|
13
|
+
# completions for behaviour/engine names.
|
|
14
|
+
#
|
|
15
|
+
# @author hugo
|
|
16
|
+
# @author claude
|
|
17
|
+
class FishGenerator
|
|
18
|
+
# @param command_tree [Hash] command tree from Generator
|
|
19
|
+
def initialize(command_tree)
|
|
20
|
+
@command_tree = command_tree
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @return [String] fish completion script
|
|
24
|
+
def generate
|
|
25
|
+
lines = []
|
|
26
|
+
lines << header
|
|
27
|
+
lines << disable_file_completion
|
|
28
|
+
lines << top_level_completions
|
|
29
|
+
lines << subcommand_completions
|
|
30
|
+
lines.join("\n")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def header
|
|
36
|
+
<<~FISH
|
|
37
|
+
# fish completion for Rosett-AI — generated by rai completion fish
|
|
38
|
+
# Do not edit manually; regenerate with: rai completion fish
|
|
39
|
+
FISH
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def disable_file_completion
|
|
43
|
+
"complete -c rosett-ai -f\n"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def top_level_completions
|
|
47
|
+
lines = []
|
|
48
|
+
@command_tree.each do |name, info|
|
|
49
|
+
desc = fish_escape(info[:description].to_s)
|
|
50
|
+
lines << "complete -c rosett-ai -n '__fish_use_subcommand' " \
|
|
51
|
+
"-a '#{fish_escape(name)}' -d '#{desc}'"
|
|
52
|
+
end
|
|
53
|
+
"#{lines.sort.join("\n")}\n"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def subcommand_completions
|
|
57
|
+
lines = []
|
|
58
|
+
@command_tree.each do |name, info|
|
|
59
|
+
subs = info[:subcommands]
|
|
60
|
+
next unless subs
|
|
61
|
+
|
|
62
|
+
subs.each do |sub_name, sub_info|
|
|
63
|
+
desc = fish_escape(sub_info[:description].to_s)
|
|
64
|
+
lines << 'complete -c rosett-ai ' \
|
|
65
|
+
"-n '__fish_seen_subcommand_from #{fish_escape(name)}' " \
|
|
66
|
+
"-a '#{fish_escape(sub_name)}' -d '#{desc}'"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
"#{lines.sort.join("\n")}\n"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def fish_escape(str)
|
|
73
|
+
str.gsub("'", "\\\\'")
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|