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,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 RosettAi
|
|
7
|
+
module Retrofit
|
|
8
|
+
module Parsers
|
|
9
|
+
# Retrofit parser for AGENTS.md files.
|
|
10
|
+
#
|
|
11
|
+
# Discovers and parses AGENTS.md in the project root into
|
|
12
|
+
# a structured Rosett-AI YAML representation.
|
|
13
|
+
#
|
|
14
|
+
# @author hugo
|
|
15
|
+
# @author claude
|
|
16
|
+
class AgentsMdParser < BaseParser
|
|
17
|
+
def engine_name
|
|
18
|
+
'agents_md'
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def discover
|
|
22
|
+
project_root = Pathname.new(ENV.fetch('RAI_ORIGINAL_PWD', Dir.pwd))
|
|
23
|
+
agents_md = project_root.join('AGENTS.md')
|
|
24
|
+
|
|
25
|
+
return [agents_md] if agents_md.exist? && safe_path?(agents_md)
|
|
26
|
+
|
|
27
|
+
[]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def parse(path)
|
|
31
|
+
validate_path!(path)
|
|
32
|
+
content = File.read(path)
|
|
33
|
+
|
|
34
|
+
{
|
|
35
|
+
data: { 'agents_md_content' => content },
|
|
36
|
+
unknown_keys: [],
|
|
37
|
+
source_file: path.to_s,
|
|
38
|
+
scope: 'project'
|
|
39
|
+
}
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def validate_path!(path)
|
|
45
|
+
raise RosettAi::RetrofitError, "Path not safe: #{path}" unless safe_path?(path)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-only
|
|
4
|
+
# Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
|
|
5
|
+
|
|
6
|
+
require 'json'
|
|
7
|
+
|
|
8
|
+
module RosettAi
|
|
9
|
+
module Retrofit
|
|
10
|
+
module Parsers
|
|
11
|
+
# Retrofit parser for Claude Code configuration files.
|
|
12
|
+
#
|
|
13
|
+
# Discovers and parses ~/.claude/settings.json and project-level
|
|
14
|
+
# .claude/settings.json into Rosett-AI YAML structure.
|
|
15
|
+
#
|
|
16
|
+
# @author hugo
|
|
17
|
+
# @author claude
|
|
18
|
+
class ClaudeParser < BaseParser
|
|
19
|
+
KNOWN_KEYS = [
|
|
20
|
+
'permissions', 'allowedTools', 'deniedTools', 'trustedTools', 'env', 'hooks', 'mcpServers', 'customApiKeyResponses', 'contextProviders', 'preferences'
|
|
21
|
+
].freeze
|
|
22
|
+
|
|
23
|
+
def engine_name
|
|
24
|
+
'claude'
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def discover
|
|
28
|
+
paths = []
|
|
29
|
+
global = Pathname.new(Dir.home).join('.claude', 'settings.json')
|
|
30
|
+
paths << global if global.exist? && safe_path?(global)
|
|
31
|
+
|
|
32
|
+
local = Pathname.new(ENV.fetch('RAI_ORIGINAL_PWD', Dir.pwd)).join('.claude', 'settings.json')
|
|
33
|
+
paths << local if local.exist? && safe_path?(local) && local != global
|
|
34
|
+
|
|
35
|
+
paths
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def parse(path)
|
|
39
|
+
validate_path!(path)
|
|
40
|
+
content = File.read(path)
|
|
41
|
+
data = JSON.parse(content)
|
|
42
|
+
|
|
43
|
+
unknown_keys = data.keys.reject { |key| KNOWN_KEYS.include?(key) }
|
|
44
|
+
|
|
45
|
+
{
|
|
46
|
+
data: data,
|
|
47
|
+
unknown_keys: unknown_keys.map { |key| sanitize(key) },
|
|
48
|
+
source_file: path.to_s,
|
|
49
|
+
scope: detect_scope(path)
|
|
50
|
+
}
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def validate_path!(path)
|
|
56
|
+
raise RosettAi::RetrofitError, "Path not safe: #{path}" unless safe_path?(path)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def detect_scope(path)
|
|
60
|
+
if path.to_s.start_with?(Dir.home)
|
|
61
|
+
'user'
|
|
62
|
+
else
|
|
63
|
+
'project'
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-only
|
|
4
|
+
# Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
|
|
5
|
+
|
|
6
|
+
require 'json'
|
|
7
|
+
|
|
8
|
+
module RosettAi
|
|
9
|
+
module Retrofit
|
|
10
|
+
module Parsers
|
|
11
|
+
# Retrofit parser for Cursor configuration files.
|
|
12
|
+
#
|
|
13
|
+
# Discovers and parses .cursor/settings.json and .cursorrules
|
|
14
|
+
# into Rosett-AI YAML structure.
|
|
15
|
+
#
|
|
16
|
+
# @author hugo
|
|
17
|
+
# @author claude
|
|
18
|
+
class CursorParser < BaseParser
|
|
19
|
+
KNOWN_KEYS = [
|
|
20
|
+
'rules', 'model', 'temperature', 'maxTokens', 'contextFiles', 'ignoreFiles'
|
|
21
|
+
].freeze
|
|
22
|
+
|
|
23
|
+
def engine_name
|
|
24
|
+
'cursor'
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def discover
|
|
28
|
+
paths = []
|
|
29
|
+
project_root = Pathname.new(ENV.fetch('RAI_ORIGINAL_PWD', Dir.pwd))
|
|
30
|
+
|
|
31
|
+
settings = project_root.join('.cursor', 'settings.json')
|
|
32
|
+
paths << settings if settings.exist? && safe_path?(settings)
|
|
33
|
+
|
|
34
|
+
rules = project_root.join('.cursorrules')
|
|
35
|
+
paths << rules if rules.exist? && safe_path?(rules)
|
|
36
|
+
|
|
37
|
+
paths
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def parse(path)
|
|
41
|
+
validate_path!(path)
|
|
42
|
+
|
|
43
|
+
if path.extname == '.json'
|
|
44
|
+
parse_json(path)
|
|
45
|
+
else
|
|
46
|
+
parse_rules(path)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def validate_path!(path)
|
|
53
|
+
raise RosettAi::RetrofitError, "Path not safe: #{path}" unless safe_path?(path)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def parse_json(path)
|
|
57
|
+
content = File.read(path)
|
|
58
|
+
data = JSON.parse(content)
|
|
59
|
+
unknown_keys = data.keys.reject { |key| KNOWN_KEYS.include?(key) }
|
|
60
|
+
|
|
61
|
+
{
|
|
62
|
+
data: data,
|
|
63
|
+
unknown_keys: unknown_keys.map { |key| sanitize(key) },
|
|
64
|
+
source_file: path.to_s,
|
|
65
|
+
scope: 'project'
|
|
66
|
+
}
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def parse_rules(path)
|
|
70
|
+
content = File.read(path)
|
|
71
|
+
|
|
72
|
+
{
|
|
73
|
+
data: { 'rules' => content },
|
|
74
|
+
unknown_keys: [],
|
|
75
|
+
source_file: path.to_s,
|
|
76
|
+
scope: 'project'
|
|
77
|
+
}
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
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 Retrofit
|
|
8
|
+
# Validates that retrofit output round-trips through compilation.
|
|
9
|
+
#
|
|
10
|
+
# Compares the original native config with the result of compiling
|
|
11
|
+
# the retrofitted YAML to verify semantic equivalence.
|
|
12
|
+
#
|
|
13
|
+
# @author hugo
|
|
14
|
+
# @author claude
|
|
15
|
+
class RoundTripValidator
|
|
16
|
+
# @param original [Hash] parsed original native config
|
|
17
|
+
# @param compiled [Hash] parsed output of compiling retrofitted YAML
|
|
18
|
+
# @return [Hash] validation result with :valid and :differences keys
|
|
19
|
+
def validate(original:, compiled:)
|
|
20
|
+
differences = find_differences(original, compiled)
|
|
21
|
+
|
|
22
|
+
{
|
|
23
|
+
valid: differences.empty?,
|
|
24
|
+
differences: differences
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def find_differences(original, compiled, path = '')
|
|
31
|
+
diffs = []
|
|
32
|
+
compare_original_keys(original, compiled, path, diffs)
|
|
33
|
+
find_added_keys(original, compiled, path, diffs)
|
|
34
|
+
diffs
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def compare_original_keys(original, compiled, path, diffs)
|
|
38
|
+
original.each do |key, orig_val|
|
|
39
|
+
full_path = build_path(path, key)
|
|
40
|
+
comp_val = compiled[key]
|
|
41
|
+
|
|
42
|
+
if comp_val.nil?
|
|
43
|
+
diffs << { path: full_path, type: 'missing', original: orig_val }
|
|
44
|
+
elsif orig_val.is_a?(Hash) && comp_val.is_a?(Hash)
|
|
45
|
+
diffs.concat(find_differences(orig_val, comp_val, full_path))
|
|
46
|
+
elsif orig_val != comp_val
|
|
47
|
+
diffs << { path: full_path, type: 'changed', original: orig_val, compiled: comp_val }
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def find_added_keys(original, compiled, path, diffs)
|
|
53
|
+
compiled.each_key do |key|
|
|
54
|
+
next if original.key?(key)
|
|
55
|
+
|
|
56
|
+
diffs << { path: build_path(path, key), type: 'added', compiled: compiled[key] }
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def build_path(path, key)
|
|
61
|
+
path.empty? ? key.to_s : "#{path}.#{key}"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-only
|
|
4
|
+
# Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
|
|
5
|
+
|
|
6
|
+
module RosettAi
|
|
7
|
+
module Retrofit
|
|
8
|
+
# MCP-facing facade for retrofit operations.
|
|
9
|
+
#
|
|
10
|
+
# Delegates to {Engine} for the actual retrofit logic, providing
|
|
11
|
+
# the simplified interface expected by {Mcp::Tools::RetrofitTool}.
|
|
12
|
+
#
|
|
13
|
+
# @author hugo
|
|
14
|
+
# @author claude
|
|
15
|
+
class Scanner
|
|
16
|
+
def initialize(output_dir: Pathname.new(Dir.pwd))
|
|
17
|
+
@output_dir = output_dir
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @return [Array<String>] available parser/engine names
|
|
21
|
+
def available_parsers
|
|
22
|
+
Engine.available_engines
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Scans for native AI tool configs without writing files.
|
|
26
|
+
#
|
|
27
|
+
# @return [Array<Hash>] scan findings
|
|
28
|
+
def scan
|
|
29
|
+
engine = Engine.new(output_dir: @output_dir)
|
|
30
|
+
engine.simulate[:generated]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Converts native configs to Rosett-AI YAML.
|
|
34
|
+
#
|
|
35
|
+
# @param engine [String, nil] specific engine name
|
|
36
|
+
# @param options [Hash] :dry_run for simulation mode
|
|
37
|
+
# @return [Hash] with :converted and :files keys
|
|
38
|
+
def convert(engine: nil, options: {})
|
|
39
|
+
dry_run = options.fetch(:dry_run, true)
|
|
40
|
+
engines = engine ? [engine] : nil
|
|
41
|
+
retrofitter = Engine.new(output_dir: @output_dir, engines: engines)
|
|
42
|
+
result = dry_run ? retrofitter.simulate : retrofitter.retrofit
|
|
43
|
+
{ converted: result[:generated].size, files: result[:generated] }
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
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 Retrofit
|
|
8
|
+
# Detects sensitive values in parsed configuration data and replaces
|
|
9
|
+
# them with ${secret:env:NAME} references.
|
|
10
|
+
#
|
|
11
|
+
# Recognises API keys, tokens, passwords, and other secrets by
|
|
12
|
+
# key name patterns and value heuristics (length, entropy, prefixes).
|
|
13
|
+
#
|
|
14
|
+
# @author hugo
|
|
15
|
+
# @author claude
|
|
16
|
+
class SecretDetector
|
|
17
|
+
SENSITIVE_KEY_PATTERNS = [
|
|
18
|
+
/api[_-]?key/i,
|
|
19
|
+
/secret/i,
|
|
20
|
+
/token/i,
|
|
21
|
+
/password/i,
|
|
22
|
+
/credential/i,
|
|
23
|
+
/auth/i,
|
|
24
|
+
/private[_-]?key/i
|
|
25
|
+
].freeze
|
|
26
|
+
|
|
27
|
+
SECRET_VALUE_PREFIXES = ['sk-', 'pk-', 'ghp_', 'gho_', 'ghs_', 'github_pat_', 'xoxb-', 'xoxp-'].freeze
|
|
28
|
+
|
|
29
|
+
MIN_SECRET_LENGTH = 20
|
|
30
|
+
|
|
31
|
+
# @param warnings [Array<String>] mutable array for warning messages
|
|
32
|
+
def initialize(warnings: [])
|
|
33
|
+
@warnings = warnings
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Scans a hash and replaces sensitive values with secret references.
|
|
37
|
+
#
|
|
38
|
+
# @param data [Hash] parsed configuration data
|
|
39
|
+
# @param prefix [String] key path prefix for env var naming
|
|
40
|
+
# @return [Hash] data with secrets replaced
|
|
41
|
+
def redact(data, prefix: '')
|
|
42
|
+
data.each_with_object({}) do |(key, value), result|
|
|
43
|
+
full_key = prefix.empty? ? key.to_s : "#{prefix}_#{key}"
|
|
44
|
+
result[key] = redact_value(key.to_s, value, full_key)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def redact_value(key, value, full_key)
|
|
51
|
+
case value
|
|
52
|
+
when Hash
|
|
53
|
+
redact(value, prefix: full_key)
|
|
54
|
+
when Array
|
|
55
|
+
value.map.with_index { |item, idx| redact_value("#{key}_#{idx}", item, "#{full_key}_#{idx}") }
|
|
56
|
+
when String
|
|
57
|
+
maybe_redact_string(key, value, full_key)
|
|
58
|
+
else
|
|
59
|
+
value
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def maybe_redact_string(key, value, full_key)
|
|
64
|
+
return value unless sensitive?(key, value)
|
|
65
|
+
|
|
66
|
+
env_name = full_key.upcase.gsub(/[^A-Z0-9]/, '_').squeeze('_')
|
|
67
|
+
@warnings << "Sensitive value detected for '#{key}' — replaced with secret reference. " \
|
|
68
|
+
"Set #{env_name} in your environment."
|
|
69
|
+
"${secret:env:#{env_name}}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def sensitive?(key, value)
|
|
73
|
+
return true if SENSITIVE_KEY_PATTERNS.any? { |pattern| key.match?(pattern) } && value.length >= 8
|
|
74
|
+
return true if SECRET_VALUE_PREFIXES.any? { |prefix| value.start_with?(prefix) }
|
|
75
|
+
return true if value.length >= MIN_SECRET_LENGTH && high_entropy?(value)
|
|
76
|
+
|
|
77
|
+
false
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def high_entropy?(value)
|
|
81
|
+
chars = value.chars
|
|
82
|
+
unique_ratio = chars.uniq.size.to_f / chars.size
|
|
83
|
+
unique_ratio > 0.6 && value.match?(/[a-zA-Z]/) && value.match?(/[0-9]/)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
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
|
+
# Resolves secrets through an ordered fallback chain.
|
|
8
|
+
# Resolution order: ENV variable > secrets file (0600).
|
|
9
|
+
# System keyring support planned for GUI phase (P3).
|
|
10
|
+
module SecretsResolver
|
|
11
|
+
SAFE_PERMISSIONS = 0o600
|
|
12
|
+
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
# Resolves a secret through the fallback chain (ENV > secrets file).
|
|
16
|
+
#
|
|
17
|
+
# @param env_key [String] the environment variable name to look up
|
|
18
|
+
# @return [String] the resolved secret value
|
|
19
|
+
# @raise [RosettAi::Error] if the key is not found in any source
|
|
20
|
+
def resolve(env_key)
|
|
21
|
+
from_env(env_key) || from_secrets_file(env_key) || raise_missing(env_key)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Reads a secret from the environment.
|
|
25
|
+
#
|
|
26
|
+
# @param env_key [String] the environment variable name
|
|
27
|
+
# @return [String, nil] the value, or nil if unset or empty
|
|
28
|
+
def from_env(env_key)
|
|
29
|
+
value = ENV.fetch(env_key, nil)
|
|
30
|
+
value unless value.nil? || value.empty?
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Reads a secret from the secrets YAML file.
|
|
34
|
+
#
|
|
35
|
+
# @param env_key [String] the key to look up in the secrets file
|
|
36
|
+
# @return [String, nil] the value, or nil if the file is missing or key absent
|
|
37
|
+
def from_secrets_file(env_key)
|
|
38
|
+
secrets_path = RosettAi.paths.secrets_file.to_s
|
|
39
|
+
return nil unless File.exist?(secrets_path)
|
|
40
|
+
|
|
41
|
+
validate_permissions!(secrets_path)
|
|
42
|
+
data = RosettAi::YamlLoader.load_file(secrets_path)
|
|
43
|
+
return nil unless data.is_a?(Hash)
|
|
44
|
+
|
|
45
|
+
data[env_key]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Validates that the file has strict 0600 permissions.
|
|
49
|
+
#
|
|
50
|
+
# @param path [String] absolute path to the file
|
|
51
|
+
# @return [void]
|
|
52
|
+
# @raise [RosettAi::Error] if the file permissions are not 0600
|
|
53
|
+
def validate_permissions!(path)
|
|
54
|
+
mode = File.stat(path).mode & 0o777
|
|
55
|
+
return if mode == SAFE_PERMISSIONS
|
|
56
|
+
|
|
57
|
+
raise RosettAi::Error,
|
|
58
|
+
"Secrets file #{path} has insecure permissions #{format('%04o', mode)}. " \
|
|
59
|
+
"Expected 0600. Fix with: chmod 600 #{path}"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def raise_missing(env_key)
|
|
63
|
+
secrets_path = RosettAi.paths.secrets_file
|
|
64
|
+
raise RosettAi::Error, <<~MSG.chomp
|
|
65
|
+
#{env_key} not found. Set it via one of:
|
|
66
|
+
1. export #{env_key}=<value>
|
|
67
|
+
2. Add '#{env_key}: <value>' to #{secrets_path} (chmod 600)
|
|
68
|
+
MSG
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
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 SmartFeedback
|
|
8
|
+
# Provides "Did you mean?" suggestions using Levenshtein distance.
|
|
9
|
+
#
|
|
10
|
+
# Matches mistyped commands, subcommands, and flags against known
|
|
11
|
+
# candidates. Returns up to MAX_SUGGESTIONS closest matches within
|
|
12
|
+
# the distance threshold.
|
|
13
|
+
#
|
|
14
|
+
# @author hugo
|
|
15
|
+
# @author claude
|
|
16
|
+
class Suggester
|
|
17
|
+
MAX_SUGGESTIONS = 3
|
|
18
|
+
COMMAND_THRESHOLD = 2
|
|
19
|
+
FLAG_THRESHOLD = 3
|
|
20
|
+
|
|
21
|
+
# Find suggestion matches for a given input.
|
|
22
|
+
#
|
|
23
|
+
# @param input [String] the mistyped word
|
|
24
|
+
# @param candidates [Array<String>] valid options
|
|
25
|
+
# @param threshold [Integer] maximum edit distance
|
|
26
|
+
# @return [Array<String>] sorted suggestions (closest first)
|
|
27
|
+
def suggest(input, candidates, threshold: COMMAND_THRESHOLD)
|
|
28
|
+
input = input.to_s.downcase
|
|
29
|
+
matches = candidates.filter_map do |candidate|
|
|
30
|
+
candidate = candidate.to_s
|
|
31
|
+
dist = levenshtein(input, candidate.downcase)
|
|
32
|
+
[candidate, dist] if dist <= threshold
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
matches.sort_by(&:last)
|
|
36
|
+
.first(MAX_SUGGESTIONS)
|
|
37
|
+
.map(&:first)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Format suggestion message.
|
|
41
|
+
#
|
|
42
|
+
# @param type [String] 'command', 'subcommand', or 'option'
|
|
43
|
+
# @param input [String] the mistyped word
|
|
44
|
+
# @param suggestions [Array<String>] suggested alternatives
|
|
45
|
+
# @return [String] formatted message
|
|
46
|
+
def format_message(type, input, suggestions)
|
|
47
|
+
if suggestions.empty?
|
|
48
|
+
"Unknown #{type} '#{input}'. Run `raictl help` for available #{type}s."
|
|
49
|
+
elsif suggestions.size == 1
|
|
50
|
+
"Unknown #{type} '#{input}'. Did you mean `#{suggestions.first}`?"
|
|
51
|
+
else
|
|
52
|
+
list = suggestions.map { |s| "`#{s}`" }.join(', ')
|
|
53
|
+
"Unknown #{type} '#{input}'. Did you mean one of: #{list}?"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
# Compute Levenshtein edit distance between two strings.
|
|
60
|
+
#
|
|
61
|
+
# @param str_a [String] first string
|
|
62
|
+
# @param str_b [String] second string
|
|
63
|
+
# @return [Integer] edit distance
|
|
64
|
+
def levenshtein(str_a, str_b) # rubocop:disable Metrics/AbcSize -- Wagner-Fischer algorithm is inherently branchy
|
|
65
|
+
return str_b.length if str_a.empty?
|
|
66
|
+
return str_a.length if str_b.empty?
|
|
67
|
+
|
|
68
|
+
matrix = Array.new(str_a.length + 1) { |i| i }
|
|
69
|
+
(1..str_b.length).each do |j|
|
|
70
|
+
prev = matrix[0]
|
|
71
|
+
matrix[0] = j
|
|
72
|
+
(1..str_a.length).each do |i|
|
|
73
|
+
temp = matrix[i]
|
|
74
|
+
cost = str_a[i - 1] == str_b[j - 1] ? 0 : 1
|
|
75
|
+
matrix[i] = [matrix[i] + 1, matrix[i - 1] + 1, prev + cost].min
|
|
76
|
+
prev = temp
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
matrix[str_a.length]
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
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 SmartFeedback
|
|
8
|
+
# Middleware that intercepts Thor::UndefinedCommandError and
|
|
9
|
+
# Thor::UnknownArgumentError to provide "Did you mean?" suggestions.
|
|
10
|
+
#
|
|
11
|
+
# Integrates with Thor's error handling to enhance error messages
|
|
12
|
+
# without replacing the standard error flow.
|
|
13
|
+
#
|
|
14
|
+
# @author hugo
|
|
15
|
+
# @author claude
|
|
16
|
+
module ThorMiddleware
|
|
17
|
+
# Enhance a Thor CLI class with smart feedback.
|
|
18
|
+
#
|
|
19
|
+
# @param cli_class [Class] the Thor CLI class to enhance
|
|
20
|
+
def self.install(cli_class)
|
|
21
|
+
cli_class.singleton_class.prepend(CommandDispatch)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Prepended module that wraps Thor command dispatch with smart feedback.
|
|
25
|
+
module CommandDispatch
|
|
26
|
+
def dispatch(meth, given_args, given_opts, config)
|
|
27
|
+
super
|
|
28
|
+
rescue ::Thor::UndefinedCommandError => e
|
|
29
|
+
handle_unknown_command(e, given_args)
|
|
30
|
+
rescue ::Thor::UnknownArgumentError => e
|
|
31
|
+
handle_unknown_argument(e)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def handle_unknown_command(error, given_args)
|
|
37
|
+
command_name = given_args&.first.to_s
|
|
38
|
+
candidates = all_commands.keys.reject { |k| k == 'help' }
|
|
39
|
+
suggester = Suggester.new
|
|
40
|
+
suggestions = suggester.suggest(command_name, candidates)
|
|
41
|
+
message = suggester.format_message('command', command_name, suggestions)
|
|
42
|
+
raise ::Thor::Error, message
|
|
43
|
+
rescue ::Thor::Error
|
|
44
|
+
raise
|
|
45
|
+
rescue StandardError
|
|
46
|
+
raise error
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def handle_unknown_argument(error)
|
|
50
|
+
msg = error.message
|
|
51
|
+
flag_match = msg.match(/Unknown switches ['"]([^'"]+)['"]/) ||
|
|
52
|
+
msg.match(/could not be found.*['"]([^'"]+)['"]/)
|
|
53
|
+
|
|
54
|
+
raise error unless flag_match
|
|
55
|
+
|
|
56
|
+
flag = flag_match[1]
|
|
57
|
+
suggester = Suggester.new
|
|
58
|
+
suggestions = suggester.suggest(
|
|
59
|
+
flag.delete_prefix('--'),
|
|
60
|
+
available_flags,
|
|
61
|
+
threshold: Suggester::FLAG_THRESHOLD
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
raise error if suggestions.empty?
|
|
65
|
+
|
|
66
|
+
message = suggester.format_message('option', flag, suggestions.map { |s| "--#{s}" })
|
|
67
|
+
raise ::Thor::Error, message
|
|
68
|
+
rescue ::Thor::Error
|
|
69
|
+
raise
|
|
70
|
+
rescue StandardError
|
|
71
|
+
raise error
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def available_flags
|
|
75
|
+
all_commands.each_value.flat_map do |cmd|
|
|
76
|
+
next [] unless cmd.respond_to?(:options) && cmd.options
|
|
77
|
+
|
|
78
|
+
cmd.options.keys.map(&:to_s)
|
|
79
|
+
end.uniq
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|