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
data/kitchen.yml
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# SPDX-License-Identifier: GPL-3.0-only
|
|
2
|
+
# SPDX-FileCopyrightText: 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
|
|
3
|
+
#
|
|
4
|
+
# Test Kitchen configuration for Rosett-AI package verification
|
|
5
|
+
#
|
|
6
|
+
# Prerequisites:
|
|
7
|
+
# - Vagrant 2.3.x (MPL-2.0) with vagrant-libvirt plugin
|
|
8
|
+
# - libvirt/QEMU/KVM installed and running
|
|
9
|
+
# - Pre-built .deb in pkg/ (rake build:package)
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
# bundle exec kitchen test debian-12
|
|
13
|
+
# bundle exec kitchen converge debian-12
|
|
14
|
+
# bundle exec kitchen verify debian-12
|
|
15
|
+
# bundle exec kitchen destroy debian-12
|
|
16
|
+
|
|
17
|
+
driver:
|
|
18
|
+
name: vagrant
|
|
19
|
+
provider: libvirt
|
|
20
|
+
|
|
21
|
+
provisioner:
|
|
22
|
+
name: shell
|
|
23
|
+
script: test/integration/provision.sh
|
|
24
|
+
data_path: pkg
|
|
25
|
+
|
|
26
|
+
transport:
|
|
27
|
+
name: ssh
|
|
28
|
+
|
|
29
|
+
verifier:
|
|
30
|
+
name: inspec
|
|
31
|
+
sudo: false
|
|
32
|
+
|
|
33
|
+
platforms:
|
|
34
|
+
- name: debian-12
|
|
35
|
+
driver:
|
|
36
|
+
box: debian/bookworm64
|
|
37
|
+
|
|
38
|
+
suites:
|
|
39
|
+
- name: rai-context
|
|
40
|
+
verifier:
|
|
41
|
+
inspec_tests:
|
|
42
|
+
- test/integration/rai_context
|
|
43
|
+
- name: rai-commands
|
|
44
|
+
verifier:
|
|
45
|
+
inspec_tests:
|
|
46
|
+
- test/integration/rai_commands
|
|
@@ -0,0 +1,77 @@
|
|
|
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 Adopter
|
|
8
|
+
# Resolves the correct executor for an engine by reading its manifest
|
|
9
|
+
# to determine the API type and constructor kwargs.
|
|
10
|
+
#
|
|
11
|
+
# SDK-based engines (api_gem) receive api_key: and base_url:.
|
|
12
|
+
# HTTP-based engines (api_type: http|openai_compatible) receive
|
|
13
|
+
# endpoint: and model: from the manifest defaults.
|
|
14
|
+
# CLI-based engines (api_type: cli) receive binary: from the manifest.
|
|
15
|
+
class ExecutorResolver
|
|
16
|
+
def initialize(engine_name)
|
|
17
|
+
@engine_name = engine_name.to_s
|
|
18
|
+
@manifest = RosettAi::Engines::Registry.manifest(@engine_name)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Build an executor instance with the correct kwargs for the engine.
|
|
22
|
+
#
|
|
23
|
+
# @param api_key [String, nil] API key (SDK-based engines only)
|
|
24
|
+
# @return [Object] executor instance with #analyze method
|
|
25
|
+
def resolve(api_key: nil)
|
|
26
|
+
engine_mod = RosettAi::Engines::Registry.engine_module(@engine_name)
|
|
27
|
+
executor_klass = engine_mod.executor_class
|
|
28
|
+
raise RosettAi::AdoptError, "Engine '#{@engine_name}' does not provide an executor" unless executor_klass
|
|
29
|
+
|
|
30
|
+
executor_klass.new(**build_kwargs(api_key))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def build_kwargs(api_key)
|
|
36
|
+
execution = @manifest['execution'] || {}
|
|
37
|
+
|
|
38
|
+
if execution['api_gem']
|
|
39
|
+
guard_api_key!(api_key)
|
|
40
|
+
sdk_kwargs(api_key)
|
|
41
|
+
elsif execution['api_type'] == 'cli'
|
|
42
|
+
cli_kwargs(execution)
|
|
43
|
+
else
|
|
44
|
+
http_kwargs(execution)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def guard_api_key!(api_key)
|
|
49
|
+
return unless api_key.nil?
|
|
50
|
+
|
|
51
|
+
raise RosettAi::AdoptError,
|
|
52
|
+
"Engine '#{@engine_name}' requires an API key. " \
|
|
53
|
+
'Set ANTHROPIC_API_KEY or use --local for local-only analysis.'
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def sdk_kwargs(api_key)
|
|
57
|
+
{
|
|
58
|
+
api_key: api_key,
|
|
59
|
+
base_url: ENV.fetch('ANTHROPIC_API_BASE_URL', nil)
|
|
60
|
+
}
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def cli_kwargs(execution)
|
|
64
|
+
kwargs = {}
|
|
65
|
+
kwargs[:binary] = execution['cli_binary'] if execution['cli_binary']
|
|
66
|
+
kwargs
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def http_kwargs(execution)
|
|
70
|
+
kwargs = {}
|
|
71
|
+
kwargs[:endpoint] = execution['default_endpoint'] if execution['default_endpoint']
|
|
72
|
+
kwargs[:model] = execution['default_model'] if execution['default_model']
|
|
73
|
+
kwargs
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,154 @@
|
|
|
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 Adopter
|
|
8
|
+
# Performs local structural analysis of compiled rule files
|
|
9
|
+
# without making API calls. Checks for duplicates, missing
|
|
10
|
+
# fields, and other structural issues.
|
|
11
|
+
class LocalAnalysisCollector
|
|
12
|
+
FAIL_SEVERITIES = ['high', 'critical'].freeze
|
|
13
|
+
|
|
14
|
+
def analyze(files)
|
|
15
|
+
@findings = []
|
|
16
|
+
@all_rules = {}
|
|
17
|
+
|
|
18
|
+
collect_rules(files)
|
|
19
|
+
check_duplicate_ids
|
|
20
|
+
check_identical_descriptions
|
|
21
|
+
build_result
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def collect_rules(files)
|
|
27
|
+
files.each do |file|
|
|
28
|
+
basename = File.basename(file)
|
|
29
|
+
content = File.read(file, encoding: 'utf-8')
|
|
30
|
+
rules = extract_rules_from_markdown(content)
|
|
31
|
+
process_rules(rules, basename)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def process_rules(rules, basename)
|
|
36
|
+
rules.each do |rule|
|
|
37
|
+
rule_id = rule[:id]
|
|
38
|
+
@all_rules[rule_id] ||= []
|
|
39
|
+
@all_rules[rule_id] << { file: basename, description: rule[:description] }
|
|
40
|
+
|
|
41
|
+
check_empty_description(rule, basename)
|
|
42
|
+
check_missing_priority(rule, basename)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def check_empty_description(rule, basename)
|
|
47
|
+
return unless rule[:description].to_s.strip.empty?
|
|
48
|
+
|
|
49
|
+
@findings << finding(category: 'other', severity: 'medium', file: basename, rule_id: rule[:id],
|
|
50
|
+
summary: 'Empty rule description',
|
|
51
|
+
explanation: "Rule #{rule[:id]} has an empty or blank description.")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def check_missing_priority(rule, basename)
|
|
55
|
+
return if rule[:has_priority]
|
|
56
|
+
|
|
57
|
+
@findings << finding(category: 'other', severity: 'low', file: basename, rule_id: rule[:id],
|
|
58
|
+
summary: 'No priority set',
|
|
59
|
+
explanation: "Rule #{rule[:id]} does not have a priority specified.")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def check_duplicate_ids
|
|
63
|
+
@all_rules.each do |rule_id, occurrences|
|
|
64
|
+
next unless occurrences.size > 1
|
|
65
|
+
|
|
66
|
+
file_list = occurrences.map { |occ| occ[:file] }.join(', ')
|
|
67
|
+
@findings << finding(category: 'duplicates', severity: 'medium', file: occurrences.first[:file],
|
|
68
|
+
rule_id: rule_id, summary: 'Duplicate rule ID across files',
|
|
69
|
+
explanation: "Rule ID '#{rule_id}' appears in multiple files: #{file_list}.")
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def check_identical_descriptions
|
|
74
|
+
desc_map = build_description_map
|
|
75
|
+
desc_map.each_value do |entries|
|
|
76
|
+
next unless entries.size > 1
|
|
77
|
+
next if entries.map { |e| e[:rule_id] }.uniq.size == 1
|
|
78
|
+
|
|
79
|
+
label = entries.map { |e| "#{e[:rule_id]} (#{e[:file]})" }.join(', ')
|
|
80
|
+
@findings << finding(category: 'duplicates', severity: 'low', file: entries.first[:file],
|
|
81
|
+
rule_id: entries.first[:rule_id], summary: 'Identical rule descriptions',
|
|
82
|
+
explanation: "Multiple rules share the same description: #{label}.")
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def build_description_map
|
|
87
|
+
desc_map = {}
|
|
88
|
+
@all_rules.each do |rule_id, occurrences|
|
|
89
|
+
occurrences.each do |occ|
|
|
90
|
+
desc = occ[:description]&.strip
|
|
91
|
+
next if desc.to_s.strip.empty?
|
|
92
|
+
|
|
93
|
+
desc_map[desc] ||= []
|
|
94
|
+
desc_map[desc] << { rule_id: rule_id, file: occ[:file] }
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
desc_map
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def build_result
|
|
101
|
+
status = if @findings.any? { |finding| FAIL_SEVERITIES.include?(finding['severity']) }
|
|
102
|
+
'fail'
|
|
103
|
+
elsif @findings.any?
|
|
104
|
+
'warn'
|
|
105
|
+
else
|
|
106
|
+
'pass'
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
{
|
|
110
|
+
'findings' => @findings,
|
|
111
|
+
'overall_status' => status,
|
|
112
|
+
'summary' => @findings.empty? ? 'No structural issues detected' : "Found #{@findings.size} structural issue(s)"
|
|
113
|
+
}
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def finding(attrs)
|
|
117
|
+
{
|
|
118
|
+
'category' => attrs[:category], 'severity' => attrs[:severity],
|
|
119
|
+
'file' => attrs[:file], 'rule_id' => attrs[:rule_id],
|
|
120
|
+
'summary' => attrs[:summary], 'explanation' => attrs[:explanation]
|
|
121
|
+
}
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def extract_rules_from_markdown(content)
|
|
125
|
+
rules = extract_heading_rules(content)
|
|
126
|
+
return rules unless rules.empty?
|
|
127
|
+
|
|
128
|
+
extract_bullet_rules(content)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def extract_heading_rules(content)
|
|
132
|
+
rules = []
|
|
133
|
+
content.scan(/^### (\S+) \(priority: (\d+)\)\s*\n\n?(.*?)(?=\n### |\n\z|\z)/m) do |id, _priority, desc|
|
|
134
|
+
rules << { id: id, description: desc.strip, has_priority: true }
|
|
135
|
+
end
|
|
136
|
+
content.scan(/^### (\S+)\s*\n\n?(.*?)(?=\n### |\n\z|\z)/m) do |id, desc|
|
|
137
|
+
next if rules.any? { |rule| rule[:id] == id }
|
|
138
|
+
|
|
139
|
+
rules << { id: id, description: desc.strip, has_priority: false }
|
|
140
|
+
end
|
|
141
|
+
rules
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def extract_bullet_rules(content)
|
|
145
|
+
rules = []
|
|
146
|
+
content.scan(/^- (.+)$/) do |desc,|
|
|
147
|
+
index = rules.size + 1
|
|
148
|
+
rules << { id: format('bullet_%03d', index), description: desc.strip, has_priority: false }
|
|
149
|
+
end
|
|
150
|
+
rules
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-only
|
|
4
|
+
# Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
|
|
5
|
+
|
|
6
|
+
require 'yaml'
|
|
7
|
+
require 'digest'
|
|
8
|
+
require 'json'
|
|
9
|
+
require 'time'
|
|
10
|
+
|
|
11
|
+
module RosettAi
|
|
12
|
+
module Adopter
|
|
13
|
+
# Analyzes compiled markdown rule files for inconsistencies, conflicts,
|
|
14
|
+
# harmful content, duplicates, and other issues.
|
|
15
|
+
#
|
|
16
|
+
# Supports four layers of data privacy protection:
|
|
17
|
+
# 1. Opt-in per file — sensitive: true in YAML excludes from API analysis
|
|
18
|
+
# 2. Redaction — regex patterns replace matches before sending to API
|
|
19
|
+
# 3. Configurable endpoint — ANTHROPIC_API_BASE_URL for proxy/Bedrock/Vertex
|
|
20
|
+
# 4. Local-only mode — structural checks without API calls
|
|
21
|
+
class RuleAdopter
|
|
22
|
+
GENERATED_MARKER = '<!-- rosett-ai-'
|
|
23
|
+
|
|
24
|
+
attr_reader :rules_dir, :cache_path, :redactions_path, :engine
|
|
25
|
+
|
|
26
|
+
def initialize(rules_dir:, cache_path:, redactions_path:, engine: 'claude')
|
|
27
|
+
@rules_dir = Pathname.new(rules_dir)
|
|
28
|
+
@cache_path = Pathname.new(cache_path)
|
|
29
|
+
@redactions_path = Pathname.new(redactions_path)
|
|
30
|
+
@engine = engine.to_s
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def evaluate(local_only: false)
|
|
34
|
+
files = discover_managed_files
|
|
35
|
+
raise RosettAi::AdoptError, 'No managed rule files found in rules directory' if files.empty?
|
|
36
|
+
|
|
37
|
+
sensitive = sensitive_files
|
|
38
|
+
api_files = filter_sensitive(files, sensitive)
|
|
39
|
+
|
|
40
|
+
if local_only
|
|
41
|
+
evaluate_local(files, sensitive)
|
|
42
|
+
else
|
|
43
|
+
evaluate_remote(files, api_files, sensitive)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def discover_managed_files
|
|
48
|
+
return [] unless rules_dir.exist?
|
|
49
|
+
|
|
50
|
+
Dir.glob(rules_dir.join('*.md')).select do |file|
|
|
51
|
+
first_line = File.open(file, &:readline)
|
|
52
|
+
first_line.start_with?(GENERATED_MARKER)
|
|
53
|
+
rescue EOFError
|
|
54
|
+
false
|
|
55
|
+
end.sort
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def content_checksum(files)
|
|
59
|
+
content = files.sort.map { |f| File.read(f) }.join
|
|
60
|
+
Digest::SHA256.hexdigest(content)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def cached_result(checksum)
|
|
64
|
+
return nil unless cache_path.exist?
|
|
65
|
+
|
|
66
|
+
data = RosettAi::YamlLoader.load_file(cache_path.to_s, permitted_classes: [Time, Date])
|
|
67
|
+
return nil unless data.is_a?(Hash) && data['checksum'] == checksum
|
|
68
|
+
return nil if cache_expired?(data)
|
|
69
|
+
|
|
70
|
+
data['result']
|
|
71
|
+
rescue Psych::SyntaxError, Psych::DisallowedClass => e
|
|
72
|
+
RosettAi.logger.warn("Corrupt adopt cache (#{e.message}), will re-analyze")
|
|
73
|
+
nil
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def write_cache(checksum, result)
|
|
77
|
+
FileUtils.mkdir_p(cache_path.dirname)
|
|
78
|
+
data = {
|
|
79
|
+
'checksum' => checksum,
|
|
80
|
+
'analyzed_at' => Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ'),
|
|
81
|
+
'result' => result
|
|
82
|
+
}
|
|
83
|
+
File.open(cache_path, 'w', 0o644) { |f| f.write(data.to_yaml) }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def build_prompt(files)
|
|
87
|
+
parts = [prompt_header]
|
|
88
|
+
append_file_contents(parts, files)
|
|
89
|
+
parts.join("\n")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def redact(content)
|
|
93
|
+
patterns = load_redaction_patterns
|
|
94
|
+
result = content.dup
|
|
95
|
+
patterns.each do |entry|
|
|
96
|
+
regex = Regexp.new(entry['pattern'])
|
|
97
|
+
result.gsub!(regex, entry['replacement'])
|
|
98
|
+
rescue RegexpError => e
|
|
99
|
+
RosettAi.logger.warn("Invalid redaction pattern '#{entry['pattern']}': #{e.message}")
|
|
100
|
+
end
|
|
101
|
+
result
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def analyze(files)
|
|
105
|
+
return empty_result if files.empty?
|
|
106
|
+
|
|
107
|
+
prompt = build_prompt(files)
|
|
108
|
+
executor = resolve_executor
|
|
109
|
+
executor.analyze(prompt)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def analyze_local(files)
|
|
113
|
+
collector = LocalAnalysisCollector.new
|
|
114
|
+
collector.analyze(files)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def sensitive_files
|
|
118
|
+
behaviour_dir = RosettAi.root.join('conf', 'behaviour')
|
|
119
|
+
return [] unless behaviour_dir.exist?
|
|
120
|
+
|
|
121
|
+
Dir.glob(behaviour_dir.join('*.yml')).select do |file|
|
|
122
|
+
data = RosettAi::YamlLoader.load_file(file)
|
|
123
|
+
data.is_a?(Hash) && data['sensitive'] == true
|
|
124
|
+
rescue StandardError
|
|
125
|
+
false
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def filter_sensitive(files, sensitive_sources)
|
|
130
|
+
return files if sensitive_sources.empty?
|
|
131
|
+
|
|
132
|
+
sensitive_names = sensitive_sources.map { |f| RosettAi::TextSanitizer.normalize_nfc(File.basename(f, '.yml')) }
|
|
133
|
+
files.reject do |file|
|
|
134
|
+
name = File.basename(file, '.md').sub(/\A[^-]+-/, '')
|
|
135
|
+
sensitive_names.include?(name)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
private
|
|
140
|
+
|
|
141
|
+
def cache_expired?(data)
|
|
142
|
+
ttl_hours = RosettAi.rai_config.cache_ttl_hours
|
|
143
|
+
analyzed_at = data['analyzed_at']
|
|
144
|
+
return false unless analyzed_at
|
|
145
|
+
|
|
146
|
+
age_seconds = Time.now.utc - Time.parse(analyzed_at.to_s)
|
|
147
|
+
expired = age_seconds > (ttl_hours * 3600)
|
|
148
|
+
RosettAi.logger.debug("Adopt cache expired (#{ttl_hours}h TTL)") if expired
|
|
149
|
+
expired
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def resolve_executor
|
|
153
|
+
resolver = ExecutorResolver.new(@engine)
|
|
154
|
+
resolver.resolve(api_key: @api_key)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def evaluate_local(files, sensitive)
|
|
158
|
+
result = analyze_local(files)
|
|
159
|
+
{ result: result, sensitive_skipped: sensitive, cached: false }
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def evaluate_remote(files, api_files, sensitive)
|
|
163
|
+
checksum = content_checksum(api_files)
|
|
164
|
+
cached = cached_result(checksum)
|
|
165
|
+
return { result: cached, sensitive_skipped: sensitive, cached: true } if cached
|
|
166
|
+
|
|
167
|
+
@api_key = RosettAi::SecretsResolver.resolve('ANTHROPIC_API_KEY')
|
|
168
|
+
|
|
169
|
+
result = analyze(api_files)
|
|
170
|
+
local_findings = analyze_local(files)
|
|
171
|
+
merged = merge_results(result, local_findings)
|
|
172
|
+
write_cache(checksum, merged)
|
|
173
|
+
{ result: merged, sensitive_skipped: sensitive, cached: false }
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def prompt_header
|
|
177
|
+
<<~PROMPT
|
|
178
|
+
Analyze the following rosett-ai rule files for issues.
|
|
179
|
+
Look for:
|
|
180
|
+
- Inconsistencies between rules across files
|
|
181
|
+
- Conflicting rules that contradict each other
|
|
182
|
+
- Harmful or illegal instructions
|
|
183
|
+
- Jailbreaking attempts that try to bypass safety measures
|
|
184
|
+
- Rules that instruct breaking other rules
|
|
185
|
+
- Duplicate rules (same intent, possibly different wording)
|
|
186
|
+
- Any other problematic patterns
|
|
187
|
+
|
|
188
|
+
Respond ONLY with a JSON object (no markdown fences) in this exact format:
|
|
189
|
+
{
|
|
190
|
+
"findings": [
|
|
191
|
+
{
|
|
192
|
+
"category": "inconsistencies|conflicts|harmful|illegal|jailbreaking|rule_breaking|duplicates|other",
|
|
193
|
+
"severity": "low|medium|high|critical",
|
|
194
|
+
"file": "filename.md",
|
|
195
|
+
"rule_id": "rule_id or null",
|
|
196
|
+
"summary": "Brief description",
|
|
197
|
+
"explanation": "Detailed explanation"
|
|
198
|
+
}
|
|
199
|
+
],
|
|
200
|
+
"overall_status": "pass|warn|fail",
|
|
201
|
+
"summary": "One-line overall summary"
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
If no issues are found, return an empty findings array with overall_status "pass".
|
|
205
|
+
PROMPT
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def append_file_contents(parts, files)
|
|
209
|
+
files.each do |file|
|
|
210
|
+
basename = File.basename(file)
|
|
211
|
+
content = redact(File.read(file, encoding: 'utf-8'))
|
|
212
|
+
parts << "--- FILE: #{basename} ---"
|
|
213
|
+
parts << content
|
|
214
|
+
parts << "--- END FILE ---\n"
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def load_redaction_patterns
|
|
219
|
+
return [] unless redactions_path.exist?
|
|
220
|
+
|
|
221
|
+
data = RosettAi::YamlLoader.load_file(redactions_path.to_s)
|
|
222
|
+
return [] unless data.is_a?(Hash) && data['patterns'].is_a?(Array)
|
|
223
|
+
|
|
224
|
+
data['patterns']
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def empty_result
|
|
228
|
+
{ 'findings' => [], 'overall_status' => 'pass', 'summary' => 'No files to analyze' }
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def merge_results(api_result, local_result)
|
|
232
|
+
merged_findings = (api_result['findings'] || []) + (local_result['findings'] || [])
|
|
233
|
+
fail_severities = ['high', 'critical']
|
|
234
|
+
status = determine_status(merged_findings, fail_severities)
|
|
235
|
+
|
|
236
|
+
{
|
|
237
|
+
'findings' => merged_findings,
|
|
238
|
+
'overall_status' => status,
|
|
239
|
+
'summary' => merged_findings.empty? ? 'No issues detected' : "Found #{merged_findings.size} issue(s)"
|
|
240
|
+
}
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def determine_status(findings, fail_severities)
|
|
244
|
+
if findings.any? { |f| fail_severities.include?(f['severity']) }
|
|
245
|
+
'fail'
|
|
246
|
+
elsif findings.any?
|
|
247
|
+
'warn'
|
|
248
|
+
else
|
|
249
|
+
'pass'
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
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 AiConfig
|
|
8
|
+
# Compiles generic AI configuration to engine-native settings.
|
|
9
|
+
#
|
|
10
|
+
# Takes the canonical `.rosett-ai/conf/ai_config.yml` and transforms it
|
|
11
|
+
# into engine-specific configuration keys. Unsupported settings
|
|
12
|
+
# produce warnings (or errors in strict mode).
|
|
13
|
+
#
|
|
14
|
+
# When an engine manifest declares `capabilities.config: false`,
|
|
15
|
+
# a warning is emitted (error in strict mode). Fallback chains
|
|
16
|
+
# from config are compiled with local-to-remote transition warnings.
|
|
17
|
+
#
|
|
18
|
+
# @author hugo
|
|
19
|
+
# @author claude
|
|
20
|
+
class ConfigCompiler
|
|
21
|
+
# @param model_router [ModelRouter] model tier resolver
|
|
22
|
+
# @param strict [Boolean] treat unsupported settings as errors
|
|
23
|
+
def initialize(model_router: ModelRouter.new, strict: false)
|
|
24
|
+
@model_router = model_router
|
|
25
|
+
@strict = strict
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Compiles AI config for a specific engine.
|
|
29
|
+
#
|
|
30
|
+
# @param config [Hash] parsed ai_config.yml data
|
|
31
|
+
# @param engine [String] target engine identifier
|
|
32
|
+
# @return [Hash] compilation result with :settings and :warnings
|
|
33
|
+
def compile(config, engine:)
|
|
34
|
+
warnings = []
|
|
35
|
+
settings = {}
|
|
36
|
+
|
|
37
|
+
check_engine_capability(engine, warnings)
|
|
38
|
+
compile_model_routing(config, engine, settings, warnings)
|
|
39
|
+
compile_context_window(config, engine, settings, warnings)
|
|
40
|
+
compile_cost_controls(config, settings)
|
|
41
|
+
compile_fallback_chain(config, settings, warnings)
|
|
42
|
+
|
|
43
|
+
{ settings: settings, warnings: warnings }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def check_engine_capability(engine, warnings)
|
|
49
|
+
manifest = RosettAi::Engines::Registry.manifest(engine)
|
|
50
|
+
return if manifest.dig('capabilities', 'config') != false
|
|
51
|
+
|
|
52
|
+
handle_warning(warnings, "Engine '#{engine}' declares config capability as false")
|
|
53
|
+
rescue RosettAi::Error
|
|
54
|
+
nil
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def compile_fallback_chain(config, settings, warnings)
|
|
58
|
+
engines = config.dig('fallback_chain', 'engines')
|
|
59
|
+
return unless engines.is_a?(Array) && engines.size > 1
|
|
60
|
+
|
|
61
|
+
chain = FallbackChain.new(engines: engines)
|
|
62
|
+
settings['fallback_chain'] = chain.to_h
|
|
63
|
+
chain.warnings.each { |w| warnings << w }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def compile_model_routing(config, engine, settings, warnings)
|
|
67
|
+
routing = config.fetch('model_routing', {})
|
|
68
|
+
return if routing.empty?
|
|
69
|
+
|
|
70
|
+
resolved = {}
|
|
71
|
+
routing.each do |task_type, tier|
|
|
72
|
+
resolved[task_type] = @model_router.resolve(tier, engine: engine)
|
|
73
|
+
rescue RosettAi::AiConfigError => e
|
|
74
|
+
handle_warning(warnings, e.message)
|
|
75
|
+
end
|
|
76
|
+
settings['model_routing'] = resolved unless resolved.empty?
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def compile_context_window(config, engine, settings, warnings)
|
|
80
|
+
context = config.fetch('context_window', {})
|
|
81
|
+
return if context.empty?
|
|
82
|
+
|
|
83
|
+
window = ContextWindow.new(
|
|
84
|
+
max_tokens: context.fetch('max_tokens', ContextWindow::DEFAULT_MAX_TOKENS),
|
|
85
|
+
output_reserve: context.fetch('output_reserve', ContextWindow::DEFAULT_RESERVE),
|
|
86
|
+
truncation_strategy: context.fetch('truncation_strategy', 'tail')
|
|
87
|
+
)
|
|
88
|
+
settings['context_window'] = window.to_h
|
|
89
|
+
rescue ArgumentError => e
|
|
90
|
+
handle_warning(warnings, "Context window: #{e.message} (engine: #{engine})")
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def compile_cost_controls(config, settings)
|
|
94
|
+
cost = config.fetch('cost_controls', {})
|
|
95
|
+
return if cost.empty?
|
|
96
|
+
|
|
97
|
+
controls = CostControls.new(
|
|
98
|
+
preferred_tier: cost.fetch('preferred_tier', 'standard'),
|
|
99
|
+
monthly_budget_note: cost['monthly_budget_note']
|
|
100
|
+
)
|
|
101
|
+
settings['cost_controls'] = controls.to_h
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def handle_warning(warnings, message)
|
|
105
|
+
raise RosettAi::AiConfigError, message if @strict
|
|
106
|
+
|
|
107
|
+
warnings << message
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
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 AiConfig
|
|
8
|
+
# Manages context window configuration settings.
|
|
9
|
+
#
|
|
10
|
+
# Tracks max tokens, output reserve, and truncation strategy
|
|
11
|
+
# for compilation into engine-native settings.
|
|
12
|
+
class ContextWindow
|
|
13
|
+
TRUNCATION_STRATEGIES = ['tail', 'head', 'middle'].freeze
|
|
14
|
+
DEFAULT_MAX_TOKENS = 200_000
|
|
15
|
+
DEFAULT_RESERVE = 8192
|
|
16
|
+
|
|
17
|
+
attr_reader :max_tokens, :output_reserve, :truncation_strategy
|
|
18
|
+
|
|
19
|
+
# @param max_tokens [Integer] maximum context window tokens
|
|
20
|
+
# @param output_reserve [Integer] tokens reserved for output
|
|
21
|
+
# @param truncation_strategy [String] one of {TRUNCATION_STRATEGIES}
|
|
22
|
+
def initialize(max_tokens: DEFAULT_MAX_TOKENS, output_reserve: DEFAULT_RESERVE,
|
|
23
|
+
truncation_strategy: 'tail')
|
|
24
|
+
validate_strategy!(truncation_strategy)
|
|
25
|
+
|
|
26
|
+
@max_tokens = max_tokens
|
|
27
|
+
@output_reserve = output_reserve
|
|
28
|
+
@truncation_strategy = truncation_strategy.freeze
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @return [Integer] effective input tokens (max minus reserve)
|
|
32
|
+
def effective_input_tokens
|
|
33
|
+
@max_tokens - @output_reserve
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @return [Hash] serializable representation
|
|
37
|
+
def to_h
|
|
38
|
+
{
|
|
39
|
+
'max_tokens' => @max_tokens,
|
|
40
|
+
'output_reserve' => @output_reserve,
|
|
41
|
+
'truncation_strategy' => @truncation_strategy
|
|
42
|
+
}
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def validate_strategy!(strategy)
|
|
48
|
+
return if TRUNCATION_STRATEGIES.include?(strategy)
|
|
49
|
+
|
|
50
|
+
raise ArgumentError,
|
|
51
|
+
"Invalid truncation strategy '#{strategy}'. Allowed: #{TRUNCATION_STRATEGIES.join(', ')}"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|