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,160 @@
|
|
|
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 Mcp
|
|
8
|
+
# Centralized MCP tool, resource, and prompt registration.
|
|
9
|
+
#
|
|
10
|
+
# Always loaded before plugins. Registers all built-in tools,
|
|
11
|
+
# resources, and prompts with the MCP server instance.
|
|
12
|
+
#
|
|
13
|
+
# @author hugo
|
|
14
|
+
# @author claude
|
|
15
|
+
module Governance
|
|
16
|
+
TOOL_CLASSES = [
|
|
17
|
+
Tools::ValidateTool,
|
|
18
|
+
Tools::CompileTool,
|
|
19
|
+
Tools::BehaviourListTool,
|
|
20
|
+
Tools::BehaviourShowTool,
|
|
21
|
+
Tools::BehaviourDisplayTool,
|
|
22
|
+
Tools::BehaviourManageTool,
|
|
23
|
+
Tools::DesignListTool,
|
|
24
|
+
Tools::DesignShowTool,
|
|
25
|
+
Tools::ConfigStatusTool,
|
|
26
|
+
Tools::ConfigCompileTool,
|
|
27
|
+
Tools::AdoptTool,
|
|
28
|
+
Tools::ComplyTool,
|
|
29
|
+
Tools::DoctorTool,
|
|
30
|
+
Tools::EnginesTool,
|
|
31
|
+
Tools::HooksStatusTool,
|
|
32
|
+
Tools::LicenseStatusTool,
|
|
33
|
+
Tools::ProjectTool,
|
|
34
|
+
Tools::ProvenanceTool,
|
|
35
|
+
Tools::ProvenanceWriteTool,
|
|
36
|
+
Tools::ToolingTool,
|
|
37
|
+
Tools::WorkflowTool,
|
|
38
|
+
Tools::WorkflowExecuteTool,
|
|
39
|
+
Tools::DocumentationStatusTool,
|
|
40
|
+
Tools::InitTool,
|
|
41
|
+
Tools::BackupTool,
|
|
42
|
+
Tools::ContentTool,
|
|
43
|
+
Tools::RetrofitTool,
|
|
44
|
+
Tools::RuleSearchTool,
|
|
45
|
+
Tools::CompileStatusTool,
|
|
46
|
+
Tools::HookPreviewTool,
|
|
47
|
+
Tools::HookInstallTool,
|
|
48
|
+
Tools::ContextQueryTool,
|
|
49
|
+
Tools::SchemaGetTool
|
|
50
|
+
].freeze
|
|
51
|
+
|
|
52
|
+
RESOURCE_CLASSES = [
|
|
53
|
+
Resources::BehaviourResource,
|
|
54
|
+
Resources::DesignResource,
|
|
55
|
+
Resources::ProvenanceResource,
|
|
56
|
+
Resources::ConfigResource,
|
|
57
|
+
Resources::SchemaResource,
|
|
58
|
+
Resources::RulesResource,
|
|
59
|
+
Resources::HooksResource
|
|
60
|
+
].freeze
|
|
61
|
+
|
|
62
|
+
PROMPT_CLASSES = [
|
|
63
|
+
Prompts::ValidationPrompt,
|
|
64
|
+
Prompts::CompilationPrompt,
|
|
65
|
+
Prompts::CompliancePrompt,
|
|
66
|
+
Prompts::DiagnosticsPrompt
|
|
67
|
+
].freeze
|
|
68
|
+
|
|
69
|
+
KEYWORD_PARAM_TYPES = [:keyreq, :key].freeze
|
|
70
|
+
|
|
71
|
+
module_function
|
|
72
|
+
|
|
73
|
+
# @param server [MCP::Server]
|
|
74
|
+
def register(server)
|
|
75
|
+
register_tools(server)
|
|
76
|
+
register_resources(server)
|
|
77
|
+
register_prompts(server)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# @param server [MCP::Server]
|
|
81
|
+
def register_tools(server)
|
|
82
|
+
TOOL_CLASSES.each { |klass| register_tool(server, klass) }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# @param server [MCP::Server]
|
|
86
|
+
def register_resources(server)
|
|
87
|
+
resources = RESOURCE_CLASSES.map(&:new)
|
|
88
|
+
server.resources_list_handler { resources.flat_map(&:list) }
|
|
89
|
+
server.resources_read_handler do |uri:|
|
|
90
|
+
resource_name = uri.split('/').last
|
|
91
|
+
resource_class = find_resource_for_uri(resources, uri)
|
|
92
|
+
result = resource_class&.read(resource_name)
|
|
93
|
+
result&.fetch(:content, '')
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# @param server [MCP::Server]
|
|
98
|
+
def register_prompts(server)
|
|
99
|
+
PROMPT_CLASSES.each do |klass|
|
|
100
|
+
prompt = klass.new
|
|
101
|
+
server.define_prompt(
|
|
102
|
+
name: klass::PROMPT_NAME,
|
|
103
|
+
description: klass::DESCRIPTION
|
|
104
|
+
) do |args|
|
|
105
|
+
kwargs = (args || {}).transform_keys(&:to_sym)
|
|
106
|
+
prompt.call(**kwargs)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# @param server [MCP::Server]
|
|
112
|
+
# @param klass [Class] tool class with TOOL_NAME, DESCRIPTION, ANNOTATIONS
|
|
113
|
+
def register_tool(server, klass)
|
|
114
|
+
schema = klass.const_defined?(:INPUT_SCHEMA) ? klass::INPUT_SCHEMA : nil
|
|
115
|
+
server.define_tool(
|
|
116
|
+
name: klass::TOOL_NAME,
|
|
117
|
+
description: klass::DESCRIPTION,
|
|
118
|
+
annotations: snake_case_annotations(klass::ANNOTATIONS),
|
|
119
|
+
input_schema: schema
|
|
120
|
+
) do |**args|
|
|
121
|
+
tool = klass.new
|
|
122
|
+
kwargs = Governance.build_kwargs(tool, args)
|
|
123
|
+
result = kwargs.empty? ? tool.call : tool.call(**kwargs)
|
|
124
|
+
MCP::Tool::Response.new([{ type: 'text', text: JSON.generate(result) }])
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def find_resource_for_uri(resources, uri)
|
|
129
|
+
resources.find { |r| resource_matches_uri?(r, uri) }
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def resource_matches_uri?(resource, uri)
|
|
133
|
+
resource.list.any? { |entry| entry[:uri] == uri }
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Convert camelCase annotation keys to snake_case symbols
|
|
137
|
+
# for the mcp gem's Annotations initializer.
|
|
138
|
+
def snake_case_annotations(hash)
|
|
139
|
+
hash.each_with_object({}) do |(key, value), result|
|
|
140
|
+
snake = key.to_s.gsub(/([a-z])([A-Z])/, '\1_\2').downcase.to_sym
|
|
141
|
+
result[snake] = value
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# @param tool [Object] tool instance with #call
|
|
146
|
+
# @param args [Hash, nil] MCP arguments
|
|
147
|
+
def build_kwargs(tool, args)
|
|
148
|
+
return {} unless args.is_a?(Hash)
|
|
149
|
+
|
|
150
|
+
method_params = tool.method(:call).parameters
|
|
151
|
+
param_names = method_params.filter_map { |type, name| name if KEYWORD_PARAM_TYPES.include?(type) }
|
|
152
|
+
|
|
153
|
+
args.each_with_object({}) do |(key, value), kwargs|
|
|
154
|
+
sym = key.to_sym
|
|
155
|
+
kwargs[sym] = value if param_names.include?(sym)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
@@ -0,0 +1,158 @@
|
|
|
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 Mcp
|
|
8
|
+
# Typed HTTP security configuration hierarchy.
|
|
9
|
+
#
|
|
10
|
+
# Seven typed sub-config classes covering authentication, TLS,
|
|
11
|
+
# origin validation, rate limiting, CORS, and content type.
|
|
12
|
+
#
|
|
13
|
+
# @author hugo
|
|
14
|
+
# @author claude
|
|
15
|
+
class HttpSecurityConfig
|
|
16
|
+
attr_accessor :authentication, :tls, :origin, :rate_limiting,
|
|
17
|
+
:max_request_size, :content_type_enforcement, :cors
|
|
18
|
+
|
|
19
|
+
def initialize
|
|
20
|
+
@authentication = AuthConfig.new
|
|
21
|
+
@tls = TlsConfig.new
|
|
22
|
+
@origin = OriginConfig.new
|
|
23
|
+
@rate_limiting = RateLimitConfig.new
|
|
24
|
+
@max_request_size = 1_048_576
|
|
25
|
+
@content_type_enforcement = true
|
|
26
|
+
@cors = CorsConfig.new
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Apply a configuration hash from YAML.
|
|
30
|
+
#
|
|
31
|
+
# @param hash [Hash] configuration hash
|
|
32
|
+
# @return [void]
|
|
33
|
+
def apply(hash)
|
|
34
|
+
return unless hash.is_a?(Hash)
|
|
35
|
+
|
|
36
|
+
@authentication.apply(hash['authentication']) if hash['authentication']
|
|
37
|
+
@tls.apply(hash['tls']) if hash['tls']
|
|
38
|
+
@origin.apply(hash['origin']) if hash['origin']
|
|
39
|
+
@rate_limiting.apply(hash['rate_limiting']) if hash['rate_limiting']
|
|
40
|
+
@cors.apply(hash['cors']) if hash['cors']
|
|
41
|
+
@max_request_size = hash['max_request_size'] if hash['max_request_size']
|
|
42
|
+
@content_type_enforcement = hash['content_type_enforcement'] if hash.key?('content_type_enforcement')
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Authentication sub-configuration.
|
|
46
|
+
class AuthConfig
|
|
47
|
+
attr_accessor :type, :key_source, :keyfile_path, :api_key_env
|
|
48
|
+
|
|
49
|
+
DEFAULT_KEYFILE_NAME = 'rosett-ai-mcp-keys.yml'
|
|
50
|
+
|
|
51
|
+
def initialize
|
|
52
|
+
@type = 'api_key'
|
|
53
|
+
@key_source = 'auto'
|
|
54
|
+
@keyfile_path = DEFAULT_KEYFILE_NAME
|
|
55
|
+
@api_key_env = 'RAI_MCP_API_KEY'
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Resolve the effective key source.
|
|
59
|
+
#
|
|
60
|
+
# @return [String] 'keyfile' or 'env'
|
|
61
|
+
def resolved_key_source
|
|
62
|
+
return @key_source unless @key_source == 'auto'
|
|
63
|
+
|
|
64
|
+
File.exist?(File.expand_path(@keyfile_path)) ? 'keyfile' : 'env'
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def apply(hash)
|
|
68
|
+
return unless hash.is_a?(Hash)
|
|
69
|
+
|
|
70
|
+
@type = hash['type'] if hash['type']
|
|
71
|
+
@key_source = hash['key_source'] if hash['key_source']
|
|
72
|
+
@keyfile_path = hash['keyfile_path'] if hash['keyfile_path']
|
|
73
|
+
@api_key_env = hash['api_key_env'] if hash['api_key_env']
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# TLS sub-configuration.
|
|
78
|
+
class TlsConfig
|
|
79
|
+
attr_accessor :enabled, :cert_path, :key_path
|
|
80
|
+
|
|
81
|
+
def initialize
|
|
82
|
+
@enabled = false
|
|
83
|
+
@cert_path = nil
|
|
84
|
+
@key_path = nil
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def apply(hash)
|
|
88
|
+
return unless hash.is_a?(Hash)
|
|
89
|
+
|
|
90
|
+
@enabled = hash['enabled'] if hash.key?('enabled')
|
|
91
|
+
@cert_path = hash['cert_path'] if hash['cert_path']
|
|
92
|
+
@key_path = hash['key_path'] if hash['key_path']
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Origin validation sub-configuration.
|
|
97
|
+
class OriginConfig
|
|
98
|
+
DEFAULT_ORIGINS = ['http://localhost:*', 'http://127.0.0.1:*'].freeze
|
|
99
|
+
|
|
100
|
+
attr_accessor :allowed_origins, :strict_mode
|
|
101
|
+
|
|
102
|
+
def initialize
|
|
103
|
+
@allowed_origins = DEFAULT_ORIGINS.dup
|
|
104
|
+
@strict_mode = false
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def apply(hash)
|
|
108
|
+
return unless hash.is_a?(Hash)
|
|
109
|
+
|
|
110
|
+
@allowed_origins = hash['allowed_origins'] if hash['allowed_origins']
|
|
111
|
+
@strict_mode = hash['strict_mode'] if hash.key?('strict_mode')
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Rate limiting sub-configuration.
|
|
116
|
+
class RateLimitConfig
|
|
117
|
+
attr_accessor :enabled, :unauthenticated_rpm, :authenticated_rpm
|
|
118
|
+
|
|
119
|
+
def initialize
|
|
120
|
+
@enabled = true
|
|
121
|
+
@unauthenticated_rpm = 60
|
|
122
|
+
@authenticated_rpm = 300
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def apply(hash)
|
|
126
|
+
return unless hash.is_a?(Hash)
|
|
127
|
+
|
|
128
|
+
@enabled = hash['enabled'] if hash.key?('enabled')
|
|
129
|
+
@unauthenticated_rpm = hash['unauthenticated_rpm'] if hash['unauthenticated_rpm']
|
|
130
|
+
@authenticated_rpm = hash['authenticated_rpm'] if hash['authenticated_rpm']
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# CORS sub-configuration.
|
|
135
|
+
class CorsConfig
|
|
136
|
+
attr_accessor :enabled, :origins, :methods, :headers, :max_age
|
|
137
|
+
|
|
138
|
+
def initialize
|
|
139
|
+
@enabled = false
|
|
140
|
+
@origins = []
|
|
141
|
+
@methods = ['POST', 'GET', 'DELETE']
|
|
142
|
+
@headers = ['Content-Type', 'Authorization', 'Accept', 'Mcp-Session-Id']
|
|
143
|
+
@max_age = 86_400
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def apply(hash)
|
|
147
|
+
return unless hash.is_a?(Hash)
|
|
148
|
+
|
|
149
|
+
@enabled = hash['enabled'] if hash.key?('enabled')
|
|
150
|
+
@origins = hash['origins'] if hash['origins']
|
|
151
|
+
@methods = hash['methods'] if hash['methods']
|
|
152
|
+
@headers = hash['headers'] if hash['headers']
|
|
153
|
+
@max_age = hash['max_age'] if hash['max_age']
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
@@ -0,0 +1,266 @@
|
|
|
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 Mcp
|
|
8
|
+
# Generates the MCP server instructions markdown for AI client
|
|
9
|
+
# auto-discovery. The instructions field is read by AI clients
|
|
10
|
+
# on session start to understand available tools, resources,
|
|
11
|
+
# and workflows without manual lookup.
|
|
12
|
+
#
|
|
13
|
+
# Content is generated dynamically from the registered tool and
|
|
14
|
+
# resource classes in {Governance}, ensuring documentation-tool
|
|
15
|
+
# parity (DES-RAI-MCP-002 constraint).
|
|
16
|
+
#
|
|
17
|
+
# @author hugo
|
|
18
|
+
# @author claude
|
|
19
|
+
module Instructions
|
|
20
|
+
# Tool categories for the instructions inventory.
|
|
21
|
+
# Maps category name to the tool class constants that belong to it.
|
|
22
|
+
CATEGORIES = {
|
|
23
|
+
'Discovery' => [
|
|
24
|
+
'rai_behaviour_list', 'rai_behaviour_show', 'rai_behaviour_display',
|
|
25
|
+
'rai_design_list', 'rai_design_show', 'rai_documentation_status',
|
|
26
|
+
'rai_rule_search', 'rai_schema_get', 'rai_compile_status'
|
|
27
|
+
],
|
|
28
|
+
'Enforcement' => [
|
|
29
|
+
'rai_hook_preview', 'rai_hook_install', 'rai_context_query'
|
|
30
|
+
],
|
|
31
|
+
'Management' => [
|
|
32
|
+
'rai_behaviour_manage', 'rai_compile', 'rai_config_compile',
|
|
33
|
+
'rai_validate', 'rai_init', 'rai_backup', 'rai_content', 'rai_retrofit'
|
|
34
|
+
],
|
|
35
|
+
'Compliance' => [
|
|
36
|
+
'rai_comply', 'rai_provenance', 'rai_provenance_init', 'rai_adopt', 'rai_tooling'
|
|
37
|
+
],
|
|
38
|
+
'Operations' => [
|
|
39
|
+
'rai_doctor', 'rai_project', 'rosett_ai_engines',
|
|
40
|
+
'rai_hooks_status', 'rai_config_status', 'rai_license_status'
|
|
41
|
+
],
|
|
42
|
+
'Workflow' => [
|
|
43
|
+
'rai_workflow', 'rai_workflow_execute'
|
|
44
|
+
]
|
|
45
|
+
}.freeze
|
|
46
|
+
|
|
47
|
+
# Resource URI prefixes for the instructions inventory.
|
|
48
|
+
RESOURCE_URIS = {
|
|
49
|
+
'rosett-ai://behaviour/{name}' => 'Behaviour YAML via 3-tier lookup (supports ?tier=xdg)',
|
|
50
|
+
'rosett-ai://design/{name}' => 'Design document YAML content',
|
|
51
|
+
'rosett-ai://provenance/' => 'AI provenance entries',
|
|
52
|
+
'rosett-ai://config/{scope}' => 'Settings for a scope (managed, user, project, local)',
|
|
53
|
+
'rosett-ai://schema/{name}' => 'JSON Schema content for validation',
|
|
54
|
+
'rosett-ai://rules/{name}' => 'Compiled rule markdown content',
|
|
55
|
+
'rosett-ai://hooks/{scope}' => 'Installed enforcement hook scripts'
|
|
56
|
+
}.freeze
|
|
57
|
+
|
|
58
|
+
module_function
|
|
59
|
+
|
|
60
|
+
# Generates the complete instructions markdown string.
|
|
61
|
+
#
|
|
62
|
+
# @return [String] markdown instructions for AI client consumption
|
|
63
|
+
def generate
|
|
64
|
+
sections = [
|
|
65
|
+
header_section,
|
|
66
|
+
tool_inventory_section,
|
|
67
|
+
resource_section,
|
|
68
|
+
scope_hierarchy_section,
|
|
69
|
+
workflow_section,
|
|
70
|
+
mutation_safety_section
|
|
71
|
+
]
|
|
72
|
+
sections.join("\n\n")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# @return [String] header section
|
|
76
|
+
def header_section
|
|
77
|
+
<<~MARKDOWN.chomp
|
|
78
|
+
# Rosett-AI MCP Server
|
|
79
|
+
|
|
80
|
+
This server exposes rosett-ai configuration management tools via the
|
|
81
|
+
Model Context Protocol. Use this guide to select the right tool for
|
|
82
|
+
your task.
|
|
83
|
+
|
|
84
|
+
Version: #{RosettAi::VERSION}
|
|
85
|
+
MARKDOWN
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Generates the tool inventory grouped by category.
|
|
89
|
+
#
|
|
90
|
+
# @return [String] tool inventory markdown
|
|
91
|
+
def tool_inventory_section
|
|
92
|
+
lines = ["## Tool Inventory\n"]
|
|
93
|
+
|
|
94
|
+
CATEGORIES.each do |category, tool_names|
|
|
95
|
+
append_tool_table(lines, category, tool_entries_for(tool_names))
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
uncategorized = uncategorized_tools
|
|
99
|
+
append_tool_table(lines, 'Other', uncategorized) unless uncategorized.empty?
|
|
100
|
+
|
|
101
|
+
lines.join("\n")
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Appends a category tool table to the lines array.
|
|
105
|
+
#
|
|
106
|
+
# @param lines [Array<String>] accumulator
|
|
107
|
+
# @param heading [String] category heading
|
|
108
|
+
# @param entries [Array<Hash>] tool entries
|
|
109
|
+
# @return [void]
|
|
110
|
+
def append_tool_table(lines, heading, entries)
|
|
111
|
+
lines << "### #{heading}\n"
|
|
112
|
+
lines << '| Tool | Description | Read-only |'
|
|
113
|
+
lines << '|------|-------------|-----------|'
|
|
114
|
+
entries.each { |e| lines << format_tool_row(e) }
|
|
115
|
+
lines << ''
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Formats a single tool entry as a markdown table row.
|
|
119
|
+
#
|
|
120
|
+
# @param entry [Hash] tool entry with :name, :description, :read_only
|
|
121
|
+
# @return [String] markdown table row
|
|
122
|
+
def format_tool_row(entry)
|
|
123
|
+
ro = entry[:read_only] ? 'Yes' : 'No'
|
|
124
|
+
"| `#{entry[:name]}` | #{entry[:description]} | #{ro} |"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Generates the resource URI documentation.
|
|
128
|
+
#
|
|
129
|
+
# @return [String] resource section markdown
|
|
130
|
+
def resource_section
|
|
131
|
+
table_rows = RESOURCE_URIS.map { |uri, desc| "| `#{uri}` | #{desc} |" }
|
|
132
|
+
|
|
133
|
+
[
|
|
134
|
+
"## Resources\n",
|
|
135
|
+
'| URI Pattern | Description |',
|
|
136
|
+
'|-------------|-------------|',
|
|
137
|
+
*table_rows,
|
|
138
|
+
'',
|
|
139
|
+
'Query parameters:',
|
|
140
|
+
'- `?tier=project|xdg|packaged` — request a specific tier (bypasses merge)',
|
|
141
|
+
'- `?strategy=first_wins|deep_merge|array_union` — override merge strategy'
|
|
142
|
+
].join("\n")
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Explains the scope hierarchy for rule resolution.
|
|
146
|
+
#
|
|
147
|
+
# @return [String] scope hierarchy markdown
|
|
148
|
+
def scope_hierarchy_section
|
|
149
|
+
<<~MARKDOWN.chomp
|
|
150
|
+
## Scope Hierarchy
|
|
151
|
+
|
|
152
|
+
Rules are resolved in priority order (highest scope wins):
|
|
153
|
+
|
|
154
|
+
1. **Project** (`.rosett-ai/conf/behaviour/`) — project-specific overrides
|
|
155
|
+
2. **XDG user** (`~/.config/rosett-ai/conf/behaviour/`) — user-authored global rules
|
|
156
|
+
3. **Packaged** (`/opt/rosett-ai/app/conf/behaviour/`) — default templates from installation
|
|
157
|
+
|
|
158
|
+
Compiled output goes to `~/.claude/rules/` via `rai_compile`.
|
|
159
|
+
MARKDOWN
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Documents common workflows.
|
|
163
|
+
#
|
|
164
|
+
# @return [String] workflow section markdown
|
|
165
|
+
def workflow_section
|
|
166
|
+
<<~MARKDOWN.chomp
|
|
167
|
+
## Common Workflows
|
|
168
|
+
|
|
169
|
+
### List and inspect behaviours
|
|
170
|
+
|
|
171
|
+
1. `rai_behaviour_list` — see all available behaviours
|
|
172
|
+
2. `rai_behaviour_show(name: "criticalthinking")` — inspect rules
|
|
173
|
+
3. Read `rosett-ai://behaviour/criticalthinking` — raw YAML content
|
|
174
|
+
|
|
175
|
+
### Search and query rules
|
|
176
|
+
|
|
177
|
+
1. `rai_rule_search(keyword: "TMPDIR")` — find rules by keyword
|
|
178
|
+
2. `rai_context_query(tool_name: "Bash")` — what rules apply?
|
|
179
|
+
3. `rai_schema_get(schema_name: "behaviour")` — get validation schema
|
|
180
|
+
|
|
181
|
+
### Compile rules for an AI engine
|
|
182
|
+
|
|
183
|
+
1. `rai_validate` — check all config files are valid
|
|
184
|
+
2. `rai_compile(engine: "claude", simulate: true)` — preview output
|
|
185
|
+
3. `rai_compile(engine: "claude")` — compile to `~/.claude/rules/`
|
|
186
|
+
4. `rai_compile_status` — check what needs recompilation
|
|
187
|
+
|
|
188
|
+
### Install enforcement hooks
|
|
189
|
+
|
|
190
|
+
Rules with an `enforcement` block (type: enforceable, pattern, applies_to,
|
|
191
|
+
action) can be compiled into Claude Code PreToolUse hooks:
|
|
192
|
+
|
|
193
|
+
1. `rai_hook_preview(behaviour_name: "X")` — preview hook script
|
|
194
|
+
2. Review the output — check which rules are enforceable vs downgraded
|
|
195
|
+
3. `rai_hook_install(behaviour_name: "X", confirm: true)` — install
|
|
196
|
+
|
|
197
|
+
The enforcement pipeline validates patterns (rejects degenerate patterns
|
|
198
|
+
like `.*`), generates self-contained Ruby scripts, and installs them as
|
|
199
|
+
PreToolUse hooks. Invalid enforceable rules are downgraded to advisory.
|
|
200
|
+
|
|
201
|
+
### Check project health
|
|
202
|
+
|
|
203
|
+
1. `rai_doctor` — run diagnostic checks
|
|
204
|
+
2. `rai_comply` — CRA, license, and SPDX header compliance
|
|
205
|
+
3. `rai_config_status` — configuration scope status
|
|
206
|
+
MARKDOWN
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Documents mutation safety rules.
|
|
210
|
+
#
|
|
211
|
+
# @return [String] mutation safety markdown
|
|
212
|
+
def mutation_safety_section
|
|
213
|
+
<<~MARKDOWN.chomp
|
|
214
|
+
## Mutation Safety
|
|
215
|
+
|
|
216
|
+
Tools marked as **not read-only** perform writes. Before calling
|
|
217
|
+
mutation tools:
|
|
218
|
+
|
|
219
|
+
1. Present the action to the user and ask for confirmation
|
|
220
|
+
2. Prefer `simulate: true` first (where supported) to preview changes
|
|
221
|
+
3. The server does not enforce confirmation — the AI client must
|
|
222
|
+
|
|
223
|
+
Mutation tools: `rai_compile`, `rai_config_compile`, `rai_behaviour_manage`,
|
|
224
|
+
`rai_init`, `rai_backup`, `rai_content`, `rai_retrofit`,
|
|
225
|
+
`rai_provenance_init`, `rai_workflow_execute`, `rai_hook_install`.
|
|
226
|
+
|
|
227
|
+
`rai_hook_install` requires `confirm: true` and auto-backs up
|
|
228
|
+
existing hooks before writing. Use `rai_hook_preview` first.
|
|
229
|
+
MARKDOWN
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Builds tool entries for a list of tool names from Governance.
|
|
233
|
+
#
|
|
234
|
+
# @param tool_names [Array<String>] tool names to look up
|
|
235
|
+
# @return [Array<Hash>] tool entry hashes with :name, :description, :read_only
|
|
236
|
+
def tool_entries_for(tool_names)
|
|
237
|
+
tool_index = build_tool_index
|
|
238
|
+
tool_names.filter_map { |name| tool_index[name] }
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Returns tool entries not assigned to any category.
|
|
242
|
+
#
|
|
243
|
+
# @return [Array<Hash>] uncategorized tool entries
|
|
244
|
+
def uncategorized_tools
|
|
245
|
+
all_categorized = CATEGORIES.values.flatten
|
|
246
|
+
tool_index = build_tool_index
|
|
247
|
+
tool_index.except(*all_categorized)
|
|
248
|
+
.values
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Builds an index of tool name to entry hash from Governance.
|
|
252
|
+
#
|
|
253
|
+
# @return [Hash{String => Hash}] tool name to entry mapping
|
|
254
|
+
def build_tool_index
|
|
255
|
+
Governance::TOOL_CLASSES.each_with_object({}) do |klass, index|
|
|
256
|
+
name = klass::TOOL_NAME
|
|
257
|
+
index[name] = {
|
|
258
|
+
name: name,
|
|
259
|
+
description: klass::DESCRIPTION,
|
|
260
|
+
read_only: klass::ANNOTATIONS['readOnlyHint'] == true
|
|
261
|
+
}
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
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 'openssl'
|
|
7
|
+
require 'securerandom'
|
|
8
|
+
|
|
9
|
+
module RosettAi
|
|
10
|
+
module Mcp
|
|
11
|
+
# Cryptographic key hashing with per-key random salt.
|
|
12
|
+
#
|
|
13
|
+
# Uses SHA-256 with 16-byte random salt. Verification uses
|
|
14
|
+
# constant-time comparison via Rack::Utils.secure_compare.
|
|
15
|
+
#
|
|
16
|
+
# @author hugo
|
|
17
|
+
# @author claude
|
|
18
|
+
module KeyHasher
|
|
19
|
+
SALT_BYTES = 16
|
|
20
|
+
KEY_PREFIX = 'nncc_'
|
|
21
|
+
|
|
22
|
+
module_function
|
|
23
|
+
|
|
24
|
+
# Hash a plaintext API key with a random salt.
|
|
25
|
+
#
|
|
26
|
+
# @param plaintext [String] the plaintext key
|
|
27
|
+
# @return [String] salted hash in format "salt$digest"
|
|
28
|
+
def hash_key(plaintext)
|
|
29
|
+
salt = SecureRandom.hex(SALT_BYTES)
|
|
30
|
+
digest = OpenSSL::Digest::SHA256.hexdigest("#{salt}#{plaintext}")
|
|
31
|
+
"#{salt}$#{digest}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Verify a plaintext key against a stored hash.
|
|
35
|
+
#
|
|
36
|
+
# @param plaintext [String] the plaintext key to verify
|
|
37
|
+
# @param stored_hash [String] the stored "salt$digest" hash
|
|
38
|
+
# @return [Boolean] true if the key matches
|
|
39
|
+
def verify_key(plaintext, stored_hash)
|
|
40
|
+
salt, expected_digest = stored_hash.split('$', 2)
|
|
41
|
+
return false unless salt && expected_digest
|
|
42
|
+
|
|
43
|
+
actual_digest = OpenSSL::Digest::SHA256.hexdigest("#{salt}#{plaintext}")
|
|
44
|
+
secure_compare(actual_digest, expected_digest)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Generate a new random API key with the Rosett-AI_ prefix.
|
|
48
|
+
#
|
|
49
|
+
# @return [String] a new API key
|
|
50
|
+
def generate_key
|
|
51
|
+
"#{KEY_PREFIX}#{SecureRandom.hex(24)}"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Constant-time string comparison to prevent timing attacks.
|
|
55
|
+
#
|
|
56
|
+
# @param a [String] first string
|
|
57
|
+
# @param b [String] second string
|
|
58
|
+
# @return [Boolean] true if strings match
|
|
59
|
+
def secure_compare(a_str, b_str)
|
|
60
|
+
return false unless a_str.bytesize == b_str.bytesize
|
|
61
|
+
|
|
62
|
+
OpenSSL.fixed_length_secure_compare(a_str, b_str)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|