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,175 @@
|
|
|
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 GitHooks
|
|
8
|
+
# Installs and uninstalls rosett-ai-managed git hooks in .git/hooks/.
|
|
9
|
+
#
|
|
10
|
+
# Reads hook definitions from .rosett-ai/config.yml and generates POSIX sh
|
|
11
|
+
# scripts that invoke rai commands. Chains to pre-existing hooks
|
|
12
|
+
# (overcommit, husky, lefthook) without overwriting them.
|
|
13
|
+
#
|
|
14
|
+
# @author hugo
|
|
15
|
+
# @author claude
|
|
16
|
+
class Installer
|
|
17
|
+
RAI_MARKER = '# rosett-ai-managed hook'
|
|
18
|
+
BACKUP_SUFFIX = '.rosett-ai-backup'
|
|
19
|
+
|
|
20
|
+
SUPPORTED_HOOKS = [
|
|
21
|
+
'pre-commit', 'post-commit', 'post-checkout', 'post-merge', 'pre-push'
|
|
22
|
+
].freeze
|
|
23
|
+
|
|
24
|
+
# @param project_root [Pathname] path to the project root
|
|
25
|
+
# @param hooks_config [Hash] hook definitions from .rosett-ai/config.yml
|
|
26
|
+
def initialize(project_root:, hooks_config:)
|
|
27
|
+
@project_root = Pathname.new(project_root)
|
|
28
|
+
@hooks_config = hooks_config
|
|
29
|
+
@hooks_dir = @project_root.join('.git', 'hooks')
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Installs all configured hooks.
|
|
33
|
+
#
|
|
34
|
+
# @return [Hash] result with :installed, :skipped, :errors keys
|
|
35
|
+
def install
|
|
36
|
+
results = { installed: [], skipped: [], errors: [] }
|
|
37
|
+
|
|
38
|
+
unless @hooks_dir.directory?
|
|
39
|
+
results[:errors] << 'Not a git repository (missing .git/hooks/)'
|
|
40
|
+
return results
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
@hooks_config.each do |hook_name, commands|
|
|
44
|
+
normalized = normalize_hook_name(hook_name)
|
|
45
|
+
install_hook(normalized, Array(commands), results)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
results
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Uninstalls all rosett-ai-managed hooks, restoring backups.
|
|
52
|
+
#
|
|
53
|
+
# @return [Hash] result with :removed, :restored, :errors keys
|
|
54
|
+
def uninstall
|
|
55
|
+
results = { removed: [], restored: [], errors: [] }
|
|
56
|
+
|
|
57
|
+
unless @hooks_dir.directory?
|
|
58
|
+
results[:errors] << 'Not a git repository (missing .git/hooks/)'
|
|
59
|
+
return results
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
SUPPORTED_HOOKS.each { |hook| uninstall_hook(hook, results) }
|
|
63
|
+
results
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Lists currently installed rai hooks.
|
|
67
|
+
#
|
|
68
|
+
# @return [Array<Hash>] list of hook info hashes with :name and :commands
|
|
69
|
+
def list
|
|
70
|
+
SUPPORTED_HOOKS.filter_map do |hook|
|
|
71
|
+
path = @hooks_dir.join(hook)
|
|
72
|
+
next unless path.exist? && rai_managed?(path)
|
|
73
|
+
|
|
74
|
+
{ name: hook, commands: extract_commands(path) }
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Reports drift between config and installed hooks.
|
|
79
|
+
#
|
|
80
|
+
# @return [Hash] status with :in_sync, :missing, :extra, :drifted keys
|
|
81
|
+
def status
|
|
82
|
+
result = { in_sync: [], missing: [], extra: [], drifted: [] }
|
|
83
|
+
|
|
84
|
+
configured = @hooks_config.transform_keys { |key| normalize_hook_name(key) }
|
|
85
|
+
|
|
86
|
+
configured.each do |hook_name, commands|
|
|
87
|
+
path = @hooks_dir.join(hook_name)
|
|
88
|
+
classify_hook(path, hook_name, Array(commands), result)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
find_extra_hooks(configured, result)
|
|
92
|
+
result
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
def normalize_hook_name(name)
|
|
98
|
+
name.to_s.tr('_', '-')
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def install_hook(hook_name, commands, results)
|
|
102
|
+
unless SUPPORTED_HOOKS.include?(hook_name)
|
|
103
|
+
results[:errors] << "Unsupported hook: #{hook_name}"
|
|
104
|
+
return
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
path = @hooks_dir.join(hook_name)
|
|
108
|
+
backup_existing_hook(path) if path.exist? && !rai_managed?(path)
|
|
109
|
+
|
|
110
|
+
script = ScriptGenerator.generate(hook_name: hook_name, commands: commands)
|
|
111
|
+
File.write(path, script)
|
|
112
|
+
File.chmod(0o755, path)
|
|
113
|
+
results[:installed] << hook_name
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def uninstall_hook(hook_name, results)
|
|
117
|
+
path = @hooks_dir.join(hook_name)
|
|
118
|
+
return unless path.exist? && rai_managed?(path)
|
|
119
|
+
|
|
120
|
+
File.delete(path)
|
|
121
|
+
results[:removed] << hook_name
|
|
122
|
+
|
|
123
|
+
backup = Pathname.new("#{path}#{BACKUP_SUFFIX}")
|
|
124
|
+
return unless backup.exist?
|
|
125
|
+
|
|
126
|
+
FileUtils.mv(backup, path)
|
|
127
|
+
results[:restored] << hook_name
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def backup_existing_hook(path)
|
|
131
|
+
backup = Pathname.new("#{path}#{BACKUP_SUFFIX}")
|
|
132
|
+
FileUtils.cp(path, backup) unless backup.exist?
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def rai_managed?(path)
|
|
136
|
+
File.read(path).include?(RAI_MARKER)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def extract_commands(path)
|
|
140
|
+
File.read(path).scan(/^# Run: rosett-ai (.+)$/).flatten
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def classify_hook(path, hook_name, commands, result)
|
|
144
|
+
unless path.exist?
|
|
145
|
+
result[:missing] << hook_name
|
|
146
|
+
return
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
unless rai_managed?(path)
|
|
150
|
+
result[:missing] << hook_name
|
|
151
|
+
return
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
installed_cmds = extract_commands(path)
|
|
155
|
+
expected_cmds = commands.map(&:to_s)
|
|
156
|
+
|
|
157
|
+
if installed_cmds == expected_cmds
|
|
158
|
+
result[:in_sync] << hook_name
|
|
159
|
+
else
|
|
160
|
+
result[:drifted] << hook_name
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def find_extra_hooks(configured, result)
|
|
165
|
+
SUPPORTED_HOOKS.each do |hook|
|
|
166
|
+
path = @hooks_dir.join(hook)
|
|
167
|
+
next unless path.exist? && rai_managed?(path)
|
|
168
|
+
next if configured.key?(hook)
|
|
169
|
+
|
|
170
|
+
result[:extra] << hook
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
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 'shellwords'
|
|
7
|
+
|
|
8
|
+
module RosettAi
|
|
9
|
+
module GitHooks
|
|
10
|
+
# Generates POSIX sh hook scripts that invoke rai commands.
|
|
11
|
+
#
|
|
12
|
+
# Scripts include timeout handling, RAI_SKIP_HOOKS bypass,
|
|
13
|
+
# and chaining to pre-existing hooks via the backup mechanism.
|
|
14
|
+
#
|
|
15
|
+
# @author hugo
|
|
16
|
+
# @author claude
|
|
17
|
+
module ScriptGenerator
|
|
18
|
+
BACKUP_SUFFIX = '.rosett-ai-backup'
|
|
19
|
+
DEFAULT_TIMEOUT = 30
|
|
20
|
+
|
|
21
|
+
# Pattern matching shell metacharacters that indicate potential injection.
|
|
22
|
+
# Matches: ; | & ` $( )
|
|
23
|
+
DANGEROUS_PATTERN = /[;|&`]|\$\(/
|
|
24
|
+
|
|
25
|
+
# Validates a command string for shell safety.
|
|
26
|
+
#
|
|
27
|
+
# @param command [String] the command to validate
|
|
28
|
+
# @raise [ArgumentError] if the command contains dangerous shell metacharacters
|
|
29
|
+
# @return [void]
|
|
30
|
+
def self.validate_command!(command)
|
|
31
|
+
return unless command.match?(DANGEROUS_PATTERN)
|
|
32
|
+
|
|
33
|
+
raise ArgumentError,
|
|
34
|
+
"Hook command contains dangerous shell metacharacters: #{command.inspect}"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Generates a POSIX sh hook script.
|
|
38
|
+
#
|
|
39
|
+
# @param hook_name [String] the git hook name (e.g. 'pre-commit')
|
|
40
|
+
# @param commands [Array<String>] rai commands to run
|
|
41
|
+
# @param timeout [Integer] timeout in seconds (default 30)
|
|
42
|
+
# @return [String] the generated script content
|
|
43
|
+
# @raise [ArgumentError] if any command contains shell metacharacters
|
|
44
|
+
def self.generate(hook_name:, commands:, timeout: DEFAULT_TIMEOUT)
|
|
45
|
+
commands.each { |cmd| validate_command!(cmd) }
|
|
46
|
+
blocking = blocking_hook?(hook_name)
|
|
47
|
+
|
|
48
|
+
lines = [shebang, marker, skip_check, chain_call(hook_name)]
|
|
49
|
+
commands.each { |cmd| lines << command_block(cmd, timeout, blocking) }
|
|
50
|
+
lines << exit_line
|
|
51
|
+
lines.join("\n")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @param hook_name [String]
|
|
55
|
+
# @return [Boolean] true if the hook should block the git operation on failure
|
|
56
|
+
def self.blocking_hook?(hook_name)
|
|
57
|
+
['pre-commit', 'pre-push'].include?(hook_name)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def self.shebang
|
|
61
|
+
'#!/bin/sh'
|
|
62
|
+
end
|
|
63
|
+
private_class_method :shebang
|
|
64
|
+
|
|
65
|
+
def self.marker
|
|
66
|
+
"#{Installer::RAI_MARKER} — do not edit manually"
|
|
67
|
+
end
|
|
68
|
+
private_class_method :marker
|
|
69
|
+
|
|
70
|
+
def self.skip_check
|
|
71
|
+
<<~SH
|
|
72
|
+
|
|
73
|
+
# Emergency bypass
|
|
74
|
+
if [ "${RAI_SKIP_HOOKS:-0}" = "1" ]; then
|
|
75
|
+
exit 0
|
|
76
|
+
fi
|
|
77
|
+
SH
|
|
78
|
+
end
|
|
79
|
+
private_class_method :skip_check
|
|
80
|
+
|
|
81
|
+
def self.chain_call(hook_name)
|
|
82
|
+
<<~SH
|
|
83
|
+
# Chain to pre-existing hook if backed up
|
|
84
|
+
if [ -x "$(dirname "$0")/#{hook_name}#{BACKUP_SUFFIX}" ]; then
|
|
85
|
+
"$(dirname "$0")/#{hook_name}#{BACKUP_SUFFIX}" "$@"
|
|
86
|
+
chain_status=$?
|
|
87
|
+
if [ $chain_status -ne 0 ]; then
|
|
88
|
+
exit $chain_status
|
|
89
|
+
fi
|
|
90
|
+
fi
|
|
91
|
+
SH
|
|
92
|
+
end
|
|
93
|
+
private_class_method :chain_call
|
|
94
|
+
|
|
95
|
+
def self.command_block(command, timeout, blocking)
|
|
96
|
+
exit_line = blocking ? ' exit $cmd_status' : ' echo "rosett-ai: hook command failed (non-blocking)" >&2'
|
|
97
|
+
escaped = Shellwords.shellescape(command)
|
|
98
|
+
|
|
99
|
+
<<~SH
|
|
100
|
+
# Run: rosett-ai #{command}
|
|
101
|
+
if command -v timeout >/dev/null 2>&1; then
|
|
102
|
+
timeout #{timeout} rosett-ai #{escaped}
|
|
103
|
+
else
|
|
104
|
+
raictl #{escaped}
|
|
105
|
+
fi
|
|
106
|
+
cmd_status=$?
|
|
107
|
+
if [ $cmd_status -ne 0 ]; then
|
|
108
|
+
if [ $cmd_status -eq 124 ]; then
|
|
109
|
+
echo "rosett-ai: #{command} timed out after #{timeout}s. Run manually or set RAI_SKIP_HOOKS=1." >&2
|
|
110
|
+
#{' exit 1' if blocking}
|
|
111
|
+
else
|
|
112
|
+
#{exit_line}
|
|
113
|
+
fi
|
|
114
|
+
fi
|
|
115
|
+
SH
|
|
116
|
+
end
|
|
117
|
+
private_class_method :command_block
|
|
118
|
+
|
|
119
|
+
def self.exit_line
|
|
120
|
+
'exit 0'
|
|
121
|
+
end
|
|
122
|
+
private_class_method :exit_line
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
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 'pathname'
|
|
8
|
+
|
|
9
|
+
module RosettAi
|
|
10
|
+
module Gitlab
|
|
11
|
+
module Validators
|
|
12
|
+
# Validates GitLab CI YAML files for syntax correctness.
|
|
13
|
+
#
|
|
14
|
+
# Uses Psych.parse (AST-only parsing) rather than YAML.safe_load so that
|
|
15
|
+
# GitLab CI custom tags like !reference are accepted without needing
|
|
16
|
+
# explicit constructors. This catches genuine YAML syntax errors
|
|
17
|
+
# (e.g. double-quoted strings misinterpreted as YAML scalars) while
|
|
18
|
+
# remaining compatible with GitLab-specific extensions.
|
|
19
|
+
class SupplementaryGitlabCiYamlValidator
|
|
20
|
+
CI_ROOT_FILE = '.gitlab-ci.yml'
|
|
21
|
+
CI_FILES_DIR = '.gitlab-ci-files'
|
|
22
|
+
|
|
23
|
+
attr_reader :results
|
|
24
|
+
|
|
25
|
+
def initialize(project_dir: Dir.pwd)
|
|
26
|
+
@project_dir = Pathname.new(project_dir)
|
|
27
|
+
@results = { valid: [], invalid: [] }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def validate
|
|
31
|
+
@results = { valid: [], invalid: [] }
|
|
32
|
+
|
|
33
|
+
files = ci_files
|
|
34
|
+
return @results if files.empty?
|
|
35
|
+
|
|
36
|
+
files.each { |file| validate_file(file) }
|
|
37
|
+
@results
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def valid?
|
|
41
|
+
validate if @results[:valid].empty? && @results[:invalid].empty?
|
|
42
|
+
@results[:invalid].empty?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def ci_files
|
|
48
|
+
files = []
|
|
49
|
+
|
|
50
|
+
root_ci = @project_dir.join(CI_ROOT_FILE)
|
|
51
|
+
files << root_ci if root_ci.exist?
|
|
52
|
+
|
|
53
|
+
ci_dir = @project_dir.join(CI_FILES_DIR)
|
|
54
|
+
files.concat(ci_dir.glob('**/*.yml').sort) if ci_dir.exist?
|
|
55
|
+
|
|
56
|
+
files
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def validate_file(file)
|
|
60
|
+
content = File.read(file)
|
|
61
|
+
Psych.parse(content)
|
|
62
|
+
@results[:valid] << { file: relative_path(file), status: :ok }
|
|
63
|
+
rescue Psych::SyntaxError => e
|
|
64
|
+
@results[:invalid] << {
|
|
65
|
+
file: relative_path(file),
|
|
66
|
+
status: :error,
|
|
67
|
+
message: e.message,
|
|
68
|
+
line: e.line,
|
|
69
|
+
column: e.column
|
|
70
|
+
}
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def relative_path(file)
|
|
74
|
+
file.relative_path_from(@project_dir).to_s
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
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 I18n
|
|
8
|
+
# Resolves the active locale using priority:
|
|
9
|
+
# explicit flag > config > LANG env > "en".
|
|
10
|
+
#
|
|
11
|
+
# Provides a fallback chain for graceful degradation:
|
|
12
|
+
# nl_BE -> nl -> en, ar_EG -> ar -> en, etc.
|
|
13
|
+
class LocaleResolver
|
|
14
|
+
# Resolves the active locale from explicit flag, env, or default.
|
|
15
|
+
#
|
|
16
|
+
# @param explicit [String, nil] explicit locale override
|
|
17
|
+
# @param env [Hash{String => String}] environment variables (defaults to ENV)
|
|
18
|
+
# @return [Hash{Symbol => Object}] hash with :locale and :chain keys
|
|
19
|
+
def resolve(explicit: nil, env: ENV)
|
|
20
|
+
locale = explicit || parse_lang(env['LANG']) || 'en'
|
|
21
|
+
{ locale: locale, chain: fallback_chain(locale) }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Builds a fallback chain from a locale string (e.g. "nl_BE" -> ["nl_BE", "nl", "en"]).
|
|
25
|
+
#
|
|
26
|
+
# @param locale [String] locale identifier
|
|
27
|
+
# @return [Array<String>] ordered fallback chain ending with "en"
|
|
28
|
+
def fallback_chain(locale)
|
|
29
|
+
parts = locale.split('_')
|
|
30
|
+
chain = [locale]
|
|
31
|
+
chain << parts.first if parts.length > 1
|
|
32
|
+
chain << 'en' unless chain.include?('en')
|
|
33
|
+
chain.uniq
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def parse_lang(lang_value)
|
|
39
|
+
return nil if lang_value.nil? || lang_value.empty?
|
|
40
|
+
|
|
41
|
+
# LANG is typically "en_US.UTF-8" — extract the locale part
|
|
42
|
+
lang_value.split('.').first
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
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 I18n
|
|
8
|
+
# Validates that the runtime environment supports UTF-8 encoding.
|
|
9
|
+
#
|
|
10
|
+
# Called early in CLI startup to fail fast with a clear error
|
|
11
|
+
# rather than producing garbled output.
|
|
12
|
+
class Utf8Checker
|
|
13
|
+
# Exits with an error if the runtime is not UTF-8.
|
|
14
|
+
#
|
|
15
|
+
# @return [void]
|
|
16
|
+
def self.check!
|
|
17
|
+
return if utf8_environment?
|
|
18
|
+
|
|
19
|
+
warn "rai requires a UTF-8 terminal. Current: #{Encoding.default_external}"
|
|
20
|
+
warn 'Set: export LANG=en_US.UTF-8'
|
|
21
|
+
exit 1
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Whether the default external encoding is UTF-8.
|
|
25
|
+
#
|
|
26
|
+
# @return [Boolean]
|
|
27
|
+
def self.utf8_environment?
|
|
28
|
+
Encoding.default_external == Encoding::UTF_8
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
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 Init
|
|
8
|
+
# Writes the default rai config file at ~/.config/rosett-ai/config.yml
|
|
9
|
+
# if it does not already exist.
|
|
10
|
+
class ConfigFileWriter
|
|
11
|
+
def self.write_default(config_dir: nil)
|
|
12
|
+
dir = config_dir || RosettAi.paths.rai_config_dir
|
|
13
|
+
path = dir.join('config.yml')
|
|
14
|
+
|
|
15
|
+
return { action: :unchanged, path: path } if path.exist?
|
|
16
|
+
|
|
17
|
+
FileUtils.mkdir_p(dir)
|
|
18
|
+
content = RosettAi::RaiConfig::DEFAULTS.to_yaml
|
|
19
|
+
File.open(path, 'w', 0o644) { |f| f.write(content) }
|
|
20
|
+
{ action: :created, path: path }
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|
+
|
|
8
|
+
module RosettAi
|
|
9
|
+
module Init
|
|
10
|
+
# Helper module for creating directories during initialization
|
|
11
|
+
module DirectoryBuilder
|
|
12
|
+
GLOBAL_DIRS = ['rules', 'locales', 'conf', 'conf/behaviour', 'conf/schemas', 'doc', 'bin', 'lib',
|
|
13
|
+
'lib/rosett-ai'].freeze
|
|
14
|
+
LOCAL_DIRS = ['rules', 'conf', 'conf/behaviour', 'conf/schemas'].freeze
|
|
15
|
+
|
|
16
|
+
module_function
|
|
17
|
+
|
|
18
|
+
def create_global_structure(base_dir)
|
|
19
|
+
create_directories(base_dir, GLOBAL_DIRS)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def create_local_structure(base_dir)
|
|
23
|
+
create_directories(base_dir, LOCAL_DIRS)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def create_directories(base_dir, subdirs)
|
|
27
|
+
created = []
|
|
28
|
+
([base_dir] + subdirs.map { |subdir| File.join(base_dir, subdir) }).each do |dir|
|
|
29
|
+
next if Dir.exist?(dir)
|
|
30
|
+
|
|
31
|
+
FileUtils.mkdir_p(dir)
|
|
32
|
+
created << dir
|
|
33
|
+
end
|
|
34
|
+
created
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
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
|
+
|
|
8
|
+
module RosettAi
|
|
9
|
+
module Init
|
|
10
|
+
# Helper module for copying files during initialization
|
|
11
|
+
module FileCopier
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
def copy_global_assets(base_dir)
|
|
15
|
+
root = RosettAi.root
|
|
16
|
+
copied = []
|
|
17
|
+
copy_single_file(root.join('settings.json'), File.join(base_dir, 'settings.json'), copied,
|
|
18
|
+
'settings.json')
|
|
19
|
+
copy_executable(root.join('bin', 'rai'), File.join(base_dir, 'bin', 'rai'), copied, 'bin/raictl')
|
|
20
|
+
copy_directory(root.join('lib', 'rosett_ai'), File.join(base_dir, 'lib', 'rosett_ai'), copied, 'lib/rosett_ai/')
|
|
21
|
+
# Conf assets (behaviours, schemas) go to XDG-compliant ~/.config/rosett-ai/conf/
|
|
22
|
+
xdg_conf = RosettAi.paths.rai_conf_dir.to_s
|
|
23
|
+
copy_conf_assets(root, xdg_conf, copied)
|
|
24
|
+
copy_glob_files(root.join('doc'), File.join(base_dir, 'doc'), '*.md', 'doc', copied)
|
|
25
|
+
copied
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def copy_local_assets(base_dir)
|
|
29
|
+
copied = []
|
|
30
|
+
# Local scope copies to .claude/conf/ (not XDG)
|
|
31
|
+
copy_conf_assets(RosettAi.root, File.join(base_dir, 'conf'), copied)
|
|
32
|
+
copied
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def copy_conf_assets(source_root, conf_base, copied)
|
|
36
|
+
FileUtils.mkdir_p(File.join(conf_base, 'behaviour'))
|
|
37
|
+
FileUtils.mkdir_p(File.join(conf_base, 'schemas'))
|
|
38
|
+
# Copy template behaviours (not internal conf/behaviour/ files)
|
|
39
|
+
copy_glob_files(source_root.join('share', 'templates', 'behaviour'),
|
|
40
|
+
File.join(conf_base, 'behaviour'), '*.yml', 'conf/behaviour', copied)
|
|
41
|
+
copy_glob_files(source_root.join('conf', 'schemas'),
|
|
42
|
+
File.join(conf_base, 'schemas'), '*.json', 'conf/schemas', copied)
|
|
43
|
+
copy_if_new(source_root.join('conf', 'adopt_redactions.yml'),
|
|
44
|
+
File.join(conf_base, 'adopt_redactions.yml'),
|
|
45
|
+
copied, 'conf/adopt_redactions.yml')
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def copy_single_file(source, target, copied, name)
|
|
49
|
+
return if File.exist?(target) || !File.exist?(source)
|
|
50
|
+
|
|
51
|
+
FileUtils.cp(source, target)
|
|
52
|
+
copied << name
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def copy_executable(source, target, copied, name)
|
|
56
|
+
return unless File.exist?(source)
|
|
57
|
+
|
|
58
|
+
FileUtils.cp(source, target)
|
|
59
|
+
FileUtils.chmod(0o755, target)
|
|
60
|
+
copied << name
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def copy_directory(source, target, copied, name)
|
|
64
|
+
return unless Dir.exist?(source)
|
|
65
|
+
|
|
66
|
+
FileUtils.cp_r("#{source}/.", target)
|
|
67
|
+
copied << name
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def copy_glob_files(source_dir, target_dir, pattern, prefix, copied)
|
|
71
|
+
return unless Dir.exist?(source_dir)
|
|
72
|
+
|
|
73
|
+
Dir.glob(File.join(source_dir, pattern)).each do |file|
|
|
74
|
+
basename = File.basename(file)
|
|
75
|
+
target = File.join(target_dir, basename)
|
|
76
|
+
|
|
77
|
+
if File.exist?(target)
|
|
78
|
+
warn " skipped #{prefix}/#{basename} (already exists)"
|
|
79
|
+
next
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
FileUtils.cp(file, target)
|
|
83
|
+
copied << "#{prefix}/#{basename}"
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def copy_if_new(source, target, copied, name)
|
|
88
|
+
return unless File.exist?(source) && !File.exist?(target)
|
|
89
|
+
|
|
90
|
+
FileUtils.cp(source, target)
|
|
91
|
+
copied << name
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
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 Init
|
|
8
|
+
# MCP-facing facade for global structure initialization.
|
|
9
|
+
#
|
|
10
|
+
# Delegates to {DirectoryBuilder} and {FileCopier} for the
|
|
11
|
+
# actual setup logic.
|
|
12
|
+
#
|
|
13
|
+
# @author hugo
|
|
14
|
+
# @author claude
|
|
15
|
+
class GlobalInitializer
|
|
16
|
+
def initialize
|
|
17
|
+
@base_dir = File.join(Dir.home, '.claude')
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Sets up the global ~/.claude/ directory structure.
|
|
21
|
+
#
|
|
22
|
+
# @return [Array<String>] created directories
|
|
23
|
+
def setup
|
|
24
|
+
DirectoryBuilder.create_global_structure(@base_dir)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
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 Init
|
|
8
|
+
# MCP-facing facade for project-local structure initialization.
|
|
9
|
+
#
|
|
10
|
+
# Delegates to {DirectoryBuilder} for the actual setup logic.
|
|
11
|
+
#
|
|
12
|
+
# @author hugo
|
|
13
|
+
# @author claude
|
|
14
|
+
class LocalInitializer
|
|
15
|
+
def initialize
|
|
16
|
+
@base_dir = File.join(Dir.pwd, '.claude')
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Sets up the project-local .claude/ directory structure.
|
|
20
|
+
#
|
|
21
|
+
# @return [Array<String>] created directories
|
|
22
|
+
def setup
|
|
23
|
+
DirectoryBuilder.create_local_structure(@base_dir)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|