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,74 @@
|
|
|
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 Workflow
|
|
8
|
+
module Steps
|
|
9
|
+
# Executes an internal rai CLI subcommand.
|
|
10
|
+
#
|
|
11
|
+
# Resolves the rai binary via PATH lookup, falling back to the
|
|
12
|
+
# development tree's bin/raictl.
|
|
13
|
+
#
|
|
14
|
+
# @author hugo
|
|
15
|
+
# @author claude
|
|
16
|
+
class RaiStep
|
|
17
|
+
# @param definition [Hash] step definition from workflow YAML
|
|
18
|
+
def initialize(definition)
|
|
19
|
+
@definition = definition
|
|
20
|
+
@command = definition.fetch('command')
|
|
21
|
+
validate!
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @return [String] step description for dry-run
|
|
25
|
+
def describe
|
|
26
|
+
"[raictl] #{@definition['name']} — #{@command}"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Executes the rai subcommand.
|
|
30
|
+
#
|
|
31
|
+
# @return [Hash] execution result with :status and :message
|
|
32
|
+
def execute
|
|
33
|
+
argv = @command.split
|
|
34
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
35
|
+
success = system(resolve_rai_binary, *argv)
|
|
36
|
+
elapsed = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000).round(1)
|
|
37
|
+
|
|
38
|
+
if success
|
|
39
|
+
{ status: 'pass', message: "Completed: raictl #{@command}", duration_ms: elapsed }
|
|
40
|
+
else
|
|
41
|
+
{ status: 'fail', message: "Failed: raictl #{@command} (exit #{$CHILD_STATUS&.exitstatus})",
|
|
42
|
+
duration_ms: elapsed }
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
# Resolves the rai binary path.
|
|
49
|
+
#
|
|
50
|
+
# Prefers a system-installed binary found via PATH, falls back
|
|
51
|
+
# to the development tree's bin/raictl.
|
|
52
|
+
#
|
|
53
|
+
# @return [String] absolute path to the rai binary
|
|
54
|
+
def resolve_rai_binary
|
|
55
|
+
@resolve_rai_binary ||= begin
|
|
56
|
+
which_result = `which rai 2>/dev/null`.strip
|
|
57
|
+
if $CHILD_STATUS&.success? && !which_result.empty?
|
|
58
|
+
which_result
|
|
59
|
+
else
|
|
60
|
+
RosettAi.root.join('bin', 'rai').to_s
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def validate!
|
|
66
|
+
return if @command.is_a?(String)
|
|
67
|
+
|
|
68
|
+
raise RosettAi::WorkflowError,
|
|
69
|
+
"Rai step '#{@definition['name']}' command must be a string"
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
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 Workflow
|
|
8
|
+
module Steps
|
|
9
|
+
# Executes a shell command in array-form (no string interpolation).
|
|
10
|
+
#
|
|
11
|
+
# @author hugo
|
|
12
|
+
# @author claude
|
|
13
|
+
class ShellStep
|
|
14
|
+
# @param definition [Hash] step definition from workflow YAML
|
|
15
|
+
def initialize(definition)
|
|
16
|
+
@definition = definition
|
|
17
|
+
@command = definition.fetch('command')
|
|
18
|
+
validate!
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @return [String] step description for dry-run
|
|
22
|
+
def describe
|
|
23
|
+
"[shell] #{@definition['name']} — #{@command.join(' ')}"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Executes the shell command.
|
|
27
|
+
#
|
|
28
|
+
# @return [Hash] execution result with :status and :message
|
|
29
|
+
def execute
|
|
30
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
31
|
+
success = system(*@command)
|
|
32
|
+
elapsed = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000).round(1)
|
|
33
|
+
|
|
34
|
+
if success
|
|
35
|
+
{ status: 'pass', message: "Completed: #{@command.join(' ')}", duration_ms: elapsed }
|
|
36
|
+
else
|
|
37
|
+
{ status: 'fail', message: "Failed: #{@command.join(' ')} (exit #{$CHILD_STATUS&.exitstatus})",
|
|
38
|
+
duration_ms: elapsed }
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def validate!
|
|
45
|
+
return if @command.is_a?(Array)
|
|
46
|
+
|
|
47
|
+
raise RosettAi::WorkflowError,
|
|
48
|
+
"Shell step '#{@definition['name']}' command must be an array"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
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
|
+
require 'yaml'
|
|
7
|
+
|
|
8
|
+
module RosettAi
|
|
9
|
+
# Centralized YAML loading with bounds checking.
|
|
10
|
+
#
|
|
11
|
+
# Enforces file-size, nesting-depth, and key-count limits to prevent
|
|
12
|
+
# denial-of-service via maliciously crafted YAML payloads.
|
|
13
|
+
module YamlLoader
|
|
14
|
+
MAX_FILE_SIZE = 1_048_576 # 1 MB
|
|
15
|
+
MAX_DEPTH = 10
|
|
16
|
+
MAX_KEY_COUNT = 1000
|
|
17
|
+
|
|
18
|
+
# Loads and validates a YAML file with bounds checking.
|
|
19
|
+
#
|
|
20
|
+
# Enforces file size, nesting depth, and key count limits before
|
|
21
|
+
# returning the parsed data.
|
|
22
|
+
#
|
|
23
|
+
# @param path [String] absolute path to the YAML file
|
|
24
|
+
# @param permitted_classes [Array<Class>] classes allowed during safe loading
|
|
25
|
+
# @return [Hash] parsed YAML data
|
|
26
|
+
# @raise [RosettAi::ValidationError] if file size, nesting depth, or key count
|
|
27
|
+
# exceeds the configured limits
|
|
28
|
+
def self.load_file(path, permitted_classes: [Date, Time])
|
|
29
|
+
check_file_size!(path)
|
|
30
|
+
data = YAML.safe_load_file(path, permitted_classes: permitted_classes)
|
|
31
|
+
check_depth!(data, path)
|
|
32
|
+
check_key_count!(data, path)
|
|
33
|
+
data
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.check_file_size!(path)
|
|
37
|
+
size = File.size(path)
|
|
38
|
+
return if size <= MAX_FILE_SIZE
|
|
39
|
+
|
|
40
|
+
raise RosettAi::ValidationError,
|
|
41
|
+
"YAML file exceeds #{MAX_FILE_SIZE} byte limit (#{size} bytes): #{path}"
|
|
42
|
+
end
|
|
43
|
+
private_class_method :check_file_size!
|
|
44
|
+
|
|
45
|
+
def self.check_depth!(data, path, current = 0)
|
|
46
|
+
raise RosettAi::ValidationError, "YAML nesting exceeds #{MAX_DEPTH} levels: #{path}" if current > MAX_DEPTH
|
|
47
|
+
|
|
48
|
+
case data
|
|
49
|
+
when Hash
|
|
50
|
+
data.each_value { |v| check_depth!(v, path, current + 1) }
|
|
51
|
+
when Array
|
|
52
|
+
data.each { |v| check_depth!(v, path, current + 1) }
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
private_class_method :check_depth!
|
|
56
|
+
|
|
57
|
+
def self.check_key_count!(data, path)
|
|
58
|
+
count = count_keys(data)
|
|
59
|
+
return if count <= MAX_KEY_COUNT
|
|
60
|
+
|
|
61
|
+
raise RosettAi::ValidationError,
|
|
62
|
+
"YAML key count exceeds #{MAX_KEY_COUNT} (#{count} keys): #{path}"
|
|
63
|
+
end
|
|
64
|
+
private_class_method :check_key_count!
|
|
65
|
+
|
|
66
|
+
def self.count_keys(data)
|
|
67
|
+
case data
|
|
68
|
+
when Hash
|
|
69
|
+
data.size + data.each_value.sum { |v| count_keys(v) }
|
|
70
|
+
when Array
|
|
71
|
+
data.sum { |v| count_keys(v) }
|
|
72
|
+
else
|
|
73
|
+
0
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
private_class_method :count_keys
|
|
77
|
+
end
|
|
78
|
+
end
|
data/lib/rosett_ai.rb
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
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 'zeitwerk'
|
|
7
|
+
require 'json'
|
|
8
|
+
require 'logger'
|
|
9
|
+
require 'pathname'
|
|
10
|
+
require 'i18n'
|
|
11
|
+
|
|
12
|
+
# NeatNerds Code Companion — engine-agnostic configuration management
|
|
13
|
+
# for AI-assisted development workflows.
|
|
14
|
+
#
|
|
15
|
+
# Author rules once in YAML, compile to any AI tool: Claude, Cursor, AGENTS.md,
|
|
16
|
+
# and any engine with a pluggable backend.
|
|
17
|
+
#
|
|
18
|
+
# @see https://neatnerds.be/rosett-ai
|
|
19
|
+
module RosettAi
|
|
20
|
+
# @!group Error hierarchy
|
|
21
|
+
|
|
22
|
+
# Base error for all rosett-ai operations.
|
|
23
|
+
class Error < StandardError; end
|
|
24
|
+
# Raised when configuration files are invalid or unreadable.
|
|
25
|
+
class ConfigurationError < Error; end
|
|
26
|
+
# Raised when schema or bounds validation fails.
|
|
27
|
+
class ValidationError < Error; end
|
|
28
|
+
# Raised when behaviour file processing fails.
|
|
29
|
+
class BehaviourError < Error; end
|
|
30
|
+
# Raised when compilation encounters an unrecoverable error.
|
|
31
|
+
class CompileError < Error; end
|
|
32
|
+
# Raised when rule adoption analysis fails.
|
|
33
|
+
class AdoptError < Error; end
|
|
34
|
+
# Raised when workspace initialization fails.
|
|
35
|
+
class InitError < Error; end
|
|
36
|
+
# Raised when backup creation or restoration fails.
|
|
37
|
+
class BackupError < Error; end
|
|
38
|
+
# Raised when package build fails.
|
|
39
|
+
class BuildError < Error; end
|
|
40
|
+
# Raised when design document operations fail.
|
|
41
|
+
class DesignError < Error; end
|
|
42
|
+
# Raised when documentation generation fails.
|
|
43
|
+
class DocumentationError < Error; end
|
|
44
|
+
# Raised when release management operations fail.
|
|
45
|
+
class ReleaseError < Error; end
|
|
46
|
+
# Raised when tooling validation fails.
|
|
47
|
+
class ToolingError < Error; end
|
|
48
|
+
# Raised when license activation or validation fails.
|
|
49
|
+
class LicenseError < Error; end
|
|
50
|
+
# Raised when content pack operations fail.
|
|
51
|
+
class ContentError < Error; end
|
|
52
|
+
# Raised when behaviour composition fails.
|
|
53
|
+
class CompositionError < Error; end
|
|
54
|
+
# Raised when AI provenance operations fail.
|
|
55
|
+
class ProvenanceError < Error; end
|
|
56
|
+
# Raised when AI tool configuration operations fail.
|
|
57
|
+
class AiConfigError < Error; end
|
|
58
|
+
# Raised when AI authorship attribution fails.
|
|
59
|
+
class AuthorshipError < Error; end
|
|
60
|
+
# Raised when policy management operations fail.
|
|
61
|
+
class PolicyError < Error; end
|
|
62
|
+
# Raised when doctor diagnostics encounter an unexpected error.
|
|
63
|
+
class DoctorError < Error; end
|
|
64
|
+
# Raised when an experimental feature is invoked without its flag enabled.
|
|
65
|
+
class FeatureFlagError < Error; end
|
|
66
|
+
# Raised when compliance checks encounter an unrecoverable error.
|
|
67
|
+
class ComplianceError < Error; end
|
|
68
|
+
# Raised when workflow definition or execution fails.
|
|
69
|
+
class WorkflowError < Error; end
|
|
70
|
+
# Raised when project management operations fail.
|
|
71
|
+
class ProjectError < Error; end
|
|
72
|
+
# Raised when reverse compilation (retrofit) fails.
|
|
73
|
+
class RetrofitError < Error; end
|
|
74
|
+
# Raised when git hook operations fail.
|
|
75
|
+
class GitHooksError < Error; end
|
|
76
|
+
# Raised when MCP server or admin operations fail.
|
|
77
|
+
class McpError < Error; end
|
|
78
|
+
|
|
79
|
+
# @!endgroup
|
|
80
|
+
|
|
81
|
+
class << self
|
|
82
|
+
# Project root directory (where the gemspec lives).
|
|
83
|
+
#
|
|
84
|
+
# @return [Pathname]
|
|
85
|
+
def root
|
|
86
|
+
@root ||= Pathname.new(File.expand_path('..', __dir__))
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Zeitwerk autoloader instance. Sets up inflections, ignores
|
|
90
|
+
# deprecated stubs and optional gems, then calls +setup+.
|
|
91
|
+
#
|
|
92
|
+
# @return [Zeitwerk::Loader]
|
|
93
|
+
def loader
|
|
94
|
+
@loader ||= begin
|
|
95
|
+
loader = Zeitwerk::Loader.for_gem
|
|
96
|
+
loader.inflector.inflect('cli' => 'CLI', 'dbus' => 'DBus')
|
|
97
|
+
loader.ignore("#{__dir__}/rubocop")
|
|
98
|
+
loader.ignore("#{__dir__}/scripts")
|
|
99
|
+
unless gem_available?('ruby-dbus')
|
|
100
|
+
# Ignore D-Bus interface/service files that require ruby-dbus,
|
|
101
|
+
# but keep pure-Ruby utilities (e.g. RateLimiter) available.
|
|
102
|
+
['service', 'compositor_detector', 'focus_monitor_interface', 'manager_interface',
|
|
103
|
+
'plugin_manager_interface', 'status_notifier_interface'].each do |f|
|
|
104
|
+
loader.ignore("#{__dir__}/rosett-ai/dbus/#{f}.rb")
|
|
105
|
+
end
|
|
106
|
+
loader.ignore("#{__dir__}/rosett-ai/dbus/focus_adapters/gnome_adapter.rb")
|
|
107
|
+
loader.ignore("#{__dir__}/rosett-ai/dbus/focus_adapters/kwin_adapter.rb")
|
|
108
|
+
loader.ignore("#{__dir__}/rosett-ai/desktop")
|
|
109
|
+
end
|
|
110
|
+
ignore_deprecated_stubs(loader)
|
|
111
|
+
loader.setup
|
|
112
|
+
loader
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Active project context (detects +.rosett-ai/+ marker).
|
|
117
|
+
#
|
|
118
|
+
# @return [RosettAi::ProjectContext]
|
|
119
|
+
def context
|
|
120
|
+
@context ||= ProjectContext.new
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Configuration root directory, scoped to the active project if present.
|
|
124
|
+
#
|
|
125
|
+
# @return [Pathname]
|
|
126
|
+
def conf_root
|
|
127
|
+
context.conf_root
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Global {Configuration} instance (settings.json hierarchy).
|
|
131
|
+
#
|
|
132
|
+
# @return [RosettAi::Configuration]
|
|
133
|
+
def config
|
|
134
|
+
@config ||= Configuration.new
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Global {PathResolver} instance for filesystem path lookups.
|
|
138
|
+
#
|
|
139
|
+
# @return [RosettAi::PathResolver]
|
|
140
|
+
def paths
|
|
141
|
+
@paths ||= PathResolver.new
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Loaded +~/.config/rosett-ai/config.yml+ settings.
|
|
145
|
+
#
|
|
146
|
+
# @return [RosettAi::RaiConfig]
|
|
147
|
+
def rai_config
|
|
148
|
+
@rai_config ||= RaiConfig.load
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Initializes I18n with rosett-ai locale files, resolves the active locale,
|
|
152
|
+
# and configures fallback chains (e.g. nl_BE -> nl -> en).
|
|
153
|
+
#
|
|
154
|
+
# @param locale [String, nil] explicit locale override (e.g. +"fr"+)
|
|
155
|
+
# @return [void]
|
|
156
|
+
def setup_i18n(locale: nil)
|
|
157
|
+
require 'i18n/backend/fallbacks'
|
|
158
|
+
::I18n::Backend::Simple.include(::I18n::Backend::Fallbacks)
|
|
159
|
+
|
|
160
|
+
::I18n.load_path += Dir[root.join('locales', '*.yml')]
|
|
161
|
+
::I18n.default_locale = :en
|
|
162
|
+
::I18n.enforce_available_locales = false
|
|
163
|
+
|
|
164
|
+
resolved_locale = resolve_locale(locale)
|
|
165
|
+
::I18n.locale = resolved_locale.to_sym
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Structured logger with correlation IDs and JSON/text output.
|
|
169
|
+
# Level controlled by +RAI_LOG_LEVEL+, format by +RAI_LOG_FORMAT+.
|
|
170
|
+
#
|
|
171
|
+
# @return [RosettAi::StructuredLogger]
|
|
172
|
+
def logger
|
|
173
|
+
@logger ||= StructuredLogger.new
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Resets all memoized singletons. Intended for test isolation.
|
|
177
|
+
#
|
|
178
|
+
# @return [void]
|
|
179
|
+
def reset_config!
|
|
180
|
+
@config = nil
|
|
181
|
+
@paths = nil
|
|
182
|
+
@rai_config = nil
|
|
183
|
+
@logger = nil
|
|
184
|
+
@context = nil
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
private
|
|
188
|
+
|
|
189
|
+
def resolve_locale(explicit)
|
|
190
|
+
resolver = RosettAi::I18n::LocaleResolver.new
|
|
191
|
+
result = resolver.resolve(explicit: explicit)
|
|
192
|
+
available = ::I18n.available_locales.map(&:to_s)
|
|
193
|
+
locale = result[:chain].find { |loc| available.include?(loc) } || 'en'
|
|
194
|
+
chain = resolver.fallback_chain(locale).map(&:to_sym)
|
|
195
|
+
::I18n.fallbacks = ::I18n::Locale::Fallbacks.new(locale.to_sym => chain)
|
|
196
|
+
locale
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Deprecated delegation stubs reference engine gem constants
|
|
200
|
+
# (RosettAiEngine::Claude::*, RosettAiEngine::Generic::*) that are only available
|
|
201
|
+
# when engine gems are installed. Ignoring them from Zeitwerk prevents
|
|
202
|
+
# NameError on autoload. Will be removed in v1.1.0.
|
|
203
|
+
def ignore_deprecated_stubs(loader)
|
|
204
|
+
['compiler/backends/claude_backend', 'compiler/backends/generic_backend',
|
|
205
|
+
'config/compiler', 'config/key_map', 'config/scope_router'].each do |stub|
|
|
206
|
+
loader.ignore("#{__dir__}/rosett-ai/#{stub}.rb")
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def gem_available?(name)
|
|
211
|
+
Gem::Specification.find_by_name(name)
|
|
212
|
+
true
|
|
213
|
+
rescue Gem::MissingSpecError
|
|
214
|
+
false
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
RosettAi.loader
|
|
220
|
+
RosettAi.setup_i18n
|
|
221
|
+
RosettAi::Plugins::Registry.discover!
|
|
@@ -0,0 +1,54 @@
|
|
|
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 RuboCop
|
|
7
|
+
module Cop
|
|
8
|
+
module RosettAi
|
|
9
|
+
# Flags `system`, `exec`, and `spawn` calls where the first argument
|
|
10
|
+
# is an interpolated string, and backtick commands with interpolation.
|
|
11
|
+
# Both patterns risk shell injection.
|
|
12
|
+
#
|
|
13
|
+
# Use array-form instead: `system('cmd', arg1, arg2)` or
|
|
14
|
+
# `IO.popen([cmd, arg], &:read)`.
|
|
15
|
+
#
|
|
16
|
+
# @example
|
|
17
|
+
# # bad
|
|
18
|
+
# system("rm #{filename}")
|
|
19
|
+
# exec("echo #{user_input}")
|
|
20
|
+
# spawn("process #{data}")
|
|
21
|
+
# `#{cmd} --flag`
|
|
22
|
+
#
|
|
23
|
+
# # good
|
|
24
|
+
# system('rm', filename)
|
|
25
|
+
# system(ENV.fetch('EDITOR', 'vim'), file)
|
|
26
|
+
# IO.popen([cmd, '--flag'], &:read)
|
|
27
|
+
# `ls -la`
|
|
28
|
+
class ShellInterpolation < Base
|
|
29
|
+
MSG = 'Use array-form `system(cmd, arg, ...)` instead of interpolated string to avoid shell injection.'
|
|
30
|
+
BACKTICK_MSG = 'Use `IO.popen([cmd, arg], &:read)` instead of backtick interpolation to avoid shell injection.'
|
|
31
|
+
|
|
32
|
+
SHELL_METHODS = [:system, :exec, :spawn].freeze
|
|
33
|
+
|
|
34
|
+
# system/exec/spawn with a dstr (interpolated string) as first argument
|
|
35
|
+
# @!method shell_with_interpolation?(node)
|
|
36
|
+
def_node_matcher :shell_with_interpolation?, <<~PATTERN
|
|
37
|
+
(send nil? {#{SHELL_METHODS.map { |method| ":#{method}" }.join(' ')}} dstr ...)
|
|
38
|
+
PATTERN
|
|
39
|
+
|
|
40
|
+
def on_send(node)
|
|
41
|
+
return unless shell_with_interpolation?(node)
|
|
42
|
+
|
|
43
|
+
add_offense(node)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def on_xstr(node)
|
|
47
|
+
return unless node.children.any?(&:begin_type?)
|
|
48
|
+
|
|
49
|
+
add_offense(node, message: BACKTICK_MSG)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
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 RuboCop
|
|
7
|
+
module Cop
|
|
8
|
+
module RosettAi
|
|
9
|
+
# Flags `const_get` and `constantize` calls with non-literal arguments.
|
|
10
|
+
# Dynamic constant lookup with user input can lead to arbitrary class
|
|
11
|
+
# instantiation and code execution.
|
|
12
|
+
#
|
|
13
|
+
# Safe patterns use frozen constant registries that map user input to
|
|
14
|
+
# allowed class names.
|
|
15
|
+
#
|
|
16
|
+
# @example
|
|
17
|
+
# # bad
|
|
18
|
+
# Object.const_get(user_input)
|
|
19
|
+
# Kernel.const_get(params[:class])
|
|
20
|
+
# user_input.constantize
|
|
21
|
+
#
|
|
22
|
+
# # good - use a frozen allowlist
|
|
23
|
+
# ALLOWED_CLASSES = { 'foo' => Foo, 'bar' => Bar }.freeze
|
|
24
|
+
# ALLOWED_CLASSES.fetch(user_input)
|
|
25
|
+
#
|
|
26
|
+
# # good - literal constant names
|
|
27
|
+
# Object.const_get(:MyClass)
|
|
28
|
+
# Object.const_get('RosettAi::Parser')
|
|
29
|
+
class UnsafeConstGet < Base
|
|
30
|
+
MSG = 'Avoid `const_get` with non-literal arguments. ' \
|
|
31
|
+
'Use a frozen allowlist instead to prevent arbitrary class instantiation.'
|
|
32
|
+
CONSTANTIZE_MSG = 'Avoid `constantize` on dynamic strings. ' \
|
|
33
|
+
'Use a frozen allowlist instead.'
|
|
34
|
+
|
|
35
|
+
CONST_GET_METHODS = [:const_get].freeze
|
|
36
|
+
CONSTANTIZE_METHODS = [:constantize, :safe_constantize].freeze
|
|
37
|
+
|
|
38
|
+
# const_get with any argument that is not a literal symbol or string
|
|
39
|
+
# @!method const_get_with_dynamic?(node)
|
|
40
|
+
def_node_matcher :const_get_with_dynamic?, <<~PATTERN
|
|
41
|
+
(send _ {:const_get} $!{sym str})
|
|
42
|
+
PATTERN
|
|
43
|
+
|
|
44
|
+
# .constantize or .safe_constantize on any receiver
|
|
45
|
+
# @!method constantize_call?(node)
|
|
46
|
+
def_node_matcher :constantize_call?, <<~PATTERN
|
|
47
|
+
(send _ {:constantize :safe_constantize})
|
|
48
|
+
PATTERN
|
|
49
|
+
|
|
50
|
+
def on_send(node)
|
|
51
|
+
if const_get_with_dynamic?(node)
|
|
52
|
+
add_offense(node)
|
|
53
|
+
elsif constantize_call?(node)
|
|
54
|
+
add_offense(node, message: CONSTANTIZE_MSG)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-only
|
|
4
|
+
# Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
|
|
5
|
+
|
|
6
|
+
module RuboCop
|
|
7
|
+
module Cop
|
|
8
|
+
module RosettAi
|
|
9
|
+
# Flags `send`, `public_send`, and `__send__` calls with non-literal
|
|
10
|
+
# method names. Dynamic method dispatch with user input can lead to
|
|
11
|
+
# arbitrary method execution.
|
|
12
|
+
#
|
|
13
|
+
# Safe patterns validate the method name against an allowlist before
|
|
14
|
+
# calling send.
|
|
15
|
+
#
|
|
16
|
+
# @example
|
|
17
|
+
# # bad
|
|
18
|
+
# object.send(user_input)
|
|
19
|
+
# object.public_send(params[:method])
|
|
20
|
+
# receiver.__send__(dynamic_method)
|
|
21
|
+
#
|
|
22
|
+
# # good - validate against allowlist first
|
|
23
|
+
# ALLOWED_METHODS = %w[foo bar].freeze
|
|
24
|
+
# raise ArgumentError unless ALLOWED_METHODS.include?(method_name)
|
|
25
|
+
# object.send(method_name)
|
|
26
|
+
#
|
|
27
|
+
# # good - literal method names
|
|
28
|
+
# object.send(:known_method)
|
|
29
|
+
# object.public_send('safe_method')
|
|
30
|
+
class UnsafeSend < Base
|
|
31
|
+
MSG = 'Avoid `%<method>s` with non-literal method names. ' \
|
|
32
|
+
'Validate against an allowlist first to prevent arbitrary method execution.'
|
|
33
|
+
|
|
34
|
+
SEND_METHODS = [:send, :public_send, :__send__].freeze
|
|
35
|
+
|
|
36
|
+
# send/public_send/__send__ with any argument that is not a literal symbol or string
|
|
37
|
+
# @!method send_with_dynamic?(node)
|
|
38
|
+
def_node_matcher :send_with_dynamic?, <<~PATTERN
|
|
39
|
+
(send _ ${:send :public_send :__send__} $!{sym str} ...)
|
|
40
|
+
PATTERN
|
|
41
|
+
|
|
42
|
+
def on_send(node)
|
|
43
|
+
send_with_dynamic?(node) do |method_name, _arg|
|
|
44
|
+
add_offense(node, message: format(MSG, method: method_name))
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
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 RuboCop
|
|
7
|
+
module Cop
|
|
8
|
+
module RosettAi
|
|
9
|
+
# Flags calls to `YAML.load` which is unsafe for untrusted input.
|
|
10
|
+
#
|
|
11
|
+
# Use `YAML.safe_load`, `YAML.safe_load_file`, or
|
|
12
|
+
# `RosettAi::YamlLoader.load_file` instead.
|
|
13
|
+
#
|
|
14
|
+
# @example
|
|
15
|
+
# # bad
|
|
16
|
+
# YAML.load(string)
|
|
17
|
+
# YAML.load_file(path)
|
|
18
|
+
#
|
|
19
|
+
# # good
|
|
20
|
+
# YAML.safe_load(string)
|
|
21
|
+
# YAML.safe_load_file(path)
|
|
22
|
+
# RosettAi::YamlLoader.load_file(path)
|
|
23
|
+
class UnsafeYamlLoad < Base
|
|
24
|
+
MSG = 'Use `YAML.safe_load` or `RosettAi::YamlLoader.load_file` instead of `YAML.load`.'
|
|
25
|
+
|
|
26
|
+
# YAML.load(...) or YAML.load_file(...)
|
|
27
|
+
# @!method unsafe_yaml_load?(node)
|
|
28
|
+
def_node_matcher :unsafe_yaml_load?, <<~PATTERN
|
|
29
|
+
(send (const {nil? cbase} :YAML) {:load :load_file} ...)
|
|
30
|
+
PATTERN
|
|
31
|
+
|
|
32
|
+
def on_send(node)
|
|
33
|
+
return unless unsafe_yaml_load?(node)
|
|
34
|
+
|
|
35
|
+
add_offense(node)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
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_relative 'cop/rosett_ai/unsafe_yaml_load'
|
|
7
|
+
require_relative 'cop/rosett_ai/shell_interpolation'
|
|
8
|
+
require_relative 'cop/rosett_ai/unsafe_const_get'
|
|
9
|
+
require_relative 'cop/rosett_ai/unsafe_send'
|