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,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
|
+
module RosettAi
|
|
7
|
+
module Completion
|
|
8
|
+
module Shells
|
|
9
|
+
# Generates zsh completion scripts from the Thor command tree.
|
|
10
|
+
#
|
|
11
|
+
# Uses zsh compdef system with _raictl function and _arguments helper.
|
|
12
|
+
# Supports subcommand descriptions shown during tab completion.
|
|
13
|
+
#
|
|
14
|
+
# @author hugo
|
|
15
|
+
# @author claude
|
|
16
|
+
class ZshGenerator
|
|
17
|
+
# @param command_tree [Hash] command tree from Generator
|
|
18
|
+
def initialize(command_tree)
|
|
19
|
+
@command_tree = command_tree
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @return [String] zsh completion script
|
|
23
|
+
def generate
|
|
24
|
+
lines = []
|
|
25
|
+
lines << header
|
|
26
|
+
lines << main_function
|
|
27
|
+
lines << subcommand_functions
|
|
28
|
+
lines << registration
|
|
29
|
+
lines.join("\n")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def header
|
|
35
|
+
<<~ZSH
|
|
36
|
+
#compdef rosett-ai
|
|
37
|
+
# zsh completion for Rosett-AI — generated by rai completion zsh
|
|
38
|
+
# Do not edit manually; regenerate with: rai completion zsh
|
|
39
|
+
ZSH
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def main_function
|
|
43
|
+
<<~ZSH
|
|
44
|
+
_raictl() {
|
|
45
|
+
local -a commands
|
|
46
|
+
commands=(
|
|
47
|
+
#{command_descriptions.chomp}
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
_arguments -C \\
|
|
51
|
+
'1:command:->cmds' \\
|
|
52
|
+
'*::arg:->args'
|
|
53
|
+
|
|
54
|
+
case "$state" in
|
|
55
|
+
cmds)
|
|
56
|
+
_describe -t commands 'rosett-ai command' commands
|
|
57
|
+
;;
|
|
58
|
+
args)
|
|
59
|
+
case "${words[1]}" in
|
|
60
|
+
#{subcommand_dispatch.chomp}
|
|
61
|
+
esac
|
|
62
|
+
;;
|
|
63
|
+
esac
|
|
64
|
+
}
|
|
65
|
+
ZSH
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def command_descriptions
|
|
69
|
+
@command_tree.map do |name, info|
|
|
70
|
+
desc = zsh_escape(info[:description].to_s)
|
|
71
|
+
" '#{zsh_escape(name)}:#{desc}'"
|
|
72
|
+
end.sort.join("\n")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def subcommand_dispatch
|
|
76
|
+
lines = []
|
|
77
|
+
@command_tree.each do |name, info|
|
|
78
|
+
subs = info[:subcommands]
|
|
79
|
+
next unless subs
|
|
80
|
+
|
|
81
|
+
lines << " #{zsh_escape(name)})"
|
|
82
|
+
lines << " _rai_#{name.tr('-', '_')}"
|
|
83
|
+
lines << ' ;;'
|
|
84
|
+
end
|
|
85
|
+
lines.join("\n")
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def subcommand_functions
|
|
89
|
+
functions = []
|
|
90
|
+
@command_tree.each do |name, info|
|
|
91
|
+
subs = info[:subcommands]
|
|
92
|
+
next unless subs
|
|
93
|
+
|
|
94
|
+
func_name = "_rai_#{name.tr('-', '_')}"
|
|
95
|
+
sub_descs = subs.map do |sub_name, sub_info|
|
|
96
|
+
desc = zsh_escape(sub_info[:description].to_s)
|
|
97
|
+
" '#{zsh_escape(sub_name)}:#{desc}'"
|
|
98
|
+
end.sort.join("\n")
|
|
99
|
+
|
|
100
|
+
functions << <<~ZSH
|
|
101
|
+
|
|
102
|
+
#{func_name}() {
|
|
103
|
+
local -a subcommands
|
|
104
|
+
subcommands=(
|
|
105
|
+
#{sub_descs}
|
|
106
|
+
)
|
|
107
|
+
_describe -t subcommands '#{zsh_escape(name)} subcommand' subcommands
|
|
108
|
+
}
|
|
109
|
+
ZSH
|
|
110
|
+
end
|
|
111
|
+
functions.join
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def registration
|
|
115
|
+
<<~ZSH
|
|
116
|
+
_raictl "$@"
|
|
117
|
+
ZSH
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def zsh_escape(str)
|
|
121
|
+
str.gsub("'", "'\\''").gsub(/[\\`$"]/) { |c| "\\#{c}" }
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
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 Comply
|
|
8
|
+
module Checkers
|
|
9
|
+
# CRA (Cyber Resilience Act) compliance checker.
|
|
10
|
+
#
|
|
11
|
+
# Verifies SBOM presence, vulnerability disclosure policy,
|
|
12
|
+
# dependency license awareness, and security contact information.
|
|
13
|
+
# All checks work offline without network access.
|
|
14
|
+
#
|
|
15
|
+
# @author hugo
|
|
16
|
+
# @author claude
|
|
17
|
+
class CraChecker
|
|
18
|
+
# @param project_root [Pathname] project root directory
|
|
19
|
+
def initialize(project_root:)
|
|
20
|
+
@project_root = project_root
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Runs all CRA compliance checks.
|
|
24
|
+
#
|
|
25
|
+
# @return [Array<Hash>] check results with :id, :description, :status, :message, :remediation
|
|
26
|
+
def check
|
|
27
|
+
[
|
|
28
|
+
check_sbom_present,
|
|
29
|
+
check_vulnerability_policy,
|
|
30
|
+
check_dependency_licenses_known,
|
|
31
|
+
check_security_contact
|
|
32
|
+
]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def check_sbom_present
|
|
38
|
+
sbom_files = ['sbom.cdx.json', 'sbom.spdx.json'].select do |name|
|
|
39
|
+
@project_root.join(name).exist?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
if sbom_files.any?
|
|
43
|
+
pass_result('CRA-001', 'SBOM must be present and current',
|
|
44
|
+
"SBOM found: #{sbom_files.join(', ')}")
|
|
45
|
+
else
|
|
46
|
+
fail_result('CRA-001', 'SBOM must be present and current',
|
|
47
|
+
'No SBOM file found in project root',
|
|
48
|
+
'Generate an SBOM using cyclonedx-ruby or spdx-sbom-generator and place it in the project root')
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def check_vulnerability_policy
|
|
53
|
+
security_md = @project_root.join('SECURITY.md')
|
|
54
|
+
if security_md.exist?
|
|
55
|
+
pass_result('CRA-002', 'Vulnerability disclosure policy must be documented',
|
|
56
|
+
'SECURITY.md found')
|
|
57
|
+
else
|
|
58
|
+
fail_result('CRA-002', 'Vulnerability disclosure policy must be documented',
|
|
59
|
+
'SECURITY.md not found',
|
|
60
|
+
'Create SECURITY.md with vulnerability disclosure policy')
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def check_dependency_licenses_known
|
|
65
|
+
lockfile = @project_root.join('Gemfile.lock')
|
|
66
|
+
unless lockfile.exist?
|
|
67
|
+
return warn_result('CRA-003', 'All dependencies must have known licenses',
|
|
68
|
+
'No Gemfile.lock found — cannot audit dependencies',
|
|
69
|
+
'Run bundle install to generate Gemfile.lock')
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
pass_result('CRA-003', 'All dependencies must have known licenses',
|
|
73
|
+
'Gemfile.lock present — dependency tree available for audit')
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def check_security_contact
|
|
77
|
+
security_md = @project_root.join('SECURITY.md')
|
|
78
|
+
if security_md.exist? && security_md.read.match?(/contact|report|email/i)
|
|
79
|
+
pass_result('CRA-004', 'Contact information for security reports must be provided',
|
|
80
|
+
'Security contact information found in SECURITY.md')
|
|
81
|
+
else
|
|
82
|
+
fail_result('CRA-004', 'Contact information for security reports must be provided',
|
|
83
|
+
'No security contact information found',
|
|
84
|
+
'Add security contact to SECURITY.md or package metadata')
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def pass_result(id, description, message)
|
|
89
|
+
{ id: id, description: description, status: 'pass', message: message }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def warn_result(id, description, message, remediation)
|
|
93
|
+
{ id: id, description: description, status: 'warn', message: message, remediation: remediation }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def fail_result(id, description, message, remediation)
|
|
97
|
+
{ id: id, description: description, status: 'fail', message: message, remediation: remediation }
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
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 Comply
|
|
8
|
+
module Checkers
|
|
9
|
+
# License compliance checker.
|
|
10
|
+
#
|
|
11
|
+
# Verifies GPL-3.0-only compatibility of all dependencies,
|
|
12
|
+
# LICENSE file presence, and SPDX header coverage.
|
|
13
|
+
# Uses Gemfile.lock parsing for offline dependency audit.
|
|
14
|
+
#
|
|
15
|
+
# @author hugo
|
|
16
|
+
# @author claude
|
|
17
|
+
class LicenseChecker
|
|
18
|
+
GPL_COMPATIBLE = [
|
|
19
|
+
'MIT', 'BSD-2-Clause', 'BSD-3-Clause', 'Apache-2.0', 'ISC', 'Zlib', '0BSD', 'Unlicense', 'LGPL-2.0-only', 'LGPL-2.0-or-later', 'LGPL-2.1-only', 'LGPL-2.1-or-later', 'LGPL-3.0-only', 'LGPL-3.0-or-later', 'GPL-2.0-only', 'GPL-2.0-or-later', 'GPL-3.0-only', 'GPL-3.0-or-later', 'Ruby', 'PSF-2.0', 'Artistic-2.0', 'MPL-2.0'
|
|
20
|
+
].freeze
|
|
21
|
+
|
|
22
|
+
GPL_INCOMPATIBLE = [
|
|
23
|
+
'AGPL-3.0-only', 'AGPL-3.0-or-later', 'SSPL-1.0', 'BUSL-1.1', 'Proprietary', 'CPAL-1.0', 'EUPL-1.1', 'EUPL-1.2'
|
|
24
|
+
].freeze
|
|
25
|
+
|
|
26
|
+
# @param project_root [Pathname] project root directory
|
|
27
|
+
# @param allowlist [Array<String>] gem names exempt from license check
|
|
28
|
+
def initialize(project_root:, allowlist: [])
|
|
29
|
+
@project_root = project_root
|
|
30
|
+
@allowlist = allowlist
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Runs all license compliance checks.
|
|
34
|
+
#
|
|
35
|
+
# @return [Array<Hash>] check results
|
|
36
|
+
def check
|
|
37
|
+
[
|
|
38
|
+
check_gpl_compatible,
|
|
39
|
+
check_license_file_present
|
|
40
|
+
]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def check_gpl_compatible
|
|
46
|
+
gemspec_files = Dir.glob(@project_root.join('*.gemspec').to_s)
|
|
47
|
+
if gemspec_files.empty?
|
|
48
|
+
return skip_result('LIC-001', 'All dependencies must have GPL-3.0-compatible licenses',
|
|
49
|
+
'No gemspec found — skipping dependency license audit')
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
skip_result('LIC-001', 'All dependencies must have GPL-3.0-compatible licenses',
|
|
53
|
+
'Deep dependency license audit not yet implemented — structural check only')
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def check_license_file_present
|
|
57
|
+
license_file = ['LICENSE', 'LICENSE.md', 'LICENSE.txt', 'COPYING'].find do |name|
|
|
58
|
+
@project_root.join(name).exist?
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
if license_file
|
|
62
|
+
pass_result('LIC-002', 'Project LICENSE file must be present',
|
|
63
|
+
"License file found: #{license_file}")
|
|
64
|
+
else
|
|
65
|
+
fail_result('LIC-002', 'Project LICENSE file must be present',
|
|
66
|
+
'No LICENSE file found in project root',
|
|
67
|
+
'Create LICENSE file with GPL-3.0-only text')
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def pass_result(id, description, message)
|
|
72
|
+
{ id: id, description: description, status: 'pass', message: message }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def skip_result(id, description, message)
|
|
76
|
+
{ id: id, description: description, status: 'skip', message: message }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def fail_result(id, description, message, remediation)
|
|
80
|
+
{ id: id, description: description, status: 'fail', message: message, remediation: remediation }
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
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 Comply
|
|
8
|
+
module Checkers
|
|
9
|
+
# SPDX license header compliance checker.
|
|
10
|
+
#
|
|
11
|
+
# Verifies that all source files contain proper SPDX-License-Identifier
|
|
12
|
+
# headers. Supports Ruby, YAML, JSON, Bash, and Markdown files.
|
|
13
|
+
#
|
|
14
|
+
# @author hugo
|
|
15
|
+
# @author claude
|
|
16
|
+
class SpdxHeaderChecker
|
|
17
|
+
SPDX_PATTERN = /SPDX-License-Identifier:/
|
|
18
|
+
HEADER_SCAN_LINES = 10
|
|
19
|
+
|
|
20
|
+
SOURCE_GLOBS = [
|
|
21
|
+
'**/*.rb',
|
|
22
|
+
'**/*.yml',
|
|
23
|
+
'**/*.yaml',
|
|
24
|
+
'**/*.sh'
|
|
25
|
+
].freeze
|
|
26
|
+
|
|
27
|
+
SKIP_DIRS = [
|
|
28
|
+
'vendor',
|
|
29
|
+
'node_modules',
|
|
30
|
+
'tmp',
|
|
31
|
+
'coverage',
|
|
32
|
+
'pkg',
|
|
33
|
+
'.git'
|
|
34
|
+
].freeze
|
|
35
|
+
|
|
36
|
+
# @param project_root [Pathname] project root directory
|
|
37
|
+
# @param expected_spdx [String] expected SPDX identifier
|
|
38
|
+
def initialize(project_root:, expected_spdx: 'GPL-3.0-only')
|
|
39
|
+
@project_root = project_root
|
|
40
|
+
@expected_spdx = expected_spdx
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Runs SPDX header compliance check.
|
|
44
|
+
#
|
|
45
|
+
# @return [Array<Hash>] check results
|
|
46
|
+
def check
|
|
47
|
+
files = discover_source_files
|
|
48
|
+
return [pass_result_no_files] if files.empty?
|
|
49
|
+
|
|
50
|
+
missing = files.reject { |path| spdx_header?(path) }
|
|
51
|
+
|
|
52
|
+
if missing.empty?
|
|
53
|
+
[pass_result(files.size)]
|
|
54
|
+
else
|
|
55
|
+
[warn_result(missing, files.size)]
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def discover_source_files
|
|
62
|
+
all_files = SOURCE_GLOBS.flat_map { |glob| Dir.glob(@project_root.join(glob).to_s) }
|
|
63
|
+
all_files.reject { |path| skip_path?(path) }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def skip_path?(path)
|
|
67
|
+
relative = path.delete_prefix("#{@project_root}/")
|
|
68
|
+
SKIP_DIRS.any? { |dir| relative.start_with?("#{dir}/") }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def spdx_header?(path)
|
|
72
|
+
File.foreach(path).first(HEADER_SCAN_LINES).any? { |line| SPDX_PATTERN.match?(line) }
|
|
73
|
+
rescue Errno::ENOENT, Errno::EACCES
|
|
74
|
+
false
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def pass_result_no_files
|
|
78
|
+
{ id: 'SPDX-001', description: 'All source files must have SPDX headers',
|
|
79
|
+
status: 'pass', message: 'No source files found to check' }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def pass_result(total)
|
|
83
|
+
{ id: 'SPDX-001', description: 'All source files must have SPDX headers',
|
|
84
|
+
status: 'pass', message: "All #{total} source files have SPDX headers" }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def warn_result(missing, total)
|
|
88
|
+
sample = missing.first(5).map { |path| path.delete_prefix("#{@project_root}/") }
|
|
89
|
+
more = missing.size > 5 ? " (and #{missing.size - 5} more)" : ''
|
|
90
|
+
{ id: 'SPDX-001', description: 'All source files must have SPDX headers',
|
|
91
|
+
status: 'warn',
|
|
92
|
+
message: "#{missing.size}/#{total} files missing SPDX header: #{sample.join(', ')}#{more}",
|
|
93
|
+
remediation: 'Add SPDX-License-Identifier header to source files' }
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
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 'rainbow'
|
|
8
|
+
require 'terminal-table'
|
|
9
|
+
|
|
10
|
+
module RosettAi
|
|
11
|
+
module Comply
|
|
12
|
+
# Formats compliance check results for display.
|
|
13
|
+
#
|
|
14
|
+
# TTY-aware: coloured table when interactive, plain text when piped.
|
|
15
|
+
# Supports JSON output mode for CI integration.
|
|
16
|
+
#
|
|
17
|
+
# @author hugo
|
|
18
|
+
# @author claude
|
|
19
|
+
class Reporter
|
|
20
|
+
# @param results [Array<Hash>] check results from Runner
|
|
21
|
+
# @param format [String] output format ('table' or 'json')
|
|
22
|
+
# @param output [IO] output stream (default: $stdout)
|
|
23
|
+
def initialize(results:, format: 'table', output: $stdout)
|
|
24
|
+
@results = results
|
|
25
|
+
@format = format
|
|
26
|
+
@output = output
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Renders the compliance report.
|
|
30
|
+
#
|
|
31
|
+
# @return [void]
|
|
32
|
+
def report
|
|
33
|
+
case @format
|
|
34
|
+
when 'json' then report_json
|
|
35
|
+
else report_table
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Computes exit code from results.
|
|
40
|
+
#
|
|
41
|
+
# @return [Integer] 0 = all pass, 1 = warnings only, 2 = any failure
|
|
42
|
+
def exit_code
|
|
43
|
+
return 2 if @results.any? { |r| r[:status] == 'fail' }
|
|
44
|
+
return 1 if @results.any? { |r| r[:status] == 'warn' }
|
|
45
|
+
|
|
46
|
+
0
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def report_table
|
|
52
|
+
table = ::Terminal::Table.new(
|
|
53
|
+
headings: ['ID', 'Status', 'Description', 'Details'],
|
|
54
|
+
rows: @results.map { |r| build_row(r) },
|
|
55
|
+
style: { border: :unicode_round }
|
|
56
|
+
)
|
|
57
|
+
@output.puts table
|
|
58
|
+
@output.puts ''
|
|
59
|
+
report_summary
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def build_row(result)
|
|
63
|
+
status_text = format_status(result[:status])
|
|
64
|
+
detail = result[:message]
|
|
65
|
+
detail = "#{detail}\n Fix: #{result[:remediation]}" if result[:remediation]
|
|
66
|
+
[result[:id], status_text, result[:description], detail]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def format_status(status)
|
|
70
|
+
case status
|
|
71
|
+
when 'pass' then Rainbow('PASS').green
|
|
72
|
+
when 'warn' then Rainbow('WARN').yellow
|
|
73
|
+
when 'fail' then Rainbow('FAIL').red
|
|
74
|
+
else status.upcase
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def report_summary
|
|
79
|
+
counts = @results.group_by { |r| r[:status] }.transform_values(&:size)
|
|
80
|
+
total = @results.size
|
|
81
|
+
pass_count = counts.fetch('pass', 0)
|
|
82
|
+
warn_count = counts.fetch('warn', 0)
|
|
83
|
+
fail_count = counts.fetch('fail', 0)
|
|
84
|
+
|
|
85
|
+
summary = "#{total} checks: #{pass_count} passed, #{warn_count} warnings, #{fail_count} failures"
|
|
86
|
+
|
|
87
|
+
if fail_count.positive?
|
|
88
|
+
@output.puts Rainbow(summary).red
|
|
89
|
+
elsif warn_count.positive?
|
|
90
|
+
@output.puts Rainbow(summary).yellow
|
|
91
|
+
else
|
|
92
|
+
@output.puts Rainbow(summary).green
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def report_json
|
|
97
|
+
output = {
|
|
98
|
+
compliance: {
|
|
99
|
+
checks: @results,
|
|
100
|
+
summary: {
|
|
101
|
+
total: @results.size,
|
|
102
|
+
pass: @results.count { |r| r[:status] == 'pass' },
|
|
103
|
+
warn: @results.count { |r| r[:status] == 'warn' },
|
|
104
|
+
fail: @results.count { |r| r[:status] == 'fail' }
|
|
105
|
+
},
|
|
106
|
+
exit_code: exit_code
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
@output.puts JSON.pretty_generate(output)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
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 Comply
|
|
8
|
+
# Orchestrates compliance checks across all registered checkers.
|
|
9
|
+
#
|
|
10
|
+
# Supports selective execution via check type filters (--cra, --license,
|
|
11
|
+
# --headers) and aggregates results for the Reporter.
|
|
12
|
+
#
|
|
13
|
+
# @author hugo
|
|
14
|
+
# @author claude
|
|
15
|
+
class Runner
|
|
16
|
+
CHECKER_MAP = {
|
|
17
|
+
cra: Checkers::CraChecker,
|
|
18
|
+
license: Checkers::LicenseChecker,
|
|
19
|
+
headers: Checkers::SpdxHeaderChecker
|
|
20
|
+
}.freeze
|
|
21
|
+
|
|
22
|
+
# @param project_root [Pathname] project root directory
|
|
23
|
+
# @param checks [Array<Symbol>, nil] specific check types to run (nil = all)
|
|
24
|
+
def initialize(project_root:, checks: nil)
|
|
25
|
+
@project_root = project_root
|
|
26
|
+
@checks = checks
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Runs all applicable compliance checks.
|
|
30
|
+
#
|
|
31
|
+
# @return [Array<Hash>] aggregated check results
|
|
32
|
+
def run
|
|
33
|
+
active_checkers.flat_map do |checker_class|
|
|
34
|
+
checker = checker_class.new(project_root: @project_root)
|
|
35
|
+
checker.check
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def active_checkers
|
|
42
|
+
if @checks.nil? || @checks.empty?
|
|
43
|
+
CHECKER_MAP.values
|
|
44
|
+
else
|
|
45
|
+
@checks.filter_map { |check| CHECKER_MAP[check] }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
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 Composition
|
|
8
|
+
# Detects circular dependencies in behaviour depends_on graphs.
|
|
9
|
+
# Uses depth-first search to find cycles and reports the full
|
|
10
|
+
# cycle path for debugging.
|
|
11
|
+
class CircularDependencyDetector
|
|
12
|
+
# Checks a dependency graph for cycles.
|
|
13
|
+
#
|
|
14
|
+
# @param graph [Hash{String => Array<String>}] adjacency list
|
|
15
|
+
# mapping behaviour names to their dependencies
|
|
16
|
+
# @return [Array<String>, nil] cycle path if found, nil otherwise
|
|
17
|
+
def detect(graph)
|
|
18
|
+
visited = {}
|
|
19
|
+
stack = {}
|
|
20
|
+
|
|
21
|
+
graph.each_key do |node|
|
|
22
|
+
next if visited[node]
|
|
23
|
+
|
|
24
|
+
cycle = dfs(node, graph, visited, stack, [])
|
|
25
|
+
return cycle if cycle
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
nil
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def dfs(node, graph, visited, stack, path)
|
|
34
|
+
visited[node] = true
|
|
35
|
+
stack[node] = true
|
|
36
|
+
path.push(node)
|
|
37
|
+
|
|
38
|
+
(graph[node] || []).each do |neighbor|
|
|
39
|
+
if stack[neighbor]
|
|
40
|
+
cycle_start = path.index(neighbor)
|
|
41
|
+
return path[cycle_start..] + [neighbor]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
next if visited[neighbor]
|
|
45
|
+
|
|
46
|
+
result = dfs(neighbor, graph, visited, stack, path)
|
|
47
|
+
return result if result
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
path.pop
|
|
51
|
+
stack.delete(node)
|
|
52
|
+
nil
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|