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,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-only
|
|
4
|
+
# Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
|
|
5
|
+
|
|
6
|
+
require 'json'
|
|
7
|
+
|
|
8
|
+
module RosettAi
|
|
9
|
+
module Init
|
|
10
|
+
# Registers the rosett-ai MCP server in Claude Code settings.json.
|
|
11
|
+
#
|
|
12
|
+
# Called by `raictl init --global` to enable AI client auto-discovery
|
|
13
|
+
# of rosett-ai tools via the MCP protocol (DES-RAI-MCP-002).
|
|
14
|
+
#
|
|
15
|
+
# The registration is idempotent: if the rosett-ai entry already
|
|
16
|
+
# exists, no changes are made.
|
|
17
|
+
#
|
|
18
|
+
# @author hugo
|
|
19
|
+
# @author claude
|
|
20
|
+
class McpRegistrar
|
|
21
|
+
MCP_SERVER_NAME = 'rosett-ai'
|
|
22
|
+
|
|
23
|
+
# @param settings_path [String] path to Claude Code settings.json
|
|
24
|
+
def initialize(settings_path: nil)
|
|
25
|
+
@settings_path = settings_path || default_settings_path
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Registers the rosett-ai MCP server in settings.json.
|
|
29
|
+
#
|
|
30
|
+
# @return [Hash] result with :action (:registered, :already_registered, :skipped)
|
|
31
|
+
# and :path
|
|
32
|
+
def register
|
|
33
|
+
return skip_result('Settings file not found') unless File.exist?(@settings_path)
|
|
34
|
+
|
|
35
|
+
settings = read_settings
|
|
36
|
+
return already_registered_result if registered?(settings)
|
|
37
|
+
|
|
38
|
+
settings['mcpServers'] ||= {}
|
|
39
|
+
settings['mcpServers'][MCP_SERVER_NAME] = server_entry
|
|
40
|
+
write_settings(settings)
|
|
41
|
+
|
|
42
|
+
{ action: :registered, path: @settings_path }
|
|
43
|
+
rescue JSON::ParserError => e
|
|
44
|
+
skip_result("Invalid JSON in settings: #{e.message}")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
# @return [String] default path to Claude Code settings.json
|
|
50
|
+
def default_settings_path
|
|
51
|
+
File.join(Dir.home, '.claude', 'settings.json')
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @return [Hash] the MCP server entry for settings.json
|
|
55
|
+
def server_entry
|
|
56
|
+
{
|
|
57
|
+
'command' => resolve_command,
|
|
58
|
+
'args' => ['mcp', 'serve']
|
|
59
|
+
}
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Resolves the raictl command path.
|
|
63
|
+
#
|
|
64
|
+
# Checks known install locations in order, falls back to
|
|
65
|
+
# absolute path from `which` so MCP clients without PATH
|
|
66
|
+
# can still find the binary.
|
|
67
|
+
#
|
|
68
|
+
# @return [String] absolute path or command name
|
|
69
|
+
def resolve_command
|
|
70
|
+
['/usr/local/bin/raictl', '/usr/bin/raictl'].each do |path|
|
|
71
|
+
return path if File.executable?(path)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
which_result = `which raictl 2>/dev/null`.strip
|
|
75
|
+
return which_result unless which_result.empty?
|
|
76
|
+
|
|
77
|
+
'raictl'
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# @param settings [Hash] parsed settings
|
|
81
|
+
# @return [Boolean] true if rosett-ai is already registered
|
|
82
|
+
def registered?(settings)
|
|
83
|
+
settings.dig('mcpServers', MCP_SERVER_NAME).is_a?(Hash)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# @return [Hash] parsed settings.json
|
|
87
|
+
def read_settings
|
|
88
|
+
JSON.parse(File.read(@settings_path))
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# @param settings [Hash] settings to write
|
|
92
|
+
# @return [void]
|
|
93
|
+
def write_settings(settings)
|
|
94
|
+
File.write(@settings_path, "#{JSON.pretty_generate(settings)}\n")
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# @return [Hash] result for already registered case
|
|
98
|
+
def already_registered_result
|
|
99
|
+
{ action: :already_registered, path: @settings_path }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# @param reason [String] why registration was skipped
|
|
103
|
+
# @return [Hash] result for skip case
|
|
104
|
+
def skip_result(reason)
|
|
105
|
+
{ action: :skipped, path: @settings_path, reason: reason }
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-only
|
|
4
|
+
# Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
|
|
5
|
+
|
|
6
|
+
require 'fileutils'
|
|
7
|
+
|
|
8
|
+
module RosettAi
|
|
9
|
+
module Init
|
|
10
|
+
# MCP-facing facade for .rosett-ai/ project structure initialization.
|
|
11
|
+
#
|
|
12
|
+
# Creates the .rosett-ai/ directory with config.yml and conf/ subdirectories.
|
|
13
|
+
#
|
|
14
|
+
# @author hugo
|
|
15
|
+
# @author claude
|
|
16
|
+
class ProjectInitializer
|
|
17
|
+
PROJECT_DIR = '.rosett-ai'
|
|
18
|
+
|
|
19
|
+
# Sets up the .rosett-ai/ project directory structure.
|
|
20
|
+
#
|
|
21
|
+
# @return [Array<String>] created directories
|
|
22
|
+
def setup
|
|
23
|
+
base_dir = File.join(Dir.pwd, PROJECT_DIR)
|
|
24
|
+
subdirs = ['conf', 'conf/behaviour', 'conf/design']
|
|
25
|
+
|
|
26
|
+
created = DirectoryBuilder.create_directories(base_dir, subdirs)
|
|
27
|
+
write_config(base_dir) unless File.exist?(File.join(base_dir, 'config.yml'))
|
|
28
|
+
created
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def write_config(base_dir)
|
|
34
|
+
ConfigFileWriter.write_project_config(base_dir)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,139 @@
|
|
|
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 'jwt'
|
|
7
|
+
require 'jwt/eddsa'
|
|
8
|
+
require 'ed25519'
|
|
9
|
+
|
|
10
|
+
module RosettAi
|
|
11
|
+
module Licensing
|
|
12
|
+
# Decodes and validates Ed25519-signed JWT license keys.
|
|
13
|
+
#
|
|
14
|
+
# The public verification key is safe to embed in source — it can
|
|
15
|
+
# only verify signatures, not forge them. The private signing key
|
|
16
|
+
# is held server-side and NEVER distributed.
|
|
17
|
+
class LicenseKey
|
|
18
|
+
PUBLIC_KEY_HEX = '91f54336c0d42a7c88642b8d7f8bacab3adba4a4d633a33002fc15f676bfd8f0'
|
|
19
|
+
RAI_PREFIX = 'NNCC-'
|
|
20
|
+
GRACE_PERIOD_DAYS = 14
|
|
21
|
+
OFFLINE_GRACE_DAYS = 30
|
|
22
|
+
|
|
23
|
+
attr_reader :claims
|
|
24
|
+
|
|
25
|
+
# @param raw_key [String] JWT license key, optionally prefixed with "NNCC-"
|
|
26
|
+
def initialize(raw_key)
|
|
27
|
+
@raw_key = raw_key
|
|
28
|
+
@claims = nil
|
|
29
|
+
@decoded = false
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Decodes and verifies the Ed25519 JWT signature.
|
|
33
|
+
#
|
|
34
|
+
# @return [self]
|
|
35
|
+
# @raise [RosettAi::LicenseError] if signature verification or decoding fails
|
|
36
|
+
def decode
|
|
37
|
+
return self if @decoded
|
|
38
|
+
|
|
39
|
+
stripped = strip_prefix(@raw_key)
|
|
40
|
+
verify_key = Ed25519::VerifyKey.new([PUBLIC_KEY_HEX].pack('H*'))
|
|
41
|
+
decoded = JWT.decode(stripped, verify_key, true, algorithms: ['EdDSA'], verify_expiration: false)
|
|
42
|
+
@claims = decoded.first
|
|
43
|
+
@decoded = true
|
|
44
|
+
self
|
|
45
|
+
rescue Ed25519::VerifyError, JWT::DecodeError, JWT::VerificationError => e
|
|
46
|
+
raise RosettAi::LicenseError, "signature verification failed: #{e.message}"
|
|
47
|
+
rescue ArgumentError, TypeError => e
|
|
48
|
+
raise RosettAi::LicenseError, "invalid key format: #{e.message}"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Whether the license is currently valid (not expired or within grace).
|
|
52
|
+
#
|
|
53
|
+
# @return [Boolean]
|
|
54
|
+
def valid?
|
|
55
|
+
decode
|
|
56
|
+
!expired? || within_grace?
|
|
57
|
+
rescue RosettAi::LicenseError
|
|
58
|
+
false
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Returns the license tier.
|
|
62
|
+
#
|
|
63
|
+
# @return [Tier] tier from claims, defaults to community
|
|
64
|
+
def tier
|
|
65
|
+
decode
|
|
66
|
+
Tier.new(claims['tier'] || 'community')
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Whether the license has passed its expiration timestamp.
|
|
70
|
+
#
|
|
71
|
+
# @return [Boolean]
|
|
72
|
+
def expired?
|
|
73
|
+
decode
|
|
74
|
+
return false if perpetual?
|
|
75
|
+
|
|
76
|
+
exp = claims['exp']
|
|
77
|
+
return false unless exp
|
|
78
|
+
|
|
79
|
+
Time.now.to_i > exp
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Whether the license is perpetual (never expires).
|
|
83
|
+
#
|
|
84
|
+
# @return [Boolean]
|
|
85
|
+
def perpetual?
|
|
86
|
+
decode
|
|
87
|
+
claims['perpetual'] == true
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Whether the license tier is subscriber or enterprise.
|
|
91
|
+
#
|
|
92
|
+
# @return [Boolean]
|
|
93
|
+
def subscriber?
|
|
94
|
+
decode
|
|
95
|
+
['subscriber', 'enterprise'].include?(claims['tier'])
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Days remaining in the post-expiry grace period.
|
|
99
|
+
#
|
|
100
|
+
# @return [Integer, nil] days remaining (0 if exhausted, nil if perpetual or no expiry)
|
|
101
|
+
def grace_remaining
|
|
102
|
+
decode
|
|
103
|
+
return nil if perpetual?
|
|
104
|
+
|
|
105
|
+
exp = claims['exp']
|
|
106
|
+
return nil unless exp
|
|
107
|
+
|
|
108
|
+
elapsed = (Time.now.to_i - exp) / 86_400
|
|
109
|
+
return GRACE_PERIOD_DAYS if elapsed.negative?
|
|
110
|
+
|
|
111
|
+
remaining = GRACE_PERIOD_DAYS - elapsed
|
|
112
|
+
[remaining, 0].max
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Whether the license is valid for offline use.
|
|
116
|
+
#
|
|
117
|
+
# @return [Boolean]
|
|
118
|
+
def offline_valid?
|
|
119
|
+
decode
|
|
120
|
+
return true if perpetual?
|
|
121
|
+
|
|
122
|
+
exp = claims['exp']
|
|
123
|
+
return true unless exp
|
|
124
|
+
|
|
125
|
+
(Time.now.to_i - exp) < (OFFLINE_GRACE_DAYS * 86_400)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
private
|
|
129
|
+
|
|
130
|
+
def strip_prefix(key)
|
|
131
|
+
key.start_with?(RAI_PREFIX) ? key[RAI_PREFIX.length..] : key
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def within_grace?
|
|
135
|
+
grace_remaining&.positive?
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
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 Licensing
|
|
8
|
+
# Persists the license key at ~/.config/rosett-ai/license.key with
|
|
9
|
+
# strict file permissions (0600, owned by current uid).
|
|
10
|
+
class LicenseStore
|
|
11
|
+
SAFE_FILE_MODE = 0o600
|
|
12
|
+
SAFE_DIR_MODE = 0o700
|
|
13
|
+
|
|
14
|
+
def save(raw_key)
|
|
15
|
+
ensure_parent_directory!
|
|
16
|
+
File.write(license_path.to_s, raw_key, mode: 'w', perm: SAFE_FILE_MODE)
|
|
17
|
+
validate_permissions!
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def load
|
|
21
|
+
return nil unless exists?
|
|
22
|
+
|
|
23
|
+
validate_permissions!
|
|
24
|
+
File.read(license_path.to_s).chomp
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def remove!
|
|
28
|
+
File.delete(license_path.to_s) if exists?
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def exists?
|
|
32
|
+
license_path.exist?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def license_path
|
|
38
|
+
RosettAi.paths.license_file
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def ensure_parent_directory!
|
|
42
|
+
dir = license_path.dirname
|
|
43
|
+
return if dir.exist?
|
|
44
|
+
|
|
45
|
+
FileUtils.mkdir_p(dir.to_s, mode: SAFE_DIR_MODE)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def validate_permissions!
|
|
49
|
+
stat = File.stat(license_path.to_s)
|
|
50
|
+
|
|
51
|
+
mode = stat.mode & 0o777
|
|
52
|
+
unless mode == SAFE_FILE_MODE
|
|
53
|
+
raise RosettAi::LicenseError,
|
|
54
|
+
"license file permissions #{format('%04o', mode)} are not 0600"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
return if stat.uid == Process.uid
|
|
58
|
+
|
|
59
|
+
raise RosettAi::LicenseError,
|
|
60
|
+
"license file owned by uid #{stat.uid}, expected #{Process.uid}"
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-only
|
|
4
|
+
# Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
|
|
5
|
+
|
|
6
|
+
module RosettAi
|
|
7
|
+
module Licensing
|
|
8
|
+
# Orchestrates the full license validation flow:
|
|
9
|
+
# load from store -> decode -> verify signature -> check expiry.
|
|
10
|
+
#
|
|
11
|
+
# Returns a structured result hash. Never raises for expected
|
|
12
|
+
# states (missing license, expired subscription).
|
|
13
|
+
class LicenseValidator
|
|
14
|
+
# Runs the full license validation flow.
|
|
15
|
+
#
|
|
16
|
+
# @return [Hash{Symbol => Object}] result with :valid, :tier, :warnings, :errors keys
|
|
17
|
+
def validate
|
|
18
|
+
store = LicenseStore.new
|
|
19
|
+
return community_result unless store.exists?
|
|
20
|
+
|
|
21
|
+
raw_key = store.load
|
|
22
|
+
return community_result if raw_key.nil? || raw_key.empty?
|
|
23
|
+
|
|
24
|
+
validate_key(raw_key)
|
|
25
|
+
rescue RosettAi::LicenseError => e
|
|
26
|
+
error_result(e.message)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def validate_key(raw_key)
|
|
32
|
+
key = LicenseKey.new(raw_key)
|
|
33
|
+
key.decode
|
|
34
|
+
|
|
35
|
+
result = { valid: true, tier: key.tier, warnings: [], errors: [] }
|
|
36
|
+
add_expiry_warnings(key, result) if key.expired?
|
|
37
|
+
result
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def add_expiry_warnings(key, result)
|
|
41
|
+
remaining = key.grace_remaining
|
|
42
|
+
if remaining&.positive?
|
|
43
|
+
result[:warnings] << "Subscription expired. Content available for #{remaining} " \
|
|
44
|
+
'more day(s). Renew to continue receiving updates.'
|
|
45
|
+
else
|
|
46
|
+
result[:warnings] << 'Subscription expired. Premium content frozen at last installed version.'
|
|
47
|
+
result[:valid] = false
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def community_result
|
|
52
|
+
{ valid: true, tier: Tier.new(:community), warnings: [], errors: [] }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def error_result(message)
|
|
56
|
+
{ valid: false, tier: Tier.new(:community), warnings: [], errors: [message] }
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
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 Licensing
|
|
8
|
+
# Represents a license tier with comparable ordering.
|
|
9
|
+
#
|
|
10
|
+
# Tiers control access to premium content pack downloads — they
|
|
11
|
+
# NEVER restrict software functionality (GPL-3.0 compliance).
|
|
12
|
+
class Tier
|
|
13
|
+
include Comparable
|
|
14
|
+
|
|
15
|
+
HIERARCHY = { community: 0, supporter: 1, subscriber: 2, enterprise: 3 }.freeze
|
|
16
|
+
NAMES = HIERARCHY.keys.freeze
|
|
17
|
+
|
|
18
|
+
attr_reader :name, :level
|
|
19
|
+
|
|
20
|
+
def initialize(name)
|
|
21
|
+
sym = name.to_s.downcase.to_sym
|
|
22
|
+
raise RosettAi::LicenseError, "unknown tier: #{name}" unless HIERARCHY.key?(sym)
|
|
23
|
+
|
|
24
|
+
@name = sym
|
|
25
|
+
@level = HIERARCHY[sym]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def <=>(other)
|
|
29
|
+
level <=> other.level
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def entitled_to?(required)
|
|
33
|
+
other = required.is_a?(Tier) ? required : Tier.new(required)
|
|
34
|
+
self >= other
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def to_s
|
|
38
|
+
name.to_s.capitalize
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
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
|
+
module Admin
|
|
9
|
+
# Compliance auditor for MCP server configurations.
|
|
10
|
+
#
|
|
11
|
+
# Generates structured audit reports covering configuration
|
|
12
|
+
# provenance, validation status, and transport security.
|
|
13
|
+
# Supports CRA/NIS2/DORA audit documentation requirements.
|
|
14
|
+
#
|
|
15
|
+
# @author hugo
|
|
16
|
+
# @author claude
|
|
17
|
+
class Auditor
|
|
18
|
+
# @param registry [Registry] server registry
|
|
19
|
+
# @param health_checker [HealthChecker] health checker instance
|
|
20
|
+
# @param schema_validator [SchemaValidator] schema validator instance
|
|
21
|
+
def initialize(registry:, health_checker:, schema_validator:)
|
|
22
|
+
@registry = registry
|
|
23
|
+
@health_checker = health_checker
|
|
24
|
+
@schema_validator = schema_validator
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Generates a full audit report.
|
|
28
|
+
#
|
|
29
|
+
# @return [Hash] audit report with :timestamp, :servers, :summary
|
|
30
|
+
def audit
|
|
31
|
+
servers = @registry.list
|
|
32
|
+
health_results = @health_checker.check_all(@registry)
|
|
33
|
+
health_map = health_results.to_h { |r| [r[:name], r] }
|
|
34
|
+
|
|
35
|
+
entries = servers.map { |server| audit_server(server, health_map) }
|
|
36
|
+
|
|
37
|
+
{
|
|
38
|
+
timestamp: Time.now.utc.iso8601,
|
|
39
|
+
servers: entries,
|
|
40
|
+
summary: build_summary(entries)
|
|
41
|
+
}
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def audit_server(server, health_map)
|
|
47
|
+
health = health_map[server[:name]] || { status: :unknown }
|
|
48
|
+
validation = @schema_validator.validate(server)
|
|
49
|
+
|
|
50
|
+
{
|
|
51
|
+
name: server[:name],
|
|
52
|
+
transport: server[:transport],
|
|
53
|
+
health_status: health[:status].to_s,
|
|
54
|
+
schema_valid: validation[:valid],
|
|
55
|
+
validation_errors: validation[:errors],
|
|
56
|
+
source_file: server[:source_file],
|
|
57
|
+
transport_security: assess_transport_security(server),
|
|
58
|
+
audited_at: Time.now.utc.iso8601
|
|
59
|
+
}
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def assess_transport_security(server)
|
|
63
|
+
case server[:transport]
|
|
64
|
+
when 'stdio'
|
|
65
|
+
'local-only'
|
|
66
|
+
when 'http', 'streamable-http'
|
|
67
|
+
url = server[:url].to_s
|
|
68
|
+
url.start_with?('https://') ? 'tls-encrypted' : 'plaintext-warning'
|
|
69
|
+
else
|
|
70
|
+
'unknown'
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
SECURE_TRANSPORTS = ['local-only', 'tls-encrypted'].freeze
|
|
75
|
+
private_constant :SECURE_TRANSPORTS
|
|
76
|
+
|
|
77
|
+
def build_summary(entries)
|
|
78
|
+
{
|
|
79
|
+
total: entries.size,
|
|
80
|
+
healthy: entries.count { |e| e[:health_status] == 'available' },
|
|
81
|
+
schema_valid: entries.count { |e| e[:schema_valid] },
|
|
82
|
+
secure_transport: entries.count { |e| SECURE_TRANSPORTS.include?(e[:transport_security]) }
|
|
83
|
+
}
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
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
|
+
module Admin
|
|
9
|
+
# Health checker for configured MCP servers.
|
|
10
|
+
#
|
|
11
|
+
# Probes each server with a configurable timeout (default 5s)
|
|
12
|
+
# to verify availability and capability negotiation.
|
|
13
|
+
#
|
|
14
|
+
# @author hugo
|
|
15
|
+
# @author claude
|
|
16
|
+
class HealthChecker
|
|
17
|
+
DEFAULT_TIMEOUT = 5
|
|
18
|
+
|
|
19
|
+
# @param timeout [Integer] maximum seconds per health check
|
|
20
|
+
def initialize(timeout: DEFAULT_TIMEOUT)
|
|
21
|
+
@timeout = timeout
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Checks health of a single server.
|
|
25
|
+
#
|
|
26
|
+
# @param server [Hash] server entry from Registry
|
|
27
|
+
# @return [Hash] result with :name, :status, :transport, :message
|
|
28
|
+
def check(server)
|
|
29
|
+
case server[:transport]
|
|
30
|
+
when 'stdio'
|
|
31
|
+
check_stdio(server)
|
|
32
|
+
when 'http', 'streamable-http'
|
|
33
|
+
check_http(server)
|
|
34
|
+
else
|
|
35
|
+
{ name: server[:name], status: :unknown, transport: server[:transport],
|
|
36
|
+
message: "Unknown transport: #{server[:transport]}" }
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Checks health of all servers in a registry.
|
|
41
|
+
#
|
|
42
|
+
# @param registry [Registry] server registry
|
|
43
|
+
# @return [Array<Hash>] health results
|
|
44
|
+
def check_all(registry)
|
|
45
|
+
registry.list.map { |server| check(server) }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def check_stdio(server)
|
|
51
|
+
command = server[:command]
|
|
52
|
+
unless command && command_exists?(command)
|
|
53
|
+
return { name: server[:name], status: :unavailable, transport: 'stdio',
|
|
54
|
+
message: "Command not found: #{command}" }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
{ name: server[:name], status: :available, transport: 'stdio',
|
|
58
|
+
message: 'Command found' }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def check_http(server)
|
|
62
|
+
url = server[:url]
|
|
63
|
+
unless url
|
|
64
|
+
return { name: server[:name], status: :unavailable, transport: server[:transport],
|
|
65
|
+
message: 'No URL configured' }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
{ name: server[:name], status: :available, transport: server[:transport],
|
|
69
|
+
message: "URL configured: #{url}" }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def command_exists?(command)
|
|
73
|
+
cmd = command.is_a?(Array) ? command.first : command.split.first
|
|
74
|
+
ENV.fetch('PATH', '').split(File::PATH_SEPARATOR).any? do |dir|
|
|
75
|
+
File.executable?(File.join(dir, cmd))
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|