boxwerk 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 45cabb7d61d5029a494703a95c1b974f1af7ee1286001d457ea7aaf8b870496c
4
+ data.tar.gz: fb14ec2556787d19618ee3599685a06a06a4d1ed40a3113c47f61420f84fb202
5
+ SHA512:
6
+ metadata.gz: 8e11bee2fd442bc29b537d4e7099e4380b19bed851f4f40bd42e119c35f139ee680866bd0f6189334e46652881c224559e10c6ab4dcf722c4d50c455fccdf16e
7
+ data.tar.gz: 0c69bf3a342d8fd502d6e64d085edfa9d1a2864b60d8f3f683addc680080f5762246f9901926c81667c40ddad82fdae7ead6e939ea3f297c0f907c983e21622f
data/CHANGELOG.md ADDED
@@ -0,0 +1,8 @@
1
+ # Changelog
2
+
3
+ ## [0.1.0] - 2026-01-05
4
+
5
+ Initial release.
6
+
7
+ [unreleased]: https://github.com/dtcristo/boxwerk/compare/v0.1.0...HEAD
8
+ [0.1.0]: https://github.com/dtcristo/boxwerk/releases/tag/v0.1.0
data/LICENSE.txt ADDED
@@ -0,0 +1,19 @@
1
+ The MIT License (MIT)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,376 @@
1
+ # Boxwerk
2
+
3
+ Boxwerk is a runtime package system for Ruby with strict isolation of constants using Ruby 4.0's [`Ruby::Box`](https://docs.ruby-lang.org/en/master/Ruby/Box.html). It is used to organize code into packages with explicit dependency graphs and strict access to constants between packages. It is inspired by [Packwerk](https://github.com/Shopify/packwerk), a static package system.
4
+
5
+ ## Features
6
+
7
+ - **Strict Isolation**: Each package runs in its own `Ruby::Box`, preventing constants from leaking without explicit imports or exports.
8
+ - **Explicit Dependencies**: Dependencies are declared in `package.yml` files, forming a validated DAG.
9
+ - **Ergonomic Imports**: Flexible import strategies (namespaced, aliased, selective, renamed).
10
+
11
+ ## Limtations
12
+
13
+ - There is no isolation of gems.
14
+ - Gems are required to be eager loaded in the root box to be accessible in packages.
15
+ - No support for reloading of constants.
16
+ - Exported constants must follow Zeitwerk naming conventions for their source location.
17
+
18
+ ## Requirements
19
+
20
+ - Ruby 4.0+ with [`Ruby::Box`](https://docs.ruby-lang.org/en/master/Ruby/Box.html) support
21
+ - `RUBY_BOX=1` environment variable must be set at process startup
22
+
23
+ ## Quick Start
24
+
25
+ ### 1. Create a Package Structure
26
+
27
+ ```
28
+ my_app/
29
+ ├── Gemfile # Your gem dependencies
30
+ ├── package.yml # Root package
31
+ ├── app.rb # Your application entrypoint
32
+ └── packages/
33
+ └── billing/
34
+ ├── package.yml # Package manifest
35
+ └── lib/
36
+ └── invoice.rb # Package code
37
+ ```
38
+
39
+ ### 2. Define Your Gemfile
40
+
41
+ **`Gemfile`:**
42
+ ```ruby
43
+ source 'https://rubygems.org'
44
+
45
+ gem 'boxwerk'
46
+ gem 'money' # Example: gems are auto-required and globally accessible
47
+ ```
48
+
49
+ ### 3. Define Packages
50
+
51
+ **Root `package.yml`:**
52
+ ```yaml
53
+ imports:
54
+ - packages/finance # Will define a `Finance` module to hold finance package exports
55
+ ```
56
+
57
+ **`packages/finance/package.yml`:**
58
+ ```yaml
59
+ exports:
60
+ - Invoice
61
+ - TaxCalculator
62
+ ```
63
+
64
+ **`packages/finance/lib/invoice.rb`:**
65
+ ```ruby
66
+ class Invoice
67
+ def initialize(amount_cents)
68
+ # Money gem is accessible because it's in the Gemfile
69
+ @amount = Money.new(amount_cents, 'USD')
70
+ end
71
+
72
+ def total
73
+ @amount
74
+ end
75
+ end
76
+ ```
77
+
78
+ **`packages/finance/lib/tax_calculator.rb`:**
79
+ ```ruby
80
+ class TaxCalculator
81
+ # ...
82
+ end
83
+ ```
84
+
85
+ ### 4. Use in Your Application
86
+
87
+ **`app.rb`:**
88
+ ```ruby
89
+ # No requires needed - imports are wired by Boxwerk
90
+ invoice = Finance::Invoice.new(10_000)
91
+ puts invoice.total # => #<Money fractional:10000 currency:USD>
92
+ ```
93
+
94
+ ### 5. Run Your Application
95
+
96
+ ```bash
97
+ RUBY_BOX=1 boxwerk run app.rb
98
+ ```
99
+
100
+ Boxwerk automatically:
101
+ 1. Sets up Bundler
102
+ 2. Requires all gems from your Gemfile in the root box
103
+ 3. Loads and wires all packages
104
+ 4. Executes your script in the root package context
105
+
106
+ ## Usage
107
+
108
+ ### Running Scripts
109
+
110
+ Execute a Ruby script in the root package context:
111
+
112
+ ```bash
113
+ boxwerk run script.rb [args...]
114
+ ```
115
+
116
+ The script has access to:
117
+ - All gems from your Gemfile (automatically required)
118
+ - All imports defined in the root `package.yml`
119
+
120
+ ### Interactive Console
121
+
122
+ TODO: This feature is currenly broken and will run IRB from the root box, not the root package as desired.
123
+
124
+ Start an IRB session in the root package context:
125
+
126
+ ```bash
127
+ boxwerk console [irb-args...]
128
+ ```
129
+
130
+ All imports and gems are available for interactive exploration.
131
+
132
+ ### Help
133
+
134
+ ```bash
135
+ boxwerk help
136
+ ```
137
+
138
+ ## Package Configuration
139
+
140
+ A `package.yml` defines what a package exports and imports:
141
+
142
+ ```yaml
143
+ exports:
144
+ - PublicClass
145
+ - PublicModule
146
+
147
+ imports:
148
+ - packages/dependency1
149
+ - packages/dependency2: Alias
150
+ ```
151
+
152
+ ### Exports
153
+
154
+ Constants that should be visible to packages that import this one.
155
+
156
+ ### Imports
157
+
158
+ Dependencies this package needs. **Note**: Dependencies are NOT transitive. If package A imports B, and B imports C, then A cannot access C unless it explicitly imports it.
159
+
160
+ ## Import Strategies
161
+
162
+ Boxwerk supports four import strategies in `package.yml`:
163
+
164
+ ### 1. Default Namespace
165
+
166
+ Import all exports under a module named after the package:
167
+
168
+ ```yaml
169
+ imports:
170
+ - packages/finance
171
+ ```
172
+
173
+ Result: `Finance::Invoice`, `Finance::TaxCalculator`
174
+
175
+ ### 2. Aliased Namespace
176
+
177
+ Import under a custom module name:
178
+
179
+ ```yaml
180
+ imports:
181
+ - packages/finance: Billing
182
+ ```
183
+
184
+ Result: `Billing::Invoice`, `Billing::TaxCalculator`
185
+
186
+ **Single Export Optimization**: If a package exports only one constant, it's imported directly (not wrapped in a module):
187
+
188
+ ```yaml
189
+ # util exports only Calculator
190
+ imports:
191
+ - packages/util: Calc
192
+ ```
193
+
194
+ Result: `Calc` (not `Calc::Calculator`)
195
+
196
+ ### 3. Selective Import
197
+
198
+ Import specific constants directly:
199
+
200
+ ```yaml
201
+ imports:
202
+ - packages/finance:
203
+ - Invoice
204
+ - TaxCalculator
205
+ ```
206
+
207
+ Result: `Invoice`, `TaxCalculator` (no namespace)
208
+
209
+ ### 4. Selective Rename
210
+
211
+ Import specific constants with custom names:
212
+
213
+ ```yaml
214
+ imports:
215
+ - packages/finance:
216
+ Invoice: Bill
217
+ TaxCalculator: Calculator
218
+ ```
219
+
220
+ Result: `Bill`, `Calculator`
221
+
222
+ ## Gems and Packages
223
+
224
+ ### How Gems Work in Boxwerk
225
+
226
+ When you run `boxwerk`, all gems in your `Gemfile` are:
227
+ 1. Automatically loaded via Bundler in the root box
228
+ 2. Accessible globally in all package boxes (gems are not isolated)
229
+
230
+ This means:
231
+ - You can use any gem from your Gemfile in any package.
232
+ - Gems don't need to be declared in `package.yml`.
233
+ - You do not `require` gems manually.
234
+
235
+ ### Isolation Model
236
+
237
+ - **Root Box**: The box where Ruby bootstraps and all builtin classes/modules are defined. In Boxwerk, the root box performs all setup operations (Bundler setup, gem loading, dependency graph building, package box creation, and import wiring).
238
+ - **Main Box**: The first user box created automatically by Ruby (copied from root box). In Boxwerk, it only runs the `exe/boxwerk` executable file, which then calls into the root box to execute the setup. The main box has no other purpose.
239
+ - **Package Boxes**: Each package (including root package) runs in its own isolated `Ruby::Box` (created by copying from root box after gems are loaded).
240
+ - **Box Inheritance**: All boxes are created via copy-on-write from the root box, inheriting builtin classes and loaded gems.
241
+ - **Gems are Global**: All gems from Gemfile are accessible in all boxes (loaded in root box before package boxes are created).
242
+ - **Package Exports are Isolated**: Only explicit imports from packages are accessible.
243
+ - **No Transitive Access**: Packages can only see their explicit imports.
244
+
245
+ For more details on how Ruby::Box works, see the [official Ruby::Box documentation](https://docs.ruby-lang.org/en/master/Ruby/Box.html).
246
+
247
+ ## Known Issues
248
+
249
+ These issues are related to the current state of Ruby::Box in Ruby 4.0+. See the [Ruby::Box documentation](https://docs.ruby-lang.org/en/master/Ruby/Box.html) for known issues with the feature itself.
250
+
251
+ ### Gem Requiring in Boxes
252
+
253
+ Requiring any gem from within a box (after boot) currently crashes the Ruby VM. This is likely an issue with Ruby::Box itself. As a workaround, Boxwerk automatically requires all gems from the Gemfile in the root box before creating package boxes, so gems are already loaded and accessible everywhere.
254
+
255
+ ### Console Context
256
+
257
+ The console does not correctly run in the root package box—it runs in the context of the root box instead. It should run in the root package box. However, if we attempt to `require 'irb'` in the root package box, the Ruby VM crashes due to the gem requiring issue described above.
258
+
259
+ ### IRB Autocomplete
260
+
261
+ Autocomplete is disabled for the console/IRB by default. When enabled, the Ruby VM crashes as soon as any key is pressed. This appears to be an issue with Ruby::Box and IRB's autocomplete feature interacting poorly.
262
+
263
+ ## Architecture
264
+
265
+ ### Boot Process
266
+
267
+ 1. Setup Bundler and require all gems in the root box
268
+ 2. Find root `package.yml` (searches up from current directory)
269
+ 3. Build dependency graph from package manifests
270
+ 4. Validate dependency graph (no circular dependencies)
271
+ 5. Boot packages in topological order
272
+ 6. Wire imports into each package box
273
+ 7. Execute command in root package context
274
+
275
+ ### Internal Components
276
+
277
+ Boxwerk consists of several internal components that work together to provide package isolation:
278
+
279
+ #### `Boxwerk::CLI`
280
+
281
+ The command-line interface handler that:
282
+ - Parses commands (`run`, `console`, `help`)
283
+ - Validates the Ruby environment (checks for `RUBY_BOX=1` and Ruby::Box support)
284
+ - Delegates to `Boxwerk::Setup` for the boot process
285
+ - Executes the requested command in the root package's box context
286
+
287
+ #### `Boxwerk::Setup`
288
+
289
+ The setup orchestrator that:
290
+ - Searches up the directory tree to find the root `package.yml`
291
+ - Creates a `Boxwerk::Graph` instance to build and validate the dependency graph
292
+ - Creates a `Boxwerk::Registry` instance to track booted packages
293
+ - Calls `Boxwerk::Loader.boot_all` to boot all packages in topological order
294
+ - Returns the loaded graph for introspection
295
+
296
+ #### `Boxwerk::Graph`
297
+
298
+ The dependency graph builder that:
299
+ - Parses the root `package.yml` and recursively discovers all package dependencies
300
+ - Builds a directed acyclic graph (DAG) of package relationships
301
+ - Validates that there are no circular dependencies
302
+ - Performs topological sorting to determine boot order (dependencies before consumers)
303
+ - Provides access to all packages in the graph
304
+
305
+ #### `Boxwerk::Package`
306
+
307
+ The package manifest parser that:
308
+ - Represents a single package with its configuration
309
+ - Parses `package.yml` files to extract exports and imports
310
+ - Normalizes the polymorphic import syntax (String, Array, Hash)
311
+ - Stores the package path, name, exports, imports, and box reference
312
+ - Tracks which exports have been loaded via `loaded_exports` hash (export name → file path)
313
+ - Tracks whether the package has been booted
314
+
315
+ #### `Boxwerk::Loader`
316
+
317
+ The package loader that:
318
+ - Creates a new `Ruby::Box` for each package (including the root package)
319
+ - Loads exported constants lazily on-demand when they are imported by other packages
320
+ - Uses Zeitwerk naming conventions to discover file locations for exported constants
321
+ - Caches loaded exports in `package.loaded_exports` to avoid redundant file loading
322
+ - Wires imports by injecting constants from dependency boxes into consumer boxes
323
+ - Implements all four import strategies (default namespace, aliased namespace, selective import, selective rename)
324
+ - Handles the single-export optimization for namespace imports
325
+ - Registers each booted package in the registry
326
+ - Only loads files that define exported constants, never loading non-exported code
327
+
328
+ #### `Boxwerk::Registry`
329
+
330
+ The package registry that:
331
+ - Tracks all booted package instances
332
+ - Allows packages to be retrieved by name during the wiring phase
333
+ - Ensures each package is only booted once
334
+ - Provides a clean interface for package lookup
335
+
336
+ ## Example
337
+
338
+ See the [example/](example/) directory for a complete working example with:
339
+
340
+ - Multi-package application
341
+ - Gem usage
342
+ - Transitive dependency demonstration
343
+ - Isolation verification
344
+ - Console usage examples
345
+
346
+ Run it with:
347
+
348
+ ```bash
349
+ cd example
350
+ RUBY_BOX=1 boxwerk run app.rb
351
+ ```
352
+
353
+ Or explore interactively:
354
+
355
+ ```bash
356
+ cd example
357
+ RUBY_BOX=1 boxwerk console
358
+ ```
359
+
360
+ ## Development
361
+
362
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run the tests:
363
+
364
+ ```bash
365
+ RUBY_BOX=1 bundle exec rake test
366
+ ```
367
+
368
+ To install this gem onto your local machine, run `bundle exec rake install`.
369
+
370
+ ## License
371
+
372
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
373
+
374
+ ## Contribution
375
+
376
+ Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you shall be dual licensed as above, without any additional terms or conditions.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'minitest/test_task'
5
+
6
+ Minitest::TestTask.create
7
+
8
+ task default: :test
data/example/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gem 'boxwerk', path: '..'
6
+ gem 'money'
@@ -0,0 +1,66 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ boxwerk (0.1.0)
5
+ irb (~> 1.16)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ bigdecimal (4.0.1)
11
+ concurrent-ruby (1.3.6)
12
+ date (3.5.1)
13
+ erb (6.0.1)
14
+ i18n (1.14.8)
15
+ concurrent-ruby (~> 1.0)
16
+ io-console (0.8.2)
17
+ irb (1.16.0)
18
+ pp (>= 0.6.0)
19
+ rdoc (>= 4.0.0)
20
+ reline (>= 0.4.2)
21
+ money (7.0.0)
22
+ bigdecimal
23
+ i18n (~> 1.9)
24
+ pp (0.6.3)
25
+ prettyprint
26
+ prettyprint (0.2.0)
27
+ psych (5.3.1)
28
+ date
29
+ stringio
30
+ rdoc (7.0.3)
31
+ erb
32
+ psych (>= 4.0.0)
33
+ tsort
34
+ reline (0.6.3)
35
+ io-console (~> 0.5)
36
+ stringio (3.2.0)
37
+ tsort (0.2.0)
38
+
39
+ PLATFORMS
40
+ arm64-darwin-25
41
+ ruby
42
+
43
+ DEPENDENCIES
44
+ boxwerk!
45
+ money
46
+
47
+ CHECKSUMS
48
+ bigdecimal (4.0.1) sha256=8b07d3d065a9f921c80ceaea7c9d4ae596697295b584c296fe599dd0ad01c4a7
49
+ boxwerk (0.1.0)
50
+ concurrent-ruby (1.3.6) sha256=6b56837e1e7e5292f9864f34b69c5a2cbc75c0cf5338f1ce9903d10fa762d5ab
51
+ date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0
52
+ erb (6.0.1) sha256=28ecdd99c5472aebd5674d6061e3c6b0a45c049578b071e5a52c2a7f13c197e5
53
+ i18n (1.14.8) sha256=285778639134865c5e0f6269e0b818256017e8cde89993fdfcbfb64d088824a5
54
+ io-console (0.8.2) sha256=d6e3ae7a7cc7574f4b8893b4fca2162e57a825b223a177b7afa236c5ef9814cc
55
+ irb (1.16.0) sha256=2abe56c9ac947cdcb2f150572904ba798c1e93c890c256f8429981a7675b0806
56
+ money (7.0.0) sha256=6de776ee00aced8b9a435d3aac25ce8c600566625809b3d69bbe0c319e941dd5
57
+ pp (0.6.3) sha256=2951d514450b93ccfeb1df7d021cae0da16e0a7f95ee1e2273719669d0ab9df6
58
+ prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193
59
+ psych (5.3.1) sha256=eb7a57cef10c9d70173ff74e739d843ac3b2c019a003de48447b2963d81b1974
60
+ rdoc (7.0.3) sha256=dfe3d0981d19b7bba71d9dbaeb57c9f4e3a7a4103162148a559c4fc687ea81f9
61
+ reline (0.6.3) sha256=1198b04973565b36ec0f11542ab3f5cfeeec34823f4e54cebde90968092b1835
62
+ stringio (3.2.0) sha256=c37cb2e58b4ffbd33fe5cd948c05934af997b36e0b6ca6fdf43afa234cf222e1
63
+ tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f
64
+
65
+ BUNDLED WITH
66
+ 4.0.3
data/example/README.md ADDED
@@ -0,0 +1,130 @@
1
+ # Boxwerk Example Application
2
+
3
+ This directory contains a complete example of a Boxwerk application demonstrating strict package isolation and dependency management.
4
+
5
+ ## Architecture
6
+
7
+ ```
8
+ example/
9
+ ├── Gemfile # Gem dependencies (money gem)
10
+ ├── package.yml # Root package (imports finance)
11
+ ├── app.rb # Application entry point
12
+ └── packages/
13
+ ├── finance/
14
+ │ ├── package.yml # Exports Invoice, TaxCalculator; imports util
15
+ │ └── lib/
16
+ │ ├── invoice.rb
17
+ │ └── tax_calculator.rb
18
+ └── util/
19
+ ├── package.yml # Exports Calculator, Geometry
20
+ └── lib/
21
+ ├── calculator.rb
22
+ └── geometry.rb
23
+ ```
24
+
25
+ ## Dependency Graph
26
+
27
+ ```
28
+ util (isolated box)
29
+ └── Calculator, Geometry
30
+
31
+ finance (isolated box)
32
+ ├── imports: util (Calculator renamed to UtilCalculator)
33
+ └── Invoice, TaxCalculator
34
+
35
+ root (isolated box)
36
+ ├── imports: finance
37
+ └── Finance::Invoice, Finance::TaxCalculator available
38
+ ```
39
+
40
+ ## Running the Example
41
+
42
+ ```bash
43
+ cd example
44
+ RUBY_BOX=1 boxwerk run app.rb
45
+ ```
46
+
47
+ **Architecture:**
48
+ - Root box: Contains gems (including money gem) and Boxwerk runtime
49
+ - Util box: Contains Calculator and Geometry classes
50
+ - Finance box: Contains Invoice and TaxCalculator (imports Calculator from Util box as UtilCalculator)
51
+ - Root package box: Runs app.rb (imports from Finance box)
52
+
53
+ **Important:** ALL packages (including root) run in isolated boxes. The main Ruby process only contains gems and the Boxwerk runtime.
54
+
55
+ ### Interactive Console
56
+
57
+ You can also start an IRB console in the root package context:
58
+
59
+ ```bash
60
+ cd example
61
+ RUBY_BOX=1 boxwerk console
62
+ ```
63
+
64
+ This gives you an interactive session with all imports available (e.g., `Finance::Invoice`).
65
+
66
+ ## What This Demonstrates
67
+
68
+ ### ✓ Strict Isolation
69
+ - `Finance::Invoice` and `Finance::TaxCalculator` are accessible (explicit import)
70
+ - `Calculator` and `Geometry` are NOT accessible (transitive dependency - not imported)
71
+ - `UtilCalculator` is NOT accessible (only available in Finance package's box)
72
+ - `Invoice` at top level is NOT accessible (must use `Finance::` namespace)
73
+
74
+ ### ✓ Namespace Control
75
+ - Finance package exports are grouped under `Finance::` module
76
+ - Import strategies control how dependencies are wired
77
+
78
+ ### ✓ Selective Rename Strategy
79
+ - Finance imports `Calculator` from util and renames it to `UtilCalculator`
80
+ - This demonstrates the selective rename import strategy
81
+
82
+ ### ✓ Transitive Dependency Blocking
83
+ - Root imports Finance
84
+ - Finance imports Util
85
+ - Root cannot access Util classes (no transitive access)
86
+
87
+ ### ✓ Gem Access
88
+ - Money gem is globally accessible in all packages
89
+ - Gems from Gemfile are auto-required and available everywhere
90
+
91
+ ### ✓ Clean API Surface
92
+ - Each package explicitly declares exports
93
+ - Consumers only see what's exported
94
+ - Internal implementation details remain hidden
95
+
96
+ ## Package Configuration
97
+
98
+ ### Root Package (`package.yml`)
99
+
100
+ ```yaml
101
+ imports:
102
+ - packages/finance # Default namespace strategy
103
+ ```
104
+
105
+ ### Finance Package (`packages/finance/package.yml`)
106
+
107
+ ```yaml
108
+ exports:
109
+ - Invoice
110
+ - TaxCalculator
111
+
112
+ imports:
113
+ # Import Calculator from util and rename it to UtilCalculator
114
+ - packages/util:
115
+ Calculator: UtilCalculator
116
+ ```
117
+
118
+ ### Util Package (`packages/util/package.yml`)
119
+
120
+ ```yaml
121
+ exports:
122
+ - Calculator
123
+ - Geometry
124
+ ```
125
+
126
+ ## Requirements
127
+
128
+ - Ruby 4.0+ with `Ruby::Box` support
129
+ - `RUBY_BOX=1` environment variable must be set
130
+ - Boxwerk gem installed or loaded from `../lib`