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,71 @@
|
|
|
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 Policy
|
|
8
|
+
# Manages glob-based deny list patterns for AI tool access control.
|
|
9
|
+
#
|
|
10
|
+
# Deny list patterns prevent AI tools from reading or modifying
|
|
11
|
+
# sensitive files such as SSH keys, credentials, and secrets.
|
|
12
|
+
# Patterns are compiled to engine-specific formats during compilation.
|
|
13
|
+
#
|
|
14
|
+
# All patterns are sanitized (ANSI/control chars stripped) and
|
|
15
|
+
# validated (path traversal rejected) on initialization.
|
|
16
|
+
#
|
|
17
|
+
# @author hugo
|
|
18
|
+
# @author claude
|
|
19
|
+
class DenyList
|
|
20
|
+
PATH_TRAVERSAL_PATTERN = %r{(?:^|/)\.\./} # matches ../ at start or after /
|
|
21
|
+
|
|
22
|
+
attr_reader :patterns
|
|
23
|
+
|
|
24
|
+
# @param patterns [Array<String>] glob patterns to deny
|
|
25
|
+
# @raise [RosettAi::PolicyError] if any pattern contains path traversal
|
|
26
|
+
def initialize(patterns: [])
|
|
27
|
+
sanitized = patterns.map { |pat| RosettAi::TextSanitizer.strip_ansi(pat) }
|
|
28
|
+
validate_patterns!(sanitized)
|
|
29
|
+
@patterns = sanitized.map(&:freeze).freeze
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Tests whether a file path matches any deny pattern.
|
|
33
|
+
#
|
|
34
|
+
# @param path [String] relative file path to test
|
|
35
|
+
# @return [Boolean] true if the path matches any deny pattern
|
|
36
|
+
def denied?(path)
|
|
37
|
+
@patterns.any? { |pattern| File.fnmatch?(pattern, path, File::FNM_PATHNAME | File::FNM_DOTMATCH) }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Merges another deny list into this one (union of patterns).
|
|
41
|
+
#
|
|
42
|
+
# @param other [DenyList] deny list to merge
|
|
43
|
+
# @return [DenyList] new deny list with combined patterns
|
|
44
|
+
def merge(other)
|
|
45
|
+
self.class.new(patterns: (@patterns + other.patterns).uniq)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @return [Integer] number of patterns
|
|
49
|
+
def size
|
|
50
|
+
@patterns.size
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @return [Boolean] true if no patterns defined
|
|
54
|
+
def empty?
|
|
55
|
+
@patterns.empty?
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def validate_patterns!(patterns)
|
|
61
|
+
patterns.each do |pattern|
|
|
62
|
+
next unless PATH_TRAVERSAL_PATTERN.match?(pattern)
|
|
63
|
+
|
|
64
|
+
raise RosettAi::PolicyError,
|
|
65
|
+
"Deny-list pattern contains path traversal: '#{pattern}'. " \
|
|
66
|
+
'Patterns must not reference parent directories with ..'
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
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 Policy
|
|
8
|
+
# Scans files for AI-MODIFICATION opt-out markers.
|
|
9
|
+
#
|
|
10
|
+
# Files containing +# AI-MODIFICATION: disabled+ are added to the
|
|
11
|
+
# protected files list during compilation.
|
|
12
|
+
class OptOutScanner
|
|
13
|
+
MARKER = 'AI-MODIFICATION: disabled'
|
|
14
|
+
|
|
15
|
+
# Scans a single file for opt-out markers.
|
|
16
|
+
#
|
|
17
|
+
# @param path [String] absolute or relative file path
|
|
18
|
+
# @return [Boolean] true if the file contains an opt-out marker
|
|
19
|
+
def opted_out?(path)
|
|
20
|
+
return false unless File.exist?(path)
|
|
21
|
+
return false unless File.readable?(path)
|
|
22
|
+
|
|
23
|
+
File.foreach(path).any? { |line| line.include?(MARKER) }
|
|
24
|
+
rescue StandardError
|
|
25
|
+
false
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Scans multiple files and returns those with opt-out markers.
|
|
29
|
+
#
|
|
30
|
+
# @param paths [Array<String>] file paths to scan
|
|
31
|
+
# @return [Array<String>] paths that contain opt-out markers
|
|
32
|
+
def scan(paths)
|
|
33
|
+
paths.select { |path| opted_out?(path) }
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
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 Policy
|
|
8
|
+
# Compiles policy definitions to engine-specific output.
|
|
9
|
+
#
|
|
10
|
+
# Translates deny lists, protected files, and rate limits into
|
|
11
|
+
# engine-native configuration formats. When a parent policy is
|
|
12
|
+
# provided, enforces tighten-only inheritance at compile time.
|
|
13
|
+
#
|
|
14
|
+
# @author hugo
|
|
15
|
+
# @author claude
|
|
16
|
+
class PolicyCompiler
|
|
17
|
+
# @param deny_list [DenyList] deny list to compile
|
|
18
|
+
# @param protected_files [ProtectedFiles] protected files to compile
|
|
19
|
+
# @param parent_deny_list [DenyList, nil] parent policy deny list for scope enforcement
|
|
20
|
+
def initialize(deny_list:, protected_files:, parent_deny_list: nil)
|
|
21
|
+
@deny_list = deny_list
|
|
22
|
+
@protected_files = protected_files
|
|
23
|
+
@parent_deny_list = parent_deny_list
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Compiles policies for a specific engine.
|
|
27
|
+
#
|
|
28
|
+
# @param engine [String] target engine name
|
|
29
|
+
# @return [Hash] with +:settings+ (Hash), +:annotations+ (String), +:warnings+ (Array)
|
|
30
|
+
def compile(engine:)
|
|
31
|
+
warnings = []
|
|
32
|
+
enforce_scope!(warnings)
|
|
33
|
+
settings = compile_deny_list(engine, warnings)
|
|
34
|
+
annotations = compile_protected_files(engine)
|
|
35
|
+
|
|
36
|
+
{ settings: settings, annotations: annotations, warnings: warnings }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def enforce_scope!(warnings)
|
|
42
|
+
return unless @parent_deny_list
|
|
43
|
+
|
|
44
|
+
hierarchy = RosettAi::Policy::TierHierarchy.new
|
|
45
|
+
result = hierarchy.validate_tighten_only(parent: @parent_deny_list, child: @deny_list)
|
|
46
|
+
return if result[:valid]
|
|
47
|
+
|
|
48
|
+
result[:violations].each { |v| warnings << v }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def compile_deny_list(engine, warnings)
|
|
52
|
+
return {} if @deny_list.empty?
|
|
53
|
+
|
|
54
|
+
case engine
|
|
55
|
+
when 'claude'
|
|
56
|
+
{ 'deniedPaths' => @deny_list.patterns }
|
|
57
|
+
when 'cursor'
|
|
58
|
+
{ 'ignore_patterns' => @deny_list.patterns }
|
|
59
|
+
when 'agents_md'
|
|
60
|
+
{ 'denied_access' => @deny_list.patterns }
|
|
61
|
+
else
|
|
62
|
+
warnings << "Engine '#{engine}' may not support deny list compilation"
|
|
63
|
+
{ 'deny_patterns' => @deny_list.patterns }
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def compile_protected_files(engine)
|
|
68
|
+
return '' if @protected_files.empty?
|
|
69
|
+
|
|
70
|
+
case engine
|
|
71
|
+
when 'agents_md'
|
|
72
|
+
lines = @protected_files.paths.map { |path| "- `#{path}`: READ-ONLY (AI must not modify)" }
|
|
73
|
+
"## Protected Files\n\n#{lines.join("\n")}"
|
|
74
|
+
when 'claude', 'cursor'
|
|
75
|
+
lines = @protected_files.paths.map { |path| "# Protected (read-only): #{path}" }
|
|
76
|
+
lines.join("\n")
|
|
77
|
+
else
|
|
78
|
+
lines = @protected_files.paths.map { |path| "- #{path} (read-only)" }
|
|
79
|
+
lines.join("\n")
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
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 Policy
|
|
8
|
+
# Manages the list of files that AI tools may read but not modify.
|
|
9
|
+
#
|
|
10
|
+
# Protected files are compiled to engine-specific read-only annotations.
|
|
11
|
+
# Human override requires an explicit waiver in the policy configuration.
|
|
12
|
+
class ProtectedFiles
|
|
13
|
+
attr_reader :paths
|
|
14
|
+
|
|
15
|
+
# @param paths [Array<String>] relative file paths
|
|
16
|
+
def initialize(paths: [])
|
|
17
|
+
@paths = paths.map(&:freeze).freeze
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Tests whether a file path is protected (read-only for AI).
|
|
21
|
+
#
|
|
22
|
+
# @param path [String] relative file path
|
|
23
|
+
# @return [Boolean] true if the file is protected
|
|
24
|
+
def protected?(path)
|
|
25
|
+
@paths.include?(path)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Merges another protected files list (union of paths).
|
|
29
|
+
#
|
|
30
|
+
# @param other [ProtectedFiles] protected files to merge
|
|
31
|
+
# @return [ProtectedFiles] new list with combined paths
|
|
32
|
+
def merge(other)
|
|
33
|
+
self.class.new(paths: (@paths + other.paths).uniq)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @return [Integer] number of protected files
|
|
37
|
+
def size
|
|
38
|
+
@paths.size
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @return [Boolean] true if no files protected
|
|
42
|
+
def empty?
|
|
43
|
+
@paths.empty?
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
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 Policy
|
|
8
|
+
# Enforces tighten-only policy inheritance across organisational tiers.
|
|
9
|
+
#
|
|
10
|
+
# Policy hierarchy: org > team > project. Child tiers can add patterns
|
|
11
|
+
# or restrictions but can never remove patterns set by parent tiers.
|
|
12
|
+
class TierHierarchy
|
|
13
|
+
TIERS = ['mandatory', 'advisory', 'informational'].freeze
|
|
14
|
+
SCOPES = ['org', 'team', 'project'].freeze
|
|
15
|
+
|
|
16
|
+
# Validates that a child policy does not loosen a parent policy.
|
|
17
|
+
#
|
|
18
|
+
# @param parent [DenyList] parent deny list
|
|
19
|
+
# @param child [DenyList] child deny list
|
|
20
|
+
# @return [Hash] with +:valid+ (Boolean) and +:violations+ (Array<String>)
|
|
21
|
+
def validate_tighten_only(parent:, child:)
|
|
22
|
+
removed = parent.patterns - child.patterns
|
|
23
|
+
return { valid: true, violations: [] } if removed.empty?
|
|
24
|
+
|
|
25
|
+
violations = removed.map do |pattern|
|
|
26
|
+
"Cannot remove deny-list pattern from parent policy: #{pattern}"
|
|
27
|
+
end
|
|
28
|
+
{ valid: false, violations: violations }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Validates that a policy tier is valid.
|
|
32
|
+
#
|
|
33
|
+
# @param tier [String] policy tier name
|
|
34
|
+
# @return [Boolean] true if valid
|
|
35
|
+
def valid_tier?(tier)
|
|
36
|
+
TIERS.include?(tier)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Validates that a scope is valid.
|
|
40
|
+
#
|
|
41
|
+
# @param scope [String] scope name
|
|
42
|
+
# @return [Boolean] true if valid
|
|
43
|
+
def valid_scope?(scope)
|
|
44
|
+
SCOPES.include?(scope)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
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_schemer'
|
|
7
|
+
|
|
8
|
+
module RosettAi
|
|
9
|
+
module Policy
|
|
10
|
+
# Validates policy files against the policy JSON Schema.
|
|
11
|
+
#
|
|
12
|
+
# Sanitizes all string values (ANSI/control char stripping) before
|
|
13
|
+
# schema validation to prevent injection via policy definitions.
|
|
14
|
+
#
|
|
15
|
+
# @author hugo
|
|
16
|
+
# @author claude
|
|
17
|
+
class Validator
|
|
18
|
+
SCHEMA_PATH = File.expand_path('../../../conf/schemas/policy_schema.json', __dir__)
|
|
19
|
+
|
|
20
|
+
# @return [Array<String>] validation errors (empty if valid)
|
|
21
|
+
def validate(data)
|
|
22
|
+
sanitized = RosettAi::TextSanitizer.sanitize_for_display(data)
|
|
23
|
+
schema = JSONSchemer.schema(Pathname.new(SCHEMA_PATH))
|
|
24
|
+
schema.validate(sanitized).map do |error|
|
|
25
|
+
"#{error['data_pointer']}: #{error['type']}"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @return [Boolean] true if data passes schema validation
|
|
30
|
+
def valid?(data)
|
|
31
|
+
validate(data).empty?
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
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
|
+
# Lightweight profiler for CLI command performance measurement.
|
|
8
|
+
#
|
|
9
|
+
# Enabled via `RAI_PROFILE=1` environment variable.
|
|
10
|
+
# Output goes to stderr to preserve piped command compatibility.
|
|
11
|
+
# Uses monotonic clock for accurate elapsed time measurement.
|
|
12
|
+
#
|
|
13
|
+
# @example
|
|
14
|
+
# profiler = RosettAi::Profiler.new('compile')
|
|
15
|
+
# profiler.measure('load') { load_files }
|
|
16
|
+
# profiler.measure('validate') { validate_files }
|
|
17
|
+
# profiler.report
|
|
18
|
+
#
|
|
19
|
+
# @author hugo
|
|
20
|
+
# @author claude
|
|
21
|
+
class Profiler
|
|
22
|
+
# @return [Boolean] true if profiling is enabled
|
|
23
|
+
def self.enabled?
|
|
24
|
+
ENV['RAI_PROFILE'] == '1'
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @return [Profiler] real profiler if enabled, no-op otherwise
|
|
28
|
+
def self.for(command_name)
|
|
29
|
+
enabled? ? new(command_name) : NullProfiler.new
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
attr_reader :command_name, :phases
|
|
33
|
+
|
|
34
|
+
# @param command_name [String]
|
|
35
|
+
def initialize(command_name)
|
|
36
|
+
@command_name = command_name
|
|
37
|
+
@phases = []
|
|
38
|
+
@start_time = monotonic_now
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Measure a named phase.
|
|
42
|
+
#
|
|
43
|
+
# @param phase_name [String]
|
|
44
|
+
# @return [Object] block return value
|
|
45
|
+
def measure(phase_name)
|
|
46
|
+
start = monotonic_now
|
|
47
|
+
result = yield
|
|
48
|
+
elapsed = monotonic_now - start
|
|
49
|
+
@phases << { name: phase_name, elapsed_ms: (elapsed * 1000).round(1) }
|
|
50
|
+
result
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Print profiling summary to stderr.
|
|
54
|
+
#
|
|
55
|
+
# @return [void]
|
|
56
|
+
def report
|
|
57
|
+
total = (monotonic_now - @start_time) * 1000
|
|
58
|
+
parts = @phases.map { |phase| "#{phase[:name]}: #{phase[:elapsed_ms]}ms" }
|
|
59
|
+
parts << "total: #{total.round(1)}ms"
|
|
60
|
+
warn "[profile] #{parts.join(' | ')}"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def monotonic_now
|
|
66
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# No-op profiler when RAI_PROFILE is not set.
|
|
70
|
+
# Zero overhead — all methods are empty.
|
|
71
|
+
class NullProfiler
|
|
72
|
+
def measure(_phase_name)
|
|
73
|
+
yield
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def report; end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-only
|
|
4
|
+
# Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
|
|
5
|
+
|
|
6
|
+
require 'yaml'
|
|
7
|
+
require 'digest'
|
|
8
|
+
|
|
9
|
+
module RosettAi
|
|
10
|
+
module Project
|
|
11
|
+
# Detects configuration drift in rosett-ai-managed projects.
|
|
12
|
+
#
|
|
13
|
+
# Supports two drift types:
|
|
14
|
+
# - Forward drift: source YAML differs from compiled native config
|
|
15
|
+
# - Template drift: project config differs from template baseline
|
|
16
|
+
#
|
|
17
|
+
# @author hugo
|
|
18
|
+
# @author claude
|
|
19
|
+
class DriftDetector
|
|
20
|
+
DRIFT_TYPES = ['forward', 'template'].freeze
|
|
21
|
+
|
|
22
|
+
# @param project_root [Pathname] root directory of the project
|
|
23
|
+
def initialize(project_root:)
|
|
24
|
+
@project_root = project_root
|
|
25
|
+
@project_dir = project_root.join('.rosett-ai')
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Detects drift of the specified type.
|
|
29
|
+
#
|
|
30
|
+
# @param type [String, nil] 'forward', 'template', or nil (both)
|
|
31
|
+
# @return [Hash] drift results with :forward and/or :template keys
|
|
32
|
+
def detect(type: nil)
|
|
33
|
+
results = {}
|
|
34
|
+
|
|
35
|
+
results[:forward] = detect_forward_drift if type.nil? || type == 'forward'
|
|
36
|
+
|
|
37
|
+
results[:template] = detect_template_drift if type.nil? || type == 'template'
|
|
38
|
+
|
|
39
|
+
results
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @return [Boolean] true if any drift detected
|
|
43
|
+
def drifted?(type: nil)
|
|
44
|
+
results = detect(type: type)
|
|
45
|
+
results.values.any?(&:any?)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def detect_forward_drift
|
|
51
|
+
drifts = []
|
|
52
|
+
source_dir = @project_dir.join('conf', 'behaviour')
|
|
53
|
+
return drifts unless source_dir.directory?
|
|
54
|
+
|
|
55
|
+
Dir.glob(source_dir.join('*.yml').to_s).each do |src_path|
|
|
56
|
+
src = Pathname.new(src_path)
|
|
57
|
+
compiled_name = File.basename(src_path, '.yml')
|
|
58
|
+
compiled = find_compiled_file(compiled_name)
|
|
59
|
+
next unless compiled&.exist?
|
|
60
|
+
|
|
61
|
+
src_hash = Digest::SHA256.file(src.to_s).hexdigest
|
|
62
|
+
compiled_hash = Digest::SHA256.file(compiled.to_s).hexdigest
|
|
63
|
+
|
|
64
|
+
next if src_hash == compiled_hash
|
|
65
|
+
|
|
66
|
+
drifts << {
|
|
67
|
+
file: src.relative_path_from(@project_root).to_s,
|
|
68
|
+
compiled: compiled.relative_path_from(@project_root).to_s,
|
|
69
|
+
type: 'forward'
|
|
70
|
+
}
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
drifts
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def detect_template_drift
|
|
77
|
+
drifts = []
|
|
78
|
+
origin_file = @project_dir.join('.template_origin.yml')
|
|
79
|
+
return drifts unless origin_file.exist?
|
|
80
|
+
|
|
81
|
+
template_dir = resolve_template_dir(origin_file)
|
|
82
|
+
return drifts unless template_dir&.directory?
|
|
83
|
+
|
|
84
|
+
collect_template_drifts(template_dir, drifts)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def resolve_template_dir(origin_file)
|
|
88
|
+
origin = YAML.safe_load(origin_file.read)
|
|
89
|
+
RosettAi.root.join(TemplateApplier::TEMPLATES_DIR, origin['template'])
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def collect_template_drifts(template_dir, drifts)
|
|
93
|
+
Dir.glob(template_dir.join('**', '*').to_s).each do |tmpl_path|
|
|
94
|
+
tmpl = Pathname.new(tmpl_path)
|
|
95
|
+
next if tmpl.directory?
|
|
96
|
+
|
|
97
|
+
check_template_file_drift(tmpl, template_dir, drifts)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
drifts
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def check_template_file_drift(tmpl, template_dir, drifts)
|
|
104
|
+
relative = tmpl.relative_path_from(template_dir)
|
|
105
|
+
project_file = @project_dir.join(relative)
|
|
106
|
+
return unless project_file.exist?
|
|
107
|
+
return if Digest::SHA256.file(tmpl.to_s).hexdigest == Digest::SHA256.file(project_file.to_s).hexdigest
|
|
108
|
+
|
|
109
|
+
drifts << {
|
|
110
|
+
file: project_file.relative_path_from(@project_root).to_s,
|
|
111
|
+
template: relative.to_s,
|
|
112
|
+
type: 'template'
|
|
113
|
+
}
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def find_compiled_file(name)
|
|
117
|
+
# Check common compiled output locations
|
|
118
|
+
candidates = [
|
|
119
|
+
@project_root.join('.claude', 'rules', "#{name}.md"),
|
|
120
|
+
@project_root.join('rules', "#{name}.md")
|
|
121
|
+
]
|
|
122
|
+
candidates.find(&:exist?)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-only
|
|
4
|
+
# Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
|
|
5
|
+
|
|
6
|
+
require 'yaml'
|
|
7
|
+
require_relative 'template_applier'
|
|
8
|
+
require_relative 'drift_detector'
|
|
9
|
+
require_relative 'sync_manager'
|
|
10
|
+
|
|
11
|
+
module RosettAi
|
|
12
|
+
module Project
|
|
13
|
+
# High-level project lifecycle manager.
|
|
14
|
+
#
|
|
15
|
+
# Orchestrates template application, drift detection, upstream sync,
|
|
16
|
+
# and project health reporting. Delegates to specialised classes.
|
|
17
|
+
#
|
|
18
|
+
# @author hugo
|
|
19
|
+
# @author claude
|
|
20
|
+
class Manager
|
|
21
|
+
# @param project_root [Pathname] root directory of the project
|
|
22
|
+
def initialize(project_root:)
|
|
23
|
+
@project_root = project_root
|
|
24
|
+
@project_dir = project_root.join('.rosett-ai')
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Returns project metadata and health status.
|
|
28
|
+
#
|
|
29
|
+
# @return [Hash] project info including name, engine, template, behaviours
|
|
30
|
+
def info
|
|
31
|
+
validate_project!
|
|
32
|
+
|
|
33
|
+
config = load_project_config
|
|
34
|
+
origin = load_template_origin
|
|
35
|
+
|
|
36
|
+
{
|
|
37
|
+
project_name: config['project_name'] || File.basename(@project_root),
|
|
38
|
+
default_engine: config['default_engine'] || 'generic',
|
|
39
|
+
template: origin&.fetch('template', nil),
|
|
40
|
+
template_applied_at: origin&.fetch('applied_at', nil),
|
|
41
|
+
behaviours: count_behaviours,
|
|
42
|
+
designs: count_designs,
|
|
43
|
+
project_root: @project_root.to_s,
|
|
44
|
+
project_dir: @project_dir.to_s
|
|
45
|
+
}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Returns comprehensive project status.
|
|
49
|
+
#
|
|
50
|
+
# @return [Hash] status with :info, :drift, :health
|
|
51
|
+
def status
|
|
52
|
+
project_info = info
|
|
53
|
+
drift = DriftDetector.new(project_root: @project_root)
|
|
54
|
+
drift_results = drift.detect
|
|
55
|
+
|
|
56
|
+
{
|
|
57
|
+
info: project_info,
|
|
58
|
+
drift: drift_results,
|
|
59
|
+
health: compute_health(project_info, drift_results)
|
|
60
|
+
}
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def validate_project!
|
|
66
|
+
return if @project_dir.directory?
|
|
67
|
+
|
|
68
|
+
raise RosettAi::ProjectError,
|
|
69
|
+
'Not inside an rai project. Run rai init --project first.'
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def load_project_config
|
|
73
|
+
config_path = @project_dir.join('config.yml')
|
|
74
|
+
return {} unless config_path.exist?
|
|
75
|
+
|
|
76
|
+
YAML.safe_load(config_path.read) || {}
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def load_template_origin
|
|
80
|
+
origin_path = @project_dir.join('.template_origin.yml')
|
|
81
|
+
return nil unless origin_path.exist?
|
|
82
|
+
|
|
83
|
+
YAML.safe_load(origin_path.read)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def count_behaviours
|
|
87
|
+
dir = @project_dir.join('conf', 'behaviour')
|
|
88
|
+
return 0 unless dir.directory?
|
|
89
|
+
|
|
90
|
+
Dir.glob(dir.join('*.yml').to_s).size
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def count_designs
|
|
94
|
+
dir = @project_dir.join('conf', 'design')
|
|
95
|
+
return 0 unless dir.directory?
|
|
96
|
+
|
|
97
|
+
Dir.glob(dir.join('*.yml').to_s).size
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def compute_health(project_info, drift_results)
|
|
101
|
+
issues = []
|
|
102
|
+
issues << 'No template applied' unless project_info[:template]
|
|
103
|
+
issues << 'No behaviours defined' if project_info[:behaviours].zero?
|
|
104
|
+
|
|
105
|
+
forward_drift = drift_results.fetch(:forward, [])
|
|
106
|
+
template_drift = drift_results.fetch(:template, [])
|
|
107
|
+
|
|
108
|
+
issues << "Forward drift detected (#{forward_drift.size} files)" unless forward_drift.empty?
|
|
109
|
+
issues << "Template drift detected (#{template_drift.size} files)" unless template_drift.empty?
|
|
110
|
+
|
|
111
|
+
{ healthy: issues.empty?, issues: issues }
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|