parapet-rails 0.1.0

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 (231) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +48 -0
  4. data/Rakefile +7 -0
  5. data/app/assets/config/parapet/grammar-registry.json +52 -0
  6. data/app/assets/images/parapet/icons/activities/component-inspector.svg +4 -0
  7. data/app/assets/images/parapet/icons/activities/coverage-heatmap.svg +3 -0
  8. data/app/assets/images/parapet/icons/activities/discovery-sidebar.svg +3 -0
  9. data/app/assets/images/parapet/icons/activities/environment-toggles.svg +4 -0
  10. data/app/assets/images/parapet/icons/activities/failure-inspector.svg +5 -0
  11. data/app/assets/images/parapet/icons/activities/flaky-history.svg +3 -0
  12. data/app/assets/images/parapet/icons/activities/flight-recorder.svg +5 -0
  13. data/app/assets/images/parapet/icons/activities/health-graph.svg +3 -0
  14. data/app/assets/images/parapet/icons/activities/injection-engine.svg +3 -0
  15. data/app/assets/images/parapet/icons/activities/live-sql-stream.svg +5 -0
  16. data/app/assets/images/parapet/icons/activities/live-web-preview.svg +4 -0
  17. data/app/assets/images/parapet/icons/activities/monaco-editor.svg +5 -0
  18. data/app/assets/images/parapet/icons/activities/progress-area.svg +3 -0
  19. data/app/assets/images/parapet/icons/activities/registry-metadata.svg +5 -0
  20. data/app/assets/images/parapet/icons/activities/seed-inspector.svg +4 -0
  21. data/app/assets/images/parapet/icons/activities/slow-test-bottlenecks.svg +4 -0
  22. data/app/assets/images/parapet/icons/activities/smart-search.svg +4 -0
  23. data/app/assets/images/parapet/icons/activities/snippet-library.svg +4 -0
  24. data/app/assets/images/parapet/icons/activities/terminal.svg +4 -0
  25. data/app/assets/images/parapet/icons/activities/unified-schema-view.svg +4 -0
  26. data/app/assets/images/parapet/icons/activities/visual-comparison.svg +3 -0
  27. data/app/assets/images/parapet/icons/activities/visual-summary-bar.svg +5 -0
  28. data/app/assets/images/parapet/icons/nav/ideas.svg +4 -0
  29. data/app/assets/images/parapet/icons/nav/languages.svg +8 -0
  30. data/app/assets/images/parapet/icons/nav/profile.svg +4 -0
  31. data/app/assets/images/parapet/icons/nav/pulse.svg +7 -0
  32. data/app/assets/images/parapet/icons/nav/settings.svg +4 -0
  33. data/app/assets/images/parapet/icons/nav/state.svg +5 -0
  34. data/app/assets/images/parapet/icons/nav/styles.svg +7 -0
  35. data/app/assets/images/parapet/icons/nav/tests.svg +4 -0
  36. data/app/assets/images/parapet/icons/nav/traffic.svg +12 -0
  37. data/app/assets/javascripts/parapet/activity-card.js +439 -0
  38. data/app/assets/javascripts/parapet/activity-cards.js +291 -0
  39. data/app/assets/javascripts/parapet/activity-registry.js +181 -0
  40. data/app/assets/javascripts/parapet/activity-toggles.js +76 -0
  41. data/app/assets/javascripts/parapet/application.js +37 -0
  42. data/app/assets/javascripts/parapet/card-failure-inspector.js +134 -0
  43. data/app/assets/javascripts/parapet/card-file-editor.js +1722 -0
  44. data/app/assets/javascripts/parapet/card-progress-area.js +297 -0
  45. data/app/assets/javascripts/parapet/card-smart-search.js +114 -0
  46. data/app/assets/javascripts/parapet/card-snippet-library.js +251 -0
  47. data/app/assets/javascripts/parapet/explorer-resizer.js +92 -0
  48. data/app/assets/javascripts/parapet/parapet_qss.js +342 -0
  49. data/app/assets/javascripts/parapet/proficiency-filter.js +53 -0
  50. data/app/assets/javascripts/parapet/sidebar-resizer.js +197 -0
  51. data/app/assets/javascripts/parapet/styles-workbench.js +819 -0
  52. data/app/assets/javascripts/parapet/tree-logic.js +320 -0
  53. data/app/assets/stylesheets/parapet/_qss_variables.scss +122 -0
  54. data/app/assets/stylesheets/parapet/parapet_qss.scss +69 -0
  55. data/app/assets/stylesheets/parapet/qss_application.css +260940 -0
  56. data/app/assets/stylesheets/parapet/qss_application.css.map +1 -0
  57. data/app/assets/stylesheets/parapet/qss_application.scss +5 -0
  58. data/app/assets/stylesheets/parapet/qss_workspace.scss +209 -0
  59. data/app/assets/stylesheets/parapet/sample.qss +22 -0
  60. data/app/controllers/parapet/application_controller.rb +54 -0
  61. data/app/controllers/parapet/base_controller.rb +18 -0
  62. data/app/controllers/parapet/build_suite/dashboard_controller.rb +10 -0
  63. data/app/controllers/parapet/css_suite/compiler_controller.rb +95 -0
  64. data/app/controllers/parapet/css_suite/dashboard_controller.rb +10 -0
  65. data/app/controllers/parapet/css_suite/preview_controller.rb +46 -0
  66. data/app/controllers/parapet/dashboard_controller.rb +8 -0
  67. data/app/controllers/parapet/drafts_controller.rb +30 -0
  68. data/app/controllers/parapet/explorers_controller.rb +53 -0
  69. data/app/controllers/parapet/files_controller.rb +208 -0
  70. data/app/controllers/parapet/footers_controller.rb +24 -0
  71. data/app/controllers/parapet/intentions_suite/dashboard_controller.rb +10 -0
  72. data/app/controllers/parapet/landing_controller.rb +14 -0
  73. data/app/controllers/parapet/monitor_suite/dashboard_controller.rb +10 -0
  74. data/app/controllers/parapet/pm_suite/publish_controller.rb +16 -0
  75. data/app/controllers/parapet/polyglot_suite/dashboard_controller.rb +10 -0
  76. data/app/controllers/parapet/runner_controller.rb +34 -0
  77. data/app/controllers/parapet/sessions_controller.rb +35 -0
  78. data/app/controllers/parapet/signal_suite/dashboard_controller.rb +10 -0
  79. data/app/controllers/parapet/test_suite/dashboard_controller.rb +10 -0
  80. data/app/controllers/parapet/widgets_controller.rb +33 -0
  81. data/app/helpers/parapet/application_helper.rb +23 -0
  82. data/app/helpers/parapet/icon_helper.rb +57 -0
  83. data/app/jobs/parapet/application_job.rb +4 -0
  84. data/app/mailers/parapet/application_mailer.rb +6 -0
  85. data/app/models/parapet/activity.rb +14 -0
  86. data/app/models/parapet/application_record.rb +5 -0
  87. data/app/models/parapet/current.rb +6 -0
  88. data/app/models/parapet/draft_recovery.rb +9 -0
  89. data/app/models/parapet/internal_record.rb +34 -0
  90. data/app/models/parapet/run_history.rb +22 -0
  91. data/app/models/parapet/runtime_setting.rb +8 -0
  92. data/app/models/parapet/session.rb +7 -0
  93. data/app/models/parapet/suite_assignment.rb +30 -0
  94. data/app/models/parapet/user.rb +12 -0
  95. data/app/services/parapet/activity_phrase_renderer.rb +35 -0
  96. data/app/services/parapet/activity_writer.rb +26 -0
  97. data/app/services/parapet/authorization.rb +198 -0
  98. data/app/services/parapet/consistency_auditor.rb +58 -0
  99. data/app/services/parapet/directory_scanner.rb +159 -0
  100. data/app/services/parapet/draft_recovery_store.rb +18 -0
  101. data/app/services/parapet/explorer_manager.rb +402 -0
  102. data/app/services/parapet/file_save_service.rb +21 -0
  103. data/app/services/parapet/git_side_ref_committer.rb +50 -0
  104. data/app/services/parapet/noun_discovery.rb +26 -0
  105. data/app/services/parapet/publish_gate.rb +83 -0
  106. data/app/services/parapet/test_runner.rb +178 -0
  107. data/app/views/layouts/parapet/application.html.erb +83 -0
  108. data/app/views/layouts/parapet/landing.html.erb +19 -0
  109. data/app/views/parapet/activities/_ai_error_explainer.html.erb +33 -0
  110. data/app/views/parapet/activities/_ai_explainer_widget.html.erb +20 -0
  111. data/app/views/parapet/activities/_calendar_widget.html.erb +31 -0
  112. data/app/views/parapet/activities/_code_snippets_widget.html.erb +22 -0
  113. data/app/views/parapet/activities/_coverage_explorer.html.erb +1 -0
  114. data/app/views/parapet/activities/_dropdown_test.html.erb +20 -0
  115. data/app/views/parapet/activities/_explorer_node.html.erb +102 -0
  116. data/app/views/parapet/activities/_explorer_tree.html.erb +16 -0
  117. data/app/views/parapet/activities/_file_editor.html.erb +183 -0
  118. data/app/views/parapet/activities/_file_explorer.html.erb +23 -0
  119. data/app/views/parapet/activities/_fix_suggestions_widget.html.erb +13 -0
  120. data/app/views/parapet/activities/_generic.html.erb +6 -0
  121. data/app/views/parapet/activities/_live_preview.html.erb +13 -0
  122. data/app/views/parapet/activities/_perspective_filters.html.erb +36 -0
  123. data/app/views/parapet/activities/_progress_area.html.erb +37 -0
  124. data/app/views/parapet/activities/_project_explorer.html.erb +1 -0
  125. data/app/views/parapet/activities/_registry_explorer.html.erb +1 -0
  126. data/app/views/parapet/activities/_run_history.html.erb +2 -0
  127. data/app/views/parapet/activities/_smart_search.html.erb +11 -0
  128. data/app/views/parapet/activities/file_explorer/_perspective_scripts.html.erb +58 -0
  129. data/app/views/parapet/activities/file_explorer/_perspectives.html.erb +0 -0
  130. data/app/views/parapet/activities/file_explorer/_viewport_scripts.html.erb +33 -0
  131. data/app/views/parapet/css_suite/_workbench.html.erb +51 -0
  132. data/app/views/parapet/dashboards/index.html.erb +113 -0
  133. data/app/views/parapet/debug_panel.html.erb +79 -0
  134. data/app/views/parapet/debug_panel_tailwind.html.erb +98 -0
  135. data/app/views/parapet/erb_patch_test.html.erb +5 -0
  136. data/app/views/parapet/explorers/show.html.erb +1 -0
  137. data/app/views/parapet/git_viewer.html.erb +62 -0
  138. data/app/views/parapet/git_viewer_after.html.erb +14 -0
  139. data/app/views/parapet/git_viewer_before.html.erb +14 -0
  140. data/app/views/parapet/git_viewer_governed.html.erb +88 -0
  141. data/app/views/parapet/git_viewer_governed_after.html.erb +36 -0
  142. data/app/views/parapet/git_viewer_governed_before.html.erb +36 -0
  143. data/app/views/parapet/git_viewer_tailwind.html.erb +88 -0
  144. data/app/views/parapet/git_viewer_thin_wrapper.html.erb +85 -0
  145. data/app/views/parapet/hierarchical_layout_test.html.erb +85 -0
  146. data/app/views/parapet/hybrid_stress_test.html.erb +25 -0
  147. data/app/views/parapet/index.html.erb +35 -0
  148. data/app/views/parapet/mutation_stress_test.html.erb +82 -0
  149. data/app/views/parapet/recursive_anchoring_test.html.erb +66 -0
  150. data/app/views/parapet/sessions/new.html.erb +28 -0
  151. data/app/views/parapet/shared/_cad_inspector.html.erb +92 -0
  152. data/app/views/parapet/shared/_discovery_sidebar.html.erb +48 -0
  153. data/app/views/parapet/shared/_explorer_bar.html.erb +71 -0
  154. data/app/views/parapet/shared/_footer.html.erb +10 -0
  155. data/app/views/parapet/shared/_footer_ideas.html.erb +36 -0
  156. data/app/views/parapet/shared/_footer_languages.html.erb +33 -0
  157. data/app/views/parapet/shared/_footer_pulse.html.erb +33 -0
  158. data/app/views/parapet/shared/_footer_state.html.erb +33 -0
  159. data/app/views/parapet/shared/_footer_styles.html.erb +37 -0
  160. data/app/views/parapet/shared/_footer_tests.html.erb +39 -0
  161. data/app/views/parapet/shared/_footer_traffic.html.erb +45 -0
  162. data/app/views/parapet/shared/_global_nav.html.erb +55 -0
  163. data/app/views/parapet/shared/_head.html.erb +4 -0
  164. data/app/views/parapet/shared/_header.html.erb +20 -0
  165. data/app/views/parapet/shared/_object_inspector.html.erb +76 -0
  166. data/app/views/parapet/shared/_ribbon_header.html.erb +3 -0
  167. data/app/views/parapet/shared/_root_selector_modal.html.erb +15 -0
  168. data/app/views/parapet/shared/_tab_bar.html.erb +62 -0
  169. data/app/views/parapet/shared/activities_ribbon/_agent_group.html.erb +15 -0
  170. data/app/views/parapet/shared/activities_ribbon/_analysis_group.html.erb +15 -0
  171. data/app/views/parapet/shared/activities_ribbon/_architect_group.html.erb +21 -0
  172. data/app/views/parapet/shared/activities_ribbon/_explorer_group.html.erb +12 -0
  173. data/app/views/parapet/shared/activities_ribbon/_inspector_group.html.erb +15 -0
  174. data/app/views/parapet/shared/activities_ribbon/_proficiency_filter.html.erb +24 -0
  175. data/app/views/parapet/shared/activities_ribbon/_runner_group.html.erb +21 -0
  176. data/app/views/parapet/shared/activities_ribbon/_spatial_inspector_group.html.erb +18 -0
  177. data/app/views/parapet/shared/activities_ribbon/_studio_group.html.erb +18 -0
  178. data/app/views/parapet/shared/activities_ribbon/_stylist_group.html.erb +18 -0
  179. data/app/views/parapet/shared/explorers/_base.html.erb +107 -0
  180. data/app/views/parapet/shared/explorers/_css.html.erb +1 -0
  181. data/app/views/parapet/shared/explorers/_intentions.html.erb +1 -0
  182. data/app/views/parapet/shared/explorers/_project.html.erb +1 -0
  183. data/app/views/parapet/shared/explorers/_tests.html.erb +1 -0
  184. data/app/views/parapet/shared/head/_meta.html.erb +3 -0
  185. data/app/views/parapet/shared/head/_styles_activities.html.erb +152 -0
  186. data/app/views/parapet/shared/head/_styles_shared.html.erb +52 -0
  187. data/app/views/parapet/shared/head/_styles_workspace.html.erb +67 -0
  188. data/app/views/parapet/shared/ribbon_header/_diagnostic_controls.html.erb +17 -0
  189. data/app/views/parapet/shared/ribbon_header/_execution_controls.html.erb +28 -0
  190. data/app/views/parapet/shared/ribbon_header/_focus_search.html.erb +5 -0
  191. data/app/views/parapet/shared/root_selector/_header.html.erb +10 -0
  192. data/app/views/parapet/shared/root_selector/_path_input.html.erb +13 -0
  193. data/app/views/parapet/shared/root_selector/_quick_select.html.erb +20 -0
  194. data/app/views/parapet/shared/root_selector/_scripts.html.erb +61 -0
  195. data/app/views/parapet/shared/root_selector/_warning.html.erb +11 -0
  196. data/app/views/parapet/shared/widgets/_base.html.erb +6 -0
  197. data/app/views/parapet/shared/widgets/_css.html.erb +79 -0
  198. data/app/views/parapet/widgets/_explainer.html.erb +17 -0
  199. data/app/views/parapet/widgets/_hotkeys.html.erb +20 -0
  200. data/app/views/parapet/widgets/_search.html.erb +68 -0
  201. data/app/views/parapet/widgets/_spatial_inspector.html.erb +54 -0
  202. data/app/views/parapet/widgets/_widget-explainer.html.erb +17 -0
  203. data/app/views/parapet/widgets/_widget-hotkeys.html.erb +20 -0
  204. data/app/views/parapet/widgets/_widget-search.html.erb +68 -0
  205. data/app/views/parapet/widgets/_widget-snippets.html.erb +18 -0
  206. data/app/views/parapet/widgets/_widget-suggestions.html.erb +15 -0
  207. data/app/views/parapet/widgets/show.html.erb +3 -0
  208. data/app/views/shared/_footer.html.erb +0 -0
  209. data/config/parapet/qss_config.yml +117 -0
  210. data/config/parapet/qss_registry.yml +14 -0
  211. data/config/routes.rb +40 -0
  212. data/db/migrate/20260916035300_create_run_histories.rb +16 -0
  213. data/db/migrate/20261002000000_expand_run_histories_for_activity_tracking.rb +7 -0
  214. data/db/parapet_migrate/20260929184200_create_parapet_users.rb +12 -0
  215. data/db/parapet_migrate/20260929184300_create_parapet_sessions.rb +9 -0
  216. data/db/parapet_migrate/20260930000000_create_parapet_runtime_settings.rb +12 -0
  217. data/db/parapet_migrate/20260930000100_create_parapet_suite_assignments.rb +14 -0
  218. data/db/parapet_migrate/20261002000000_create_parapet_activities.rb +16 -0
  219. data/db/parapet_migrate/20261002000100_create_parapet_draft_recoveries.rb +13 -0
  220. data/db/parapet_schema.rb +64 -0
  221. data/db/schema.rb +21 -0
  222. data/db/schema.released.rb +0 -0
  223. data/lib/parapet/engine.rb +11 -0
  224. data/lib/parapet/environment_guard.rb +14 -0
  225. data/lib/parapet/version.rb +3 -0
  226. data/lib/parapet-rails.rb +1 -0
  227. data/lib/parapet.rb +59 -0
  228. data/lib/tasks/parapet_qss.rake +26 -0
  229. data/lib/tasks/parapet_tasks.rake +25 -0
  230. data/lib/tasks/randomize_explorer_health.rb +101 -0
  231. metadata +423 -0
@@ -0,0 +1,9 @@
1
+ class CreateParapetSessions < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :sessions do |t|
4
+ t.references :user, null: false, foreign_key: true
5
+
6
+ t.timestamps
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ class CreateParapetRuntimeSettings < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :runtime_settings do |t|
4
+ t.string :setting_key, null: false
5
+ t.text :value, null: false
6
+
7
+ t.timestamps
8
+ end
9
+
10
+ add_index :runtime_settings, :setting_key, unique: true
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ class CreateParapetSuiteAssignments < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :suite_assignments do |t|
4
+ t.references :user, null: false, foreign_key: true
5
+ t.string :suite, null: false
6
+ t.string :role, limit: 64
7
+ t.string :tier, null: false, default: "novice"
8
+
9
+ t.timestamps
10
+ end
11
+
12
+ add_index :suite_assignments, [ :user_id, :suite ], unique: true
13
+ end
14
+ end
@@ -0,0 +1,16 @@
1
+ class CreateParapetActivities < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :activities do |t|
4
+ t.references :user, foreign_key: true
5
+ t.string :activity_type, null: false
6
+ t.string :action, null: false
7
+ t.string :performed_via, null: false, default: "human"
8
+ t.references :trackable, polymorphic: true
9
+ t.json :details, null: false, default: {}
10
+
11
+ t.timestamps
12
+ end
13
+
14
+ add_index :activities, [ :activity_type, :action ]
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ class CreateParapetDraftRecoveries < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :draft_recoveries do |t|
4
+ t.references :user, null: false, foreign_key: true
5
+ t.string :file_path, null: false
6
+ t.text :content, null: false, default: ""
7
+
8
+ t.timestamps
9
+ end
10
+
11
+ add_index :draft_recoveries, [ :user_id, :file_path ], unique: true
12
+ end
13
+ end
@@ -0,0 +1,64 @@
1
+ ActiveRecord::Schema[8.1].define(version: 2026_10_02_000100) do
2
+ create_table "activities", force: :cascade do |t|
3
+ t.string "activity_type", null: false
4
+ t.string "action", null: false
5
+ t.string "performed_via", default: "human", null: false
6
+ t.integer "trackable_id"
7
+ t.string "trackable_type"
8
+ t.json "details", default: {}, null: false
9
+ t.integer "user_id"
10
+ t.datetime "created_at", null: false
11
+ t.datetime "updated_at", null: false
12
+ t.index ["activity_type", "action"], name: "index_activities_on_activity_type_and_action"
13
+ t.index ["trackable_type", "trackable_id"], name: "index_activities_on_trackable"
14
+ t.index ["user_id"], name: "index_activities_on_user_id"
15
+ end
16
+
17
+ create_table "draft_recoveries", force: :cascade do |t|
18
+ t.integer "user_id", null: false
19
+ t.string "file_path", null: false
20
+ t.text "content", default: "", null: false
21
+ t.datetime "created_at", null: false
22
+ t.datetime "updated_at", null: false
23
+ t.index ["user_id", "file_path"], name: "index_draft_recoveries_on_user_id_and_file_path", unique: true
24
+ t.index ["user_id"], name: "index_draft_recoveries_on_user_id"
25
+ end
26
+ create_table "runtime_settings", force: :cascade do |t|
27
+ t.datetime "created_at", null: false
28
+ t.string "setting_key", null: false
29
+ t.datetime "updated_at", null: false
30
+ t.text "value", null: false
31
+ t.index ["setting_key"], name: "index_runtime_settings_on_setting_key", unique: true
32
+ end
33
+
34
+ create_table "sessions", force: :cascade do |t|
35
+ t.datetime "created_at", null: false
36
+ t.datetime "updated_at", null: false
37
+ t.integer "user_id", null: false
38
+ t.index ["user_id"], name: "index_sessions_on_user_id"
39
+ end
40
+
41
+ create_table "suite_assignments", force: :cascade do |t|
42
+ t.datetime "created_at", null: false
43
+ t.string "role", limit: 64
44
+ t.string "suite", null: false
45
+ t.string "tier", default: "novice", null: false
46
+ t.datetime "updated_at", null: false
47
+ t.integer "user_id", null: false
48
+ t.index ["user_id", "suite"], name: "index_suite_assignments_on_user_id_and_suite", unique: true
49
+ t.index ["user_id"], name: "index_suite_assignments_on_user_id"
50
+ end
51
+
52
+ create_table "users", force: :cascade do |t|
53
+ t.datetime "created_at", null: false
54
+ t.string "email_address", null: false
55
+ t.string "password_digest", null: false
56
+ t.datetime "updated_at", null: false
57
+ t.index ["email_address"], name: "index_users_on_email_address", unique: true
58
+ end
59
+
60
+ add_foreign_key "activities", "users"
61
+ add_foreign_key "draft_recoveries", "users"
62
+ add_foreign_key "sessions", "users"
63
+ add_foreign_key "suite_assignments", "users"
64
+ end
data/db/schema.rb ADDED
@@ -0,0 +1,21 @@
1
+ # Parapet Schema VCS — HEAD
2
+ # This file is the source of truth for the run-history database.
3
+ # Edit directly, then run db:rebuild.
4
+
5
+ ActiveRecord::Schema[7.1].define(version: 2026_10_02_000000) do
6
+ create_table "run_history", force: :cascade do |t|
7
+ t.string "run_type", null: false
8
+ t.string "command", null: false
9
+ t.string "status", null: false
10
+ t.integer "exit_code"
11
+ t.text "raw_output"
12
+ t.string "triggered_by_actor_type", null: false
13
+ t.integer "user_id"
14
+ t.string "performed_via", default: "human", null: false
15
+ t.datetime "started_at", null: false
16
+ t.datetime "finished_at"
17
+ t.datetime "created_at", null: false
18
+ t.datetime "updated_at", null: false
19
+ t.index ["user_id"], name: "index_run_history_on_user_id"
20
+ end
21
+ end
File without changes
@@ -0,0 +1,11 @@
1
+ require "parapet/environment_guard"
2
+ require "turbo-rails"
3
+ require "sass-embedded"
4
+
5
+ module Parapet
6
+ class Engine < ::Rails::Engine
7
+ EnvironmentGuard.verify!(::Rails.env)
8
+
9
+ isolate_namespace Parapet
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ module Parapet
2
+ class EnvironmentError < StandardError; end
3
+
4
+ module EnvironmentGuard
5
+ ALLOWED_ENVIRONMENTS = %w[development test].freeze
6
+
7
+ def self.verify!(env)
8
+ environment = env.to_s
9
+ return if ALLOWED_ENVIRONMENTS.include?(environment)
10
+
11
+ raise EnvironmentError, "Parapet can only run in development or test (current environment: #{environment})"
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,3 @@
1
+ module Parapet
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1 @@
1
+ require "parapet"
data/lib/parapet.rb ADDED
@@ -0,0 +1,59 @@
1
+ require "parapet/version"
2
+ require "parapet/engine"
3
+ require "qss"
4
+ # Parapet is a QSS consumer: its QSS settings (prefix, palette extras, registry, ...) live in its own config file.
5
+ QSS.configure(root: File.expand_path("..", __dir__), config: "config/parapet/qss_config.yml")
6
+ require "sass-embedded"
7
+ require "turbo-rails"
8
+ require "ostruct"
9
+
10
+ module Parapet
11
+ class << self
12
+ attr_accessor :test_framework
13
+ attr_writer :test_root
14
+ attr_writer :mount_subdomain
15
+
16
+ def configure
17
+ yield self
18
+ end
19
+
20
+ def test_framework
21
+ @test_framework || :rspec
22
+ end
23
+
24
+ def test_framework=(val)
25
+ @test_framework = val
26
+ end
27
+
28
+ def test_root=(val)
29
+ @test_root = val
30
+ end
31
+
32
+ def test_root
33
+ @test_root || default_test_root
34
+ end
35
+
36
+ # The subdomain Parapet's engine is mounted under, e.g. "parapet" or, for a host
37
+ # app that already lives on its own subdomain, a compound value like "parapet.sub".
38
+ # Priority: ENV var (12-factor/container-friendly) > Ruby config (set via
39
+ # Parapet.configure, e.g. from a host app's config/initializers/parapet.rb) >
40
+ # the "parapet" default. The engine's mount constraint and the request-time
41
+ # subdomain check both read this single method so they can never drift apart.
42
+ def mount_subdomain
43
+ ENV["PARAPET_SUBDOMAIN"] || @mount_subdomain || "parapet"
44
+ end
45
+
46
+ private
47
+
48
+ def default_test_root
49
+ case (test_framework || :rspec).to_sym
50
+ when :rspec
51
+ "spec"
52
+ else
53
+ "test"
54
+ end
55
+ end
56
+ end
57
+
58
+ self.test_framework = :rspec
59
+ end
@@ -0,0 +1,26 @@
1
+ # Parapet's QSS tasks. QSS itself is the qss gem (in this repo at qss/); these tasks apply Parapet's settings to it.
2
+ namespace :parapet do
3
+ namespace :qss do
4
+ parapet_root = File.expand_path("../..", __dir__)
5
+
6
+ desc "Compile Parapet's stylesheet: its QSS settings, QSS core (from the qss gem), then Parapet's own rules"
7
+ task :compile do
8
+ require "qss"
9
+ require "sass-embedded"
10
+ stylesheets = File.join(parapet_root, "app", "assets", "stylesheets")
11
+ input = File.join(stylesheets, "parapet", "qss_application.scss")
12
+ output = File.join(stylesheets, "parapet", "qss_application.css")
13
+ File.write(output, Sass.compile(input, load_paths: [ stylesheets, QSS.stylesheets_path ]).css)
14
+ puts "Compiled #{output}"
15
+ end
16
+
17
+ desc "Regenerate QSS's generated files with Parapet's config (config/parapet/qss_config.yml)"
18
+ task :sync do
19
+ require "qss"
20
+ require "rbconfig"
21
+ Dir.chdir(parapet_root) do
22
+ sh({ "QSS_CONSUMER_CONFIG" => "config/parapet/qss_config.yml" }, RbConfig.ruby, File.join(QSS.root, "exe", "qss-sync"))
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,25 @@
1
+ namespace :parapet do
2
+ desc "Run RSpec and Cucumber to verify the codebase before landing changes"
3
+ task publish: :environment do
4
+ puts "Starting Parapet Publish Gate..."
5
+ result = Parapet::PublishGate.new(triggered_by_actor_type: "human").run
6
+
7
+ if result.passed?
8
+ puts "\n✅ PUBLISH PASSED"
9
+ puts result.run_history.raw_output
10
+ exit 0
11
+ else
12
+ puts "\n❌ PUBLISH FAILED (Exit Code: #{result.run_history.exit_code})"
13
+ puts "--------------------------------------------------------------------------------"
14
+ puts result.run_history.raw_output
15
+ puts "--------------------------------------------------------------------------------"
16
+ exit result.run_history.exit_code || 1
17
+ end
18
+ end
19
+
20
+ namespace :db do
21
+ task migrate: :environment do
22
+ Parapet::InternalRecord.migrate!
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,101 @@
1
+ require 'json'
2
+
3
+ def randomize_status
4
+ # Large percentage red or green, some amber, few grey
5
+ # red: 35%, green: 40%, orange (amber): 15%, grey: 10%
6
+ r = rand(100)
7
+ if r < 35
8
+ 'red'
9
+ elsif r < 75
10
+ 'green'
11
+ elsif r < 90
12
+ 'orange'
13
+ else
14
+ 'grey'
15
+ end
16
+ end
17
+
18
+ def process_node(node, is_top_level = false)
19
+ if node['type'] == 'file'
20
+ node['status'] = randomize_status
21
+ return {
22
+ 'counts' => { node['status'] => 1 },
23
+ 'file_count' => 1,
24
+ 'sub_folder_count' => 0
25
+ }
26
+ end
27
+
28
+ # It's a directory
29
+ total_counts = { 'red' => 0, 'green' => 0, 'orange' => 0, 'grey' => 0 }
30
+ total_files = 0
31
+ sub_folders = 0
32
+
33
+ if node['children']
34
+ node['children'].each do |child|
35
+ sub_folders += 1 if child['type'] == 'directory'
36
+ result = process_node(child, false)
37
+ result['counts'].each { |status, count| total_counts[status] += count }
38
+ total_files += result['file_count']
39
+ end
40
+ end
41
+
42
+ # Calculate health percentages
43
+ if total_files > 0
44
+ health = {}
45
+ total_counts.each do |status, count|
46
+ percent = (count.to_f / total_files * 100).round
47
+ health[status] = percent if percent > 0
48
+ end
49
+
50
+ # Enforce constraints
51
+ show_health = false
52
+ if is_top_level
53
+ # display only if it contains >= 5 files total OR > 2 sub-folders
54
+ show_health = true if total_files >= 5 || sub_folders > 2
55
+ else
56
+ # sub-folders: display only if it contains >= 3 files
57
+ show_health = true if total_files >= 3
58
+ end
59
+
60
+ if show_health
61
+ node['health'] = health
62
+ else
63
+ node.delete('health')
64
+ end
65
+ else
66
+ node.delete('health')
67
+ end
68
+
69
+ {
70
+ 'counts' => total_counts,
71
+ 'file_count' => total_files,
72
+ 'sub_folder_count' => sub_folders
73
+ }
74
+ end
75
+
76
+ # Process all seed files
77
+ Dir.glob(File.expand_path("../../storage/seeds-*-explorer.json", __dir__)).each do |file_path|
78
+ puts "Processing #{file_path}..."
79
+ data = JSON.parse(File.read(file_path))
80
+
81
+ global_counts = { 'red' => 0, 'green' => 0, 'orange' => 0, 'grey' => 0 }
82
+ global_files = 0
83
+
84
+ # Root itself is not shown usually, so we process its children as top-level
85
+ if data['children']
86
+ data['children'].each do |top_level_node|
87
+ result = process_node(top_level_node, true)
88
+ result['counts'].each { |status, count| global_counts[status] += count }
89
+ global_files += result['file_count']
90
+ end
91
+ end
92
+
93
+ data['summary'] = {
94
+ 'counts' => global_counts,
95
+ 'total_files' => global_files
96
+ }
97
+
98
+ File.write(file_path, JSON.pretty_generate(data))
99
+ end
100
+
101
+ puts "Done!"