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.
Files changed (527) hide show
  1. checksums.yaml +7 -0
  2. data/.ai-provenance.yml +119 -0
  3. data/.debride_whitelist +186 -0
  4. data/.fasterer.yml +29 -0
  5. data/.mdl_style.rb +10 -0
  6. data/.mdlrc +3 -0
  7. data/.mutant.yml +49 -0
  8. data/.namespace-allowlist +42 -0
  9. data/.reek.yml +1040 -0
  10. data/.rosett-ai/config.yml +3 -0
  11. data/.rspec +5 -0
  12. data/.rubocop.yml +380 -0
  13. data/.ruby-version +1 -0
  14. data/.yamllint +51 -0
  15. data/.yardopts +12 -0
  16. data/AI-DISCLOSURE.md +48 -0
  17. data/CHANGELOG.md +519 -0
  18. data/CLAUDE.md +141 -0
  19. data/CONTRIBUTING.md +734 -0
  20. data/INSTALL.md +154 -0
  21. data/LICENSE +674 -0
  22. data/LICENSE.md +675 -0
  23. data/QUICKSTART.md +73 -0
  24. data/README.md +366 -0
  25. data/Rakefile +200 -0
  26. data/SECURITY.md +114 -0
  27. data/bin/rai +1 -0
  28. data/cliff.toml +52 -0
  29. data/conf/adopt_redactions.yml +8 -0
  30. data/conf/behaviour/.gitkeep +0 -0
  31. data/conf/compliance/cra_rules.yml +25 -0
  32. data/conf/compliance/license_rules.yml +20 -0
  33. data/conf/design/aaif_alignment.yml +181 -0
  34. data/conf/design/ab_testing.yml +172 -0
  35. data/conf/design/accessibility.yml +84 -0
  36. data/conf/design/ai_authorship.yml +210 -0
  37. data/conf/design/ai_provenance.yml +224 -0
  38. data/conf/design/ai_tool_configuration.yml +207 -0
  39. data/conf/design/architecture.yml +139 -0
  40. data/conf/design/autocompletion.yml +115 -0
  41. data/conf/design/backward_compatibility.yml +112 -0
  42. data/conf/design/behaviour_composition.yml +246 -0
  43. data/conf/design/build_rake_extraction.yml +57 -0
  44. data/conf/design/ci_pipeline.yml +100 -0
  45. data/conf/design/claude_code_configuration.yml +157 -0
  46. data/conf/design/compiler.yml +128 -0
  47. data/conf/design/comply.yml +153 -0
  48. data/conf/design/content_packs.yml +84 -0
  49. data/conf/design/desktop_integration.yml +289 -0
  50. data/conf/design/distribution.yml +216 -0
  51. data/conf/design/doctor.yml +184 -0
  52. data/conf/design/documentation.yml +152 -0
  53. data/conf/design/engine_architecture.yml +257 -0
  54. data/conf/design/error_handling.yml +103 -0
  55. data/conf/design/feature_flags.yml +142 -0
  56. data/conf/design/git_hooks.yml +165 -0
  57. data/conf/design/gui_plugins.yml +475 -0
  58. data/conf/design/i18n.yml +84 -0
  59. data/conf/design/integration_testing.yml +56 -0
  60. data/conf/design/licensing_system.yml +88 -0
  61. data/conf/design/lifecycle_management.yml +208 -0
  62. data/conf/design/mcp_integration.yml +207 -0
  63. data/conf/design/mcp_settings.yml +126 -0
  64. data/conf/design/migration.yml +56 -0
  65. data/conf/design/monitoring_observability.yml +194 -0
  66. data/conf/design/namespace_cleanup.yml +145 -0
  67. data/conf/design/plugin_test_segregation.yml +145 -0
  68. data/conf/design/policy_management.yml +229 -0
  69. data/conf/design/project_management.yml +183 -0
  70. data/conf/design/rai_mcp_asset_discovery.yml +164 -0
  71. data/conf/design/rai_mcp_server.yml +605 -0
  72. data/conf/design/release_management.yml +117 -0
  73. data/conf/design/retrofit.yml +199 -0
  74. data/conf/design/retrospective_analyzer.yml +79 -0
  75. data/conf/design/scope_hierarchy.yml +352 -0
  76. data/conf/design/security.yml +115 -0
  77. data/conf/design/session_retrospective.yml +85 -0
  78. data/conf/design/smart_ui_feedback.yml +89 -0
  79. data/conf/design/structured_logging.yml +148 -0
  80. data/conf/design/styles.yml +123 -0
  81. data/conf/design/test_peer_review.yml +89 -0
  82. data/conf/design/testing.yml +136 -0
  83. data/conf/design/threat_model.yml +108 -0
  84. data/conf/design/ui_framework.yml +111 -0
  85. data/conf/design/usage_optimization.yml +122 -0
  86. data/conf/design/version_management.yml +60 -0
  87. data/conf/design/workflow.yml +227 -0
  88. data/conf/mcp/server_defaults.yml +42 -0
  89. data/conf/mcp/trust.yml +21 -0
  90. data/conf/packaging/core.yml +12 -0
  91. data/conf/packaging/gtk4.yml +11 -0
  92. data/conf/packaging/qt6.yml +11 -0
  93. data/conf/policy/default_deny_list.yml +197 -0
  94. data/conf/review/cli-command-audit.yml +857 -0
  95. data/conf/review/design-docs.yml +1064 -0
  96. data/conf/review/design-questionnaire.yml +153 -0
  97. data/conf/review/questionnaire.yml +146 -0
  98. data/conf/review/rosett-ai-core.yml +2919 -0
  99. data/conf/schemas/ai_config_schema.json +73 -0
  100. data/conf/schemas/behaviour_schema.json +132 -0
  101. data/conf/schemas/compliance_rule_schema.json +63 -0
  102. data/conf/schemas/content_pack_manifest_schema.json +51 -0
  103. data/conf/schemas/design_schema.json +210 -0
  104. data/conf/schemas/engine_manifest_schema.json +144 -0
  105. data/conf/schemas/lockfile_schema.json +74 -0
  106. data/conf/schemas/mcp_server_schema.json +48 -0
  107. data/conf/schemas/packaging_schema.json +70 -0
  108. data/conf/schemas/policy_schema.json +85 -0
  109. data/conf/schemas/provenance_schema.json +84 -0
  110. data/conf/schemas/rai_config_schema.json +56 -0
  111. data/conf/schemas/rai_project_schema.json +20 -0
  112. data/conf/schemas/scope_hierarchy_schema.json +49 -0
  113. data/conf/schemas/target_schema.json +67 -0
  114. data/conf/schemas/tooling_schema.json +65 -0
  115. data/conf/schemas/workflow_schema.json +112 -0
  116. data/conf/targets/agents_md.yml +17 -0
  117. data/conf/targets/claude.yml +12 -0
  118. data/conf/tooling/tools.yml +58 -0
  119. data/dist/rosett-ai-mcp.service +48 -0
  120. data/dist/rosett-ai-mcp.yml.default +45 -0
  121. data/doc/AAIF_POSITIONING.md +58 -0
  122. data/doc/ADOPT.md +224 -0
  123. data/doc/AI_PROVENANCE.md +139 -0
  124. data/doc/ARCHITECTURE.md +920 -0
  125. data/doc/BEHAVIOUR.md +409 -0
  126. data/doc/BUILD.md +138 -0
  127. data/doc/CI_CD_RECIPES.md +171 -0
  128. data/doc/CLAUDE_SESSIONS_MOVED.md +16 -0
  129. data/doc/COMMAND_ANALYSIS.md +229 -0
  130. data/doc/CONFIGURATION.md +281 -0
  131. data/doc/DESIGN_AUDIT.md +235 -0
  132. data/doc/DESIGN_PEER_REVIEW.md +771 -0
  133. data/doc/DESKTOP.md +447 -0
  134. data/doc/ENGINES.md +567 -0
  135. data/doc/ENGINE_DEVELOPMENT_GUIDE.md +417 -0
  136. data/doc/FEATURE_AUDIT.md +218 -0
  137. data/doc/IMPLEMENTATION_PLAN.md +669 -0
  138. data/doc/INCIDENT_REPORT_2026-02-02.md +251 -0
  139. data/doc/MIGRATION_GUIDE.md +88 -0
  140. data/doc/PACKAGING.md +232 -0
  141. data/doc/PROJECT_DASHBOARD.md +153 -0
  142. data/doc/PULP_DEPLOYMENT.md +164 -0
  143. data/doc/QUALITY_FIX_SUMMARY.md +110 -0
  144. data/doc/QUICK_START.md +162 -0
  145. data/doc/REEK_CONFIGURATION.md +166 -0
  146. data/doc/REFERENCE.md +253 -0
  147. data/doc/REFERENCES.md +324 -0
  148. data/doc/SECURITY_REVIEW_CHECKLIST.md +72 -0
  149. data/doc/SESSION_2026-02-28_GTK4_HARDENING.md +359 -0
  150. data/doc/SETUP.md +202 -0
  151. data/doc/TEST_PEER_REVIEW.md +152 -0
  152. data/doc/THREAT_MODEL.md +230 -0
  153. data/doc/USAGE.md +545 -0
  154. data/doc/USER_MANUAL.md +585 -0
  155. data/doc/ai_test_review_checklist.md +110 -0
  156. data/doc/changes/2026-02-18-packaging-fpm.md +155 -0
  157. data/doc/changes/2026-02-19-testing-infrastructure.md +221 -0
  158. data/doc/changes/2026-02-20-security-implementation.md +281 -0
  159. data/doc/changes/2026-02-20-styles-implementation.md +220 -0
  160. data/doc/changes/2026-02-21-architecture-completion.md +95 -0
  161. data/doc/changes/2026-02-21-architecture-ui-layer.md +253 -0
  162. data/doc/changes/2026-02-21-cc-config-implementation.md +108 -0
  163. data/doc/changes/2026-02-21-ci-pipeline-implementation.md +214 -0
  164. data/doc/changes/2026-02-21-compiler-multi-target-pipeline.md +241 -0
  165. data/doc/changes/2026-02-21-config-design-show-commands.md +61 -0
  166. data/doc/changes/2026-02-21-design-implementation-overview.md +455 -0
  167. data/doc/changes/2026-02-21-lifecycle-management.md +196 -0
  168. data/doc/changes/2026-02-21-path-resolver.md +128 -0
  169. data/doc/changes/2026-02-24-ci-tmpdir-mutant-fetch.md +45 -0
  170. data/doc/changes/2026-03-01-ci-bundler-strategy.md +120 -0
  171. data/doc/changes/2026-03-20-security-hardening-phase2.md +163 -0
  172. data/doc/context/SESSION-HANDOFF.md +69 -0
  173. data/doc/context/ai-engine-usage-trends-2026.md +80 -0
  174. data/doc/context/plan-pluggable-engines.md +590 -0
  175. data/doc/decisions/001-flog-deferred.md +32 -0
  176. data/doc/decisions/002-path-resolution-strategy.md +158 -0
  177. data/doc/decisions/003-ui-adapter-selection.md +193 -0
  178. data/doc/decisions/004-design-document-validation.md +179 -0
  179. data/doc/decisions/005-package-splitting-strategy.md +200 -0
  180. data/doc/decisions/006-multi-engine-architecture.md +147 -0
  181. data/doc/decisions/007-engine-agnostic-pivot.md +219 -0
  182. data/doc/decisions/008-ci-bundler-strategy.md +129 -0
  183. data/doc/decisions/009-core-only-v1-release.md +60 -0
  184. data/doc/decisions/010-engine-debian-packaging.md +66 -0
  185. data/doc/decisions/011-context-aware-cli.md +71 -0
  186. data/doc/dependency_decisions.yml +247 -0
  187. data/doc/issues/001-wrapper-missing-environment-variables.md +197 -0
  188. data/doc/issues/002-embedded-ruby-wrong-prefix.md +217 -0
  189. data/doc/issues/003-smoke-test-false-positive.md +127 -0
  190. data/doc/issues/004-market-research-design-updates.md +109 -0
  191. data/doc/issues/005-compile-scope-coexistence.md +161 -0
  192. data/doc/locales/.gitkeep +0 -0
  193. data/doc/man/rai.1.ronn +505 -0
  194. data/doc/operations/packaging.md +133 -0
  195. data/doc/operations/rosett-ai-release.md +65 -0
  196. data/doc/reference/error-catalog.md +107 -0
  197. data/doc/reference/rosett-ai-technical-reference.pdf +0 -0
  198. data/doc/reference/src/Pictures/cover.jpg +0 -0
  199. data/doc/reference/src/Pictures/head1.jpg +0 -0
  200. data/doc/reference/src/Pictures/head2.jpg +0 -0
  201. data/doc/reference/src/Pictures/head3.jpg +0 -0
  202. data/doc/reference/src/Pictures/head4.jpg +0 -0
  203. data/doc/reference/src/Pictures/head5.jpg +0 -0
  204. data/doc/reference/src/Pictures/head6.jpg +0 -0
  205. data/doc/reference/src/Pictures/head7.jpg +0 -0
  206. data/doc/reference/src/Pictures/head8.jpg +0 -0
  207. data/doc/reference/src/StyleInd.ist +4 -0
  208. data/doc/reference/src/bibliography.bib +79 -0
  209. data/doc/reference/src/main.tex +1288 -0
  210. data/doc/reference/src/structure.tex +303 -0
  211. data/doc/rosett-ai-bookmarks.html +301 -0
  212. data/kitchen.yml +46 -0
  213. data/lib/rosett_ai/adopter/executor_resolver.rb +77 -0
  214. data/lib/rosett_ai/adopter/local_analysis_collector.rb +154 -0
  215. data/lib/rosett_ai/adopter/rule_adopter.rb +254 -0
  216. data/lib/rosett_ai/ai_config/config_compiler.rb +111 -0
  217. data/lib/rosett_ai/ai_config/context_window.rb +55 -0
  218. data/lib/rosett_ai/ai_config/cost_controls.rb +44 -0
  219. data/lib/rosett_ai/ai_config/fallback_chain.rb +64 -0
  220. data/lib/rosett_ai/ai_config/model_router.rb +121 -0
  221. data/lib/rosett_ai/ai_config/validator.rb +45 -0
  222. data/lib/rosett_ai/authorship/attribution_compiler.rb +99 -0
  223. data/lib/rosett_ai/authorship/disclosure_policy.rb +81 -0
  224. data/lib/rosett_ai/authorship/review_validator.rb +39 -0
  225. data/lib/rosett_ai/authorship/trailer_generator.rb +88 -0
  226. data/lib/rosett_ai/backup/compressor.rb +180 -0
  227. data/lib/rosett_ai/backup/destination.rb +91 -0
  228. data/lib/rosett_ai/behaviour/manager.rb +156 -0
  229. data/lib/rosett_ai/compiler/backend.rb +86 -0
  230. data/lib/rosett_ai/compiler/backends/agents_md_backend.rb +80 -0
  231. data/lib/rosett_ai/compiler/backends/claude_backend.rb +88 -0
  232. data/lib/rosett_ai/compiler/backends/generic_backend.rb +15 -0
  233. data/lib/rosett_ai/compiler/behaviour_compiler.rb +40 -0
  234. data/lib/rosett_ai/compiler/capability_checker.rb +104 -0
  235. data/lib/rosett_ai/compiler/compilation_pipeline.rb +361 -0
  236. data/lib/rosett_ai/compiler/compiled_output.rb +39 -0
  237. data/lib/rosett_ai/compiler/locale_compiler.rb +250 -0
  238. data/lib/rosett_ai/compiler/target_profile.rb +112 -0
  239. data/lib/rosett_ai/completion/generator.rb +101 -0
  240. data/lib/rosett_ai/completion/shells/bash_generator.rb +126 -0
  241. data/lib/rosett_ai/completion/shells/fish_generator.rb +78 -0
  242. data/lib/rosett_ai/completion/shells/zsh_generator.rb +126 -0
  243. data/lib/rosett_ai/comply/checkers/cra_checker.rb +102 -0
  244. data/lib/rosett_ai/comply/checkers/license_checker.rb +85 -0
  245. data/lib/rosett_ai/comply/checkers/spdx_header_checker.rb +98 -0
  246. data/lib/rosett_ai/comply/reporter.rb +113 -0
  247. data/lib/rosett_ai/comply/runner.rb +50 -0
  248. data/lib/rosett_ai/composition/circular_dependency_detector.rb +56 -0
  249. data/lib/rosett_ai/composition/composer.rb +158 -0
  250. data/lib/rosett_ai/composition/composition_result.rb +64 -0
  251. data/lib/rosett_ai/composition/conflict_detector.rb +53 -0
  252. data/lib/rosett_ai/composition/lockfile.rb +103 -0
  253. data/lib/rosett_ai/composition/merge_strategy.rb +131 -0
  254. data/lib/rosett_ai/composition/priority_sorter.rb +29 -0
  255. data/lib/rosett_ai/composition/scope_resolver.rb +55 -0
  256. data/lib/rosett_ai/config/compile_result.rb +37 -0
  257. data/lib/rosett_ai/config/compiler.rb +13 -0
  258. data/lib/rosett_ai/config/domain_transformer.rb +13 -0
  259. data/lib/rosett_ai/config/key_map.rb +13 -0
  260. data/lib/rosett_ai/config/masking_secret_resolver.rb +40 -0
  261. data/lib/rosett_ai/config/scope_router.rb +13 -0
  262. data/lib/rosett_ai/config/secret_resolver.rb +125 -0
  263. data/lib/rosett_ai/configuration.rb +119 -0
  264. data/lib/rosett_ai/content/content_client.rb +60 -0
  265. data/lib/rosett_ai/content/pack_installer.rb +117 -0
  266. data/lib/rosett_ai/content/pack_manifest.rb +50 -0
  267. data/lib/rosett_ai/content/pack_registry.rb +68 -0
  268. data/lib/rosett_ai/content_packs/manager.rb +50 -0
  269. data/lib/rosett_ai/dbus/compositor_detector.rb +77 -0
  270. data/lib/rosett_ai/dbus/focus_adapters/base.rb +59 -0
  271. data/lib/rosett_ai/dbus/focus_adapters/gnome_adapter.rb +172 -0
  272. data/lib/rosett_ai/dbus/focus_adapters/hyprland_adapter.rb +77 -0
  273. data/lib/rosett_ai/dbus/focus_adapters/i3_adapter.rb +65 -0
  274. data/lib/rosett_ai/dbus/focus_adapters/kwin_adapter.rb +103 -0
  275. data/lib/rosett_ai/dbus/focus_adapters/x11_adapter.rb +105 -0
  276. data/lib/rosett_ai/dbus/focus_monitor_interface.rb +103 -0
  277. data/lib/rosett_ai/dbus/manager_interface.rb +213 -0
  278. data/lib/rosett_ai/dbus/plugin_manager_interface.rb +169 -0
  279. data/lib/rosett_ai/dbus/rate_limiter.rb +89 -0
  280. data/lib/rosett_ai/dbus/service.rb +121 -0
  281. data/lib/rosett_ai/dbus/status_notifier_interface.rb +79 -0
  282. data/lib/rosett_ai/deprecation.rb +79 -0
  283. data/lib/rosett_ai/desktop/dbus_client.rb +259 -0
  284. data/lib/rosett_ai/desktop/gtk4_app.rb +371 -0
  285. data/lib/rosett_ai/desktop/gtk4_preferences.rb +331 -0
  286. data/lib/rosett_ai/desktop/gui_logger.rb +236 -0
  287. data/lib/rosett_ai/doctor/check.rb +92 -0
  288. data/lib/rosett_ai/doctor/checks/cache_health_check.rb +50 -0
  289. data/lib/rosett_ai/doctor/checks/dbus_availability_check.rb +39 -0
  290. data/lib/rosett_ai/doctor/checks/engine_detection_check.rb +46 -0
  291. data/lib/rosett_ai/doctor/checks/file_permission_check.rb +44 -0
  292. data/lib/rosett_ai/doctor/checks/gem_dependency_check.rb +55 -0
  293. data/lib/rosett_ai/doctor/checks/ruby_version_check.rb +50 -0
  294. data/lib/rosett_ai/doctor/checks/stale_config_nncc_check.rb +57 -0
  295. data/lib/rosett_ai/doctor/checks/stale_home_nncc_check.rb +59 -0
  296. data/lib/rosett_ai/doctor.rb +81 -0
  297. data/lib/rosett_ai/documentation/reference_compiler.rb +122 -0
  298. data/lib/rosett_ai/documentation/translator.rb +62 -0
  299. data/lib/rosett_ai/engines/base_config_compiler.rb +203 -0
  300. data/lib/rosett_ai/engines/detector.rb +63 -0
  301. data/lib/rosett_ai/engines/registry.rb +50 -0
  302. data/lib/rosett_ai/error_handler.rb +139 -0
  303. data/lib/rosett_ai/exit_codes.rb +76 -0
  304. data/lib/rosett_ai/feature_flags.rb +102 -0
  305. data/lib/rosett_ai/formatting.rb +33 -0
  306. data/lib/rosett_ai/gem_consistency_checker.rb +199 -0
  307. data/lib/rosett_ai/git_hooks/chain_detector.rb +86 -0
  308. data/lib/rosett_ai/git_hooks/installer.rb +175 -0
  309. data/lib/rosett_ai/git_hooks/script_generator.rb +125 -0
  310. data/lib/rosett_ai/gitlab/validators/supplementary_gitlab_ci_yaml_validator.rb +79 -0
  311. data/lib/rosett_ai/i18n/locale_resolver.rb +46 -0
  312. data/lib/rosett_ai/i18n/utf8_checker.rb +32 -0
  313. data/lib/rosett_ai/init/config_file_writer.rb +24 -0
  314. data/lib/rosett_ai/init/directory_builder.rb +38 -0
  315. data/lib/rosett_ai/init/file_copier.rb +95 -0
  316. data/lib/rosett_ai/init/global_initializer.rb +28 -0
  317. data/lib/rosett_ai/init/local_initializer.rb +27 -0
  318. data/lib/rosett_ai/init/mcp_registrar.rb +109 -0
  319. data/lib/rosett_ai/init/project_initializer.rb +38 -0
  320. data/lib/rosett_ai/licensing/license_key.rb +139 -0
  321. data/lib/rosett_ai/licensing/license_store.rb +64 -0
  322. data/lib/rosett_ai/licensing/license_validator.rb +60 -0
  323. data/lib/rosett_ai/licensing/tier.rb +42 -0
  324. data/lib/rosett_ai/mcp/admin/auditor.rb +88 -0
  325. data/lib/rosett_ai/mcp/admin/health_checker.rb +81 -0
  326. data/lib/rosett_ai/mcp/admin/registry.rb +100 -0
  327. data/lib/rosett_ai/mcp/admin/schema_validator.rb +63 -0
  328. data/lib/rosett_ai/mcp/enforcement/.gitkeep +0 -0
  329. data/lib/rosett_ai/mcp/enforcement/hook_generator.rb +197 -0
  330. data/lib/rosett_ai/mcp/enforcement/validator.rb +215 -0
  331. data/lib/rosett_ai/mcp/governance.rb +160 -0
  332. data/lib/rosett_ai/mcp/http_security_config.rb +158 -0
  333. data/lib/rosett_ai/mcp/instructions.rb +266 -0
  334. data/lib/rosett_ai/mcp/key_hasher.rb +66 -0
  335. data/lib/rosett_ai/mcp/keyfile.rb +221 -0
  336. data/lib/rosett_ai/mcp/middleware/authentication.rb +146 -0
  337. data/lib/rosett_ai/mcp/middleware/content_type.rb +56 -0
  338. data/lib/rosett_ai/mcp/middleware/cors.rb +83 -0
  339. data/lib/rosett_ai/mcp/middleware/origin_validation.rb +73 -0
  340. data/lib/rosett_ai/mcp/middleware/rate_limit.rb +106 -0
  341. data/lib/rosett_ai/mcp/middleware/request_size.rb +51 -0
  342. data/lib/rosett_ai/mcp/plugins.rb +143 -0
  343. data/lib/rosett_ai/mcp/prompts/compilation_prompt.rb +40 -0
  344. data/lib/rosett_ai/mcp/prompts/compliance_prompt.rb +41 -0
  345. data/lib/rosett_ai/mcp/prompts/diagnostics_prompt.rb +41 -0
  346. data/lib/rosett_ai/mcp/prompts/validation_prompt.rb +41 -0
  347. data/lib/rosett_ai/mcp/resources/behaviour_resource.rb +127 -0
  348. data/lib/rosett_ai/mcp/resources/config_resource.rb +72 -0
  349. data/lib/rosett_ai/mcp/resources/design_resource.rb +58 -0
  350. data/lib/rosett_ai/mcp/resources/hooks_resource.rb +74 -0
  351. data/lib/rosett_ai/mcp/resources/provenance_resource.rb +51 -0
  352. data/lib/rosett_ai/mcp/resources/rules_resource.rb +60 -0
  353. data/lib/rosett_ai/mcp/resources/schema_resource.rb +72 -0
  354. data/lib/rosett_ai/mcp/response_helper.rb +46 -0
  355. data/lib/rosett_ai/mcp/security_logger.rb +60 -0
  356. data/lib/rosett_ai/mcp/server.rb +212 -0
  357. data/lib/rosett_ai/mcp/settings/server_installer.rb +112 -0
  358. data/lib/rosett_ai/mcp/settings/trust_manager.rb +142 -0
  359. data/lib/rosett_ai/mcp/tools/adopt_tool.rb +70 -0
  360. data/lib/rosett_ai/mcp/tools/backup_tool.rb +64 -0
  361. data/lib/rosett_ai/mcp/tools/behaviour_display_tool.rb +72 -0
  362. data/lib/rosett_ai/mcp/tools/behaviour_list_tool.rb +56 -0
  363. data/lib/rosett_ai/mcp/tools/behaviour_manage_tool.rb +114 -0
  364. data/lib/rosett_ai/mcp/tools/behaviour_show_tool.rb +62 -0
  365. data/lib/rosett_ai/mcp/tools/compile_status_tool.rb +122 -0
  366. data/lib/rosett_ai/mcp/tools/compile_tool.rb +191 -0
  367. data/lib/rosett_ai/mcp/tools/comply_tool.rb +79 -0
  368. data/lib/rosett_ai/mcp/tools/config_compile_tool.rb +71 -0
  369. data/lib/rosett_ai/mcp/tools/config_status_tool.rb +79 -0
  370. data/lib/rosett_ai/mcp/tools/content_tool.rb +78 -0
  371. data/lib/rosett_ai/mcp/tools/context_query_tool.rb +156 -0
  372. data/lib/rosett_ai/mcp/tools/design_list_tool.rb +57 -0
  373. data/lib/rosett_ai/mcp/tools/design_show_tool.rb +69 -0
  374. data/lib/rosett_ai/mcp/tools/doctor_tool.rb +62 -0
  375. data/lib/rosett_ai/mcp/tools/documentation_status_tool.rb +45 -0
  376. data/lib/rosett_ai/mcp/tools/engines_tool.rb +84 -0
  377. data/lib/rosett_ai/mcp/tools/hook_install_tool.rb +190 -0
  378. data/lib/rosett_ai/mcp/tools/hook_preview_tool.rb +173 -0
  379. data/lib/rosett_ai/mcp/tools/hooks_status_tool.rb +84 -0
  380. data/lib/rosett_ai/mcp/tools/init_tool.rb +87 -0
  381. data/lib/rosett_ai/mcp/tools/license_status_tool.rb +44 -0
  382. data/lib/rosett_ai/mcp/tools/project_tool.rb +117 -0
  383. data/lib/rosett_ai/mcp/tools/provenance_tool.rb +97 -0
  384. data/lib/rosett_ai/mcp/tools/provenance_write_tool.rb +40 -0
  385. data/lib/rosett_ai/mcp/tools/retrofit_tool.rb +81 -0
  386. data/lib/rosett_ai/mcp/tools/rule_search_tool.rb +163 -0
  387. data/lib/rosett_ai/mcp/tools/schema_get_tool.rb +94 -0
  388. data/lib/rosett_ai/mcp/tools/tooling_tool.rb +86 -0
  389. data/lib/rosett_ai/mcp/tools/validate_tool.rb +105 -0
  390. data/lib/rosett_ai/mcp/tools/workflow_execute_tool.rb +74 -0
  391. data/lib/rosett_ai/mcp/tools/workflow_tool.rb +78 -0
  392. data/lib/rosett_ai/migration/detector.rb +117 -0
  393. data/lib/rosett_ai/migration/nncc_config_migrator.rb +94 -0
  394. data/lib/rosett_ai/migration/nncc_project_migrator.rb +90 -0
  395. data/lib/rosett_ai/migration/xdg_migrator.rb +123 -0
  396. data/lib/rosett_ai/package_manager/apt.rb +108 -0
  397. data/lib/rosett_ai/package_manager/base.rb +68 -0
  398. data/lib/rosett_ai/package_manager/gem_backend.rb +90 -0
  399. data/lib/rosett_ai/packaging/variant_config.rb +92 -0
  400. data/lib/rosett_ai/path_resolver.rb +115 -0
  401. data/lib/rosett_ai/plugins/contract.rb +43 -0
  402. data/lib/rosett_ai/plugins/engine_contract.rb +60 -0
  403. data/lib/rosett_ai/plugins/gui_contract.rb +74 -0
  404. data/lib/rosett_ai/plugins/mcp_contract.rb +48 -0
  405. data/lib/rosett_ai/plugins/registry.rb +150 -0
  406. data/lib/rosett_ai/policy/auditor.rb +41 -0
  407. data/lib/rosett_ai/policy/deny_list.rb +71 -0
  408. data/lib/rosett_ai/policy/opt_out_scanner.rb +37 -0
  409. data/lib/rosett_ai/policy/policy_compiler.rb +84 -0
  410. data/lib/rosett_ai/policy/protected_files.rb +47 -0
  411. data/lib/rosett_ai/policy/tier_hierarchy.rb +48 -0
  412. data/lib/rosett_ai/policy/validator.rb +35 -0
  413. data/lib/rosett_ai/profiler.rb +79 -0
  414. data/lib/rosett_ai/project/drift_detector.rb +126 -0
  415. data/lib/rosett_ai/project/manager.rb +115 -0
  416. data/lib/rosett_ai/project/sync_manager.rb +138 -0
  417. data/lib/rosett_ai/project/template_applier.rb +105 -0
  418. data/lib/rosett_ai/project_context.rb +82 -0
  419. data/lib/rosett_ai/provenance/entry.rb +63 -0
  420. data/lib/rosett_ai/provenance/file_source.rb +32 -0
  421. data/lib/rosett_ai/provenance/source.rb +62 -0
  422. data/lib/rosett_ai/provenance/store.rb +153 -0
  423. data/lib/rosett_ai/provenance/tracker.rb +62 -0
  424. data/lib/rosett_ai/provenance/trailer_generator.rb +43 -0
  425. data/lib/rosett_ai/provenance/validator.rb +45 -0
  426. data/lib/rosett_ai/quorum/collector.rb +59 -0
  427. data/lib/rosett_ai/quorum/comparator.rb +81 -0
  428. data/lib/rosett_ai/quorum/dispatcher.rb +57 -0
  429. data/lib/rosett_ai/quorum/strategies/adopt.rb +56 -0
  430. data/lib/rosett_ai/rai_config.rb +107 -0
  431. data/lib/rosett_ai/retrofit/base_parser.rb +66 -0
  432. data/lib/rosett_ai/retrofit/engine.rb +171 -0
  433. data/lib/rosett_ai/retrofit/parsers/agents_md_parser.rb +50 -0
  434. data/lib/rosett_ai/retrofit/parsers/claude_parser.rb +69 -0
  435. data/lib/rosett_ai/retrofit/parsers/cursor_parser.rb +82 -0
  436. data/lib/rosett_ai/retrofit/round_trip_validator.rb +65 -0
  437. data/lib/rosett_ai/retrofit/scanner.rb +47 -0
  438. data/lib/rosett_ai/retrofit/secret_detector.rb +87 -0
  439. data/lib/rosett_ai/secrets_resolver.rb +71 -0
  440. data/lib/rosett_ai/smart_feedback/suggester.rb +83 -0
  441. data/lib/rosett_ai/smart_feedback/thor_middleware.rb +84 -0
  442. data/lib/rosett_ai/structured_logger.rb +110 -0
  443. data/lib/rosett_ai/telemetry/json_lines_writer.rb +50 -0
  444. data/lib/rosett_ai/telemetry/log_rotator.rb +67 -0
  445. data/lib/rosett_ai/telemetry/provider.rb +26 -0
  446. data/lib/rosett_ai/telemetry/reporter.rb +144 -0
  447. data/lib/rosett_ai/telemetry.rb +47 -0
  448. data/lib/rosett_ai/text_sanitizer.rb +62 -0
  449. data/lib/rosett_ai/thor/cli.rb +269 -0
  450. data/lib/rosett_ai/thor/tasks/adopt.rb +250 -0
  451. data/lib/rosett_ai/thor/tasks/backup.rb +420 -0
  452. data/lib/rosett_ai/thor/tasks/behaviour.rb +474 -0
  453. data/lib/rosett_ai/thor/tasks/build.rb +1162 -0
  454. data/lib/rosett_ai/thor/tasks/compile.rb +415 -0
  455. data/lib/rosett_ai/thor/tasks/completion.rb +123 -0
  456. data/lib/rosett_ai/thor/tasks/comply.rb +82 -0
  457. data/lib/rosett_ai/thor/tasks/config.rb +265 -0
  458. data/lib/rosett_ai/thor/tasks/content.rb +193 -0
  459. data/lib/rosett_ai/thor/tasks/dbus.rb +321 -0
  460. data/lib/rosett_ai/thor/tasks/design.rb +258 -0
  461. data/lib/rosett_ai/thor/tasks/desktop.rb +129 -0
  462. data/lib/rosett_ai/thor/tasks/doctor.rb +127 -0
  463. data/lib/rosett_ai/thor/tasks/documentation.rb +321 -0
  464. data/lib/rosett_ai/thor/tasks/engines.rb +167 -0
  465. data/lib/rosett_ai/thor/tasks/hooks.rb +219 -0
  466. data/lib/rosett_ai/thor/tasks/init.rb +259 -0
  467. data/lib/rosett_ai/thor/tasks/license.rb +120 -0
  468. data/lib/rosett_ai/thor/tasks/mcp.rb +535 -0
  469. data/lib/rosett_ai/thor/tasks/migrate.rb +121 -0
  470. data/lib/rosett_ai/thor/tasks/plugins.rb +157 -0
  471. data/lib/rosett_ai/thor/tasks/project.rb +260 -0
  472. data/lib/rosett_ai/thor/tasks/provenance.rb +195 -0
  473. data/lib/rosett_ai/thor/tasks/release.rb +314 -0
  474. data/lib/rosett_ai/thor/tasks/retrofit.rb +90 -0
  475. data/lib/rosett_ai/thor/tasks/tooling.rb +308 -0
  476. data/lib/rosett_ai/thor/tasks/validate.rb +108 -0
  477. data/lib/rosett_ai/thor/tasks/workflow.rb +196 -0
  478. data/lib/rosett_ai/tooling/ci_yaml_validator.rb +37 -0
  479. data/lib/rosett_ai/tooling/version_checker.rb +35 -0
  480. data/lib/rosett_ai/ui/accessible_tui.rb +61 -0
  481. data/lib/rosett_ai/ui/base.rb +46 -0
  482. data/lib/rosett_ai/ui/gtk4.rb +98 -0
  483. data/lib/rosett_ai/ui/kde.rb +40 -0
  484. data/lib/rosett_ai/ui/qt6.rb +40 -0
  485. data/lib/rosett_ai/ui/registry.rb +60 -0
  486. data/lib/rosett_ai/ui/tty_helper.rb +74 -0
  487. data/lib/rosett_ai/ui/tui.rb +59 -0
  488. data/lib/rosett_ai/validators/behaviour_validator.rb +20 -0
  489. data/lib/rosett_ai/validators/design_validator.rb +17 -0
  490. data/lib/rosett_ai/validators/schema_validator.rb +84 -0
  491. data/lib/rosett_ai/validators/tooling_validator.rb +17 -0
  492. data/lib/rosett_ai/version.rb +8 -0
  493. data/lib/rosett_ai/version_consistency_checker.rb +129 -0
  494. data/lib/rosett_ai/workflow/audit_log.rb +86 -0
  495. data/lib/rosett_ai/workflow/engine.rb +142 -0
  496. data/lib/rosett_ai/workflow/manager.rb +82 -0
  497. data/lib/rosett_ai/workflow/schema_validator.rb +71 -0
  498. data/lib/rosett_ai/workflow/step_runner.rb +61 -0
  499. data/lib/rosett_ai/workflow/steps/prompt_step.rb +62 -0
  500. data/lib/rosett_ai/workflow/steps/rai_step.rb +74 -0
  501. data/lib/rosett_ai/workflow/steps/shell_step.rb +53 -0
  502. data/lib/rosett_ai/yaml_loader.rb +78 -0
  503. data/lib/rosett_ai.rb +221 -0
  504. data/lib/rubocop/cop/rosett_ai/shell_interpolation.rb +54 -0
  505. data/lib/rubocop/cop/rosett_ai/unsafe_const_get.rb +60 -0
  506. data/lib/rubocop/cop/rosett_ai/unsafe_send.rb +50 -0
  507. data/lib/rubocop/cop/rosett_ai/unsafe_yaml_load.rb +40 -0
  508. data/lib/rubocop/rosett_ai.rb +9 -0
  509. data/lib/scripts/generated/docker_hub_tags.rb +126 -0
  510. data/locales/.gitkeep +0 -0
  511. data/locales/ar.yml +579 -0
  512. data/locales/en.yml +571 -0
  513. data/locales/fr.yml +567 -0
  514. data/packaging/build-engine-deb.sh +81 -0
  515. data/packaging/scripts/postinst +17 -0
  516. data/packaging/scripts/postrm +19 -0
  517. data/packaging/scripts/prerm +10 -0
  518. data/packaging/wrapper.sh.template +38 -0
  519. data/rosett-ai.gemspec +63 -0
  520. data/rules/.gitkeep +0 -0
  521. data/scripts/publish/pulp_upload.sh +123 -0
  522. data/settings.json +29 -0
  523. data/share/applications/be.neatnerds.rosettai.desktop +29 -0
  524. data/share/dbus-1/interfaces/be.neatnerds.rosettai.xml +103 -0
  525. data/share/dbus-1/services/be.neatnerds.rosettai.service +3 -0
  526. data/share/templates/behaviour/criticalthinking.yml +69 -0
  527. metadata +810 -0
@@ -0,0 +1,155 @@
1
+ # Replace Omnibus with fpm + ruby-build for Debian packaging
2
+
3
+ **Branch**: `packaging_fpm`
4
+ **Date**: 2026-02-02 through 2026-02-18
5
+ **Commits**: 11 (excluding merge commit)
6
+
7
+ ## Motivation
8
+
9
+ The Omnibus-based packaging approach had become a significant maintenance burden. Key issues included:
10
+
11
+ - **Excessive complexity**: 11 configuration files across `omnibus/` (Gemfile, Rakefile, omnibus.rb, project config, software config, ERB template, 4 package scripts)
12
+ - **Long build times**: 30-60 minutes per build due to compiling Ruby, OpenSSL, zlib, and libyaml from source
13
+ - **Large package size**: 100MB+ `.deb` files
14
+ - **Hardcoded gem paths**: The wrapper template used hardcoded `gems/3.3.0` paths that would break on Ruby version changes
15
+ - **Missing conffiles**: dpkg did not track `/etc/rosett-ai/settings.json`, causing user edits to be overwritten on upgrade
16
+ - **Broken symlinks**: Removal scripts did not properly clean up `/usr/local/bin/raictl`
17
+ - **Dependency overrides**: OpenSSL, zlib, libyaml were compiled from source despite always being present on Debian systems
18
+
19
+ ## Solution: fpm + ruby-build
20
+
21
+ The replacement uses two focused tools:
22
+
23
+ - **ruby-build** (already mirrored internally via rbenv): Compiles Ruby to `/opt/rosett-ai/embedded/` with system-linked shared libraries
24
+ - **fpm** (~> 1.16, Ruby gem): Packages the staging directory into a `.deb` with proper metadata
25
+
26
+ ### Key design decisions
27
+
28
+ 1. **No source compilation of system libraries** - libc6, libssl3, zlib1g, and libyaml-0-2 are declared as package dependencies instead of being compiled from source. These are always present on Debian systems.
29
+ 2. **Dynamic GEM_HOME** - The wrapper script uses `RbConfig::CONFIG["ruby_program_version"]` instead of hardcoded paths.
30
+ 3. **Direct Ruby invocation** - `exec ruby -I"${APP_DIR}/lib" bin/raictl "$@"` instead of `bundle exec`, eliminating bundler overhead at runtime.
31
+ 4. **dpkg conffile management** - `settings.json` ships at `/etc/rosett-ai/settings.json` and is registered as a conffile, so dpkg handles user modifications on upgrade.
32
+
33
+ ## Changes by area
34
+
35
+ ### New: Packaging infrastructure
36
+
37
+ | File | Purpose |
38
+ |------|---------|
39
+ | `lib/rosett_ai/thor/tasks/build.rb` (650 LOC) | Thor task orchestrating the full build pipeline |
40
+ | `packaging/wrapper.sh` | Runtime wrapper with dynamic paths |
41
+ | `packaging/scripts/postinst` | Post-install: creates `/usr/local/bin/raictl` symlink |
42
+ | `packaging/scripts/prerm` | Pre-removal: removes symlink |
43
+ | `packaging/scripts/postrm` | Post-removal: purge removes `/opt/rosett-ai` and `/etc/rosett-ai` |
44
+
45
+ The build task implements a stage-based pipeline:
46
+
47
+ 1. **Validate environment** - Checks for ruby-build, fakeroot, dpkg-deb
48
+ 2. **Compile Ruby** - Runs ruby-build into staging directory
49
+ 3. **Sync application** - Copies app files (excluding `.git`, `spec`, `tmp`, `pkg`, etc.)
50
+ 4. **Install gems** - `bundle install --deployment` using embedded Ruby
51
+ 5. **Install wrapper** - Copies wrapper script to `staging/opt/rosett-ai/bin/raictl`
52
+ 6. **Install default config** - Ships settings.json as dpkg conffile
53
+ 7. **Build package** - Invokes fpm with all metadata, dependencies, and maintainer scripts
54
+
55
+ Each stage is timed and reported in a summary table. Failures are caught and reported with context. Build logs are written to `tmp/build-<uuid>.log`.
56
+
57
+ #### Build context extraction
58
+
59
+ Mutable build state (build ID, log path, timings, failure info) was extracted into a `BuildContext` Struct to reduce class-level instance variable count and improve testability:
60
+
61
+ ```ruby
62
+ BuildContext = Struct.new(
63
+ :build_id, :build_start, :build_log,
64
+ :stage_timings, :completed_stages,
65
+ :failed_stage, :failure_reason, :summary_printed,
66
+ keyword_init: true
67
+ )
68
+ ```
69
+
70
+ ### Removed: Omnibus
71
+
72
+ All files under `omnibus/` were deleted:
73
+
74
+ - `omnibus/Gemfile` and `omnibus/Rakefile`
75
+ - `omnibus/omnibus.rb` (configuration)
76
+ - `omnibus/config/projects/rosett-ai.rb` (project definition)
77
+ - `omnibus/config/software/rosett-ai.rb` (software definition)
78
+ - `omnibus/config/templates/rosett-ai/nncc_wrapper.erb` (ERB wrapper template)
79
+ - `omnibus/package-scripts/rosett-ai/{postinst,postrm,preinst,prerm}` (4 scripts)
80
+
81
+ ### Refactored: Init task
82
+
83
+ The `init` command (`lib/rosett_ai/thor/tasks/init.rb`) was rewritten:
84
+
85
+ - Extracted `RosettAi::Init::DirectoryBuilder` and `RosettAi::Init::FileCopier` for clear separation
86
+ - `setup_global` and `setup_local` now properly delegate and store results
87
+ - Added `--no-compile` flag to skip the compilation step after init
88
+ - Summary table shows directory and file counts per scope
89
+
90
+ ### Refactored: Tooling
91
+
92
+ - `lib/rosett_ai/thor/tasks/tooling.rb` was simplified from a multi-tool manager (~420 LOC) to a focused GitLab CI YAML validator
93
+ - Five orphaned library files removed from `lib/rosett_ai/tooling/`:
94
+ - `detector.rb`, `display_helpers.rb`, `package_manager.rb`, `settings_manager.rb`, `tool_registry.rb`
95
+ - Four corresponding spec files removed from `spec/rosett_ai/tooling/`
96
+
97
+ ### CI/CD pipeline
98
+
99
+ | File | Change |
100
+ |------|--------|
101
+ | `.gitlab-ci-files/build/omnibus.yml` | Renamed to `package.yml`, rewritten for fpm |
102
+ | `.gitlab-ci-files/global/defaults.yml` | Added retry policy (max 2) for transient failures |
103
+ | `.gitlab-ci-files/global/stages.yml` | Updated stage ordering |
104
+ | `.gitlab-ci-files/global/variables.yml` | Added build-related variables |
105
+ | `.gitlab-ci-files/security_scan/gitleaks.yml` | Fixed false positives |
106
+ | `.gitlab-ci.yml` | Updated include path from omnibus to package |
107
+ | `.gitleaks.toml` | Added allowlist entries for test fixtures |
108
+
109
+ Retry policy covers: `runner_system_failure`, `job_execution_timeout`, `stuck_or_timeout_failure`, `api_failure`.
110
+
111
+ ### Test suite
112
+
113
+ - **277 examples, 0 failures** (up from ~230 on main)
114
+ - **92.92% line coverage**
115
+ - New spec files:
116
+ - `spec/rosett_ai/thor/tasks/build_package_spec.rb` (30 examples)
117
+ - `spec/rosett_ai/thor/tasks/build_spec.rb` (2 examples)
118
+ - `spec/rosett_ai/thor/tasks/init_spec.rb` (rewritten, 9 examples)
119
+
120
+ #### Thor warning suppression
121
+
122
+ RSpec's `allow_any_instance_of` uses `define_method` on Thor subclasses, which triggers Thor's `method_added` hook and produces `[WARNING] Attempted to create command` messages. This was solved by:
123
+
124
+ 1. Prepending `ThorTestSilencer` on `Thor.singleton_class` in `spec_helper.rb` - temporarily redirects `$stdout` during `create_command` to suppress the warning output
125
+ 2. Using `allow_any_instance_of` (required because Thor's `invoke` creates a new internal instance, making instance-level stubs ineffective)
126
+ 3. Adding `RSpec/AnyInstance` exclusion for `spec/rosett_ai/thor/tasks/**/*` in `.rubocop.yml`
127
+
128
+ ### Code quality configuration
129
+
130
+ - `.reek.yml`: Added targeted exclusions for the build task's inherent complexity (stage orchestration, environment validation, summary printing)
131
+ - `.rubocop.yml`: Added `RSpec/AnyInstance` exclusion for Thor task specs
132
+ - All linters pass clean: rubocop (0 offenses), reek (0 warnings), overcommit (all hooks OK)
133
+
134
+ ## Package details
135
+
136
+ | Property | Value |
137
+ |----------|-------|
138
+ | Package name | `rosett-ai` |
139
+ | Install path | `/opt/rosett-ai/` |
140
+ | Config path | `/etc/rosett-ai/` |
141
+ | Binary symlink | `/usr/local/bin/raictl` |
142
+ | Embedded Ruby | `/opt/rosett-ai/embedded/bin/ruby` |
143
+ | Package size | ~38 MB |
144
+ | Build time | ~5 minutes |
145
+ | Dependencies | libc6, libssl3, zlib1g, libyaml-0-2 |
146
+ | Compression | xz |
147
+
148
+ ## Verification checklist
149
+
150
+ - [x] `bin/raictl build package --clean --verbose` produces a `.deb`
151
+ - [x] `dpkg-deb --info` shows correct metadata and dependencies
152
+ - [x] `bundle exec rspec` passes (277 examples, 0 failures)
153
+ - [x] `bundle exec rubocop` passes (0 offenses)
154
+ - [x] `bundle exec reek` passes (0 warnings)
155
+ - [x] `overcommit -r` passes (all hooks OK)
@@ -0,0 +1,221 @@
1
+ # Implement testing.yml design document (P1)
2
+
3
+ **Branch**: `design_implementation`
4
+ **Date**: 2026-02-19
5
+ **Design doc**: `conf/design/testing.yml` v1.0.0
6
+
7
+ ## Motivation
8
+
9
+ The testing design document establishes a testing strategy that validates both
10
+ code correctness AND test quality. When AI writes both code and tests, the test
11
+ suite can be perfectly consistent yet meaningless. Mutation testing (Mutant) acts
12
+ as an independent third-party validator that mechanically verifies tests catch
13
+ real bugs, regardless of who wrote them.
14
+
15
+ This must be in place before feature development begins so all future code is
16
+ validated from day one. Testing is the second P1 domain after security.
17
+
18
+ ## Acceptance criteria
19
+
20
+ All 9 acceptance criteria from `testing.yml` are satisfied:
21
+
22
+ | # | Criterion | Evidence |
23
+ |---|-----------|----------|
24
+ | 1 | mutant-rspec installed + `.mutant.yml` | `.mutant.yml` with `usage: opensource`, rspec integration |
25
+ | 2 | SimpleCov thresholds block CI | 90% overall, 80% per-file (was 50/40) |
26
+ | 3 | RSpec on every CI push + JUnit XML | Already existed in `.gitlab-ci-files/test/rspec.yml` |
27
+ | 4 | Mutant on MR, blocks merge | `.gitlab-ci-files/test/mutant.yml`, `allow_failure: false` |
28
+ | 5 | factory_bot factories | `spec/support/factories/{behaviours,rules}.rb` |
29
+ | 6 | shared_examples for UI base | `spec/support/shared_examples/ui_implementation.rb` |
30
+ | 7 | Test fixtures | 7 files in `spec/fixtures/behaviours/` |
31
+ | 8 | AI test review checklist | `doc/ai_test_review_checklist.md` (11 points) |
32
+ | 9 | Property-based test | `spec/rosett_ai/yaml_loader_property_spec.rb` using Rantly |
33
+
34
+ ## Changes by area
35
+
36
+ ### New gems
37
+
38
+ | Gem | Version | Purpose |
39
+ |-----|---------|---------|
40
+ | `factory_bot` | ~> 6.5 | Test data factories for domain objects |
41
+ | `mutant-rspec` | ~> 0.14 | Mutation testing with RSpec integration |
42
+ | `rantly` | ~> 3.0 | Property-based / generative testing |
43
+
44
+ ### Mutant configuration (`.mutant.yml`)
45
+
46
+ ```yaml
47
+ usage: opensource # No license gem needed (>= 0.12)
48
+ integration: rspec
49
+ mutation:
50
+ timeout: 10.0
51
+ matcher:
52
+ subjects: # Library code with logic
53
+ - "RosettAi::Compiler*"
54
+ - "RosettAi::YamlLoader"
55
+ - "RosettAi::TextSanitizer"
56
+ - "RosettAi::Validators*"
57
+ - "RosettAi::Configuration"
58
+ - "RosettAi::Adopter*"
59
+ ignore: # CLI wiring (integration-tested)
60
+ - "RosettAi::Thor*"
61
+ - "RosettAi::VERSION"
62
+ ```
63
+
64
+ Verified mutation scores (after targeted test hardening):
65
+
66
+ - **Overall**: 97.83% (555 mutations, 543 killed, 12 equivalent survivors)
67
+ - **YamlLoader**: all non-equivalent mutations killed
68
+ - **TextSanitizer**: all non-equivalent mutations killed
69
+ - **Configuration**: all non-equivalent mutations killed
70
+
71
+ The 12 surviving mutations are all provably equivalent:
72
+
73
+ | Pattern | Count | Why equivalent |
74
+ |---------|-------|---------------|
75
+ | `RosettAi::ValidationError` → `ValidationError` | 3 | Same class resolution inside `module RosettAi` |
76
+ | `is_a?` → `instance_of?` | 4 | JSON.parse only produces plain Hash/Array |
77
+ | `to_h.empty?` → `.empty?` / `to_hash.empty?` | 2 | Same behaviour for Hash |
78
+ | `e.message` → `e` | 1 | StandardError#to_s == #message |
79
+ | `.each_value.sum` → `.sum` | 1 | Hash#sum yields [k,v]; count_keys(string)=0 |
80
+ | `.unicode_normalize(:nfc)` → `.unicode_normalize` | 1 | Ruby defaults to :nfc |
81
+
82
+ ### CI pipeline
83
+
84
+ New job in `.gitlab-ci-files/test/mutant.yml`:
85
+
86
+ - Runs only on merge requests (`merge_request_event`)
87
+ - Uses `--since $CI_MERGE_REQUEST_TARGET_BRANCH_NAME` for incremental analysis
88
+ - `GIT_DEPTH: 0` overrides the global shallow clone (mutant needs full history)
89
+ - `allow_failure: false` blocks merge if mutation score drops
90
+
91
+ ### SimpleCov changes
92
+
93
+ | Setting | Before | After |
94
+ |---------|--------|-------|
95
+ | `minimum_coverage` | 50% | 90% |
96
+ | `minimum_coverage_by_file` | 40% | 80% |
97
+ | Coverage groups | Library, Thor Tasks, Validators | Compilers, Validators, Adopter, Backup, Thor Tasks |
98
+
99
+ Current coverage: **93.09%** (1562/1678 lines), well above the 90% threshold.
100
+
101
+ ### factory_bot factories
102
+
103
+ Hash-based factories (not ActiveRecord) using `initialize_with` and `skip_create`:
104
+
105
+ - **`:behaviour`** — full behaviour hash with traits `:sensitive`, `:empty_rules`
106
+ - **`:rule`** — rule hash with sequenced IDs, traits `:disabled`, `:high_priority`
107
+
108
+ All keys are strings (matching YAML load output).
109
+
110
+ ### Test fixtures
111
+
112
+ | Fixture | Tests |
113
+ |---------|-------|
114
+ | `valid_behaviour.yml` | Happy path with 2 rules |
115
+ | `invalid_missing_fields.yml` | Missing `name` and `version` |
116
+ | `invalid_bad_types.yml` | Priority as string, enabled as string |
117
+ | `malicious_yaml_bomb.yml` | YAML anchor expansion (blocked by `safe_load`) |
118
+ | `malicious_ansi_injection.yml` | ANSI CSI + OSC sequences in descriptions |
119
+ | `unicode_nfc.yml` | Precomposed NFC diacritics |
120
+ | `unicode_mixed.yml` | NFD decomposed, CJK, RTL Arabic |
121
+
122
+ ### Property-based test
123
+
124
+ `spec/rosett_ai/yaml_loader_property_spec.rb` uses Rantly to verify:
125
+
126
+ 1. Random valid hashes within bounds always load successfully (50 trials)
127
+ 2. Random oversized payloads always raise `ValidationError` (10 trials)
128
+ 3. Random deeply-nested structures beyond depth limit always raise (10 trials)
129
+
130
+ ### Shared examples
131
+
132
+ `spec/support/shared_examples/ui_implementation.rb` defines the interface
133
+ contract (`:render`, `:display_table`, `:prompt_user`, `:show_spinner`) that
134
+ future UI implementations must satisfy. No consumers yet (P3 — ui_framework).
135
+
136
+ ### AI test review checklist
137
+
138
+ `doc/ai_test_review_checklist.md` documents the 11-point checklist:
139
+
140
+ 1. No tautological assertions
141
+ 2. Concrete expected values
142
+ 3. Mocks only at boundaries
143
+ 4. Unit under test never mocked
144
+ 5. Both positive and negative assertions
145
+ 6. Edge cases: empty, nil, boundary values
146
+ 7. Error cases tested
147
+ 8. No implementation coupling
148
+ 9. Behaviour-focused descriptions
149
+ 10. Mutant score >= 85%
150
+ 11. No redundant tests
151
+
152
+ ### Test hardening (mutation coverage 80% → 97.83%)
153
+
154
+ Targeted test improvements to kill 99 of the 111 surviving mutations:
155
+
156
+ **`spec/rosett_ai/configuration_spec.rb`**:
157
+
158
+ - Replaced `include` matchers with exact `eq` assertions
159
+ - Added nested hash deep-merge, array union, and deduplication tests
160
+ - Added type-mismatch tests: scalar↔hash, scalar↔array in both directions
161
+ - Added object identity test for empty overlay (kills `deep_merge` guard mutations)
162
+ - Added error message content assertion (parser detail, not just filename)
163
+ - Full `reload!` coverage: all 4 ivars cleared, re-reads files after reload
164
+ - Exact `schema_path` assertion with `eq` instead of `end_with`
165
+
166
+ **`spec/rosett_ai/yaml_loader_spec.rb`**:
167
+
168
+ - Restructured all tests under `describe '.load_file'` (mutant test selection fix)
169
+ - Added `Time` class permitted-by-default test
170
+ - Added boundary tests at exactly 11 levels (hash and array nesting)
171
+ - Added exact byte count and key count in error message assertions
172
+ - Added mixed structure key counting: arrays-of-hashes, nested totals
173
+ - Fixed context wording for RuboCop `RSpec/ContextWording`
174
+
175
+ **`spec/rosett_ai/text_sanitizer_spec.rb`**:
176
+
177
+ - Added non-UTF-8 encoding test (ISO-8859-1 → UTF-8 conversion)
178
+ - Added NFC-specific single codepoint assertion (length=1, not NFD length=2)
179
+ - Added encoding verification on output
180
+
181
+ ## Files created
182
+
183
+ | File | Purpose |
184
+ |------|---------|
185
+ | `.mutant.yml` | Mutant configuration |
186
+ | `.gitlab-ci-files/test/mutant.yml` | Mutant CI job (MR only) |
187
+ | `spec/support/factory_bot.rb` | FactoryBot require + RSpec config |
188
+ | `spec/support/factories/behaviours.rb` | Behaviour hash factory |
189
+ | `spec/support/factories/rules.rb` | Rule hash factory |
190
+ | `spec/support/shared_examples/ui_implementation.rb` | UI interface contract |
191
+ | `spec/fixtures/behaviours/valid_behaviour.yml` | Valid fixture |
192
+ | `spec/fixtures/behaviours/invalid_missing_fields.yml` | Invalid fixture |
193
+ | `spec/fixtures/behaviours/invalid_bad_types.yml` | Invalid fixture |
194
+ | `spec/fixtures/behaviours/malicious_yaml_bomb.yml` | Malicious fixture |
195
+ | `spec/fixtures/behaviours/malicious_ansi_injection.yml` | Malicious fixture |
196
+ | `spec/fixtures/behaviours/unicode_nfc.yml` | Unicode fixture |
197
+ | `spec/fixtures/behaviours/unicode_mixed.yml` | Unicode fixture |
198
+ | `doc/ai_test_review_checklist.md` | 11-point review checklist |
199
+ | `spec/rosett_ai/yaml_loader_property_spec.rb` | Property-based test |
200
+ | `doc/changes/2026-02-19-testing-infrastructure.md` | This change doc |
201
+
202
+ ## Files modified
203
+
204
+ | File | Change |
205
+ |------|--------|
206
+ | `Gemfile` | Added factory_bot, mutant-rspec, rantly |
207
+ | `spec/spec_helper.rb` | Thresholds 90/80, coverage groups, support autoloading |
208
+ | `.gitlab-ci.yml` | Added mutant.yml include |
209
+ | `.rubocop.yml` | Added rubocop-factory_bot plugin |
210
+ | `CHANGELOG.md` | Added testing infrastructure entries |
211
+
212
+ ## Verification
213
+
214
+ - [x] `bundle install` — 26 gems, 122 total
215
+ - [x] `bundle exec rspec` — 351 examples, 0 failures
216
+ - [x] `bundle exec rubocop` — 0 offenses
217
+ - [x] `bundle exec reek lib/` — 0 warnings
218
+ - [x] `bundle exec mutant run` — 97.83% coverage (543/555 killed, 12 equivalent)
219
+ - [x] `.mutant.yml` auto-loaded (no `--usage` flag needed)
220
+ - [x] Factory_bot factories produce valid string-keyed hashes
221
+ - [x] All 7 fixture files parse correctly (YAML bomb blocked by `safe_load`)
@@ -0,0 +1,281 @@
1
+ # Implement security.yml design document (P1)
2
+
3
+ **Branch**: `design_implementation`
4
+ **Date**: 2026-02-19 to 2026-02-21
5
+ **Design doc**: `conf/design/security.yml` v1.1.0
6
+ **Commits**: fdb73e0, 9fc7e55, 46aa519, 1d0bfb9 (+ supporting: dc42c3c, 7423856, ff3d81e)
7
+
8
+ ## Motivation
9
+
10
+ Security is the foundation layer of rosett-ai. Every line of code written after this
11
+ document is adopted must follow its constraints. Retrofitting security is 10x
12
+ more expensive than building it in. This implementation establishes hard rules,
13
+ required tooling, and safe coding patterns that apply to ALL code in the project
14
+ — core, TUI, GUI, compilers, and tests.
15
+
16
+ The security design document defines 12 acceptance criteria covering: dependency
17
+ auditing, static analysis, safe YAML parsing, shell injection prevention, file
18
+ permissions, YAML bounds enforcement, ANSI sanitization, NFC normalization, and
19
+ secrets resolution.
20
+
21
+ ## Acceptance criteria
22
+
23
+ All 12 acceptance criteria from `security.yml` are satisfied:
24
+
25
+ | # | Criterion | Evidence |
26
+ |---|-----------|----------|
27
+ | 1 | bundler-audit runs in CI and blocks merge on any known CVE | `.gitlab-ci-files/security_scan/bundler-audit.yml` (pre-existing), `.overcommit.yml` hook |
28
+ | 2 | ruby_audit runs in CI and blocks merge on Ruby stdlib vulnerabilities | `.gitlab-ci-files/security_scan/ruby-audit.yml`, `.overcommit.yml` RubyAudit hook |
29
+ | 3 | RuboCop security cops are enabled and enforced | `.rubocop.yml` requires `./lib/rubocop/rosett_ai`, cops at `Severity: error` |
30
+ | 4 | No instance of YAML.load exists in codebase (only safe_load) | `RosettAi/UnsafeYamlLoad` cop blocks; all call sites migrated to `RosettAi::YamlLoader.load_file` |
31
+ | 5 | No string-interpolated system() calls exist | `RosettAi/ShellInterpolation` cop blocks; backtick interpolation also detected |
32
+ | 6 | All file write operations use explicit permission setting | All `File.open` calls use mode arg (0o644 for files, 0o600 for secrets) |
33
+ | 7 | CI pipeline rejects code that violates any security constraint | Custom cops at `Severity: error` fail CI; bundler-audit + ruby-audit block |
34
+ | 8 | Custom RuboCop cops flag unsafe patterns | `UnsafeYamlLoad` + `ShellInterpolation` cops with full test suites |
35
+ | 9 | YAML files exceeding bounds are rejected with clear error | `YamlLoader` enforces 1 MB size, 10-level depth, 1000 key count |
36
+ | 10 | TUI output containing ANSI escapes from YAML is sanitized | `TextSanitizer.sanitize_for_display` at all output boundaries |
37
+ | 11 | Filenames and identifiers are NFC-normalized | `TextSanitizer.normalize_nfc` at input boundaries (compiler, behaviour, adopt) |
38
+ | 12 | Secrets are never written to disk unless explicitly configured | `SecretsResolver`: ENV > secrets file (0600); never writes secrets |
39
+
40
+ ## Changes by area
41
+
42
+ ### Ruby upgrade 3.3.8 to 3.3.10
43
+
44
+ **Commit**: fdb73e0
45
+
46
+ Three CVEs resolved by upgrading the Ruby runtime:
47
+
48
+ | CVE | Component | Severity |
49
+ |-----|-----------|----------|
50
+ | CVE-2025-24294 | resolv (DNS) | DoS via crafted response |
51
+ | CVE-2025-58767 | REXML | DoS via XML entity expansion |
52
+ | CVE-2025-61594 | URI | Credential leak in parsing |
53
+
54
+ Files updated: `.ruby-version`, `CLAUDE.md`, `README.md`, `doc/PACKAGING.md`,
55
+ `doc/USAGE.md`, `spec/rosett_ai/thor/tasks/build_package_spec.rb`.
56
+
57
+ ### Shell injection elimination
58
+
59
+ **Commit**: 9fc7e55
60
+
61
+ Every `system()` call using string interpolation was replaced with array-form,
62
+ and every backtick command with interpolation was replaced with `IO.popen`.
63
+
64
+ **build.rb** — backtick version check to IO.popen:
65
+
66
+ ```diff
67
+ - installed = `#{ruby_bin} -e "puts RUBY_VERSION"`.strip
68
+ + installed = IO.popen([ruby_bin.to_s, '-e', 'puts RUBY_VERSION'], &:read).strip
69
+ ```
70
+
71
+ **build.rb** — `command_available?` shell to pure Ruby:
72
+
73
+ ```diff
74
+ - def command_available?(cmd)
75
+ - system("command -v #{cmd} > /dev/null 2>&1")
76
+ + def command_available?(cmd)
77
+ + ENV.fetch('PATH', '').split(File::PATH_SEPARATOR).any? do |dir|
78
+ + File.executable?(File.join(dir, cmd))
79
+ + end
80
+ ```
81
+
82
+ **compressor.rb** — xz pipeline to array-form:
83
+
84
+ ```diff
85
+ - xz_cmd = "xz#{" -#{level}" if level} --stdout #{tar_path} > #{output_path}"
86
+ - success = system(xz_cmd)
87
+ + xz_args = ['xz']
88
+ + xz_args.push("-#{level}") if level
89
+ + xz_args.push('--stdout', tar_path)
90
+ + success = File.open(output_path, 'wb', 0o644) do |out|
91
+ + system(*xz_args, out: out)
92
+ + end
93
+ ```
94
+
95
+ ### File permissions enforcement
96
+
97
+ **Commit**: 9fc7e55
98
+
99
+ All `File.write` and `File.open(..., 'w')` calls now use explicit permission
100
+ mode arguments:
101
+
102
+ | File | Permission | Rationale |
103
+ |------|-----------|-----------|
104
+ | Build log | 0o644 | Non-sensitive output |
105
+ | Compiled rules | 0o644 | User configuration files |
106
+ | Lockfile | 0o644 | Version tracking metadata |
107
+ | Behaviour temp files | 0o644 | Temporary editor copies |
108
+ | Adopt cache | 0o644 | Non-sensitive cache |
109
+ | Compressed archives | 0o644 | User-accessible output |
110
+ | Secrets file | 0o600 | Contains API keys (validated by `SecretsResolver`) |
111
+
112
+ ### Custom RuboCop cops
113
+
114
+ **Commit**: 46aa519
115
+
116
+ Two custom cops enforce security rules statically:
117
+
118
+ **`RosettAi/UnsafeYamlLoad`** (`lib/rubocop/cop/rosett-ai/unsafe_yaml_load.rb`):
119
+
120
+ ```ruby
121
+ def_node_matcher :unsafe_yaml_load?, <<~PATTERN
122
+ (send (const {nil? cbase} :YAML) {:load :load_file} ...)
123
+ PATTERN
124
+ ```
125
+
126
+ Flags `YAML.load` and `YAML.load_file`. Severity: error (blocks CI).
127
+
128
+ **`RosettAi/ShellInterpolation`** (`lib/rubocop/cop/rosett-ai/shell_interpolation.rb`):
129
+
130
+ ```ruby
131
+ def_node_matcher :shell_with_interpolation?, <<~PATTERN
132
+ (send nil? {:system :exec :spawn} dstr ...)
133
+ PATTERN
134
+
135
+ def on_xstr(node)
136
+ return unless node.children.any?(&:begin_type?)
137
+ add_offense(node, message: BACKTICK_MSG)
138
+ end
139
+ ```
140
+
141
+ Flags `system("cmd #{arg}")`, `exec("#{cmd}")`, `spawn("#{x}")`, and backtick
142
+ interpolation `` `#{cmd} --flag` ``. Severity: error (blocks CI).
143
+
144
+ ### YAML bounds enforcement (YamlLoader)
145
+
146
+ **Commit**: 46aa519
147
+
148
+ `lib/rosett_ai/yaml_loader.rb` enforces three bounds before returning parsed data:
149
+
150
+ | Bound | Limit | Check method |
151
+ |-------|-------|-------------|
152
+ | File size | 1 MB (1,048,576 bytes) | `check_file_size!` — before parsing |
153
+ | Nesting depth | 10 levels | `check_depth!` — recursive walk |
154
+ | Key count | 1,000 keys | `check_key_count!` — recursive count |
155
+
156
+ All existing YAML.safe_load call sites were migrated to use `YamlLoader.load_file`:
157
+ `rule_adopter.rb`, `behaviour_compiler.rb`, `behaviour.rb` (5 call sites),
158
+ `behaviour_validator.rb`.
159
+
160
+ ### ANSI sanitization (TextSanitizer)
161
+
162
+ **Commit**: 46aa519
163
+
164
+ `lib/rosett_ai/text_sanitizer.rb` provides two sanitization methods:
165
+
166
+ **`strip_ansi`** — removes ANSI CSI sequences (`\e[...X`), OSC sequences
167
+ (`\e]...\a`), and non-printable control characters (except tab, newline, CR):
168
+
169
+ ```ruby
170
+ ANSI_PATTERN = /\e\[\d*(?:;\d*)*[A-Za-z]|\e\][^\a]*\a|[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/
171
+ ```
172
+
173
+ **`sanitize_for_display`** — recursively strips ANSI from strings, hashes,
174
+ and arrays. Integrated at display boundaries in `adopt.rb` and `behaviour.rb`.
175
+
176
+ ### NFC normalization
177
+
178
+ **Commit**: 46aa519
179
+
180
+ `TextSanitizer.normalize_nfc` applies Unicode NFC normalization at input
181
+ boundaries. Used in:
182
+
183
+ - `behaviour_compiler.rb` — filename normalization before key generation
184
+ - `behaviour.rb` — name parameters in add/modify/delete/show/validate commands
185
+ - `rule_adopter.rb` — sensitive source filename comparison
186
+
187
+ ### Secrets resolution (SecretsResolver)
188
+
189
+ **Commit**: bf9f15e
190
+
191
+ `lib/rosett_ai/secrets_resolver.rb` resolves secrets through an ordered fallback:
192
+
193
+ 1. Environment variable (`ENV.fetch`)
194
+ 2. Secrets file (`~/.config/rosett-ai/secrets.yml` with 0600 permissions)
195
+ 3. Raise with instructions if not found
196
+
197
+ Permission validation rejects secrets files without 0600 permissions.
198
+
199
+ ### Backtick interpolation detection
200
+
201
+ **Commit**: 1d0bfb9
202
+
203
+ Extended the `ShellInterpolation` cop with `on_xstr` handler to detect
204
+ backtick interpolation (`` `#{cmd}` ``), not just `system()`/`exec()`/`spawn()`.
205
+
206
+ ## Secrets resolution flow
207
+
208
+ ```mermaid
209
+ flowchart TD
210
+ START[resolve env_key] --> ENV{ENV variable<br/>set and non-empty?}
211
+ ENV -->|yes| RETURN_ENV[Return ENV value]
212
+ ENV -->|no| FILE{Secrets file<br/>exists?}
213
+ FILE -->|no| ERROR[Raise: key not found<br/>with setup instructions]
214
+ FILE -->|yes| PERMS{Permissions<br/>== 0600?}
215
+ PERMS -->|no| PERMS_ERROR[Raise: insecure permissions<br/>expected 0600]
216
+ PERMS -->|yes| YAML[Load via YamlLoader]
217
+ YAML --> KEY{Key in<br/>YAML data?}
218
+ KEY -->|yes| RETURN_FILE[Return file value]
219
+ KEY -->|no| ERROR
220
+ ```
221
+
222
+ ## YAML bounds pipeline
223
+
224
+ ```mermaid
225
+ flowchart LR
226
+ INPUT[YAML file path] --> SIZE{File size<br/>≤ 1 MB?}
227
+ SIZE -->|no| REJECT1[Raise: exceeds size limit]
228
+ SIZE -->|yes| PARSE[YAML.safe_load_file<br/>permitted: Date, Time]
229
+ PARSE --> DEPTH{Nesting<br/>≤ 10 levels?}
230
+ DEPTH -->|no| REJECT2[Raise: exceeds depth limit]
231
+ DEPTH -->|yes| KEYS{Key count<br/>≤ 1000?}
232
+ KEYS -->|no| REJECT3[Raise: exceeds key limit]
233
+ KEYS -->|yes| RETURN[Return parsed data]
234
+ ```
235
+
236
+ ## Files created
237
+
238
+ | File | Purpose |
239
+ |------|---------|
240
+ | `lib/rosett_ai/yaml_loader.rb` | Centralized YAML loading with bounds |
241
+ | `lib/rosett_ai/text_sanitizer.rb` | ANSI stripping + NFC normalization |
242
+ | `lib/rosett_ai/secrets_resolver.rb` | Multi-source secret resolution |
243
+ | `lib/rubocop/cop/rosett-ai/shell_interpolation.rb` | Shell injection detection cop |
244
+ | `lib/rubocop/cop/rosett-ai/unsafe_yaml_load.rb` | Unsafe YAML.load detection cop |
245
+ | `lib/rubocop/rosett_ai.rb` | Cop loader |
246
+ | `spec/rosett_ai/yaml_loader_spec.rb` | YamlLoader unit tests |
247
+ | `spec/rosett_ai/text_sanitizer_spec.rb` | TextSanitizer unit tests |
248
+ | `spec/rosett_ai/secrets_resolver_spec.rb` | SecretsResolver unit tests (167 lines) |
249
+ | `spec/rubocop/cop/rosett-ai/shell_interpolation_spec.rb` | ShellInterpolation cop tests |
250
+ | `spec/rubocop/cop/rosett-ai/unsafe_yaml_load_spec.rb` | UnsafeYamlLoad cop tests |
251
+ | `.gitlab-ci-files/security_scan/ruby-audit.yml` | Ruby stdlib CVE scanning CI job |
252
+
253
+ ## Files modified
254
+
255
+ | File | Change |
256
+ |------|--------|
257
+ | `lib/rosett_ai/thor/tasks/build.rb` | Backtick to IO.popen, shell to pure Ruby, File.open with perms |
258
+ | `lib/rosett_ai/backup/compressor.rb` | Shell pipeline to array-form, File.open with perms |
259
+ | `lib/rosett_ai/thor/tasks/behaviour.rb` | YamlLoader + TextSanitizer integration (5 call sites) |
260
+ | `lib/rosett_ai/thor/tasks/adopt.rb` | TextSanitizer for display, SecretsResolver for API key |
261
+ | `lib/rosett_ai/adopter/rule_adopter.rb` | YamlLoader for cache/redactions, TextSanitizer for filenames |
262
+ | `lib/rosett_ai/compiler/behaviour_compiler.rb` | YamlLoader + NFC filename normalization |
263
+ | `lib/rosett_ai/validators/behaviour_validator.rb` | YamlLoader with ValidationError handling |
264
+ | `lib/rosett_ai/thor/tasks/compile.rb` | File.open with explicit permissions |
265
+ | `.rubocop.yml` | Added custom cop require + enforcement at Severity: error |
266
+ | `.overcommit.yml` | Added RubyAudit hook |
267
+ | `.ruby-version` | 3.3.8 to 3.3.10 |
268
+ | `Gemfile` | Added ruby_audit, flog |
269
+
270
+ ## Verification
271
+
272
+ - [x] `bundle exec ruby-audit check` — 0 vulnerabilities
273
+ - [x] `bundle exec bundler-audit check` — 0 vulnerabilities
274
+ - [x] `bundle exec rubocop` — 0 offenses (custom cops active)
275
+ - [x] `bundle exec reek lib/` — 0 warnings
276
+ - [x] `bundle exec rspec` — 437 examples, 0 failures
277
+ - [x] `bundle exec mutant run` — 97.83% kill rate
278
+ - [x] No `YAML.load` calls in codebase (cop enforced)
279
+ - [x] No interpolated system/exec/spawn/backtick calls (cop enforced)
280
+ - [x] All File.open/File.write use explicit permissions
281
+ - [x] SecretsResolver validates 0600 on secrets file