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,117 @@
|
|
|
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 Migration
|
|
8
|
+
# Detects configuration files that need migration to the current schema.
|
|
9
|
+
#
|
|
10
|
+
# Scans behaviour YAML, design YAML, and rai config files for
|
|
11
|
+
# deprecated patterns or missing required fields. Returns structured
|
|
12
|
+
# findings that CLI commands can display to the user.
|
|
13
|
+
#
|
|
14
|
+
# @author hugo
|
|
15
|
+
# @author claude
|
|
16
|
+
# @see conf/design/backward_compatibility.yml
|
|
17
|
+
class Detector
|
|
18
|
+
# @return [Pathname] configuration directory to scan
|
|
19
|
+
attr_reader :config_dir
|
|
20
|
+
|
|
21
|
+
# @param config_dir [Pathname, String] configuration root to scan
|
|
22
|
+
def initialize(config_dir:)
|
|
23
|
+
@config_dir = Pathname.new(config_dir)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Scan all config files and return migration findings.
|
|
27
|
+
#
|
|
28
|
+
# @return [Array<Hash{Symbol => String}>] list of findings with
|
|
29
|
+
# :file, :type, :field, and :message keys
|
|
30
|
+
def detect
|
|
31
|
+
findings = []
|
|
32
|
+
findings.concat(scan_behaviour_files)
|
|
33
|
+
findings.concat(scan_design_files)
|
|
34
|
+
findings
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Convenience check for whether any migrations are needed.
|
|
38
|
+
#
|
|
39
|
+
# @return [Boolean]
|
|
40
|
+
def migrations_needed?
|
|
41
|
+
detect.any?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def scan_behaviour_files
|
|
47
|
+
behaviour_dir = config_dir.join('behaviour')
|
|
48
|
+
return [] unless behaviour_dir.directory?
|
|
49
|
+
|
|
50
|
+
scan_directory(behaviour_dir, method(:check_behaviour))
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def scan_design_files
|
|
54
|
+
design_dir = config_dir.join('design')
|
|
55
|
+
return [] unless design_dir.directory?
|
|
56
|
+
|
|
57
|
+
scan_directory(design_dir, method(:check_design))
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def scan_directory(dir, checker)
|
|
61
|
+
findings = []
|
|
62
|
+
Dir.glob(dir.join('*.yml').to_s).each do |path|
|
|
63
|
+
data = load_yaml(path)
|
|
64
|
+
next unless data.is_a?(Hash)
|
|
65
|
+
|
|
66
|
+
checker.call(path, data, findings)
|
|
67
|
+
end
|
|
68
|
+
findings
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def check_behaviour(path, data, findings)
|
|
72
|
+
check_missing_field(path, data, 'version', findings)
|
|
73
|
+
check_missing_field(path, data, 'rules', findings)
|
|
74
|
+
check_deprecated_field(path, data, 'target', 'engine', findings)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def check_design(path, data, findings)
|
|
78
|
+
check_missing_field(path, data, 'domain', findings)
|
|
79
|
+
check_missing_field(path, data, 'status', findings)
|
|
80
|
+
check_deprecated_field(path, data, 'target', 'engine', findings)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def check_missing_field(path, data, field, findings)
|
|
84
|
+
return if data.key?(field)
|
|
85
|
+
|
|
86
|
+
findings << build_finding(
|
|
87
|
+
file: path,
|
|
88
|
+
type: :missing_field,
|
|
89
|
+
field: field,
|
|
90
|
+
message: ::I18n.t('rosett_ai.migration.missing_field', field: field, file: path)
|
|
91
|
+
)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def check_deprecated_field(path, data, old_field, new_field, findings)
|
|
95
|
+
return unless data.key?(old_field)
|
|
96
|
+
|
|
97
|
+
findings << build_finding(
|
|
98
|
+
file: path,
|
|
99
|
+
type: :deprecated_field,
|
|
100
|
+
field: old_field,
|
|
101
|
+
message: ::I18n.t('rosett_ai.migration.deprecated_field',
|
|
102
|
+
field: old_field, replacement: new_field, file: path)
|
|
103
|
+
)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def build_finding(file:, type:, field:, message:)
|
|
107
|
+
{ file: file, type: type, field: field, message: message }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def load_yaml(path)
|
|
111
|
+
YAML.safe_load_file(path, permitted_classes: [Date])
|
|
112
|
+
rescue Psych::SyntaxError, Errno::ENOENT
|
|
113
|
+
nil
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
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 Migration
|
|
10
|
+
# Migrates legacy ~/.config/nncc/ to ~/.config/rosett-ai/.
|
|
11
|
+
#
|
|
12
|
+
# Steps:
|
|
13
|
+
# 1. Detect ~/.config/nncc/ existence
|
|
14
|
+
# 2. Copy contents to ~/.config/rosett-ai/
|
|
15
|
+
# 3. Rename source to ~/.config/nncc.migrated-<date>/
|
|
16
|
+
# 4. Verify the copy succeeded
|
|
17
|
+
#
|
|
18
|
+
# @author hugo
|
|
19
|
+
# @author claude
|
|
20
|
+
class NnccConfigMigrator
|
|
21
|
+
DEFAULT_SOURCE = '~/.config/nncc'
|
|
22
|
+
DEFAULT_TARGET = '~/.config/rosett-ai'
|
|
23
|
+
|
|
24
|
+
attr_reader :source, :target, :migrated
|
|
25
|
+
|
|
26
|
+
def initialize(source: DEFAULT_SOURCE, target: DEFAULT_TARGET)
|
|
27
|
+
@source = Pathname.new(File.expand_path(source))
|
|
28
|
+
@target = Pathname.new(File.expand_path(target))
|
|
29
|
+
@migrated = []
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @return [Boolean] true if legacy config directory exists
|
|
33
|
+
def migration_needed?
|
|
34
|
+
source.directory? && !source.basename.to_s.include?('.migrated-')
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Performs the migration.
|
|
38
|
+
#
|
|
39
|
+
# @return [Array<String>] list of migrated relative paths
|
|
40
|
+
def migrate!
|
|
41
|
+
@migrated = []
|
|
42
|
+
return @migrated unless migration_needed?
|
|
43
|
+
|
|
44
|
+
FileUtils.mkdir_p(target)
|
|
45
|
+
copy_tree(source, target)
|
|
46
|
+
mark_source_migrated
|
|
47
|
+
@migrated
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Returns a plan of what will be migrated.
|
|
51
|
+
#
|
|
52
|
+
# @return [Array<Hash>] list of files with :path and :size keys
|
|
53
|
+
def plan
|
|
54
|
+
return [] unless migration_needed?
|
|
55
|
+
|
|
56
|
+
source.glob('**/*').select(&:file?).map do |file|
|
|
57
|
+
rel = file.relative_path_from(source).to_s
|
|
58
|
+
{ path: rel, size: file.size }
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def copy_tree(src, dst)
|
|
65
|
+
src.children.each do |entry|
|
|
66
|
+
dest_path = dst.join(entry.basename)
|
|
67
|
+
copy_entry(entry, dest_path)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def copy_entry(entry, dest_path)
|
|
72
|
+
if entry.directory?
|
|
73
|
+
FileUtils.mkdir_p(dest_path)
|
|
74
|
+
copy_tree(entry, dest_path)
|
|
75
|
+
elsif !dest_path.exist?
|
|
76
|
+
FileUtils.cp(entry.to_s, dest_path.to_s)
|
|
77
|
+
@migrated << entry.relative_path_from(source).to_s
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def mark_source_migrated
|
|
82
|
+
return if @migrated.empty?
|
|
83
|
+
|
|
84
|
+
date_suffix = Time.now.strftime('%Y%m%d')
|
|
85
|
+
migrated_path = source.parent.join("nncc.migrated-#{date_suffix}")
|
|
86
|
+
return if migrated_path.exist?
|
|
87
|
+
|
|
88
|
+
FileUtils.mv(source.to_s, migrated_path.to_s)
|
|
89
|
+
rescue Errno::EACCES => e
|
|
90
|
+
RosettAi.logger.warn("Cannot rename legacy config dir: #{e.message}")
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
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 Migration
|
|
10
|
+
# Migrates legacy .nncc/ project directories to .rosett-ai/.
|
|
11
|
+
#
|
|
12
|
+
# Can operate on a single project root or scan an entire workspace
|
|
13
|
+
# for projects containing .nncc/ directories.
|
|
14
|
+
#
|
|
15
|
+
# @author hugo
|
|
16
|
+
# @author claude
|
|
17
|
+
class NnccProjectMigrator
|
|
18
|
+
attr_reader :results
|
|
19
|
+
|
|
20
|
+
def initialize
|
|
21
|
+
@results = []
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Migrate a single project's .nncc/ to .rosett-ai/.
|
|
25
|
+
#
|
|
26
|
+
# @param project_root [Pathname, String] path to project root
|
|
27
|
+
# @return [Hash] result with :path, :status, :message keys
|
|
28
|
+
def migrate_project(project_root)
|
|
29
|
+
root = Pathname.new(project_root)
|
|
30
|
+
perform_migration(root)
|
|
31
|
+
rescue Errno::EACCES => e
|
|
32
|
+
record_result(root, :error, "Permission denied: #{e.message}")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Scan a workspace for projects with .nncc/ and migrate them.
|
|
36
|
+
#
|
|
37
|
+
# @param workspace [Pathname, String] root directory to scan
|
|
38
|
+
# @return [Array<Hash>] results for each project found
|
|
39
|
+
def migrate_workspace(workspace)
|
|
40
|
+
find_nncc_projects(workspace).each { |path| migrate_project(path) }
|
|
41
|
+
@results
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Detect projects with .nncc/ in a workspace (dry-run).
|
|
45
|
+
#
|
|
46
|
+
# @param workspace [Pathname, String] root directory to scan
|
|
47
|
+
# @return [Array<String>] list of project paths with .nncc/
|
|
48
|
+
def detect(workspace)
|
|
49
|
+
find_nncc_projects(workspace)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def perform_migration(root)
|
|
55
|
+
nncc_dir = root.join('.nncc')
|
|
56
|
+
rosett_dir = root.join('.rosett-ai')
|
|
57
|
+
|
|
58
|
+
return record_result(root, :skipped, 'No .nncc/ directory found') unless nncc_dir.directory?
|
|
59
|
+
return record_result(root, :skipped, '.rosett-ai/ already exists') if rosett_dir.directory?
|
|
60
|
+
|
|
61
|
+
FileUtils.mv(nncc_dir.to_s, rosett_dir.to_s)
|
|
62
|
+
update_config_yml(rosett_dir)
|
|
63
|
+
record_result(root, :migrated, '.nncc/ renamed to .rosett-ai/')
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def find_nncc_projects(workspace)
|
|
67
|
+
Dir.glob(Pathname.new(workspace).join('**', '.nncc'), File::FNM_DOTMATCH).map do |nncc_path|
|
|
68
|
+
File.dirname(nncc_path)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def record_result(root, status, message)
|
|
73
|
+
result = { path: root.to_s, status: status, message: message }
|
|
74
|
+
@results << result
|
|
75
|
+
result
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def update_config_yml(rosett_dir)
|
|
79
|
+
config = rosett_dir.join('config.yml')
|
|
80
|
+
return unless config.exist?
|
|
81
|
+
|
|
82
|
+
content = File.read(config)
|
|
83
|
+
updated = content.gsub('.nncc', '.rosett-ai').gsub(/\bnncc\b/, 'rosett-ai')
|
|
84
|
+
return if content == updated
|
|
85
|
+
|
|
86
|
+
File.write(config, updated)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,123 @@
|
|
|
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 Migration
|
|
10
|
+
# Migrates behaviour/schema files from legacy ~/.claude/conf/ to
|
|
11
|
+
# XDG-compliant ~/.config/rosett-ai/conf/.
|
|
12
|
+
#
|
|
13
|
+
# Created after WP#943: global compile scope was reading from the
|
|
14
|
+
# installation directory because init placed files at ~/.claude/conf/
|
|
15
|
+
# instead of the XDG path specified in scope_hierarchy.yml.
|
|
16
|
+
class XdgMigrator
|
|
17
|
+
LEGACY_CONF = File.expand_path('~/.claude/conf')
|
|
18
|
+
MIGRATED_SUFFIX = '.migrated-to-xdg'
|
|
19
|
+
|
|
20
|
+
attr_reader :legacy_dir, :xdg_dir, :migrated
|
|
21
|
+
|
|
22
|
+
def initialize
|
|
23
|
+
@legacy_dir = Pathname.new(LEGACY_CONF)
|
|
24
|
+
@xdg_dir = RosettAi.paths.rai_conf_dir
|
|
25
|
+
@migrated = []
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Detects whether migration is needed.
|
|
29
|
+
#
|
|
30
|
+
# @return [Boolean] true if legacy dir has files and XDG dir is empty/missing
|
|
31
|
+
def migration_needed?
|
|
32
|
+
return false unless legacy_dir.join('behaviour').exist?
|
|
33
|
+
return false if legacy_dir.basename.to_s.end_with?(MIGRATED_SUFFIX)
|
|
34
|
+
|
|
35
|
+
legacy_files = Dir.glob(legacy_dir.join('behaviour', '*.yml'))
|
|
36
|
+
return false if legacy_files.empty?
|
|
37
|
+
|
|
38
|
+
xdg_files = xdg_dir.join('behaviour').exist? ? Dir.glob(xdg_dir.join('behaviour', '*.yml')) : []
|
|
39
|
+
xdg_files.empty? || legacy_has_newer_files?(legacy_files, xdg_files)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Performs the migration: copy files, then rename legacy directory.
|
|
43
|
+
#
|
|
44
|
+
# @return [Array<String>] list of migrated file basenames
|
|
45
|
+
def migrate!
|
|
46
|
+
@migrated = []
|
|
47
|
+
FileUtils.mkdir_p(xdg_dir.join('behaviour'))
|
|
48
|
+
FileUtils.mkdir_p(xdg_dir.join('schemas'))
|
|
49
|
+
|
|
50
|
+
migrate_directory('behaviour', '*.yml')
|
|
51
|
+
migrate_directory('schemas', '*.json')
|
|
52
|
+
migrate_file('adopt_redactions.yml')
|
|
53
|
+
|
|
54
|
+
mark_legacy_migrated
|
|
55
|
+
@migrated
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Returns a user-facing warning message about the pending migration.
|
|
59
|
+
#
|
|
60
|
+
# @return [String] warning text
|
|
61
|
+
def warning_message
|
|
62
|
+
legacy_count = Dir.glob(legacy_dir.join('behaviour', '*.yml')).size
|
|
63
|
+
<<~MSG.strip
|
|
64
|
+
[MIGRATION] Found #{legacy_count} behaviour file(s) at legacy path:
|
|
65
|
+
#{legacy_dir}/behaviour/
|
|
66
|
+
These will be migrated to XDG-compliant path:
|
|
67
|
+
#{xdg_dir}/behaviour/
|
|
68
|
+
Original files will be preserved at #{legacy_dir}#{MIGRATED_SUFFIX}/
|
|
69
|
+
MSG
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def legacy_has_newer_files?(legacy_files, xdg_files)
|
|
75
|
+
xdg_names = xdg_files.map { |f| File.basename(f) }
|
|
76
|
+
legacy_files.any? { |f| !xdg_names.include?(File.basename(f)) }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def migrate_directory(subdir, pattern)
|
|
80
|
+
source = legacy_dir.join(subdir)
|
|
81
|
+
return unless source.exist?
|
|
82
|
+
|
|
83
|
+
target_dir = xdg_dir.join(subdir)
|
|
84
|
+
FileUtils.mkdir_p(target_dir)
|
|
85
|
+
|
|
86
|
+
Dir.glob(source.join(pattern)).each do |file|
|
|
87
|
+
basename = File.basename(file)
|
|
88
|
+
target = target_dir.join(basename)
|
|
89
|
+
|
|
90
|
+
if target.exist?
|
|
91
|
+
RosettAi.logger.debug("XDG migration: #{subdir}/#{basename} already exists, skipping")
|
|
92
|
+
next
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
FileUtils.cp(file, target)
|
|
96
|
+
@migrated << "#{subdir}/#{basename}"
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def migrate_file(filename)
|
|
101
|
+
source = legacy_dir.join(filename)
|
|
102
|
+
return unless source.exist?
|
|
103
|
+
|
|
104
|
+
target = xdg_dir.join(filename)
|
|
105
|
+
return if target.exist?
|
|
106
|
+
|
|
107
|
+
FileUtils.cp(source.to_s, target.to_s)
|
|
108
|
+
@migrated << filename
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def mark_legacy_migrated
|
|
112
|
+
return if @migrated.empty?
|
|
113
|
+
|
|
114
|
+
migrated_path = Pathname.new("#{legacy_dir}#{MIGRATED_SUFFIX}")
|
|
115
|
+
return if migrated_path.exist?
|
|
116
|
+
|
|
117
|
+
FileUtils.mv(legacy_dir.to_s, migrated_path.to_s)
|
|
118
|
+
rescue Errno::EACCES => e
|
|
119
|
+
RosettAi.logger.warn("Cannot rename legacy conf dir: #{e.message}")
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
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 PackageManager
|
|
8
|
+
# APT package manager backend for Debian-based systems.
|
|
9
|
+
#
|
|
10
|
+
# Uses pkexec for privilege escalation (install/remove).
|
|
11
|
+
# All commands use array-form system() for security.
|
|
12
|
+
#
|
|
13
|
+
# @author hugo
|
|
14
|
+
# @author claude
|
|
15
|
+
class Apt < Base
|
|
16
|
+
DPKG_QUERY = 'dpkg-query'
|
|
17
|
+
APT_CACHE = 'apt-cache'
|
|
18
|
+
PKEXEC = 'pkexec'
|
|
19
|
+
APT_GET = 'apt-get'
|
|
20
|
+
|
|
21
|
+
# @return [String]
|
|
22
|
+
def backend_name
|
|
23
|
+
'apt'
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @return [Boolean]
|
|
27
|
+
def available?
|
|
28
|
+
system(DPKG_QUERY, '--version', out: File::NULL, err: File::NULL) == true
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Install a .deb package via pkexec + apt-get.
|
|
32
|
+
#
|
|
33
|
+
# @param package_name [String]
|
|
34
|
+
# @return [Boolean]
|
|
35
|
+
def install(package_name)
|
|
36
|
+
validate_package_name!(package_name)
|
|
37
|
+
system(PKEXEC, APT_GET, 'install', '-y', package_name, out: File::NULL)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Remove a .deb package via pkexec + apt-get.
|
|
41
|
+
#
|
|
42
|
+
# @param package_name [String]
|
|
43
|
+
# @return [Boolean]
|
|
44
|
+
def remove(package_name)
|
|
45
|
+
validate_package_name!(package_name)
|
|
46
|
+
system(PKEXEC, APT_GET, 'remove', '-y', package_name, out: File::NULL)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Check if a .deb package is installed via dpkg-query.
|
|
50
|
+
#
|
|
51
|
+
# @param package_name [String]
|
|
52
|
+
# @return [Boolean]
|
|
53
|
+
def installed?(package_name)
|
|
54
|
+
validate_package_name!(package_name)
|
|
55
|
+
system(DPKG_QUERY, '-W', '-f', '${Status}', package_name,
|
|
56
|
+
out: File::NULL, err: File::NULL) == true
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Search for available .deb packages via apt-cache.
|
|
60
|
+
#
|
|
61
|
+
# @param pattern [String]
|
|
62
|
+
# @return [Array<Hash>]
|
|
63
|
+
def available_packages(pattern)
|
|
64
|
+
validate_package_name!(pattern)
|
|
65
|
+
output = run_command(APT_CACHE, 'search', pattern)
|
|
66
|
+
return [] unless output
|
|
67
|
+
|
|
68
|
+
output.each_line.filter_map do |line|
|
|
69
|
+
name, description = line.strip.split(' - ', 2)
|
|
70
|
+
next unless name && description
|
|
71
|
+
|
|
72
|
+
{ name: name.strip, description: description.strip }
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Update APT index via pkexec + apt-get.
|
|
77
|
+
#
|
|
78
|
+
# @return [Boolean]
|
|
79
|
+
def update_index
|
|
80
|
+
system(PKEXEC, APT_GET, 'update', out: File::NULL)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
# Validate package name to prevent command injection.
|
|
86
|
+
# Debian package names: lowercase letters, digits, +, -, .
|
|
87
|
+
#
|
|
88
|
+
# @param name [String]
|
|
89
|
+
# @raise [PackageManagerError] if invalid
|
|
90
|
+
def validate_package_name!(name)
|
|
91
|
+
return if name.match?(/\A[a-z0-9][a-z0-9.+-]*\z/)
|
|
92
|
+
|
|
93
|
+
raise PackageManagerError, "Invalid package name: #{name}"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Run a command and capture stdout.
|
|
97
|
+
#
|
|
98
|
+
# @return [String, nil] stdout or nil on failure
|
|
99
|
+
def run_command(*)
|
|
100
|
+
require 'open3'
|
|
101
|
+
stdout, _status = Open3.capture2(*)
|
|
102
|
+
stdout
|
|
103
|
+
rescue StandardError
|
|
104
|
+
nil
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
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 PackageManager
|
|
8
|
+
# Abstract base class for system package management.
|
|
9
|
+
#
|
|
10
|
+
# Concrete implementations handle apt (Debian) and gem backends.
|
|
11
|
+
# All backends use array-form system() for security.
|
|
12
|
+
#
|
|
13
|
+
# @author hugo
|
|
14
|
+
# @author claude
|
|
15
|
+
class Base
|
|
16
|
+
class PackageManagerError < RosettAi::Error; end
|
|
17
|
+
|
|
18
|
+
# Install a package by name.
|
|
19
|
+
#
|
|
20
|
+
# @param package_name [String] package to install
|
|
21
|
+
# @return [Boolean] true if installed successfully
|
|
22
|
+
def install(package_name)
|
|
23
|
+
raise NotImplementedError, "#{self.class}#install must be implemented"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Remove a package by name.
|
|
27
|
+
#
|
|
28
|
+
# @param package_name [String] package to remove
|
|
29
|
+
# @return [Boolean] true if removed successfully
|
|
30
|
+
def remove(package_name)
|
|
31
|
+
raise NotImplementedError, "#{self.class}#remove must be implemented"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Check if a package is installed.
|
|
35
|
+
#
|
|
36
|
+
# @param package_name [String] package to check
|
|
37
|
+
# @return [Boolean]
|
|
38
|
+
def installed?(package_name)
|
|
39
|
+
raise NotImplementedError, "#{self.class}#installed? must be implemented"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Search for available packages matching a pattern.
|
|
43
|
+
#
|
|
44
|
+
# @param pattern [String] search pattern
|
|
45
|
+
# @return [Array<Hash>] matching packages with :name and :version keys
|
|
46
|
+
def available_packages(pattern)
|
|
47
|
+
raise NotImplementedError, "#{self.class}#available_packages must be implemented"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Update the package index.
|
|
51
|
+
#
|
|
52
|
+
# @return [Boolean] true if updated successfully
|
|
53
|
+
def update_index
|
|
54
|
+
raise NotImplementedError, "#{self.class}#update_index must be implemented"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @return [String] backend identifier
|
|
58
|
+
def backend_name
|
|
59
|
+
raise NotImplementedError, "#{self.class}#backend_name must be implemented"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @return [Boolean] true if this backend is available on the system
|
|
63
|
+
def available?
|
|
64
|
+
raise NotImplementedError, "#{self.class}#available? must be implemented"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
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 PackageManager
|
|
8
|
+
# RubyGems package manager backend.
|
|
9
|
+
#
|
|
10
|
+
# Does NOT require privilege escalation. Installs gems into
|
|
11
|
+
# the user's gem directory or the system gem path.
|
|
12
|
+
#
|
|
13
|
+
# @author hugo
|
|
14
|
+
# @author claude
|
|
15
|
+
class GemBackend < Base
|
|
16
|
+
GEM_COMMAND = 'gem'
|
|
17
|
+
|
|
18
|
+
# @return [String]
|
|
19
|
+
def backend_name
|
|
20
|
+
'gem'
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @return [Boolean]
|
|
24
|
+
def available?
|
|
25
|
+
defined?(::Gem) == 'constant'
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Install a gem.
|
|
29
|
+
#
|
|
30
|
+
# @param package_name [String]
|
|
31
|
+
# @return [Boolean]
|
|
32
|
+
def install(package_name)
|
|
33
|
+
validate_gem_name!(package_name)
|
|
34
|
+
system(GEM_COMMAND, 'install', package_name, '--no-document', out: File::NULL)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Remove a gem.
|
|
38
|
+
#
|
|
39
|
+
# @param package_name [String]
|
|
40
|
+
# @return [Boolean]
|
|
41
|
+
def remove(package_name)
|
|
42
|
+
validate_gem_name!(package_name)
|
|
43
|
+
system(GEM_COMMAND, 'uninstall', package_name, '-x', out: File::NULL)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Check if a gem is installed.
|
|
47
|
+
#
|
|
48
|
+
# @param package_name [String]
|
|
49
|
+
# @return [Boolean]
|
|
50
|
+
def installed?(package_name)
|
|
51
|
+
validate_gem_name!(package_name)
|
|
52
|
+
!Gem::Specification.find_all_by_name(package_name).empty?
|
|
53
|
+
rescue Gem::MissingSpecError
|
|
54
|
+
false
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Search for available gems matching a pattern.
|
|
58
|
+
#
|
|
59
|
+
# @param pattern [String]
|
|
60
|
+
# @return [Array<Hash>]
|
|
61
|
+
def available_packages(pattern)
|
|
62
|
+
validate_gem_name!(pattern)
|
|
63
|
+
specs = Gem::Specification.find_all_by_name(/#{Regexp.escape(pattern)}/)
|
|
64
|
+
specs.map { |s| { name: s.name, version: s.version.to_s } }
|
|
65
|
+
rescue Gem::MissingSpecError
|
|
66
|
+
[]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# No-op for gems (no index to update).
|
|
70
|
+
#
|
|
71
|
+
# @return [Boolean]
|
|
72
|
+
def update_index # rubocop:disable Naming/PredicateMethod -- imperative action (Base interface), not a predicate
|
|
73
|
+
true
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
# Validate gem name to prevent abuse.
|
|
79
|
+
# Gem names: alphanumeric, hyphens, underscores.
|
|
80
|
+
#
|
|
81
|
+
# @param name [String]
|
|
82
|
+
# @raise [PackageManagerError] if invalid
|
|
83
|
+
def validate_gem_name!(name)
|
|
84
|
+
return if name.match?(/\A[a-zA-Z0-9][a-zA-Z0-9._-]*\z/)
|
|
85
|
+
|
|
86
|
+
raise PackageManagerError, "Invalid gem name: #{name}"
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|