rosett-ai 1.3.3 → 1.5.10
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 +4 -4
- data/CHANGELOG.md +413 -22
- data/README.md +56 -42
- data/bin/raictl +13 -0
- data/conf/adopt_redactions.yml +2 -0
- data/conf/compliance/cra_rules.yml +2 -0
- data/conf/compliance/license_rules.yml +2 -0
- data/conf/mcp/server_defaults.yml +2 -0
- data/conf/mcp/trust.yml +1 -0
- data/conf/packaging/core.yml +2 -0
- data/conf/packaging/gtk4.yml +2 -0
- data/conf/packaging/qt6.yml +2 -0
- data/conf/policy/default_deny_list.yml +2 -0
- data/conf/review/cli-command-audit.yml +2 -0
- data/conf/review/design-docs.yml +2 -0
- data/conf/review/design-questionnaire.yml +2 -0
- data/conf/review/questionnaire.yml +2 -0
- data/conf/review/rosett-ai-core.yml +2 -0
- data/conf/schemas/ai_config_schema.json +14 -2
- data/conf/schemas/autoqueue.schema.json +153 -0
- data/conf/schemas/behaviour_schema.json +31 -7
- data/conf/schemas/compliance_rule_schema.json +17 -4
- data/conf/schemas/content_pack_manifest_schema.json +23 -4
- data/conf/schemas/design_schema.json +177 -42
- data/conf/schemas/engine_manifest_schema.json +22 -5
- data/conf/schemas/lockfile_schema.json +26 -4
- data/conf/schemas/mcp_server_schema.json +50 -9
- data/conf/schemas/packaging_schema.json +23 -5
- data/conf/schemas/policy_schema.json +35 -9
- data/conf/schemas/provenance_schema.json +33 -5
- data/conf/schemas/rai_config_schema.json +6 -2
- data/conf/schemas/rai_project_schema.json +8 -2
- data/conf/schemas/scope_hierarchy_schema.json +22 -5
- data/conf/schemas/target_schema.json +33 -6
- data/conf/schemas/tooling_schema.json +53 -12
- data/conf/schemas/workflow_schema.json +39 -9
- data/conf/targets/agents_md.yml +1 -0
- data/conf/targets/claude.yml +2 -0
- data/conf/tooling/tools.yml +2 -0
- data/lib/rosett_ai/adopter/local_analysis_collector.rb +5 -0
- data/lib/rosett_ai/adopter/rule_adopter.rb +40 -0
- data/lib/rosett_ai/ai_config/context_window.rb +3 -0
- data/lib/rosett_ai/ai_config/cost_controls.rb +1 -0
- data/lib/rosett_ai/ai_config/fallback_chain.rb +1 -0
- data/lib/rosett_ai/ai_config/model_router.rb +1 -0
- data/lib/rosett_ai/ai_config/validator.rb +2 -0
- data/lib/rosett_ai/authorship/attribution_compiler.rb +1 -0
- data/lib/rosett_ai/authorship/disclosure_policy.rb +2 -0
- data/lib/rosett_ai/authorship/review_validator.rb +1 -0
- data/lib/rosett_ai/authorship/trailer_generator.rb +1 -0
- data/lib/rosett_ai/backup/compressor.rb +3 -0
- data/lib/rosett_ai/backup/destination.rb +1 -0
- data/lib/rosett_ai/behaviour/manager.rb +1 -0
- data/lib/rosett_ai/compiler/backend.rb +3 -0
- data/lib/rosett_ai/compiler/backends/agents_md_backend.rb +1 -0
- data/lib/rosett_ai/compiler/backends/claude_backend.rb +1 -0
- data/lib/rosett_ai/compiler/behaviour_compiler.rb +1 -0
- data/lib/rosett_ai/compiler/compilation_pipeline.rb +1 -7
- data/lib/rosett_ai/compiler/locale_compiler.rb +2 -0
- data/lib/rosett_ai/compiler/target_profile.rb +11 -0
- data/lib/rosett_ai/completion/generator.rb +2 -0
- data/lib/rosett_ai/completion/shells/zsh_generator.rb +1 -0
- data/lib/rosett_ai/comply/checkers/cra_checker.rb +1 -0
- data/lib/rosett_ai/comply/checkers/license_checker.rb +2 -0
- data/lib/rosett_ai/comply/checkers/spdx_header_checker.rb +4 -0
- data/lib/rosett_ai/comply/runner.rb +2 -0
- data/lib/rosett_ai/composition/composer.rb +1 -0
- data/lib/rosett_ai/composition/lockfile.rb +1 -0
- data/lib/rosett_ai/composition/merge_strategy.rb +1 -0
- data/lib/rosett_ai/config/masking_secret_resolver.rb +9 -0
- data/lib/rosett_ai/config/secret_resolver.rb +4 -0
- data/lib/rosett_ai/configuration.rb +4 -0
- data/lib/rosett_ai/content/content_client.rb +15 -0
- data/lib/rosett_ai/content/pack_installer.rb +1 -0
- data/lib/rosett_ai/content/pack_manifest.rb +21 -0
- data/lib/rosett_ai/content_packs/manager.rb +1 -0
- data/lib/rosett_ai/dbus/compositor_detector.rb +2 -0
- data/lib/rosett_ai/dbus/focus_adapters/base.rb +1 -0
- data/lib/rosett_ai/dbus/focus_adapters/gnome_adapter.rb +5 -0
- data/lib/rosett_ai/dbus/focus_adapters/hyprland_adapter.rb +4 -0
- data/lib/rosett_ai/dbus/focus_adapters/i3_adapter.rb +4 -0
- data/lib/rosett_ai/dbus/focus_adapters/kwin_adapter.rb +4 -0
- data/lib/rosett_ai/dbus/focus_adapters/x11_adapter.rb +4 -0
- data/lib/rosett_ai/dbus/focus_monitor_interface.rb +1 -0
- data/lib/rosett_ai/dbus/manager_interface.rb +1 -0
- data/lib/rosett_ai/dbus/plugin_manager_interface.rb +2 -0
- data/lib/rosett_ai/dbus/rate_limiter.rb +1 -0
- data/lib/rosett_ai/dbus/service.rb +3 -0
- data/lib/rosett_ai/dbus/status_notifier_interface.rb +14 -0
- data/lib/rosett_ai/desktop/dbus_client.rb +6 -0
- data/lib/rosett_ai/desktop/gtk4_app.rb +3 -0
- data/lib/rosett_ai/desktop/gtk4_preferences.rb +1 -0
- data/lib/rosett_ai/desktop/gui_logger.rb +2 -0
- data/lib/rosett_ai/doctor/check.rb +1 -1
- data/lib/rosett_ai/doctor/checks/cache_health_check.rb +1 -0
- data/lib/rosett_ai/doctor.rb +1 -0
- data/lib/rosett_ai/documentation/reference_compiler.rb +9 -0
- data/lib/rosett_ai/documentation/translator.rb +8 -0
- data/lib/rosett_ai/engines/base_config_compiler.rb +71 -1
- data/lib/rosett_ai/engines/detector.rb +5 -0
- data/lib/rosett_ai/engines/registry.rb +7 -0
- data/lib/rosett_ai/exit_codes.rb +6 -0
- data/lib/rosett_ai/formatting.rb +1 -0
- data/lib/rosett_ai/gem_consistency_checker.rb +6 -0
- data/lib/rosett_ai/git_hooks/chain_detector.rb +1 -0
- data/lib/rosett_ai/git_hooks/installer.rb +4 -0
- data/lib/rosett_ai/git_hooks/script_generator.rb +2 -0
- data/lib/rosett_ai/gitlab/validators/supplementary_gitlab_ci_yaml_validator.rb +6 -0
- data/lib/rosett_ai/i18n/utf8_checker.rb +1 -0
- data/lib/rosett_ai/init/config_file_writer.rb +4 -0
- data/lib/rosett_ai/init/directory_builder.rb +15 -0
- data/lib/rosett_ai/init/file_copier.rb +51 -0
- data/lib/rosett_ai/init/mcp_registrar.rb +1 -0
- data/lib/rosett_ai/init/project_initializer.rb +1 -0
- data/lib/rosett_ai/licensing/license_key.rb +4 -0
- data/lib/rosett_ai/licensing/license_store.rb +11 -0
- data/lib/rosett_ai/licensing/tier.rb +9 -0
- data/lib/rosett_ai/mcp/admin/auditor.rb +1 -0
- data/lib/rosett_ai/mcp/admin/health_checker.rb +1 -0
- data/lib/rosett_ai/mcp/admin/registry.rb +1 -0
- data/lib/rosett_ai/mcp/enforcement/hook_generator.rb +27 -4
- data/lib/rosett_ai/mcp/enforcement/validator.rb +24 -3
- data/lib/rosett_ai/mcp/governance.rb +9 -0
- data/lib/rosett_ai/mcp/http_security_config.rb +22 -0
- data/lib/rosett_ai/mcp/key_hasher.rb +4 -2
- data/lib/rosett_ai/mcp/keyfile.rb +4 -0
- data/lib/rosett_ai/mcp/middleware/cors.rb +4 -0
- data/lib/rosett_ai/mcp/middleware/origin_validation.rb +1 -0
- data/lib/rosett_ai/mcp/middleware/rate_limit.rb +3 -0
- data/lib/rosett_ai/mcp/plugins.rb +1 -0
- data/lib/rosett_ai/mcp/prompts/compilation_prompt.rb +2 -0
- data/lib/rosett_ai/mcp/prompts/compliance_prompt.rb +3 -0
- data/lib/rosett_ai/mcp/prompts/diagnostics_prompt.rb +2 -0
- data/lib/rosett_ai/mcp/prompts/validation_prompt.rb +2 -0
- data/lib/rosett_ai/mcp/resources/behaviour_resource.rb +2 -0
- data/lib/rosett_ai/mcp/resources/config_resource.rb +2 -0
- data/lib/rosett_ai/mcp/resources/design_resource.rb +1 -0
- data/lib/rosett_ai/mcp/resources/hooks_resource.rb +3 -0
- data/lib/rosett_ai/mcp/resources/provenance_resource.rb +2 -0
- data/lib/rosett_ai/mcp/resources/rules_resource.rb +1 -0
- data/lib/rosett_ai/mcp/resources/schema_resource.rb +1 -0
- data/lib/rosett_ai/mcp/server.rb +7 -1
- data/lib/rosett_ai/mcp/settings/trust_manager.rb +3 -0
- data/lib/rosett_ai/mcp/tools/adopt_tool.rb +4 -0
- data/lib/rosett_ai/mcp/tools/backup_tool.rb +4 -0
- data/lib/rosett_ai/mcp/tools/behaviour_display_tool.rb +4 -0
- data/lib/rosett_ai/mcp/tools/behaviour_list_tool.rb +3 -0
- data/lib/rosett_ai/mcp/tools/behaviour_manage_tool.rb +5 -0
- data/lib/rosett_ai/mcp/tools/behaviour_show_tool.rb +4 -0
- data/lib/rosett_ai/mcp/tools/compile_status_tool.rb +4 -0
- data/lib/rosett_ai/mcp/tools/compile_tool.rb +4 -0
- data/lib/rosett_ai/mcp/tools/comply_tool.rb +4 -0
- data/lib/rosett_ai/mcp/tools/config_compile_tool.rb +4 -0
- data/lib/rosett_ai/mcp/tools/config_status_tool.rb +3 -0
- data/lib/rosett_ai/mcp/tools/content_tool.rb +5 -0
- data/lib/rosett_ai/mcp/tools/context_query_tool.rb +4 -0
- data/lib/rosett_ai/mcp/tools/design_list_tool.rb +3 -0
- data/lib/rosett_ai/mcp/tools/design_show_tool.rb +4 -0
- data/lib/rosett_ai/mcp/tools/doctor_tool.rb +4 -0
- data/lib/rosett_ai/mcp/tools/documentation_status_tool.rb +3 -0
- data/lib/rosett_ai/mcp/tools/engines_tool.rb +5 -0
- data/lib/rosett_ai/mcp/tools/hook_install_tool.rb +7 -0
- data/lib/rosett_ai/mcp/tools/hook_preview_tool.rb +33 -8
- data/lib/rosett_ai/mcp/tools/hooks_status_tool.rb +5 -0
- data/lib/rosett_ai/mcp/tools/init_tool.rb +5 -0
- data/lib/rosett_ai/mcp/tools/license_status_tool.rb +3 -0
- data/lib/rosett_ai/mcp/tools/project_tool.rb +5 -0
- data/lib/rosett_ai/mcp/tools/provenance_tool.rb +5 -0
- data/lib/rosett_ai/mcp/tools/provenance_write_tool.rb +3 -0
- data/lib/rosett_ai/mcp/tools/retrofit_tool.rb +5 -0
- data/lib/rosett_ai/mcp/tools/rule_search_tool.rb +4 -0
- data/lib/rosett_ai/mcp/tools/schema_get_tool.rb +5 -0
- data/lib/rosett_ai/mcp/tools/tooling_tool.rb +5 -0
- data/lib/rosett_ai/mcp/tools/validate_tool.rb +4 -0
- data/lib/rosett_ai/mcp/tools/workflow_execute_tool.rb +4 -0
- data/lib/rosett_ai/mcp/tools/workflow_tool.rb +5 -0
- data/lib/rosett_ai/migration/detector.rb +1 -0
- data/lib/rosett_ai/migration/nncc_config_migrator.rb +2 -0
- data/lib/rosett_ai/migration/xdg_migrator.rb +3 -1
- data/lib/rosett_ai/package_manager/apt.rb +5 -0
- data/lib/rosett_ai/package_manager/base.rb +1 -0
- data/lib/rosett_ai/package_manager/gem_backend.rb +1 -0
- data/lib/rosett_ai/packaging/variant_config.rb +12 -0
- data/lib/rosett_ai/plugins/contract.rb +1 -0
- data/lib/rosett_ai/plugins/engine_contract.rb +2 -0
- data/lib/rosett_ai/plugins/gui_contract.rb +2 -0
- data/lib/rosett_ai/plugins/mcp_contract.rb +2 -0
- data/lib/rosett_ai/plugins/registry.rb +10 -0
- data/lib/rosett_ai/policy/auditor.rb +1 -0
- data/lib/rosett_ai/policy/opt_out_scanner.rb +1 -0
- data/lib/rosett_ai/policy/tier_hierarchy.rb +2 -0
- data/lib/rosett_ai/policy/validator.rb +1 -0
- data/lib/rosett_ai/profiler.rb +7 -0
- data/lib/rosett_ai/project/drift_detector.rb +1 -0
- data/lib/rosett_ai/project/manager.rb +1 -0
- data/lib/rosett_ai/project/template_applier.rb +1 -0
- data/lib/rosett_ai/provenance/entry.rb +2 -0
- data/lib/rosett_ai/provenance/source.rb +1 -0
- data/lib/rosett_ai/provenance/store.rb +3 -0
- data/lib/rosett_ai/provenance/validator.rb +1 -0
- data/lib/rosett_ai/quorum/collector.rb +1 -0
- data/lib/rosett_ai/quorum/comparator.rb +2 -0
- data/lib/rosett_ai/quorum/strategies/adopt.rb +1 -0
- data/lib/rosett_ai/rai_config.rb +9 -0
- data/lib/rosett_ai/retrofit/engine.rb +2 -0
- data/lib/rosett_ai/retrofit/parsers/agents_md_parser.rb +8 -0
- data/lib/rosett_ai/retrofit/parsers/claude_parser.rb +10 -0
- data/lib/rosett_ai/retrofit/parsers/cursor_parser.rb +9 -0
- data/lib/rosett_ai/retrofit/secret_detector.rb +3 -0
- data/lib/rosett_ai/secrets_resolver.rb +1 -0
- data/lib/rosett_ai/session_hooks/installer.rb +130 -0
- data/lib/rosett_ai/smart_feedback/suggester.rb +4 -0
- data/lib/rosett_ai/smart_feedback/thor_middleware.rb +7 -0
- data/lib/rosett_ai/structured_logger.rb +1 -0
- data/lib/rosett_ai/telemetry/log_rotator.rb +1 -0
- data/lib/rosett_ai/telemetry.rb +1 -0
- data/lib/rosett_ai/thor/cli.rb +9 -0
- data/lib/rosett_ai/thor/tasks/adopt.rb +2 -0
- data/lib/rosett_ai/thor/tasks/backfill_user_yml.rb +207 -0
- data/lib/rosett_ai/thor/tasks/backup.rb +6 -0
- data/lib/rosett_ai/thor/tasks/behaviour.rb +12 -0
- data/lib/rosett_ai/thor/tasks/build.rb +106 -21
- data/lib/rosett_ai/thor/tasks/compile.rb +76 -32
- data/lib/rosett_ai/thor/tasks/completion.rb +9 -0
- data/lib/rosett_ai/thor/tasks/comply.rb +2 -0
- data/lib/rosett_ai/thor/tasks/config.rb +21 -5
- data/lib/rosett_ai/thor/tasks/content.rb +8 -0
- data/lib/rosett_ai/thor/tasks/dbus.rb +13 -0
- data/lib/rosett_ai/thor/tasks/design.rb +2 -0
- data/lib/rosett_ai/thor/tasks/desktop.rb +4 -0
- data/lib/rosett_ai/thor/tasks/doctor.rb +2 -0
- data/lib/rosett_ai/thor/tasks/documentation.rb +7 -0
- data/lib/rosett_ai/thor/tasks/engines.rb +8 -0
- data/lib/rosett_ai/thor/tasks/hooks.rb +8 -0
- data/lib/rosett_ai/thor/tasks/init.rb +1 -0
- data/lib/rosett_ai/thor/tasks/license.rb +8 -0
- data/lib/rosett_ai/thor/tasks/mcp.rb +40 -0
- data/lib/rosett_ai/thor/tasks/migrate.rb +2 -0
- data/lib/rosett_ai/thor/tasks/plugins.rb +15 -0
- data/lib/rosett_ai/thor/tasks/project.rb +8 -0
- data/lib/rosett_ai/thor/tasks/provenance.rb +8 -0
- data/lib/rosett_ai/thor/tasks/release.rb +8 -0
- data/lib/rosett_ai/thor/tasks/retrofit.rb +4 -0
- data/lib/rosett_ai/thor/tasks/validate.rb +1 -0
- data/lib/rosett_ai/thor/tasks/workflow.rb +14 -0
- data/lib/rosett_ai/tooling/version_checker.rb +1 -0
- data/lib/rosett_ai/ui/accessible_tui.rb +18 -0
- data/lib/rosett_ai/ui/base.rb +3 -0
- data/lib/rosett_ai/ui/gtk4.rb +19 -0
- data/lib/rosett_ai/ui/kde.rb +1 -0
- data/lib/rosett_ai/ui/qt6.rb +1 -0
- data/lib/rosett_ai/ui/registry.rb +15 -0
- data/lib/rosett_ai/ui/tui.rb +22 -0
- data/lib/rosett_ai/validators/behaviour_validator.rb +1 -0
- data/lib/rosett_ai/validators/design_validator.rb +2 -0
- data/lib/rosett_ai/validators/schema_validator.rb +4 -0
- data/lib/rosett_ai/validators/tooling_validator.rb +1 -0
- data/lib/rosett_ai/version.rb +2 -1
- data/lib/rosett_ai/version_consistency_checker.rb +6 -0
- data/lib/rosett_ai/workflow/engine.rb +1 -0
- data/lib/rosett_ai/workflow/manager.rb +1 -0
- data/lib/rosett_ai/workflow/schema_validator.rb +2 -0
- data/lib/rosett_ai/workflow/step_runner.rb +1 -0
- data/lib/rosett_ai/workflow/steps/rai_step.rb +1 -0
- data/lib/rosett_ai/yaml_loader.rb +2 -0
- data/lib/rosett_ai.rb +2 -0
- data/lib/scripts/generated/docker_hub_tags.rb +2 -0
- data/locales/ar.yml +2 -0
- data/locales/en.yml +5 -0
- data/locales/fr.yml +2 -0
- metadata +9 -189
- data/.ai-provenance.yml +0 -119
- data/.debride_whitelist +0 -186
- data/.fasterer.yml +0 -29
- data/.mdl_style.rb +0 -10
- data/.mdlrc +0 -3
- data/.mutant.yml +0 -49
- data/.namespace-allowlist +0 -42
- data/.reek.yml +0 -1040
- data/.rosett-ai/config.yml +0 -3
- data/.rspec +0 -5
- data/.rubocop.yml +0 -380
- data/.ruby-version +0 -1
- data/.yamllint +0 -51
- data/.yardopts +0 -12
- data/AI-DISCLOSURE.md +0 -48
- data/CLAUDE.md +0 -141
- data/CONTRIBUTING.md +0 -734
- data/INSTALL.md +0 -154
- data/LICENSE.md +0 -675
- data/QUICKSTART.md +0 -73
- data/Rakefile +0 -200
- data/SECURITY.md +0 -114
- data/cliff.toml +0 -52
- data/conf/behaviour/.gitkeep +0 -0
- data/conf/design/aaif_alignment.yml +0 -181
- data/conf/design/ab_testing.yml +0 -172
- data/conf/design/accessibility.yml +0 -84
- data/conf/design/ai_authorship.yml +0 -210
- data/conf/design/ai_provenance.yml +0 -224
- data/conf/design/ai_tool_configuration.yml +0 -207
- data/conf/design/architecture.yml +0 -139
- data/conf/design/autocompletion.yml +0 -115
- data/conf/design/backward_compatibility.yml +0 -112
- data/conf/design/behaviour_composition.yml +0 -246
- data/conf/design/build_rake_extraction.yml +0 -57
- data/conf/design/ci_pipeline.yml +0 -100
- data/conf/design/claude_code_configuration.yml +0 -157
- data/conf/design/compiler.yml +0 -128
- data/conf/design/comply.yml +0 -153
- data/conf/design/content_packs.yml +0 -84
- data/conf/design/desktop_integration.yml +0 -289
- data/conf/design/distribution.yml +0 -216
- data/conf/design/doctor.yml +0 -184
- data/conf/design/documentation.yml +0 -152
- data/conf/design/engine_architecture.yml +0 -257
- data/conf/design/error_handling.yml +0 -103
- data/conf/design/feature_flags.yml +0 -142
- data/conf/design/git_hooks.yml +0 -165
- data/conf/design/gui_plugins.yml +0 -475
- data/conf/design/i18n.yml +0 -84
- data/conf/design/integration_testing.yml +0 -56
- data/conf/design/licensing_system.yml +0 -88
- data/conf/design/lifecycle_management.yml +0 -208
- data/conf/design/mcp_integration.yml +0 -207
- data/conf/design/mcp_settings.yml +0 -126
- data/conf/design/migration.yml +0 -56
- data/conf/design/monitoring_observability.yml +0 -194
- data/conf/design/namespace_cleanup.yml +0 -145
- data/conf/design/plugin_test_segregation.yml +0 -145
- data/conf/design/policy_management.yml +0 -229
- data/conf/design/project_management.yml +0 -183
- data/conf/design/rai_mcp_asset_discovery.yml +0 -164
- data/conf/design/rai_mcp_server.yml +0 -605
- data/conf/design/release_management.yml +0 -117
- data/conf/design/retrofit.yml +0 -199
- data/conf/design/retrospective_analyzer.yml +0 -79
- data/conf/design/scope_hierarchy.yml +0 -352
- data/conf/design/security.yml +0 -115
- data/conf/design/session_retrospective.yml +0 -85
- data/conf/design/smart_ui_feedback.yml +0 -89
- data/conf/design/structured_logging.yml +0 -148
- data/conf/design/styles.yml +0 -123
- data/conf/design/test_peer_review.yml +0 -89
- data/conf/design/testing.yml +0 -136
- data/conf/design/threat_model.yml +0 -108
- data/conf/design/ui_framework.yml +0 -111
- data/conf/design/usage_optimization.yml +0 -122
- data/conf/design/version_management.yml +0 -60
- data/conf/design/workflow.yml +0 -227
- data/doc/AAIF_POSITIONING.md +0 -58
- data/doc/ADOPT.md +0 -224
- data/doc/AI_PROVENANCE.md +0 -139
- data/doc/ARCHITECTURE.md +0 -920
- data/doc/BEHAVIOUR.md +0 -409
- data/doc/BUILD.md +0 -138
- data/doc/CI_CD_RECIPES.md +0 -171
- data/doc/CLAUDE_SESSIONS_MOVED.md +0 -16
- data/doc/COMMAND_ANALYSIS.md +0 -229
- data/doc/CONFIGURATION.md +0 -281
- data/doc/DESIGN_AUDIT.md +0 -235
- data/doc/DESIGN_PEER_REVIEW.md +0 -771
- data/doc/DESKTOP.md +0 -447
- data/doc/ENGINES.md +0 -567
- data/doc/ENGINE_DEVELOPMENT_GUIDE.md +0 -417
- data/doc/FEATURE_AUDIT.md +0 -218
- data/doc/IMPLEMENTATION_PLAN.md +0 -669
- data/doc/INCIDENT_REPORT_2026-02-02.md +0 -251
- data/doc/MIGRATION_GUIDE.md +0 -88
- data/doc/PACKAGING.md +0 -232
- data/doc/PROJECT_DASHBOARD.md +0 -153
- data/doc/PULP_DEPLOYMENT.md +0 -164
- data/doc/QUALITY_FIX_SUMMARY.md +0 -110
- data/doc/QUICK_START.md +0 -162
- data/doc/REEK_CONFIGURATION.md +0 -166
- data/doc/REFERENCE.md +0 -253
- data/doc/REFERENCES.md +0 -324
- data/doc/SECURITY_REVIEW_CHECKLIST.md +0 -72
- data/doc/SESSION_2026-02-28_GTK4_HARDENING.md +0 -359
- data/doc/SETUP.md +0 -202
- data/doc/TEST_PEER_REVIEW.md +0 -152
- data/doc/THREAT_MODEL.md +0 -230
- data/doc/USAGE.md +0 -545
- data/doc/USER_MANUAL.md +0 -585
- data/doc/ai_test_review_checklist.md +0 -110
- data/doc/changes/2026-02-18-packaging-fpm.md +0 -155
- data/doc/changes/2026-02-19-testing-infrastructure.md +0 -221
- data/doc/changes/2026-02-20-security-implementation.md +0 -281
- data/doc/changes/2026-02-20-styles-implementation.md +0 -220
- data/doc/changes/2026-02-21-architecture-completion.md +0 -95
- data/doc/changes/2026-02-21-architecture-ui-layer.md +0 -253
- data/doc/changes/2026-02-21-cc-config-implementation.md +0 -108
- data/doc/changes/2026-02-21-ci-pipeline-implementation.md +0 -214
- data/doc/changes/2026-02-21-compiler-multi-target-pipeline.md +0 -241
- data/doc/changes/2026-02-21-config-design-show-commands.md +0 -61
- data/doc/changes/2026-02-21-design-implementation-overview.md +0 -455
- data/doc/changes/2026-02-21-lifecycle-management.md +0 -196
- data/doc/changes/2026-02-21-path-resolver.md +0 -128
- data/doc/changes/2026-02-24-ci-tmpdir-mutant-fetch.md +0 -45
- data/doc/changes/2026-03-01-ci-bundler-strategy.md +0 -120
- data/doc/changes/2026-03-20-security-hardening-phase2.md +0 -163
- data/doc/context/SESSION-HANDOFF.md +0 -69
- data/doc/context/ai-engine-usage-trends-2026.md +0 -80
- data/doc/context/plan-pluggable-engines.md +0 -590
- data/doc/decisions/001-flog-deferred.md +0 -32
- data/doc/decisions/002-path-resolution-strategy.md +0 -158
- data/doc/decisions/003-ui-adapter-selection.md +0 -193
- data/doc/decisions/004-design-document-validation.md +0 -179
- data/doc/decisions/005-package-splitting-strategy.md +0 -200
- data/doc/decisions/006-multi-engine-architecture.md +0 -147
- data/doc/decisions/007-engine-agnostic-pivot.md +0 -219
- data/doc/decisions/008-ci-bundler-strategy.md +0 -129
- data/doc/decisions/009-core-only-v1-release.md +0 -60
- data/doc/decisions/010-engine-debian-packaging.md +0 -66
- data/doc/decisions/011-context-aware-cli.md +0 -71
- data/doc/dependency_decisions.yml +0 -247
- data/doc/issues/001-wrapper-missing-environment-variables.md +0 -197
- data/doc/issues/002-embedded-ruby-wrong-prefix.md +0 -217
- data/doc/issues/003-smoke-test-false-positive.md +0 -127
- data/doc/issues/004-market-research-design-updates.md +0 -109
- data/doc/issues/005-compile-scope-coexistence.md +0 -161
- data/doc/locales/.gitkeep +0 -0
- data/doc/man/rai.1.ronn +0 -505
- data/doc/operations/packaging.md +0 -133
- data/doc/operations/rosett-ai-release.md +0 -65
- data/doc/reference/error-catalog.md +0 -107
- 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 +0 -4
- data/doc/reference/src/bibliography.bib +0 -79
- data/doc/reference/src/main.tex +0 -1288
- data/doc/reference/src/structure.tex +0 -303
- data/doc/rosett-ai-bookmarks.html +0 -301
- data/kitchen.yml +0 -46
- data/lib/rosett_ai/mcp/enforcement/.gitkeep +0 -0
- data/locales/.gitkeep +0 -0
- data/packaging/build-engine-deb.sh +0 -81
- data/packaging/scripts/postinst +0 -17
- data/packaging/scripts/postrm +0 -19
- data/packaging/scripts/prerm +0 -10
- data/packaging/wrapper.sh.template +0 -38
- data/rosett-ai.gemspec +0 -63
- data/rules/.gitkeep +0 -0
- data/scripts/publish/pulp_upload.sh +0 -123
- data/settings.json +0 -29
|
@@ -14,10 +14,14 @@ module RosettAi
|
|
|
14
14
|
# @author hugo
|
|
15
15
|
# @author claude
|
|
16
16
|
class AgentsMdParser < BaseParser
|
|
17
|
+
# Return the engine identifier string.
|
|
18
|
+
# @return [String]
|
|
17
19
|
def engine_name
|
|
18
20
|
'agents_md'
|
|
19
21
|
end
|
|
20
22
|
|
|
23
|
+
# Discover native config files for this engine.
|
|
24
|
+
# @return [Array<String>]
|
|
21
25
|
def discover
|
|
22
26
|
project_root = Pathname.new(ENV.fetch('RAI_ORIGINAL_PWD', Dir.pwd))
|
|
23
27
|
agents_md = project_root.join('AGENTS.md')
|
|
@@ -27,6 +31,10 @@ module RosettAi
|
|
|
27
31
|
[]
|
|
28
32
|
end
|
|
29
33
|
|
|
34
|
+
# Parse a native config file into rosett-ai format.
|
|
35
|
+
#
|
|
36
|
+
# @param path [Object] the path
|
|
37
|
+
# @return [Hash]
|
|
30
38
|
def parse(path)
|
|
31
39
|
validate_path!(path)
|
|
32
40
|
content = File.read(path)
|
|
@@ -7,6 +7,7 @@ require 'json'
|
|
|
7
7
|
|
|
8
8
|
module RosettAi
|
|
9
9
|
module Retrofit
|
|
10
|
+
# Engine-specific native config parsers.
|
|
10
11
|
module Parsers
|
|
11
12
|
# Retrofit parser for Claude Code configuration files.
|
|
12
13
|
#
|
|
@@ -16,14 +17,19 @@ module RosettAi
|
|
|
16
17
|
# @author hugo
|
|
17
18
|
# @author claude
|
|
18
19
|
class ClaudeParser < BaseParser
|
|
20
|
+
# @return [Array] Configuration keys recognized by this parser.
|
|
19
21
|
KNOWN_KEYS = [
|
|
20
22
|
'permissions', 'allowedTools', 'deniedTools', 'trustedTools', 'env', 'hooks', 'mcpServers', 'customApiKeyResponses', 'contextProviders', 'preferences'
|
|
21
23
|
].freeze
|
|
22
24
|
|
|
25
|
+
# Return the engine identifier string.
|
|
26
|
+
# @return [String]
|
|
23
27
|
def engine_name
|
|
24
28
|
'claude'
|
|
25
29
|
end
|
|
26
30
|
|
|
31
|
+
# Discover native config files for this engine.
|
|
32
|
+
# @return [Array<String>]
|
|
27
33
|
def discover
|
|
28
34
|
paths = []
|
|
29
35
|
global = Pathname.new(Dir.home).join('.claude', 'settings.json')
|
|
@@ -35,6 +41,10 @@ module RosettAi
|
|
|
35
41
|
paths
|
|
36
42
|
end
|
|
37
43
|
|
|
44
|
+
# Parse a native config file into rosett-ai format.
|
|
45
|
+
#
|
|
46
|
+
# @param path [Object] the path
|
|
47
|
+
# @return [Hash]
|
|
38
48
|
def parse(path)
|
|
39
49
|
validate_path!(path)
|
|
40
50
|
content = File.read(path)
|
|
@@ -16,14 +16,19 @@ module RosettAi
|
|
|
16
16
|
# @author hugo
|
|
17
17
|
# @author claude
|
|
18
18
|
class CursorParser < BaseParser
|
|
19
|
+
# @return [Array] Configuration keys recognized by this parser.
|
|
19
20
|
KNOWN_KEYS = [
|
|
20
21
|
'rules', 'model', 'temperature', 'maxTokens', 'contextFiles', 'ignoreFiles'
|
|
21
22
|
].freeze
|
|
22
23
|
|
|
24
|
+
# Return the engine identifier string.
|
|
25
|
+
# @return [String]
|
|
23
26
|
def engine_name
|
|
24
27
|
'cursor'
|
|
25
28
|
end
|
|
26
29
|
|
|
30
|
+
# Discover native config files for this engine.
|
|
31
|
+
# @return [Array<String>]
|
|
27
32
|
def discover
|
|
28
33
|
paths = []
|
|
29
34
|
project_root = Pathname.new(ENV.fetch('RAI_ORIGINAL_PWD', Dir.pwd))
|
|
@@ -37,6 +42,10 @@ module RosettAi
|
|
|
37
42
|
paths
|
|
38
43
|
end
|
|
39
44
|
|
|
45
|
+
# Parse a native config file into rosett-ai format.
|
|
46
|
+
#
|
|
47
|
+
# @param path [Object] the path
|
|
48
|
+
# @return [Hash]
|
|
40
49
|
def parse(path)
|
|
41
50
|
validate_path!(path)
|
|
42
51
|
|
|
@@ -14,6 +14,7 @@ module RosettAi
|
|
|
14
14
|
# @author hugo
|
|
15
15
|
# @author claude
|
|
16
16
|
class SecretDetector
|
|
17
|
+
# @return [Array] Regex patterns matching sensitive configuration keys.
|
|
17
18
|
SENSITIVE_KEY_PATTERNS = [
|
|
18
19
|
/api[_-]?key/i,
|
|
19
20
|
/secret/i,
|
|
@@ -24,8 +25,10 @@ module RosettAi
|
|
|
24
25
|
/private[_-]?key/i
|
|
25
26
|
].freeze
|
|
26
27
|
|
|
28
|
+
# @return [Array] String prefixes indicating a secret value.
|
|
27
29
|
SECRET_VALUE_PREFIXES = ['sk-', 'pk-', 'ghp_', 'gho_', 'ghs_', 'github_pat_', 'xoxb-', 'xoxp-'].freeze
|
|
28
30
|
|
|
31
|
+
# @return [Integer] Minimum string length to consider as a potential secret.
|
|
29
32
|
MIN_SECRET_LENGTH = 20
|
|
30
33
|
|
|
31
34
|
# @param warnings [Array<String>] mutable array for warning messages
|
|
@@ -8,6 +8,7 @@ module RosettAi
|
|
|
8
8
|
# Resolution order: ENV variable > secrets file (0600).
|
|
9
9
|
# System keyring support planned for GUI phase (P3).
|
|
10
10
|
module SecretsResolver
|
|
11
|
+
# @return [Object] File permission mode for secrets files.
|
|
11
12
|
SAFE_PERMISSIONS = 0o600
|
|
12
13
|
|
|
13
14
|
module_function
|
|
@@ -0,0 +1,130 @@
|
|
|
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
|
+
require 'fileutils'
|
|
8
|
+
|
|
9
|
+
module RosettAi
|
|
10
|
+
module SessionHooks
|
|
11
|
+
# Idempotently wires a Ruby script into the Claude Code settings.json
|
|
12
|
+
# SessionStart and PostToolUse hook sections.
|
|
13
|
+
#
|
|
14
|
+
# The installer reads the settings file, appends a new hook block if the
|
|
15
|
+
# target command is not already present, and atomically writes the result.
|
|
16
|
+
# A second install call is a no-op.
|
|
17
|
+
#
|
|
18
|
+
# @example
|
|
19
|
+
# installer = RosettAi::SessionHooks::Installer.new(
|
|
20
|
+
# script_path: '/path/to/write_heartbeat.rb'
|
|
21
|
+
# )
|
|
22
|
+
# result = installer.install
|
|
23
|
+
# result[:modified_types] #=> ["SessionStart", "PostToolUse"]
|
|
24
|
+
#
|
|
25
|
+
# @author hugo
|
|
26
|
+
# @author claude
|
|
27
|
+
class Installer
|
|
28
|
+
# Default path to the global Claude Code settings file.
|
|
29
|
+
SETTINGS_PATH = File.expand_path('~/.claude/settings.json').freeze
|
|
30
|
+
|
|
31
|
+
# Hook event types that receive the heartbeat hook.
|
|
32
|
+
HOOK_TYPES = ['SessionStart', 'PostToolUse'].freeze
|
|
33
|
+
|
|
34
|
+
# @param script_path [String] absolute path to the Ruby hook script
|
|
35
|
+
# @param timeout [Integer] hook timeout in seconds
|
|
36
|
+
# @param settings_path [String] path to settings.json (injectable for tests)
|
|
37
|
+
def initialize(script_path:, timeout: 5, settings_path: SETTINGS_PATH)
|
|
38
|
+
@script_path = script_path.to_s
|
|
39
|
+
@timeout = Integer(timeout)
|
|
40
|
+
@settings_path = settings_path.to_s
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Install the hook into SessionStart and PostToolUse if not already present.
|
|
44
|
+
#
|
|
45
|
+
# @return [Hash] result with keys :modified_types and :already_present_types
|
|
46
|
+
# @raise [RosettAi::SessionHooksError] if settings file cannot be read or written
|
|
47
|
+
def install
|
|
48
|
+
settings = load_settings
|
|
49
|
+
modified, already_present = classify_hooks(settings)
|
|
50
|
+
write_settings(settings) unless modified.empty?
|
|
51
|
+
{ modified_types: modified, already_present_types: already_present }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Check whether the hook is present in all required event types.
|
|
55
|
+
#
|
|
56
|
+
# @return [Boolean]
|
|
57
|
+
def installed?
|
|
58
|
+
settings = load_settings
|
|
59
|
+
HOOK_TYPES.all? { |ht| hook_present?(settings, ht) }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
# @return [Array<Array>] [modified_types, already_present_types]
|
|
65
|
+
def classify_hooks(settings)
|
|
66
|
+
modified = []
|
|
67
|
+
already_present = []
|
|
68
|
+
HOOK_TYPES.each do |hook_type|
|
|
69
|
+
if hook_present?(settings, hook_type)
|
|
70
|
+
already_present << hook_type
|
|
71
|
+
else
|
|
72
|
+
inject_hook(settings, hook_type)
|
|
73
|
+
modified << hook_type
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
[modified, already_present]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# @return [Hash] parsed settings.json; empty hash if file absent
|
|
80
|
+
def load_settings
|
|
81
|
+
return {} unless File.exist?(@settings_path)
|
|
82
|
+
|
|
83
|
+
JSON.parse(File.read(@settings_path))
|
|
84
|
+
rescue JSON::ParserError => e
|
|
85
|
+
raise RosettAi::SessionHooksError, "Failed to parse #{@settings_path}: #{e.message}"
|
|
86
|
+
rescue SystemCallError => e
|
|
87
|
+
raise RosettAi::SessionHooksError, "Failed to read #{@settings_path}: #{e.message}"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Atomically persist modified settings via tmp → rename.
|
|
91
|
+
def write_settings(settings)
|
|
92
|
+
content = JSON.pretty_generate(settings)
|
|
93
|
+
tmp = "#{@settings_path}.tmp"
|
|
94
|
+
FileUtils.mkdir_p(File.dirname(@settings_path))
|
|
95
|
+
File.write(tmp, content)
|
|
96
|
+
File.rename(tmp, @settings_path)
|
|
97
|
+
rescue SystemCallError => e
|
|
98
|
+
raise RosettAi::SessionHooksError, "Failed to write #{@settings_path}: #{e.message}"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# The hook entry object for the command.
|
|
102
|
+
def hook_entry
|
|
103
|
+
{ 'type' => 'command', 'command' => command_string, 'timeout' => @timeout }
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# The shell command string identifying this hook in settings.
|
|
107
|
+
def command_string
|
|
108
|
+
"ruby #{@script_path}"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Returns true if any existing hook block in hook_type already has this command.
|
|
112
|
+
def hook_present?(settings, hook_type)
|
|
113
|
+
blocks = settings.dig('hooks', hook_type) || []
|
|
114
|
+
blocks.any? { |block| block_has_command?(block) }
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Returns true if the given hook block contains the target command.
|
|
118
|
+
def block_has_command?(block)
|
|
119
|
+
(block['hooks'] || []).any? { |h| h['command'] == command_string }
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Appends a new hook block containing the hook entry to the given event type.
|
|
123
|
+
def inject_hook(settings, hook_type)
|
|
124
|
+
hooks = settings['hooks'] ||= {}
|
|
125
|
+
hooks[hook_type] ||= []
|
|
126
|
+
hooks[hook_type] << { 'hooks' => [hook_entry] }
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
# Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
|
|
5
5
|
|
|
6
6
|
module RosettAi
|
|
7
|
+
# Intelligent CLI command and flag suggestions.
|
|
7
8
|
module SmartFeedback
|
|
8
9
|
# Provides "Did you mean?" suggestions using Levenshtein distance.
|
|
9
10
|
#
|
|
@@ -14,8 +15,11 @@ module RosettAi
|
|
|
14
15
|
# @author hugo
|
|
15
16
|
# @author claude
|
|
16
17
|
class Suggester
|
|
18
|
+
# @return [Integer] Maximum number of feedback suggestions to show.
|
|
17
19
|
MAX_SUGGESTIONS = 3
|
|
20
|
+
# @return [Integer] Minimum similarity score for command suggestions.
|
|
18
21
|
COMMAND_THRESHOLD = 2
|
|
22
|
+
# @return [Integer] Minimum similarity score for flag suggestions.
|
|
19
23
|
FLAG_THRESHOLD = 3
|
|
20
24
|
|
|
21
25
|
# Find suggestion matches for a given input.
|
|
@@ -23,6 +23,13 @@ module RosettAi
|
|
|
23
23
|
|
|
24
24
|
# Prepended module that wraps Thor command dispatch with smart feedback.
|
|
25
25
|
module CommandDispatch
|
|
26
|
+
# Dispatch a Thor command with smart feedback middleware.
|
|
27
|
+
#
|
|
28
|
+
# @param meth [Object] the meth
|
|
29
|
+
# @param given_args [Object] the given args
|
|
30
|
+
# @param given_opts [Object] the given opts
|
|
31
|
+
# @param config [Object] the config
|
|
32
|
+
# @return [Object]
|
|
26
33
|
def dispatch(meth, given_args, given_opts, config)
|
|
27
34
|
super
|
|
28
35
|
rescue ::Thor::UndefinedCommandError => e
|
|
@@ -27,6 +27,7 @@ module RosettAi
|
|
|
27
27
|
# @author hugo
|
|
28
28
|
# @author claude
|
|
29
29
|
class StructuredLogger < ::Logger
|
|
30
|
+
# @return [Array] Supported structured log output formats.
|
|
30
31
|
VALID_FORMATS = ['text', 'json'].freeze
|
|
31
32
|
|
|
32
33
|
# @return [String] correlation ID for the current invocation (UUID v4)
|
data/lib/rosett_ai/telemetry.rb
CHANGED
|
@@ -13,6 +13,7 @@ module RosettAi
|
|
|
13
13
|
# @author claude
|
|
14
14
|
# @see conf/design/monitoring_observability.yml
|
|
15
15
|
module Telemetry
|
|
16
|
+
# @return [Array] Supported level values.
|
|
16
17
|
LEVELS = ['debug', 'info', 'warn', 'error'].freeze
|
|
17
18
|
|
|
18
19
|
# @return [Boolean] true if telemetry is enabled via environment variable
|
data/lib/rosett_ai/thor/cli.rb
CHANGED
|
@@ -7,9 +7,11 @@ require 'thor'
|
|
|
7
7
|
require 'rainbow'
|
|
8
8
|
|
|
9
9
|
module RosettAi
|
|
10
|
+
# Thor CLI command framework and task definitions.
|
|
10
11
|
module Thor
|
|
11
12
|
# Main CLI entry point for Rosett-AI commands
|
|
12
13
|
class CLI < ::Thor
|
|
14
|
+
# @return [Array] Thor commands excluded from user-facing help.
|
|
13
15
|
INTERNAL_COMMANDS = ['build', 'release', 'documentation', 'dbus', 'desktop'].freeze
|
|
14
16
|
|
|
15
17
|
def self.exit_on_failure?
|
|
@@ -89,6 +91,8 @@ module RosettAi
|
|
|
89
91
|
end
|
|
90
92
|
|
|
91
93
|
desc 'version', 'Display rosett-ai version'
|
|
94
|
+
# Return the version string.
|
|
95
|
+
# @return [String]
|
|
92
96
|
def version
|
|
93
97
|
puts ::I18n.t('rosett_ai.cli.version', version: RosettAi::VERSION)
|
|
94
98
|
build_info = RosettAi.root.join('..', 'etc', 'BUILD_INFO')
|
|
@@ -146,6 +150,9 @@ module RosettAi
|
|
|
146
150
|
desc 'config SUBCOMMAND', 'Compile and inspect AI tool configurations'
|
|
147
151
|
subcommand 'config', RosettAi::Thor::Tasks::Config
|
|
148
152
|
|
|
153
|
+
desc 'backfill-user-yml', 'Backfill user.yml from ~/.claude/settings.json'
|
|
154
|
+
subcommand 'backfill-user-yml', RosettAi::Thor::Tasks::BackfillUserYml
|
|
155
|
+
|
|
149
156
|
desc 'tooling SUBCOMMAND', 'Validate CI/CD files and check version consistency'
|
|
150
157
|
subcommand 'tooling', RosettAi::Thor::Tasks::Tooling
|
|
151
158
|
|
|
@@ -195,6 +202,8 @@ module RosettAi
|
|
|
195
202
|
subcommand 'plugins', RosettAi::Thor::Tasks::Plugins
|
|
196
203
|
|
|
197
204
|
desc 'tree', 'Print a tree of all available commands'
|
|
205
|
+
# Display the CLI command tree.
|
|
206
|
+
# @return [void]
|
|
198
207
|
def tree
|
|
199
208
|
say 'rai', :blue
|
|
200
209
|
leaves, groups = tree_partition_entries(self.class)
|
|
@@ -0,0 +1,207 @@
|
|
|
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 'thor'
|
|
7
|
+
require 'json'
|
|
8
|
+
require 'rainbow'
|
|
9
|
+
|
|
10
|
+
module RosettAi
|
|
11
|
+
module Thor
|
|
12
|
+
module Tasks
|
|
13
|
+
# Backfill user.yml from the live Claude Code settings.json.
|
|
14
|
+
#
|
|
15
|
+
# Reads ~/.claude/settings.json and merges any permission entries or env vars
|
|
16
|
+
# not yet covered by the engine's user.yml source file. Shows a summary and
|
|
17
|
+
# prompts for confirmation before writing — never auto-applies unless --yes is
|
|
18
|
+
# given. Idempotent: re-running when nothing is uncovered is a no-op.
|
|
19
|
+
class BackfillUserYml < ::Thor
|
|
20
|
+
default_task :backfill
|
|
21
|
+
|
|
22
|
+
SETTINGS_PATH = File.expand_path('~/.claude/settings.json')
|
|
23
|
+
|
|
24
|
+
desc 'backfill', 'Backfill user.yml from ~/.claude/settings.json'
|
|
25
|
+
long_desc <<~LONGDESC
|
|
26
|
+
Reads ~/.claude/settings.json and merges any entries not yet captured
|
|
27
|
+
in the engine's user.yml YAML source. Shows what would be added, then
|
|
28
|
+
prompts for confirmation before writing.
|
|
29
|
+
|
|
30
|
+
Flags:
|
|
31
|
+
--target PATH YAML file to update (auto-detected if omitted)
|
|
32
|
+
--yes Apply without confirmation prompt
|
|
33
|
+
|
|
34
|
+
EXAMPLES
|
|
35
|
+
|
|
36
|
+
raictl backfill-user-yml
|
|
37
|
+
raictl backfill-user-yml --target conf/config/user.yml
|
|
38
|
+
raictl backfill-user-yml --yes
|
|
39
|
+
|
|
40
|
+
Related commands: raictl config compile, raictl config show user
|
|
41
|
+
LONGDESC
|
|
42
|
+
method_option :target, type: :string, default: nil,
|
|
43
|
+
desc: 'YAML file to update (default: auto-detected engine user.yml)'
|
|
44
|
+
method_option :yes, type: :boolean, default: false,
|
|
45
|
+
desc: 'Apply without confirmation'
|
|
46
|
+
|
|
47
|
+
# Run the backfill.
|
|
48
|
+
# @return [void]
|
|
49
|
+
def backfill
|
|
50
|
+
target_path = resolve_target
|
|
51
|
+
raise ::Thor::Error, "settings.json not found at #{SETTINGS_PATH}" unless File.exist?(SETTINGS_PATH)
|
|
52
|
+
|
|
53
|
+
unless File.exist?(target_path)
|
|
54
|
+
raise ::Thor::Error,
|
|
55
|
+
"user.yml not found at #{target_path}. Specify --target or install the engine."
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
settings = JSON.parse(File.read(SETTINGS_PATH))
|
|
59
|
+
user_yml = YAML.safe_load_file(target_path)
|
|
60
|
+
compiled = extract_compiled_stub(user_yml)
|
|
61
|
+
uncovered = find_uncovered(compiled, settings)
|
|
62
|
+
|
|
63
|
+
if uncovered.empty?
|
|
64
|
+
puts Rainbow('Nothing to backfill — user.yml already covers all entries.').green
|
|
65
|
+
return
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
total = count_leaves(uncovered)
|
|
69
|
+
say "Target : #{target_path}"
|
|
70
|
+
say "Found #{total} entr#{total == 1 ? 'y' : 'ies'} not in user.yml:\n"
|
|
71
|
+
print_uncovered(uncovered)
|
|
72
|
+
say
|
|
73
|
+
|
|
74
|
+
unless options[:yes]
|
|
75
|
+
answer = ask("Apply #{total} entr#{total == 1 ? 'y' : 'ies'} to user.yml? [y/N]")
|
|
76
|
+
unless answer.strip.downcase == 'y'
|
|
77
|
+
say 'Aborted — no changes written.'
|
|
78
|
+
return
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
apply_uncovered!(user_yml, uncovered)
|
|
83
|
+
warn_unmapped(uncovered)
|
|
84
|
+
File.write(target_path, user_yml.to_yaml)
|
|
85
|
+
say Rainbow("Wrote #{target_path} (#{total} entr#{total == 1 ? 'y' : 'ies'} added).").green
|
|
86
|
+
say 'Run `raictl config compile --simulate --verbose` to preview compiled output.'
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
def resolve_target
|
|
92
|
+
return File.expand_path(options[:target]) if options[:target]
|
|
93
|
+
|
|
94
|
+
candidates = [
|
|
95
|
+
File.expand_path('../../rosett-ai-engine-claude/conf/config/user.yml',
|
|
96
|
+
RosettAi.root),
|
|
97
|
+
find_installed_engine_user_yml
|
|
98
|
+
].compact
|
|
99
|
+
found = candidates.find { |p| File.exist?(p) }
|
|
100
|
+
return found if found
|
|
101
|
+
|
|
102
|
+
raise ::Thor::Error,
|
|
103
|
+
"Cannot auto-detect user.yml. Use --target PATH.\nTried:\n" \
|
|
104
|
+
"#{candidates.map { |p| " #{p}" }.join("\n")}"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def find_installed_engine_user_yml
|
|
108
|
+
require 'rubygems'
|
|
109
|
+
spec = Gem::Specification.find_by_name('rosett-ai-engine-claude')
|
|
110
|
+
File.join(spec.gem_dir, 'conf', 'config', 'user.yml')
|
|
111
|
+
rescue Gem::MissingSpecError
|
|
112
|
+
nil
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Build a minimal compiled-form stub from user.yml so we can diff at the
|
|
116
|
+
# same key-naming level as settings.json (camelCase for JSON keys).
|
|
117
|
+
def extract_compiled_stub(user_yml_data)
|
|
118
|
+
stub = {}
|
|
119
|
+
perms = user_yml_data['permissions']
|
|
120
|
+
if perms.is_a?(Hash)
|
|
121
|
+
stub['permissions'] = {}
|
|
122
|
+
['allow', 'deny', 'ask'].each do |k|
|
|
123
|
+
stub['permissions'][k] = Array(perms[k]) if perms.key?(k)
|
|
124
|
+
end
|
|
125
|
+
if perms.key?('additional_directories')
|
|
126
|
+
stub['permissions']['additionalDirectories'] = Array(perms['additional_directories'])
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
env = user_yml_data['env']
|
|
130
|
+
stub['env'] = env.transform_values(&:to_s) if env.is_a?(Hash)
|
|
131
|
+
stub
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def find_uncovered(source_data, existing_data)
|
|
135
|
+
existing_data.each_with_object({}) do |(key, existing_val), result|
|
|
136
|
+
if source_data.key?(key)
|
|
137
|
+
uncovered = uncovered_value(source_data[key], existing_val)
|
|
138
|
+
result[key] = uncovered if uncovered
|
|
139
|
+
else
|
|
140
|
+
result[key] = existing_val
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def uncovered_value(source_val, existing_val)
|
|
146
|
+
if source_val.is_a?(Array) && existing_val.is_a?(Array)
|
|
147
|
+
extra = existing_val - source_val
|
|
148
|
+
extra unless extra.empty?
|
|
149
|
+
elsif source_val.is_a?(Hash) && existing_val.is_a?(Hash)
|
|
150
|
+
sub = find_uncovered(source_val, existing_val)
|
|
151
|
+
sub unless sub.empty?
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def apply_uncovered!(user_yml_data, uncovered)
|
|
156
|
+
user_yml_data['permissions'] ||= {}
|
|
157
|
+
perms_uncovered = uncovered['permissions'] || {}
|
|
158
|
+
['allow', 'deny', 'ask'].each do |k|
|
|
159
|
+
next unless perms_uncovered.key?(k)
|
|
160
|
+
|
|
161
|
+
user_yml_data['permissions'][k] =
|
|
162
|
+
Array(user_yml_data['permissions'][k]) + perms_uncovered[k]
|
|
163
|
+
end
|
|
164
|
+
if perms_uncovered.key?('additionalDirectories')
|
|
165
|
+
user_yml_data['permissions']['additional_directories'] =
|
|
166
|
+
Array(user_yml_data['permissions']['additional_directories']) +
|
|
167
|
+
perms_uncovered['additionalDirectories']
|
|
168
|
+
end
|
|
169
|
+
return unless uncovered.key?('env')
|
|
170
|
+
|
|
171
|
+
user_yml_data['env'] ||= {}
|
|
172
|
+
uncovered['env'].each { |k, v| user_yml_data['env'][k] = v }
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def warn_unmapped(uncovered)
|
|
176
|
+
other = uncovered.except('permissions', 'env')
|
|
177
|
+
return if other.empty?
|
|
178
|
+
|
|
179
|
+
say Rainbow("\nWarning: the following keys have no direct YAML mapping and were skipped:").yellow
|
|
180
|
+
other.each { |k, v| say " #{k}: #{v.inspect}" }
|
|
181
|
+
say 'Add them manually if needed.'
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def count_leaves(hash)
|
|
185
|
+
hash.sum do |_k, v|
|
|
186
|
+
case v
|
|
187
|
+
when Hash then count_leaves(v)
|
|
188
|
+
when Array then v.size
|
|
189
|
+
else 1
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def print_uncovered(uncovered, prefix = '')
|
|
195
|
+
uncovered.each do |key, val|
|
|
196
|
+
path = prefix.empty? ? key.to_s : "#{prefix}.#{key}"
|
|
197
|
+
case val
|
|
198
|
+
when Hash then print_uncovered(val, path)
|
|
199
|
+
when Array then val.each { |e| say " + #{path}[]: #{e}" }
|
|
200
|
+
else say " + #{path}: #{val.inspect}"
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
@@ -67,6 +67,8 @@ module RosettAi
|
|
|
67
67
|
default: nil,
|
|
68
68
|
desc: 'Output file path (shorthand for --destination)'
|
|
69
69
|
|
|
70
|
+
# Create the resource.
|
|
71
|
+
# @return [void]
|
|
70
72
|
def create
|
|
71
73
|
validate_options!
|
|
72
74
|
compressor = resolve_compressor
|
|
@@ -105,6 +107,10 @@ module RosettAi
|
|
|
105
107
|
default: true,
|
|
106
108
|
desc: 'Restore local scope entries'
|
|
107
109
|
|
|
110
|
+
# Restore configuration from a backup archive.
|
|
111
|
+
#
|
|
112
|
+
# @param archive_path [Object] the archive path
|
|
113
|
+
# @return [void]
|
|
108
114
|
def restore(archive_path)
|
|
109
115
|
validate_archive!(archive_path)
|
|
110
116
|
entries = extract_entries(archive_path)
|
|
@@ -37,6 +37,10 @@ module RosettAi
|
|
|
37
37
|
default: ENV.fetch('USER', 'unknown'),
|
|
38
38
|
desc: 'Author of the behaviour file'
|
|
39
39
|
|
|
40
|
+
# Add a new entry.
|
|
41
|
+
#
|
|
42
|
+
# @param name [Object] the name
|
|
43
|
+
# @return [void]
|
|
40
44
|
def add(name)
|
|
41
45
|
name = RosettAi::TextSanitizer.normalize_nfc(name)
|
|
42
46
|
return if behaviour_exists?(name)
|
|
@@ -278,6 +282,8 @@ module RosettAi
|
|
|
278
282
|
enum: ['table', 'json', 'yaml'],
|
|
279
283
|
desc: 'Output format'
|
|
280
284
|
|
|
285
|
+
# Display a summary overview of behaviours.
|
|
286
|
+
# @return [void]
|
|
281
287
|
def display
|
|
282
288
|
behaviours = load_all_behaviours
|
|
283
289
|
|
|
@@ -306,6 +312,8 @@ module RosettAi
|
|
|
306
312
|
|
|
307
313
|
raictl behaviour list
|
|
308
314
|
LONGDESC
|
|
315
|
+
# List available items.
|
|
316
|
+
# @return [void]
|
|
309
317
|
def list
|
|
310
318
|
files = behaviour_files
|
|
311
319
|
if files.empty?
|
|
@@ -329,6 +337,10 @@ module RosettAi
|
|
|
329
337
|
raictl behaviour show code_generation
|
|
330
338
|
raictl behaviour show security_rules
|
|
331
339
|
LONGDESC
|
|
340
|
+
# Display detailed information for the named item.
|
|
341
|
+
#
|
|
342
|
+
# @param name [Object] the name
|
|
343
|
+
# @return [void]
|
|
332
344
|
def show(name)
|
|
333
345
|
name = RosettAi::TextSanitizer.normalize_nfc(name)
|
|
334
346
|
file_path = find_behaviour_file(name)
|