stream_weaver 0.3.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 (381) hide show
  1. checksums.yaml +7 -0
  2. data/.beads/.gitignore +77 -0
  3. data/.beads/README.md +81 -0
  4. data/.beads/config.yaml +54 -0
  5. data/.beads/metadata.json +7 -0
  6. data/.rspec +3 -0
  7. data/AGENTS.md +91 -0
  8. data/CHANGELOG.md +294 -0
  9. data/CLAUDE.md +67 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +565 -0
  12. data/Rakefile +13 -0
  13. data/assets/streamweaver-hero.jpg +0 -0
  14. data/bench/baselines/ledger.rb +58 -0
  15. data/bench/baselines/warroom.rb +51 -0
  16. data/bench/fixtures/ledger.rb +76 -0
  17. data/bench/fixtures/warroom.rb +60 -0
  18. data/bench/results/6daf8b8.md +102 -0
  19. data/bench/results/c8d749b.md +102 -0
  20. data/bench/results/eb06ab9.md +102 -0
  21. data/bench/run.rb +151 -0
  22. data/bench/support.rb +84 -0
  23. data/copy_this/dto_run_sample_run_7631.html +220 -0
  24. data/docs/SERVICE_MODE.md +302 -0
  25. data/docs/architecture/how_streamweaver_works.md +772 -0
  26. data/docs/blog/2026-01-16-streamweaver-introduction.md +515 -0
  27. data/docs/bug-2026-08-31-canvas-bridge-socket-collision.md +83 -0
  28. data/docs/canvas-ipc-session-summary.md +127 -0
  29. data/docs/canvas-panel-workflow.md +172 -0
  30. data/docs/canvas-read.md +147 -0
  31. data/docs/canvas-roadmap.md +275 -0
  32. data/docs/case-studies/2026-09-03-didx-canvas-read-shelf.md +147 -0
  33. data/docs/claude-code-companion-skill-spec.md +567 -0
  34. data/docs/components_reference.md +1223 -0
  35. data/docs/crud-patterns.md +265 -0
  36. data/docs/endpoints.md +122 -0
  37. data/docs/for_llms.md +1 -0
  38. data/docs/form-patterns.md +165 -0
  39. data/docs/frontend-only.md +169 -0
  40. data/docs/html-artifact-audit.md +238 -0
  41. data/docs/ideas/2025-01-01-as-a-service.md +203 -0
  42. data/docs/ideas/2026-01-16-charm-tui-exploration.md +542 -0
  43. data/docs/ideas/2026-08-27-static-doc-shelf-export.md +153 -0
  44. data/docs/ideas/dhh-review-future-refactors.md +142 -0
  45. data/docs/opal-jamstack.md +47 -0
  46. data/docs/opal-npm-direction.md +184 -0
  47. data/docs/opal-spike-findings.md +194 -0
  48. data/docs/plans/2025-12-16-form-blocks-design.md +124 -0
  49. data/docs/plans/2025-12-30-examples-browser-design.md +152 -0
  50. data/docs/plans/2026-01-02-charts-design.md +288 -0
  51. data/docs/plans/2026-01-10-canvas-ipc-design.md +247 -0
  52. data/docs/plans/2026-01-19-cabinet-control-components.md +176 -0
  53. data/docs/plans/2026-01-26-gem-release-and-panel.md +140 -0
  54. data/docs/plans/canvas-claude-project-design.md +238 -0
  55. data/docs/plans/canvas-doc-location-and-discovery.md +32 -0
  56. data/docs/plans/org-doc-preview-surfaces.md +293 -0
  57. data/docs/plans/shadcn-polish-plan.md +417 -0
  58. data/docs/porting-artifacts.md +252 -0
  59. data/docs/reference/agent-skills-comparison.md +369 -0
  60. data/docs/reference/travel-state-prd.artifact.html +780 -0
  61. data/docs/research/2026-08-17-hotwire-alike-landscape.md +268 -0
  62. data/docs/research/2026-08-17-hotwire-concept-map.md +404 -0
  63. data/docs/research/2026-08-22-lazy-fragments-trigger-decision.md +246 -0
  64. data/docs/research/2026-08-22-learnhotwire-syllabus-coverage.md +239 -0
  65. data/docs/research/frontend-only-matrix.md +278 -0
  66. data/docs/research/streamweaver-way-spike-findings.md +529 -0
  67. data/docs/resource-dsl.md +342 -0
  68. data/docs/routing.md +302 -0
  69. data/docs/ruby-ui-comparison.md +319 -0
  70. data/docs/shared-dsl-fragments.md +356 -0
  71. data/docs/streamweaver-for-ai-agents.md +401 -0
  72. data/docs/streamweaver-frontend-vision.md +123 -0
  73. data/docs/streamweaver_canvas/glimmer_initial_final_layer.rb +77 -0
  74. data/docs/streamweaver_canvas/save_example.rb +9 -0
  75. data/docs/templates.md +213 -0
  76. data/docs/testing.md +155 -0
  77. data/docs/theming-hooks.md +281 -0
  78. data/docs/tutorials/the-streamweaver-way.md +882 -0
  79. data/docs/university/capability-inventory.md +137 -0
  80. data/docs/university/dependency-survey.md +114 -0
  81. data/docs/university/design-spec.md +217 -0
  82. data/docs/university/mockups/code-block-doc-theme-dark.png +0 -0
  83. data/docs/university/mockups/code-block-doc-theme-light.png +0 -0
  84. data/docs/university/mockups/course-list-dark.png +0 -0
  85. data/docs/university/mockups/course-list-light.png +0 -0
  86. data/docs/university/mockups/course_canvas_mockup.rb +741 -0
  87. data/docs/university/mockups/step-screen-dark.png +0 -0
  88. data/docs/university/mockups/step-screen-light.png +0 -0
  89. data/docs/university/roadmap.md +72 -0
  90. data/docs/university/send-to-coworker.md +77 -0
  91. data/docs/university/worker-session-mining.md +109 -0
  92. data/docs/visual/browser-rendering-spike.md +173 -0
  93. data/docs/visual/builder-visual-plan-analysis.md +378 -0
  94. data/docs/visual/sw-plan-format-exploration.md +511 -0
  95. data/docs/visual/sw-plan-rendering-deep-dive.md +453 -0
  96. data/docs/visual-skills/PROGRESS.md +60 -0
  97. data/docs/visual-skills/SESSION-CONTEXT.md +207 -0
  98. data/docs/visual-skills/analysis/components.md +317 -0
  99. data/docs/visual-skills/analysis/overlap.md +360 -0
  100. data/docs/visual-skills/analysis/pi-design-deck.md +1102 -0
  101. data/docs/visual-skills/analysis/streamweaver-inventory.md +93 -0
  102. data/docs/visual-skills/analysis/unified-specs.feature +1003 -0
  103. data/docs/visual-skills/analysis/visual-explainer.md +964 -0
  104. data/docs/visual-skills/blog/blog-series-outline.md +39 -0
  105. data/docs/visual-skills/blog/token-efficiency.md +73 -0
  106. data/docs/visual-skills/design/architecture.md +1725 -0
  107. data/docs/visual-skills/design/codex-review.md +340 -0
  108. data/docs/visual-skills/design/dhh-review.md +209 -0
  109. data/docs/visual-skills/design/evolution.md +100 -0
  110. data/docs/visual-skills/design/gemini-review.md +55 -0
  111. data/docs/visual-skills/design/review-synthesis.md +149 -0
  112. data/docs/visual-skills/implementation/STATE.md +22 -0
  113. data/docs/visual-skills/implementation/plan.md +347 -0
  114. data/docs/visual-skills/implementation/spike-findings.md +237 -0
  115. data/docs/visual-skills/implementation/tasks.md +702 -0
  116. data/docs/visual-skills/lessons-learned/process.md +145 -0
  117. data/examples/README.md +96 -0
  118. data/examples/advanced/all_components.rb +177 -0
  119. data/examples/advanced/examples_browser.rb +394 -0
  120. data/examples/advanced/teachables_browser.rb +261 -0
  121. data/examples/advanced/theme_tweaker.rb +366 -0
  122. data/examples/advanced/tutorial.rb +1528 -0
  123. data/examples/agentic/agentic_form.rb +30 -0
  124. data/examples/agentic/agentic_form_autoclose.rb +30 -0
  125. data/examples/agentic/cultivation_tracker.rb +69 -0
  126. data/examples/basic/hello_world.rb +22 -0
  127. data/examples/basic/opal_tabs_table.rb +44 -0
  128. data/examples/basic/todo_list.rb +39 -0
  129. data/examples/button_loading_test.rb +36 -0
  130. data/examples/canvas/mermaid_canvas_demo.sh +151 -0
  131. data/examples/charts/bar_chart_demo.rb +57 -0
  132. data/examples/charts/line_chart_demo.rb +73 -0
  133. data/examples/charts/pie_area_demo.rb +87 -0
  134. data/examples/charts/stacked_bar_chart_demo.rb +69 -0
  135. data/examples/claude_code/README.md +85 -0
  136. data/examples/claude_code/codebreaker/.claude/commands/infiltrate.md +632 -0
  137. data/examples/claude_code/codebreaker/.claude/settings.json +11 -0
  138. data/examples/claude_code/codebreaker/.claude/settings.local.json +17 -0
  139. data/examples/claude_code/codebreaker/.claude/skills/infiltrate.md +0 -0
  140. data/examples/claude_code/codebreaker/README.md +213 -0
  141. data/examples/claude_code/tutorial/.claude/commands/learn.md +453 -0
  142. data/examples/claude_code/tutorial/README.md +102 -0
  143. data/examples/claude_code/verification_flow/.claude/commands/verify.md +171 -0
  144. data/examples/claude_code/verification_flow/README.md +126 -0
  145. data/examples/components/annotated_code_demo.rb +54 -0
  146. data/examples/components/callout_demo.rb +33 -0
  147. data/examples/components/checkbox_group_demo.rb +46 -0
  148. data/examples/components/design_review.css +468 -0
  149. data/examples/components/design_review_demo.rb +44 -0
  150. data/examples/components/design_review_dsl.rb +300 -0
  151. data/examples/components/diff_block_demo.rb +112 -0
  152. data/examples/components/events_demo.rb +184 -0
  153. data/examples/components/form_demo.rb +62 -0
  154. data/examples/components/lesson_demo.rb +53 -0
  155. data/examples/components/markdown_demo.rb +110 -0
  156. data/examples/components/mermaid_demo.rb +166 -0
  157. data/examples/components/pareto_set.rb +64 -0
  158. data/examples/components/prd_demo.rb +21 -0
  159. data/examples/components/prd_dsl.rb +385 -0
  160. data/examples/components/quiz_demo.rb +47 -0
  161. data/examples/components/run_viewer_demo.rb +100 -0
  162. data/examples/components/score_and_collapsible_demo.rb +82 -0
  163. data/examples/components/select_stale_value_smoke_test.rb +39 -0
  164. data/examples/components/table_demo.rb +247 -0
  165. data/examples/components/timer_with_state_demo.rb +58 -0
  166. data/examples/components/todo_due_dates.rb +63 -0
  167. data/examples/components/uat_gaps_demo.rb +112 -0
  168. data/examples/dashboard/feed_simulator.rb +60 -0
  169. data/examples/dashboard/live_dashboard.rb +80 -0
  170. data/examples/dashboard_components.rb +108 -0
  171. data/examples/deferred_fragments_demo.rb +62 -0
  172. data/examples/generate_more_spike/README.md +63 -0
  173. data/examples/generate_more_spike/app.rb +441 -0
  174. data/examples/git_health.sh +401 -0
  175. data/examples/layout/layout_components_demo.rb +174 -0
  176. data/examples/layout/modal_demo.rb +215 -0
  177. data/examples/layout/navigation_demo.rb +227 -0
  178. data/examples/layout/route_tabs_demo.rb +52 -0
  179. data/examples/layout/routing_demo.rb +52 -0
  180. data/examples/layout/scroll_box_demo.rb +154 -0
  181. data/examples/lazy_fragments_demo.rb +151 -0
  182. data/examples/my_todos/README.md +29 -0
  183. data/examples/my_todos/my_todos.rb +312 -0
  184. data/examples/my_todos/store.rb +86 -0
  185. data/examples/opal/reactive_demo.rb +62 -0
  186. data/examples/opal/scenarios/s1_counter.rb +15 -0
  187. data/examples/opal/scenarios/s2_search_filter.rb +17 -0
  188. data/examples/opal/scenarios/s3_sibling_tabs.rb +25 -0
  189. data/examples/opal/scenarios/s4_shopping_cart.rb +34 -0
  190. data/examples/opal/scenarios/s5_watch.rb +39 -0
  191. data/examples/opal/scenarios/s6_on_start.rb +20 -0
  192. data/examples/opal/scenarios/s7_wizard.rb +33 -0
  193. data/examples/opal/scenarios/s8_loan_calculator.rb +21 -0
  194. data/examples/opal/scenarios/s9_dashboard.rb +27 -0
  195. data/examples/operations_dashboard_demo.rb +167 -0
  196. data/examples/panel_demo.sh +447 -0
  197. data/examples/parity/assets/tyrion_slice.css +86 -0
  198. data/examples/parity/rivet_people_slice.rb +182 -0
  199. data/examples/parity/tyrion_components.css +777 -0
  200. data/examples/parity/tyrion_warroom_components.rb +414 -0
  201. data/examples/parity/tyrion_warroom_slice.rb +549 -0
  202. data/examples/puma_dev/README.md +124 -0
  203. data/examples/puma_dev/config.ru +34 -0
  204. data/examples/puma_dev/standalone_app.rb +34 -0
  205. data/examples/scaffolding/blog.rb +49 -0
  206. data/examples/scaffolding/utf_lite.rb +105 -0
  207. data/examples/styling/feedback_demo.rb +229 -0
  208. data/examples/styling/style_showcase.rb +172 -0
  209. data/examples/styling/theme_demo.rb +508 -0
  210. data/examples/timer_health_checker.rb +111 -0
  211. data/examples/timer_showcase.rb +131 -0
  212. data/examples/tutorials/render_markdown.rb +112 -0
  213. data/examples/tutorials/streamweaver_way_tutorial.rb +44 -0
  214. data/examples/tutorials/tutorial_content.rb +1082 -0
  215. data/examples/visual_skills/design_deck_demo.rb +314 -0
  216. data/examples/visual_skills/explainer_demo.rb +499 -0
  217. data/exe/streamweaver +9 -0
  218. data/gsd/ROADMAP-1.0.md +167 -0
  219. data/gsd/STATE.md +13 -0
  220. data/gsd/research/market-positioning-research.md +139 -0
  221. data/gsd/research/production-patterns-research.md +270 -0
  222. data/gsd/research/repo-audit-1.0.md +274 -0
  223. data/lib/stream_weaver/action_token.rb +58 -0
  224. data/lib/stream_weaver/adapter/alpinejs.rb +8249 -0
  225. data/lib/stream_weaver/adapter/base.rb +591 -0
  226. data/lib/stream_weaver/adapter/opal.rb +334 -0
  227. data/lib/stream_weaver/adapter/static.rb +1118 -0
  228. data/lib/stream_weaver/admin.rb +176 -0
  229. data/lib/stream_weaver/app.rb +1821 -0
  230. data/lib/stream_weaver/assets/js/sw-copy.js +50 -0
  231. data/lib/stream_weaver/assets/js/sw-heredoc-rewrite.js +68 -0
  232. data/lib/stream_weaver/assets/js/sw-keyboard.js +165 -0
  233. data/lib/stream_weaver/assets/js/sw-mermaid-zoom.js +621 -0
  234. data/lib/stream_weaver/assets/js/sw-route-tabs.js +53 -0
  235. data/lib/stream_weaver/assets/js/sw-sidebar-toc.js +116 -0
  236. data/lib/stream_weaver/assets/js/sw-slide-nav.js +103 -0
  237. data/lib/stream_weaver/canvas/bridge.rb +239 -0
  238. data/lib/stream_weaver/canvas/bridge_server.rb +646 -0
  239. data/lib/stream_weaver/canvas/client.rb +298 -0
  240. data/lib/stream_weaver/canvas/doc_roots.rb +233 -0
  241. data/lib/stream_weaver/canvas/doc_store.rb +252 -0
  242. data/lib/stream_weaver/canvas/gist_publisher.rb +264 -0
  243. data/lib/stream_weaver/canvas/gist_save_handler.rb +89 -0
  244. data/lib/stream_weaver/canvas/gist_store.rb +135 -0
  245. data/lib/stream_weaver/canvas/helpers.rb +109 -0
  246. data/lib/stream_weaver/canvas/history.rb +90 -0
  247. data/lib/stream_weaver/canvas/protocol.rb +86 -0
  248. data/lib/stream_weaver/canvas/reader.rb +853 -0
  249. data/lib/stream_weaver/canvas/save_doc_widget.rb +457 -0
  250. data/lib/stream_weaver/canvas/scroll_top_hint.rb +21 -0
  251. data/lib/stream_weaver/canvas/session.rb +132 -0
  252. data/lib/stream_weaver/cli.rb +3235 -0
  253. data/lib/stream_weaver/component_assets.rb +70 -0
  254. data/lib/stream_weaver/component_registry.rb +67 -0
  255. data/lib/stream_weaver/component_renderer.rb +48 -0
  256. data/lib/stream_weaver/components/annotated_code.rb +53 -0
  257. data/lib/stream_weaver/components/api_endpoint.rb +42 -0
  258. data/lib/stream_weaver/components/callout.rb +59 -0
  259. data/lib/stream_weaver/components/chart.rb +84 -0
  260. data/lib/stream_weaver/components/code_block.rb +77 -0
  261. data/lib/stream_weaver/components/comparison.rb +39 -0
  262. data/lib/stream_weaver/components/decision.rb +38 -0
  263. data/lib/stream_weaver/components/deck/close_overlay.rb +84 -0
  264. data/lib/stream_weaver/components/deck/confirmation_bar.rb +60 -0
  265. data/lib/stream_weaver/components/deck/deck_option.rb +61 -0
  266. data/lib/stream_weaver/components/deck/deck_slide.rb +77 -0
  267. data/lib/stream_weaver/components/deck/deck_state.rb +469 -0
  268. data/lib/stream_weaver/components/deck/deck_summary.rb +73 -0
  269. data/lib/stream_weaver/components/deck/design_deck.rb +60 -0
  270. data/lib/stream_weaver/components/deck/generate_more_controls.rb +84 -0
  271. data/lib/stream_weaver/components/deck/model_selector.rb +84 -0
  272. data/lib/stream_weaver/components/deck/skeleton_placeholder.rb +36 -0
  273. data/lib/stream_weaver/components/diff_block.rb +125 -0
  274. data/lib/stream_weaver/components/doc_header.rb +57 -0
  275. data/lib/stream_weaver/components/image_block.rb +70 -0
  276. data/lib/stream_weaver/components/implementation_map.rb +33 -0
  277. data/lib/stream_weaver/components/keyboard_shortcuts.rb +94 -0
  278. data/lib/stream_weaver/components/kpi_dashboard.rb +78 -0
  279. data/lib/stream_weaver/components/mermaid.rb +79 -0
  280. data/lib/stream_weaver/components/pipeline.rb +63 -0
  281. data/lib/stream_weaver/components/sidebar_toc.rb +46 -0
  282. data/lib/stream_weaver/components/slide_container.rb +141 -0
  283. data/lib/stream_weaver/components/timeline_event.rb +58 -0
  284. data/lib/stream_weaver/components/wireframe.rb +29 -0
  285. data/lib/stream_weaver/components/wireframe_block.rb +34 -0
  286. data/lib/stream_weaver/components.rb +2711 -0
  287. data/lib/stream_weaver/css.rb +230 -0
  288. data/lib/stream_weaver/dev_fallback_overlay.rb +61 -0
  289. data/lib/stream_weaver/display_dsl.rb +1004 -0
  290. data/lib/stream_weaver/export/html_exporter.rb +478 -0
  291. data/lib/stream_weaver/feed.rb +34 -0
  292. data/lib/stream_weaver/feed_builder.rb +31 -0
  293. data/lib/stream_weaver/fonts.rb +33 -0
  294. data/lib/stream_weaver/interaction_runner.rb +487 -0
  295. data/lib/stream_weaver/iterm.rb +460 -0
  296. data/lib/stream_weaver/layout_registry.rb +92 -0
  297. data/lib/stream_weaver/opal/bridge.rb +52 -0
  298. data/lib/stream_weaver/opal/builder.rb +193 -0
  299. data/lib/stream_weaver/opal/env.rb +29 -0
  300. data/lib/stream_weaver/opal/reactive_state.rb +75 -0
  301. data/lib/stream_weaver/opal/regexp_anchor_patch.rb +62 -0
  302. data/lib/stream_weaver/opal/renderer.rb +77 -0
  303. data/lib/stream_weaver/opal/runtime.rb +250 -0
  304. data/lib/stream_weaver/opal/shell.rb +145 -0
  305. data/lib/stream_weaver/opal/string_bridge.rb +50 -0
  306. data/lib/stream_weaver/opal/stubs/diff.min.js +1 -0
  307. data/lib/stream_weaver/opal/stubs/digest.rb +15 -0
  308. data/lib/stream_weaver/opal/stubs/marked.umd.js +79 -0
  309. data/lib/stream_weaver/opal/stubs/md5.rb +3 -0
  310. data/lib/stream_weaver/opal/stubs/morphdom.min.js +775 -0
  311. data/lib/stream_weaver/opal/stubs/prism-tomorrow.min.css +1 -0
  312. data/lib/stream_weaver/opal/stubs/prism.min.js +1967 -0
  313. data/lib/stream_weaver/opal_entry.rb +136 -0
  314. data/lib/stream_weaver/org/inline.rb +95 -0
  315. data/lib/stream_weaver/org/reader.rb +561 -0
  316. data/lib/stream_weaver/org/recording_context.rb +86 -0
  317. data/lib/stream_weaver/org/source_splitter.rb +63 -0
  318. data/lib/stream_weaver/org/writer.rb +300 -0
  319. data/lib/stream_weaver/page_shell.rb +530 -0
  320. data/lib/stream_weaver/portfile.rb +79 -0
  321. data/lib/stream_weaver/pushable.rb +42 -0
  322. data/lib/stream_weaver/resource/default_views.rb +111 -0
  323. data/lib/stream_weaver/resource/field_input.rb +25 -0
  324. data/lib/stream_weaver/resource/state_keys.rb +16 -0
  325. data/lib/stream_weaver/resource/store.rb +18 -0
  326. data/lib/stream_weaver/resource.rb +104 -0
  327. data/lib/stream_weaver/server.rb +1471 -0
  328. data/lib/stream_weaver/service.rb +1240 -0
  329. data/lib/stream_weaver/service_client.rb +104 -0
  330. data/lib/stream_weaver/session_store.rb +186 -0
  331. data/lib/stream_weaver/skills/streamweaver-canvas-safe/SKILL.md +66 -0
  332. data/lib/stream_weaver/skills/streamweaver-canvas-safe/examples/canvas-safe-showcase.rb +98 -0
  333. data/lib/stream_weaver/skills/streamweaver-canvas-safe/references/actions-and-buttons.md +47 -0
  334. data/lib/stream_weaver/skills/streamweaver-canvas-safe/references/charts-and-diagrams.md +38 -0
  335. data/lib/stream_weaver/skills/streamweaver-canvas-safe/references/deck.md +32 -0
  336. data/lib/stream_weaver/skills/streamweaver-canvas-safe/references/inputs-and-forms.md +65 -0
  337. data/lib/stream_weaver/skills/streamweaver-canvas-safe/references/tabs-and-navigation.md +53 -0
  338. data/lib/stream_weaver/skills/streamweaver-doc-builder/SKILL.md +298 -0
  339. data/lib/stream_weaver/skills/streamweaver-visual-companion/SKILL.md +130 -0
  340. data/lib/stream_weaver/skills/streamweaver-visual-companion/examples/design-review-example.css +468 -0
  341. data/lib/stream_weaver/skills/streamweaver-visual-companion/examples/design-review-example.rb +39 -0
  342. data/lib/stream_weaver/skills/streamweaver-visual-companion/examples/design-review-example_dsl.rb +300 -0
  343. data/lib/stream_weaver/skills/streamweaver-visual-companion/examples/doc-parity-example.rb +24 -0
  344. data/lib/stream_weaver/skills/streamweaver-visual-companion/examples/doc-parity-example_dsl.rb +385 -0
  345. data/lib/stream_weaver/skills/streamweaver-visual-companion/references/checkpoints-and-forms.md +28 -0
  346. data/lib/stream_weaver/skills/streamweaver-visual-companion/references/cleanup-and-panel.md +44 -0
  347. data/lib/stream_weaver/skills/streamweaver-visual-companion/references/example-gallery.md +8 -0
  348. data/lib/stream_weaver/skills/streamweaver-visual-companion/references/persistence.md +58 -0
  349. data/lib/stream_weaver/skills/streamweaver-way/SKILL.md +396 -0
  350. data/lib/stream_weaver/skills/visual-plan/SKILL.md +201 -0
  351. data/lib/stream_weaver/skills/visual-recap/SKILL.md +244 -0
  352. data/lib/stream_weaver/streamer.rb +63 -0
  353. data/lib/stream_weaver/templates/choices.rb +149 -0
  354. data/lib/stream_weaver/templates/code.rb +157 -0
  355. data/lib/stream_weaver/templates/confirm.rb +110 -0
  356. data/lib/stream_weaver/templates/diff.rb +195 -0
  357. data/lib/stream_weaver/templates/info.rb +142 -0
  358. data/lib/stream_weaver/templates/table.rb +169 -0
  359. data/lib/stream_weaver/templates/wizard.rb +271 -0
  360. data/lib/stream_weaver/theme/auto_mode.rb +116 -0
  361. data/lib/stream_weaver/theme/presets.rb +555 -0
  362. data/lib/stream_weaver/theme.rb +639 -0
  363. data/lib/stream_weaver/university/canvas.rb +924 -0
  364. data/lib/stream_weaver/university/course.rb +699 -0
  365. data/lib/stream_weaver/university/demos/counter.rb +33 -0
  366. data/lib/stream_weaver/university/demos/dashboard.rb +147 -0
  367. data/lib/stream_weaver/university/demos/decision_form.rb +143 -0
  368. data/lib/stream_weaver/university/demos.rb +50 -0
  369. data/lib/stream_weaver/university/listener.rb +467 -0
  370. data/lib/stream_weaver/university/progress.rb +201 -0
  371. data/lib/stream_weaver/university/runner.rb +138 -0
  372. data/lib/stream_weaver/university/scripts/growing_doc.rb +609 -0
  373. data/lib/stream_weaver/university/scripts/growing_doc_state.rb +91 -0
  374. data/lib/stream_weaver/utils.rb +31 -0
  375. data/lib/stream_weaver/version.rb +5 -0
  376. data/lib/stream_weaver/views/canvas/reader_layout.erb +800 -0
  377. data/lib/stream_weaver/views.rb +3940 -0
  378. data/lib/stream_weaver.rb +122 -0
  379. data/llms.txt +1269 -0
  380. data/sig/stream_weaver.rbs +4 -0
  381. metadata +598 -0
@@ -0,0 +1,298 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'socket'
4
+ require 'json'
5
+ require 'fileutils'
6
+ require_relative 'protocol'
7
+
8
+ module StreamWeaver
9
+ module Canvas
10
+ # Client for communicating with the Canvas Bridge via Unix socket.
11
+ # Used by CLI commands to send messages to the bridge.
12
+ class Client
13
+ class NotRunningError < StandardError; end
14
+ class ConnectionError < StandardError; end
15
+
16
+ SOCKET_PATH = File.expand_path('~/.streamweaver/canvas.sock')
17
+ PID_FILE_PATH = File.expand_path('~/.streamweaver/canvas.pid')
18
+ DEFAULT_TIMEOUT = 300 # 5 minutes
19
+
20
+ class << self
21
+ # STREAMWEAVER_CANVAS_SOCKET / _PID redirect the whole bridge --
22
+ # socket, pid file, and (because the bridge is spawned with the
23
+ # environment it inherits) the server half too. This exists so a
24
+ # test, or a deliberately separate instance, can run its own bridge
25
+ # without stopping the one the developer has open in a browser and
26
+ # taking every live canvas session with it.
27
+ def socket_path
28
+ ENV['STREAMWEAVER_CANVAS_SOCKET'] || SOCKET_PATH
29
+ end
30
+
31
+ def pid_file_path
32
+ ENV['STREAMWEAVER_CANVAS_PID'] || PID_FILE_PATH
33
+ end
34
+
35
+ # Check if the bridge process is running
36
+ # @return [Boolean]
37
+ def bridge_running?
38
+ return false unless File.exist?(pid_file_path)
39
+
40
+ content = File.read(pid_file_path)
41
+ # Parse pid=VALUE format
42
+ pid = content[/pid=(\d+)/, 1]&.to_i || content.strip.to_i
43
+ return false if pid.zero?
44
+
45
+ # Check if process exists
46
+ Process.kill(0, pid)
47
+ true
48
+ rescue Errno::ESRCH, Errno::EPERM
49
+ # Process not found or no permission
50
+ cleanup_stale_files
51
+ false
52
+ end
53
+
54
+ # Clean up stale PID and socket files
55
+ def cleanup_stale_files
56
+ File.delete(pid_file_path) if File.exist?(pid_file_path)
57
+ File.delete(socket_path) if File.exist?(socket_path)
58
+ end
59
+
60
+ # Send a message to the bridge and return the response
61
+ # @param message [Hash] Message to send
62
+ # @param timeout [Integer] Timeout in seconds
63
+ # @return [Hash, nil] Response message or nil
64
+ def send_message(message, timeout: 5)
65
+ raise NotRunningError, "Canvas bridge is not running" unless bridge_running?
66
+
67
+ socket = UNIXSocket.new(socket_path)
68
+ socket.write(Protocol.encode(message))
69
+
70
+ # Read response with timeout
71
+ if IO.select([socket], nil, nil, timeout)
72
+ buffer = ''
73
+ while (chunk = socket.read_nonblock(4096, exception: false))
74
+ break if chunk == :wait_readable
75
+ buffer += chunk
76
+ break if buffer.include?("\n")
77
+ end
78
+
79
+ messages, _ = Protocol.parse_buffer(buffer)
80
+ messages.first
81
+ else
82
+ nil
83
+ end
84
+ rescue Errno::ECONNREFUSED, Errno::ENOENT => e
85
+ raise ConnectionError, "Cannot connect to canvas bridge: #{e.message}"
86
+ ensure
87
+ socket&.close
88
+ end
89
+
90
+ # Send a message and wait for a specific event type
91
+ # @param message [Hash] Message to send
92
+ # @param event_type [String] Event type to wait for
93
+ # @param timeout [Integer] Timeout in seconds
94
+ # @return [Hash, nil] Event message or nil
95
+ def send_and_wait(message, event_type:, timeout: DEFAULT_TIMEOUT)
96
+ raise NotRunningError, "Canvas bridge is not running" unless bridge_running?
97
+
98
+ socket = UNIXSocket.new(socket_path)
99
+ socket.write(Protocol.encode(message))
100
+
101
+ start_time = Time.now
102
+ buffer = ''
103
+
104
+ loop do
105
+ remaining = timeout - (Time.now - start_time)
106
+ break if remaining <= 0
107
+
108
+ if IO.select([socket], nil, nil, [remaining, 1].min)
109
+ chunk = socket.read_nonblock(4096, exception: false)
110
+ next if chunk == :wait_readable
111
+ buffer += chunk
112
+
113
+ messages, buffer = Protocol.parse_buffer(buffer)
114
+ messages.each do |msg|
115
+ return msg if msg[:type] == event_type
116
+ end
117
+ end
118
+ end
119
+
120
+ nil
121
+ rescue Errno::ECONNREFUSED, Errno::ENOENT => e
122
+ raise ConnectionError, "Cannot connect to canvas bridge: #{e.message}"
123
+ ensure
124
+ socket&.close
125
+ end
126
+
127
+ # Hold ONE connection open and yield every event for `name` until the
128
+ # bridge closes it or the block breaks. This is what a long-lived
129
+ # listener wants: `send_and_wait` takes a single event and closes the
130
+ # socket, so a loop built on it is deaf for the whole gap between
131
+ # handling one click and reopening -- which is exactly when the
132
+ # re-push is happening and the user is most likely to click again.
133
+ #
134
+ # Events are filtered by session name because the bridge forwards
135
+ # every session's events to every connected client
136
+ # (docs/bug-2026-08-24-canvas-bridge-broadcasts-no-session-filtering.md).
137
+ #
138
+ # @param name [String, nil] session to filter on; nil yields all
139
+ # @yield [Hash] each event message
140
+ def each_event(name = nil)
141
+ raise NotRunningError, "Canvas bridge is not running" unless bridge_running?
142
+
143
+ socket = UNIXSocket.new(socket_path)
144
+ buffer = ''
145
+
146
+ loop do
147
+ # readpartial blocks until there are bytes and raises at EOF --
148
+ # no poll interval to burn, and no third spelling of "closed".
149
+ # A peer reset is the same event as a clean close as far as this
150
+ # loop is concerned: the bridge is gone, so return and let the
151
+ # caller decide whether to reconnect.
152
+ buffer += socket.readpartial(4096)
153
+
154
+ messages, buffer = Protocol.parse_buffer(buffer)
155
+ messages.each do |msg|
156
+ next unless msg[:type] == 'event'
157
+ next if name && msg[:name] != name
158
+
159
+ yield msg
160
+ end
161
+ rescue EOFError, Errno::ECONNRESET
162
+ break
163
+ end
164
+ rescue Errno::ECONNREFUSED, Errno::ENOENT => e
165
+ raise ConnectionError, "Cannot connect to canvas bridge: #{e.message}"
166
+ ensure
167
+ socket&.close
168
+ end
169
+
170
+ # Start the bridge process if not running
171
+ # @return [Hash] { pid: Integer, port: Integer }
172
+ def ensure_bridge_running
173
+ if bridge_running?
174
+ info = read_bridge_info
175
+ # Verify HTTP server is actually responding
176
+ if info && info[:port] && http_healthy?(info[:port])
177
+ return info
178
+ end
179
+ # Bridge process exists but HTTP not responding - restart
180
+ cleanup_stale_files
181
+ end
182
+
183
+ start_bridge
184
+ end
185
+
186
+ # Check if HTTP server is responding on given port
187
+ def http_healthy?(port)
188
+ require 'net/http'
189
+ uri = URI("http://127.0.0.1:#{port}/health")
190
+ response = Net::HTTP.start(uri.host, uri.port, open_timeout: 1, read_timeout: 1) do |http|
191
+ http.get(uri.path)
192
+ end
193
+ response.is_a?(Net::HTTPSuccess)
194
+ rescue Errno::ECONNREFUSED, Errno::EADDRNOTAVAIL, Net::OpenTimeout, Net::ReadTimeout
195
+ false
196
+ end
197
+
198
+ # Start the bridge process
199
+ # @return [Hash] { pid: Integer, port: Integer }
200
+ def start_bridge
201
+ require_relative 'bridge_server'
202
+
203
+ # Ensure directory exists
204
+ FileUtils.mkdir_p(File.dirname(pid_file_path))
205
+
206
+ # Get the lib path
207
+ lib_path = File.expand_path('../..', __dir__)
208
+
209
+ # Create startup script
210
+ script = <<~RUBY
211
+ $LOAD_PATH.unshift('#{lib_path}')
212
+ require 'stream_weaver'
213
+ require 'stream_weaver/canvas/bridge_server'
214
+ StreamWeaver::Canvas::BridgeServer.run!
215
+ RUBY
216
+
217
+ # Write script to temp file
218
+ script_file = File.join(File.dirname(pid_file_path), 'canvas_start.rb')
219
+ File.write(script_file, script)
220
+
221
+ # Log file
222
+ log_file = File.join(File.dirname(pid_file_path), 'canvas.log')
223
+
224
+ # Spawn the bridge process
225
+ pid = spawn(
226
+ RbConfig.ruby, script_file,
227
+ out: [log_file, 'a'],
228
+ err: [log_file, 'a'],
229
+ pgroup: true
230
+ )
231
+ Process.detach(pid)
232
+
233
+ # Wait for both socket AND HTTP server to be ready
234
+ 30.times do
235
+ if File.exist?(socket_path) && File.exist?(pid_file_path)
236
+ info = read_bridge_info
237
+ if info && info[:port]
238
+ # Probe HTTP server to confirm it's actually listening
239
+ begin
240
+ require 'net/http'
241
+ uri = URI("http://127.0.0.1:#{info[:port]}/health")
242
+ response = Net::HTTP.get_response(uri)
243
+ return info if response.is_a?(Net::HTTPSuccess)
244
+ rescue Errno::ECONNREFUSED, Errno::EADDRNOTAVAIL
245
+ # Server not ready yet
246
+ end
247
+ end
248
+ end
249
+ sleep 0.2
250
+ end
251
+
252
+ # Fallback - return what we have
253
+ read_bridge_info || { pid: pid, port: BridgeServer::DEFAULT_PORT }
254
+ end
255
+
256
+ # Read bridge info from PID file
257
+ # @return [Hash] { pid: Integer, port: Integer }
258
+ def read_bridge_info
259
+ return nil unless File.exist?(pid_file_path)
260
+
261
+ content = File.read(pid_file_path)
262
+ pid = content[/pid=(\d+)/, 1]&.to_i
263
+ port = content[/port=(\d+)/, 1]&.to_i || Bridge::DEFAULT_PORT
264
+
265
+ { pid: pid, port: port }
266
+ end
267
+
268
+ # Stop the bridge process
269
+ # @return [Boolean] true if stopped
270
+ def stop_bridge
271
+ return false unless bridge_running?
272
+
273
+ info = read_bridge_info
274
+ if info && info[:port]
275
+ require 'net/http'
276
+ begin
277
+ Net::HTTP.start('127.0.0.1', info[:port], open_timeout: 2, read_timeout: 2) do |http|
278
+ http.post('/shutdown', '')
279
+ end
280
+ sleep 0.5
281
+ rescue StandardError
282
+ # HTTP shutdown failed — fall back to SIGTERM
283
+ pid = info[:pid] || File.read(pid_file_path)[/pid=(\d+)/, 1]&.to_i
284
+ Process.kill('TERM', pid) if pid
285
+ sleep 0.5
286
+ end
287
+ end
288
+
289
+ cleanup_stale_files
290
+ true
291
+ rescue Errno::ESRCH
292
+ cleanup_stale_files
293
+ true
294
+ end
295
+ end
296
+ end
297
+ end
298
+ end
@@ -0,0 +1,233 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'stream_weaver/canvas/doc_store'
5
+
6
+ module StreamWeaver
7
+ module Canvas
8
+ # Finds every docs root on this machine that canvas-read should show,
9
+ # and gives each one a display label (stream_weaver-iugu).
10
+ #
11
+ # Two sources, unioned, because neither alone is sufficient:
12
+ #
13
+ # - **Scan** of `$STREAMWEAVER_DOCS_SCAN_ROOTS` (default `~/work`), one
14
+ # level deep for `*/docs/streamweaver_canvas`. Catches repos under a
15
+ # known parent automatically -- including ones that arrived via `git
16
+ # pull` and were never saved to or visited on this machine, which is
17
+ # exactly what a registry alone would miss.
18
+ # - **Registry** at `~/.streamweaver/docs_roots.log`: one absolute docs
19
+ # root per line, append-only. Catches repos that live somewhere the
20
+ # scan doesn't reach (`~/rails/...`, `~/src/...`), which is exactly
21
+ # what a scan alone would miss. Written by DocStore.save and by
22
+ # `canvas-read <explicit path>` -- visiting a pre-existing doc once is
23
+ # what backfills it, so there is no separate registration command.
24
+ #
25
+ # Deliberately NOT a database: a flat newline-delimited file of absolute
26
+ # paths needs no schema, no locking (a sub-PIPE_BUF append on a local
27
+ # filesystem is atomic), no corruption recovery, and can be fixed with a
28
+ # text editor. Everything is filtered through File.directory? on read, so
29
+ # a repo that is deleted or moved simply drops off -- self-healing, no
30
+ # `forget` operation to build or remember to call.
31
+ #
32
+ # No cache/TTL on the scan: it is a single-digit-millisecond glob at real
33
+ # repo counts, and staleness in a tool whose whole job is "show me what's
34
+ # on disk right now" is worse than the work it would save.
35
+ module DocRoots
36
+ DEFAULT_REGISTRY_PATH = File.expand_path('~/.streamweaver/docs_roots.log')
37
+
38
+ # Colon-separated, like PATH. `~/work` is the default because that's
39
+ # where the overwhelming majority of repos live; anything else is what
40
+ # the registry half is for.
41
+ DEFAULT_SCAN_ROOTS = '~/work'
42
+
43
+ # The label for DocStore::DEFAULT_ROOT. Not a repo, so it gets a name
44
+ # rather than a basename ('canvas' would be meaningless in the rail).
45
+ GLOBAL_LABEL = 'Global'
46
+
47
+ module_function
48
+
49
+ # Read fresh on every call (not memoized into a constant) so an ENV
50
+ # override flips behavior mid-process -- same contract DocStore.path
51
+ # has, and what keeps specs from writing to the real registry.
52
+ def registry_path
53
+ env = ENV['STREAMWEAVER_DOCS_REGISTRY']
54
+ env && !env.empty? ? File.expand_path(env) : DEFAULT_REGISTRY_PATH
55
+ end
56
+
57
+ # An explicitly empty STREAMWEAVER_DOCS_SCAN_ROOTS means "scan nothing"
58
+ # (registry only), which is distinct from unset meaning "scan ~/work".
59
+ def scan_roots
60
+ raw = ENV['STREAMWEAVER_DOCS_SCAN_ROOTS'] || DEFAULT_SCAN_ROOTS
61
+ raw.split(File::PATH_SEPARATOR).reject { |p| p.strip.empty? }.map { |p| File.expand_path(p.strip) }
62
+ end
63
+
64
+ # Symlink-resolved path, used only as a dedupe/comparison KEY -- never
65
+ # for display. macOS's /tmp -> /private/tmp is the everyday case: the
66
+ # same directory reached two ways must not become two sidebar groups.
67
+ # Falls back to expand_path for a path that no longer resolves.
68
+ def canonical(path)
69
+ File.realpath(path)
70
+ rescue SystemCallError
71
+ File.expand_path(path)
72
+ end
73
+
74
+ # Appends `root` to the registry. Returns the recorded absolute path,
75
+ # or nil if there was nothing to record or the write failed.
76
+ #
77
+ # Skips the append when the exact line is already present: duplicates
78
+ # are harmless (reads dedupe), but re-saving the same doc a thousand
79
+ # times shouldn't grow the file a thousand lines. The check is a read
80
+ # of a few hundred bytes, and a lost race just writes a duplicate --
81
+ # which is, again, harmless.
82
+ #
83
+ # Swallows filesystem errors on purpose: failing to record a root
84
+ # degrades discovery, and must never take down the save that triggered
85
+ # it.
86
+ def record(root)
87
+ return nil unless root.is_a?(String) && !root.empty?
88
+
89
+ path = File.expand_path(root)
90
+ return path if raw_entries.include?(path)
91
+
92
+ FileUtils.mkdir_p(File.dirname(registry_path))
93
+ File.open(registry_path, 'a') { |f| f.write("#{path}\n") }
94
+ path
95
+ rescue SystemCallError
96
+ nil
97
+ end
98
+
99
+ # Records `dir` only when nothing already discovers it -- the
100
+ # `canvas-read <explicit path>` backfill. A path the scan already
101
+ # finds needs no registry entry.
102
+ def record_if_new(dir)
103
+ return nil unless dir && File.directory?(dir)
104
+
105
+ key = canonical(dir)
106
+ return nil if roots.any? { |r| canonical(r) == key }
107
+
108
+ record(dir)
109
+ end
110
+
111
+ # Registry contents that still exist, deduped, in file order.
112
+ def registered
113
+ raw_entries.uniq.select { |p| File.directory?(p) }
114
+ end
115
+
116
+ # `*/docs/streamweaver_canvas` under each scan root, one level deep.
117
+ def scanned
118
+ scan_roots.flat_map { |r| Dir.glob(File.join(r, '*', DocStore::DOCS_SUBPATH)) }
119
+ .select { |p| File.directory?(p) }
120
+ .sort
121
+ end
122
+
123
+ # Every docs root to show, deduped by canonical path and ordered
124
+ # host-repo first, then scanned, then registered, then global.
125
+ #
126
+ # DocStore.path leads deliberately: it is the repo the reader was
127
+ # launched from, it is what the default sidebar filter selects, and it
128
+ # may be reachable by neither other source (a repo outside the scan
129
+ # roots that has been cloned but never saved to on this machine, or a
130
+ # STREAMWEAVER_DOC_ROOT override).
131
+ #
132
+ # Keyed by canonical path, NOT by basename: two repos named `docs` in
133
+ # different parents are two roots, and a basename-keyed hash would
134
+ # silently drop one of them. Basename is a display label only -- see
135
+ # #labels, which disambiguates collisions instead of discarding them.
136
+ #
137
+ # A discovered root that currently holds no `*.{rb,org}` is dropped:
138
+ # File.directory? alone answers "does this place exist," not "is there
139
+ # anything here to show," and a root that once had docs and no longer
140
+ # does (a `git pull` that removed them, deleting the last one from the
141
+ # reader -- stream_weaver-uvaj) is a sidebar heading that discloses
142
+ # nothing. The two roots the host process owns are exempt and always
143
+ # listed while they exist as directories: DocStore.path is the repo the
144
+ # reader was launched from and the target of its own Save-as-doc, and
145
+ # DocStore::DEFAULT_ROOT is the global fallback store -- both need to
146
+ # stay wired up (mtime-watched by the reader's FileList) so the FIRST
147
+ # doc saved into an empty one shows up without a restart.
148
+ def roots
149
+ always = [DocStore.path, DocStore::DEFAULT_ROOT].compact.map { |p| File.expand_path(p) }
150
+ candidates = [DocStore.path, *scanned, *registered, DocStore::DEFAULT_ROOT]
151
+ seen = {}
152
+ candidates.each do |candidate|
153
+ next if candidate.nil? || candidate.to_s.empty?
154
+
155
+ expanded = File.expand_path(candidate)
156
+ next unless File.directory?(expanded)
157
+ next unless always.include?(expanded) || docs?(expanded)
158
+
159
+ seen[canonical(expanded)] ||= expanded
160
+ end
161
+ seen.values
162
+ end
163
+
164
+ # True when `root` directly contains at least one doc file. Same
165
+ # '*.{rb,org}' glob FileList.build uses to populate the sidebar, so
166
+ # "root is listed" and "root produces a group" can't disagree.
167
+ def docs?(root)
168
+ Dir.glob(File.join(root, '*.{rb,org}')).any?
169
+ end
170
+
171
+ # {root => label} for `roots_list`, insertion-ordered. Labels are
172
+ # unique across the hash, since a label is what `?repo=` addresses --
173
+ # two groups sharing one label would be unfilterable.
174
+ def labels(roots_list = roots)
175
+ used = {}
176
+ roots_list.each_with_object({}) do |root, out|
177
+ out[root] = unique_label(root, used)
178
+ end
179
+ end
180
+
181
+ # The repo directory a docs root belongs to -- the root with the
182
+ # `docs/streamweaver_canvas` suffix removed. A root that doesn't carry
183
+ # that suffix (a STREAMWEAVER_DOC_ROOT override, the global store) is
184
+ # its own repo dir.
185
+ def repo_dir(root)
186
+ suffix = File::SEPARATOR + DocStore::DOCS_SUBPATH
187
+ root.end_with?(suffix) ? root[0...-suffix.length] : root
188
+ end
189
+
190
+ def base_label(root)
191
+ return GLOBAL_LABEL if canonical(root) == canonical(DocStore::DEFAULT_ROOT)
192
+
193
+ File.basename(repo_dir(root))
194
+ end
195
+
196
+ # Disambiguates a colliding basename with its parent directory rather
197
+ # than dropping the root -- `~/work/notes` and `~/src/notes` are two
198
+ # real repos and both have to be reachable.
199
+ def unique_label(root, used)
200
+ base = base_label(root)
201
+ return claim(base, used) unless used.key?(base)
202
+
203
+ parent = File.basename(File.dirname(repo_dir(root)))
204
+ candidate = "#{base} (#{parent})"
205
+ n = 2
206
+ while used.key?(candidate)
207
+ candidate = "#{base} (#{parent} #{n})"
208
+ n += 1
209
+ end
210
+ claim(candidate, used)
211
+ end
212
+ private_class_method :unique_label
213
+
214
+ def claim(label, used)
215
+ used[label] = true
216
+ label
217
+ end
218
+ private_class_method :claim
219
+
220
+ # Every non-blank line, expanded, without the directory filter --
221
+ # `record`'s duplicate check needs to see entries that no longer
222
+ # exist, or a deleted-and-recreated root would append forever.
223
+ def raw_entries
224
+ return [] unless File.file?(registry_path)
225
+
226
+ File.readlines(registry_path, chomp: true).map(&:strip).reject(&:empty?).map { |p| File.expand_path(p) }
227
+ rescue SystemCallError
228
+ []
229
+ end
230
+ private_class_method :raw_entries
231
+ end
232
+ end
233
+ end