taski 0.8.3 → 0.9.1

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +52 -0
  3. data/README.md +108 -50
  4. data/docs/GUIDE.md +79 -55
  5. data/examples/README.md +10 -29
  6. data/examples/clean_demo.rb +25 -65
  7. data/examples/large_tree_demo.rb +356 -0
  8. data/examples/message_demo.rb +0 -1
  9. data/examples/progress_demo.rb +13 -24
  10. data/examples/reexecution_demo.rb +8 -44
  11. data/lib/taski/execution/execution_facade.rb +150 -0
  12. data/lib/taski/execution/executor.rb +167 -359
  13. data/lib/taski/execution/fiber_protocol.rb +27 -0
  14. data/lib/taski/execution/registry.rb +15 -19
  15. data/lib/taski/execution/scheduler.rb +161 -140
  16. data/lib/taski/execution/task_observer.rb +41 -0
  17. data/lib/taski/execution/task_output_router.rb +41 -58
  18. data/lib/taski/execution/task_wrapper.rb +123 -219
  19. data/lib/taski/execution/worker_pool.rb +279 -64
  20. data/lib/taski/logging.rb +105 -0
  21. data/lib/taski/progress/layout/base.rb +600 -0
  22. data/lib/taski/progress/layout/filters.rb +126 -0
  23. data/lib/taski/progress/layout/log.rb +27 -0
  24. data/lib/taski/progress/layout/simple.rb +166 -0
  25. data/lib/taski/progress/layout/tags.rb +76 -0
  26. data/lib/taski/progress/layout/theme_drop.rb +84 -0
  27. data/lib/taski/progress/layout/tree.rb +300 -0
  28. data/lib/taski/progress/theme/base.rb +224 -0
  29. data/lib/taski/progress/theme/compact.rb +58 -0
  30. data/lib/taski/progress/theme/default.rb +25 -0
  31. data/lib/taski/progress/theme/detail.rb +48 -0
  32. data/lib/taski/progress/theme/plain.rb +40 -0
  33. data/lib/taski/static_analysis/analyzer.rb +5 -17
  34. data/lib/taski/static_analysis/dependency_graph.rb +19 -1
  35. data/lib/taski/static_analysis/start_dep_analyzer.rb +400 -0
  36. data/lib/taski/static_analysis/visitor.rb +1 -39
  37. data/lib/taski/task.rb +49 -58
  38. data/lib/taski/task_proxy.rb +59 -0
  39. data/lib/taski/test_helper/errors.rb +1 -1
  40. data/lib/taski/test_helper.rb +22 -36
  41. data/lib/taski/version.rb +1 -1
  42. data/lib/taski.rb +62 -61
  43. data/sig/taski.rbs +194 -203
  44. metadata +34 -8
  45. data/examples/section_demo.rb +0 -195
  46. data/lib/taski/execution/base_progress_display.rb +0 -393
  47. data/lib/taski/execution/execution_context.rb +0 -390
  48. data/lib/taski/execution/plain_progress_display.rb +0 -76
  49. data/lib/taski/execution/simple_progress_display.rb +0 -247
  50. data/lib/taski/execution/tree_progress_display.rb +0 -643
  51. data/lib/taski/section.rb +0 -74
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cddae184fb1f55c69b42995e51c3bf5711d31f978ec383a00a73d2ce3531ca15
4
- data.tar.gz: 3e79ca2bf3a6cd163272bbff98ff3b980f3a3b69293607dd45879501d50904c4
3
+ metadata.gz: 7c7ea0740de2f0d1fcf24a72fe5b32e75128fa4d793718b3ac12a2c041c37628
4
+ data.tar.gz: 8fb45c4d390a98709d934f4b59f14a613d689ed7538df630cb4328bc405b2803
5
5
  SHA512:
6
- metadata.gz: 9310e8b52be98d9087de88b4b7f5d105896edd85137c2f43e6c968c8daaf00dc6b4843ff9d1f228a089dcd7ceb379715c2b640d3c547ce173659dc7130cd1768
7
- data.tar.gz: 65bade2024691c9c7527f6249294d01714563ff35c211472e6d984e82ba5acd0767251d33e05d8ade4208eabc8b1326eb47fc4f932b4699bf0b986e208fffcea
6
+ metadata.gz: 7cba0b749c9f5b5e76f990731017d18c550407215f851877129d2c6b1c9872fccfb3d0e088b01db19a92064f92592971a085ca9eecca3bcd8742e44b7374a9e5
7
+ data.tar.gz: 211b4d9ed455e19688bbb29acc7beb5043150703e144bb38f973b68f6bbf5fb3aa942c7fd2d77faa4332df243cb5f0d50e4a06813c7786b7c954f5a8eb674900
data/CHANGELOG.md CHANGED
@@ -7,6 +7,58 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.9.1] - 2026-02-16
11
+
12
+ ### Changed
13
+ - Replace raw arrays and hashes with FiberProtocol Data classes for typed protocol messages ([#175](https://github.com/ahogappa/taski/pull/175))
14
+ - Add AST-guided start_dep speculative parallel execution for improved performance ([#175](https://github.com/ahogappa/taski/pull/175))
15
+ - Add TaskProxy for lazy dependency resolution with unsafe proxy usage detection ([#175](https://github.com/ahogappa/taski/pull/175))
16
+ - Remove static-graph-based task scheduling from Executor in favor of Fiber pull model ([#176](https://github.com/ahogappa/taski/pull/176))
17
+ - Replace inline `Class.new(Taski::Task)` with named fixture classes in tests ([#174](https://github.com/ahogappa/taski/pull/174))
18
+ - Add custom export methods section to README ([#172](https://github.com/ahogappa/taski/pull/172))
19
+
20
+ ### Fixed
21
+ - Fix data race on `@next_thread_index` in enqueue/enqueue_clean ([#175](https://github.com/ahogappa/taski/pull/175))
22
+
23
+ ## [0.9.0] - 2026-02-08
24
+
25
+ ### Added
26
+ - Fiber-based lazy dependency resolution replacing Monitor-based approach ([#157](https://github.com/ahogappa/taski/pull/157))
27
+ - Layout/Theme architecture for progress display ([#150](https://github.com/ahogappa/taski/pull/150))
28
+ - Layout::Tree for hierarchical task display with TTY/non-TTY dual mode ([#151](https://github.com/ahogappa/taski/pull/151))
29
+ - Structured logging support for debugging and monitoring ([#141](https://github.com/ahogappa/taski/pull/141))
30
+ - Skipped task reporting in progress display and logging ([#157](https://github.com/ahogappa/taski/pull/157))
31
+ - `clean_on_failure` option for `run_and_clean` ([#169](https://github.com/ahogappa/taski/pull/169))
32
+ - `short_name` filter for template name formatting ([#151](https://github.com/ahogappa/taski/pull/151))
33
+ - TaskDrop and ExecutionDrop for structured template variables ([#151](https://github.com/ahogappa/taski/pull/151))
34
+ - Group duration computation in Layout::Base ([#167](https://github.com/ahogappa/taski/pull/167))
35
+ - `mark_clean_failed` in Scheduler for symmetric failure tracking ([#167](https://github.com/ahogappa/taski/pull/167))
36
+ - Ruby 4.0 support in CI ([#160](https://github.com/ahogappa/taski/pull/160))
37
+
38
+ ### Changed
39
+ - Replace ExecutionContext with ExecutionFacade/TaskObserver architecture ([#167](https://github.com/ahogappa/taski/pull/167))
40
+ - Remove SharedState, unify task state in TaskWrapper ([#167](https://github.com/ahogappa/taski/pull/167))
41
+ - Remove Section API in favor of simple if-statement selection (BREAKING) ([#157](https://github.com/ahogappa/taski/pull/157))
42
+ - Simplify clean API by removing `Task.clean` and `Task.new` (BREAKING) ([#163](https://github.com/ahogappa/taski/pull/163))
43
+ - Simplify progress display configuration to single setter API ([#161](https://github.com/ahogappa/taski/pull/161))
44
+ - Rename `completed?`/`clean_completed?` to `finished?`/`clean_finished?` (BREAKING) ([#167](https://github.com/ahogappa/taski/pull/167))
45
+ - Unify `STATE_ERROR` to `STATE_FAILED` across execution layer (BREAKING) ([#167](https://github.com/ahogappa/taski/pull/167))
46
+ - Rename Template to Theme, Layout::Plain to Layout::Log ([#151](https://github.com/ahogappa/taski/pull/151))
47
+ - Rename `execution_context` to `execution_facade` across codebase ([#167](https://github.com/ahogappa/taski/pull/167))
48
+ - Merge FiberExecutor into Executor, rename FiberWorkerPool to WorkerPool ([#157](https://github.com/ahogappa/taski/pull/157))
49
+ - Replace inline `Class.new(Taski::Task)` with named fixture classes in tests ([#166](https://github.com/ahogappa/taski/pull/166))
50
+ - Drop Ruby 3.2 from CI ([#160](https://github.com/ahogappa/taski/pull/160))
51
+
52
+ ### Fixed
53
+ - Recursively add transitive dependencies in `merge_runtime_dependencies` ([#142](https://github.com/ahogappa/taski/pull/142))
54
+ - Handle `Errno::EBADF` in TaskOutputRouter pipe operations ([#159](https://github.com/ahogappa/taski/pull/159))
55
+ - Truncate simple progress line to terminal width ([#152](https://github.com/ahogappa/taski/pull/152))
56
+ - Improve thread safety in Registry and WorkerPool ([#167](https://github.com/ahogappa/taski/pull/167))
57
+ - Restore fiber context on resume, scope output capture per-fiber ([#157](https://github.com/ahogappa/taski/pull/157))
58
+ - Prevent duplicate `:start` responses and ensure observer event ordering ([#157](https://github.com/ahogappa/taski/pull/157))
59
+ - Route observer errors through structured logger instead of `warn` ([#167](https://github.com/ahogappa/taski/pull/167))
60
+ - Resolve Bundler permission error on Ruby 4.0 CI ([#162](https://github.com/ahogappa/taski/pull/162))
61
+
10
62
  ## [0.8.3] - 2026-01-26
11
63
 
12
64
  ### Fixed
data/README.md CHANGED
@@ -12,9 +12,10 @@
12
12
 
13
13
  - **Automatic Dependency Resolution**: Dependencies detected via static analysis
14
14
  - **Parallel Execution**: Independent tasks run concurrently for maximum performance
15
- - **Two Simple APIs**: Exports (value sharing) and Section (runtime selection)
15
+ - **Exports API**: Simple value sharing between tasks
16
16
  - **Real-time Progress**: Visual feedback with parallel task progress display
17
- - **Thread-Safe**: Built on Monitor-based synchronization for reliable concurrent execution
17
+ - **Fiber-Based Execution**: Lightweight Fiber-based dependency resolution for efficient parallel execution
18
+ - **Lazy Dependency Resolution**: Dependencies return lightweight proxies that defer resolution until the value is actually used, enabling better parallelism
18
19
 
19
20
  ## Quick Start
20
21
 
@@ -77,42 +78,72 @@ class Server < Taski::Task
77
78
  end
78
79
  ```
79
80
 
80
- ### Section - Runtime Implementation Selection
81
+ ### Custom Export Methods
81
82
 
82
- Switch implementations based on environment:
83
+ By default, `exports` generates a reader that returns the instance variable (e.g., `exports :value` reads `@value`). You can override this by defining your own instance method with the same name:
84
+
85
+ **Fixed values** — no computation needed in `run`:
83
86
 
84
87
  ```ruby
85
- class DatabaseSection < Taski::Section
86
- interfaces :host, :port
88
+ class Config < Taski::Task
89
+ exports :timeout
87
90
 
88
- class Production < Taski::Task
89
- def run
90
- @host = "prod.example.com"
91
- @port = 5432
92
- end
91
+ def timeout
92
+ 30
93
+ end
94
+
95
+ def run; end
96
+ end
97
+
98
+ Config.timeout # => 30
99
+ ```
100
+
101
+ **Shared logic between `run` and `clean`** — the method works as both an export and a regular instance method:
102
+
103
+ ```ruby
104
+ class DatabaseSetup < Taski::Task
105
+ exports :connection
106
+
107
+ def connection
108
+ @connection ||= Database.connect
93
109
  end
94
110
 
95
- class Development < Taski::Task
96
- def run
111
+ def run
112
+ connection.setup_schema
113
+ end
114
+
115
+ def clean
116
+ connection.close
117
+ end
118
+ end
119
+ ```
120
+
121
+ ### Conditional Logic - Runtime Selection
122
+
123
+ Use `if` statements to switch behavior based on environment:
124
+
125
+ ```ruby
126
+ class DatabaseConfig < Taski::Task
127
+ exports :host, :port
128
+
129
+ def run
130
+ if ENV['RAILS_ENV'] == 'production'
131
+ @host = "prod.example.com"
132
+ @port = 5432
133
+ else
97
134
  @host = "localhost"
98
135
  @port = 5432
99
136
  end
100
137
  end
101
-
102
- def impl
103
- ENV['RAILS_ENV'] == 'production' ? Production : Development
104
- end
105
138
  end
106
139
 
107
140
  class App < Taski::Task
108
141
  def run
109
- puts "Connecting to #{DatabaseSection.host}:#{DatabaseSection.port}"
142
+ puts "Connecting to #{DatabaseConfig.host}:#{DatabaseConfig.port}"
110
143
  end
111
144
  end
112
145
  ```
113
146
 
114
- > **Note**: Nested implementation classes automatically inherit Section's `interfaces` as `exports`.
115
-
116
147
  ## Best Practices
117
148
 
118
149
  ### Keep Tasks Small and Focused
@@ -236,16 +267,49 @@ Env API (execution environment):
236
267
  RandomTask.value # => 42
237
268
  RandomTask.value # => 99 (different value - fresh execution)
238
269
 
239
- # Instance-level caching
240
- instance = RandomTask.new
241
- instance.run # => 42
242
- instance.run # => 42 (cached within instance)
243
- instance.value # => 42
244
-
245
270
  # Dependencies within same execution share results
246
271
  DoubleConsumer.run # RandomTask runs once, both accesses get same value
247
272
  ```
248
273
 
274
+ When a task accesses a dependency (e.g., `SomeDep.value`), the result may be a lightweight proxy. The actual resolution is deferred until the value is used, allowing independent dependencies to execute in parallel transparently. This is automatic and requires no changes to your task code. Dependencies used in conditions or as arguments are automatically resolved synchronously for safety.
275
+
276
+ ### Error Handling
277
+
278
+ When a task fails, Taski wraps the error with task-specific context. Each task class automatically gets a `::Error` subclass for targeted rescue:
279
+
280
+ ```ruby
281
+ class FetchData < Taski::Task
282
+ exports :data
283
+ def run
284
+ @data = API.fetch # may raise
285
+ end
286
+ end
287
+
288
+ class ProcessData < Taski::Task
289
+ def run
290
+ FetchData.data
291
+ end
292
+ end
293
+
294
+ # Rescue a specific task's error
295
+ begin
296
+ ProcessData.run
297
+ rescue FetchData::Error => e
298
+ puts "FetchData failed: #{e.cause.message}"
299
+ end
300
+
301
+ # Rescue all task failures with AggregateError
302
+ begin
303
+ ProcessData.run
304
+ rescue Taski::AggregateError => e
305
+ e.errors.each do |failure|
306
+ puts "#{failure.task_class}: #{failure.error.message}"
307
+ end
308
+ end
309
+ ```
310
+
311
+ `AggregateError` collects all failures from parallel execution. Task-specific `::Error` classes work transparently with `rescue` — even when errors are wrapped inside an `AggregateError`.
312
+
249
313
  ### Aborting Execution
250
314
 
251
315
  Stop all pending tasks when a critical error occurs:
@@ -282,17 +346,24 @@ end
282
346
  # Run then clean in one call
283
347
  DatabaseSetup.run_and_clean
284
348
 
285
- # Or separately
286
- DatabaseSetup.run
287
- # ... do work ...
288
- DatabaseSetup.clean
349
+ # Run, do something with exported values, then clean
350
+ DatabaseSetup.run_and_clean do
351
+ deploy(DatabaseSetup.connection)
352
+ end
289
353
  ```
290
354
 
291
- See [docs/guide.md](docs/guide.md#lifecycle-management) for details.
355
+ See [docs/GUIDE.md](docs/GUIDE.md#lifecycle-management) for details.
292
356
 
293
357
  ### Progress Display
294
358
 
295
- Tree-based progress visualization is enabled by default:
359
+ Simple progress display is enabled by default:
360
+
361
+ ```text
362
+ ⠹ [3/5] DeployTask | Uploading files...
363
+ ✓ [5/5] All tasks completed (1234ms)
364
+ ```
365
+
366
+ **Tree mode** provides full dependency tree visualization:
296
367
 
297
368
  ```
298
369
  WebServer (Task)
@@ -302,14 +373,7 @@ WebServer (Task)
302
373
  └── ◻ Server (Task)
303
374
  ```
304
375
 
305
- **Simple mode** provides a compact single-line display:
306
-
307
- ```
308
- ⠹ [3/5] DeployTask | Uploading files...
309
- ✓ [5/5] All tasks completed (1234ms)
310
- ```
311
-
312
- **Plain mode** provides text output without escape codes (for CI/logs):
376
+ **Log mode** provides text output without escape codes (for CI/logs):
313
377
 
314
378
  ```
315
379
  [START] DatabaseSetup
@@ -322,18 +386,12 @@ WebServer (Task)
322
386
  **Configuration:**
323
387
 
324
388
  ```ruby
325
- # Via API
326
- Taski.progress_mode = :tree # Tree display (default)
327
- Taski.progress_mode = :simple # Single-line display
328
- Taski.progress_mode = :plain # Plain text (CI/logs)
329
-
330
- # Via environment variable
331
- TASKI_PROGRESS_MODE=simple ruby your_script.rb
332
- TASKI_PROGRESS_MODE=plain ruby your_script.rb
389
+ Taski.progress_display = Taski::Progress::Layout::Simple.new # Simple display (default)
390
+ Taski.progress_display = Taski::Progress::Layout::Tree.new # Tree display
391
+ Taski.progress_display = Taski::Progress::Layout::Log.new # Log output (CI/logs)
392
+ Taski.progress_display = nil # Disable
333
393
  ```
334
394
 
335
- To disable: `TASKI_PROGRESS_DISABLE=1 ruby your_script.rb`
336
-
337
395
  ### Tree Visualization
338
396
 
339
397
  ```ruby
data/docs/GUIDE.md CHANGED
@@ -7,6 +7,7 @@ This guide provides detailed documentation beyond the basics covered in the READ
7
7
  - [Error Handling](#error-handling)
8
8
  - [Lifecycle Management](#lifecycle-management)
9
9
  - [Progress Display](#progress-display)
10
+ - [Lazy Dependency Resolution](#lazy-dependency-resolution)
10
11
  - [Debugging](#debugging)
11
12
 
12
13
  ---
@@ -87,7 +88,9 @@ rescue DatabaseTask::Error => e
87
88
  end
88
89
  ```
89
90
 
90
- This works transparently with `AggregateError` - when you rescue `DatabaseTask::Error`, it matches an `AggregateError` that contains a `DatabaseTask::Error`.
91
+ This works transparently with `AggregateError` when you rescue `DatabaseTask::Error`, it matches an `AggregateError` that contains a `DatabaseTask::Error`.
92
+
93
+ **How this works:** Ruby's `rescue` uses the `===` operator to match exceptions. Taski's `AggregateAware` module (extended by `TaskError` and all `TaskClass::Error` classes) overrides `===` to check whether an `AggregateError` *contains* an error of that type. This means `rescue Taski::TaskError` will match an `AggregateError` wrapping TaskError instances, even though `AggregateError` does not inherit from `TaskError`.
91
94
 
92
95
  ### TaskAbortException
93
96
 
@@ -140,7 +143,7 @@ end
140
143
 
141
144
  ## Lifecycle Management
142
145
 
143
- Taski supports resource cleanup with `run`, `clean`, and `run_and_clean` methods.
146
+ Taski supports resource cleanup with `run_and_clean`, which executes the run phase followed by the clean phase in a single operation.
144
147
 
145
148
  ### Basic Lifecycle
146
149
 
@@ -168,27 +171,24 @@ class WebServer < Taski::Task
168
171
  end
169
172
  end
170
173
 
171
- # Start
172
- WebServer.run
174
+ # Run then clean in one call
175
+ WebServer.run_and_clean
173
176
  # => Database connected
174
- # => Server started with postgresql://localhost:5432/myapp
175
-
176
- # Clean (reverse dependency order)
177
- WebServer.clean
177
+ # => Server started
178
178
  # => Server stopped
179
179
  # => Database disconnected
180
180
  ```
181
181
 
182
- ### run_and_clean
182
+ ### run_and_clean with Block
183
183
 
184
- Execute run followed by clean in a single operation:
184
+ Use a block to execute code between run and clean phases. This is useful when you need to use exported values before cleanup:
185
185
 
186
186
  ```ruby
187
- WebServer.run_and_clean
188
- # => Database connected
189
- # => Server started
190
- # => Server stopped
191
- # => Database disconnected
187
+ DatabaseSetup.run_and_clean do
188
+ # Exported values are accessible here
189
+ deploy(DatabaseSetup.connection)
190
+ end
191
+ # Clean runs automatically after the block
192
192
  ```
193
193
 
194
194
  ### Idempotent Clean Methods
@@ -289,21 +289,9 @@ After completion:
289
289
 
290
290
  ### Display Modes
291
291
 
292
- Taski supports two progress display modes:
292
+ Taski supports three progress display modes:
293
293
 
294
- #### Tree Mode (Default)
295
-
296
- Full dependency tree visualization with status for each task:
297
-
298
- ```
299
- WebServer (Task)
300
- ├── ⠋ Config (Task) | Reading config.yml...
301
- │ ├── ✅ Database (Task) 45.2ms
302
- │ └── ⠙ Cache (Task) | Connecting...
303
- └── ◻ Server (Task)
304
- ```
305
-
306
- #### Simple Mode
294
+ #### Simple Mode (Default)
307
295
 
308
296
  Compact single-line display showing current progress:
309
297
 
@@ -324,7 +312,19 @@ On failure:
324
312
  ✗ [3/5] DeployTask failed: Connection refused
325
313
  ```
326
314
 
327
- #### Plain Mode
315
+ #### Tree Mode
316
+
317
+ Full dependency tree visualization with status for each task:
318
+
319
+ ```
320
+ WebServer (Task)
321
+ ├── ⠋ Config (Task) | Reading config.yml...
322
+ │ ├── ✅ Database (Task) 45.2ms
323
+ │ └── ⠙ Cache (Task) | Connecting...
324
+ └── ◻ Server (Task)
325
+ ```
326
+
327
+ #### Log Mode
328
328
 
329
329
  Plain text output without escape codes, designed for CI/logs:
330
330
 
@@ -336,49 +336,73 @@ Plain text output without escape codes, designed for CI/logs:
336
336
  [TASKI] Completed: 2/2 tasks (165ms)
337
337
  ```
338
338
 
339
- ### Configuring Progress Mode
340
-
341
- **Via API:**
339
+ ### Configuring Progress Display
342
340
 
343
341
  ```ruby
344
- Taski.progress_mode = :tree # Tree display (default)
345
- Taski.progress_mode = :simple # Single-line display
346
- Taski.progress_mode = :plain # Plain text (CI/logs)
342
+ Taski.progress_display = Taski::Progress::Layout::Simple.new # Simple display (default)
343
+ Taski.progress_display = Taski::Progress::Layout::Tree.new # Tree display
344
+ Taski.progress_display = Taski::Progress::Layout::Log.new # Log output (CI/logs)
345
+ Taski.progress_display = nil # Disable
347
346
  ```
348
347
 
349
- **Via environment variable:**
348
+ ### File Output Mode
349
+
350
+ When output is redirected, interactive spinners are automatically disabled:
350
351
 
351
352
  ```bash
352
- TASKI_PROGRESS_MODE=tree ruby your_script.rb
353
- TASKI_PROGRESS_MODE=simple ruby your_script.rb
354
- TASKI_PROGRESS_MODE=plain ruby your_script.rb
353
+ ruby build.rb > build.log 2>&1
355
354
  ```
356
355
 
357
- ### Disabling Progress Display
356
+ ---
358
357
 
359
- ```bash
360
- TASKI_PROGRESS_DISABLE=1 ruby your_script.rb
361
- ```
358
+ ## Lazy Dependency Resolution
362
359
 
363
- ### File Output Mode
360
+ ### How It Works
364
361
 
365
- When output is redirected, interactive spinners are automatically disabled:
362
+ When a task accesses a dependency's exported value (e.g., `DepTask.value`), Taski may return a lightweight **proxy object** instead of the actual value. This proxy defers dependency resolution until you call a method on it, at which point it transparently resolves the real value and forwards the method call.
366
363
 
367
- ```bash
368
- ruby build.rb > build.log 2>&1
364
+ ```ruby
365
+ class FetchData < Taski::Task
366
+ exports :data
367
+ def run
368
+ @data = expensive_api_call
369
+ end
370
+ end
371
+
372
+ class ProcessData < Taski::Task
373
+ exports :result
374
+ def run
375
+ raw = FetchData.data # May return a proxy (no blocking yet)
376
+ setup_environment # Task continues while FetchData runs
377
+ @result = raw.transform # Proxy resolves here — blocks if needed
378
+ end
379
+ end
369
380
  ```
370
381
 
382
+ From the user's perspective, the proxy is completely transparent — it behaves exactly like the real value.
383
+
384
+ ### Why It Matters
385
+
386
+ Proxy-based resolution enables better parallelism. A task can continue executing setup logic while its dependencies are still running, only blocking when the dependency value is actually used. This can significantly reduce total execution time when tasks have independent setup work before they need their dependencies.
387
+
388
+ ### Automatic Safety
389
+
390
+ Taski uses static analysis (Prism AST parsing) to determine when proxy resolution is safe. Dependencies used in positions where the proxy could cause issues — such as conditions (`if dep_value`), method arguments, or other contexts where truthiness or identity matters — are automatically resolved synchronously instead of returning a proxy.
391
+
392
+ You do not need to think about this in normal usage. The static analyzer examines your task's `run` method and only enables proxy resolution for dependency accesses that are confirmed safe (e.g., simple assignments like `x = Dep.value` followed by method calls on `x`).
393
+
371
394
  ---
372
395
 
373
396
  ## Debugging
374
397
 
375
- ### Environment Variables
398
+ ### Structured Logging
399
+
400
+ ```ruby
401
+ require "logger"
402
+ Taski.logger = Logger.new($stdout, level: Logger::DEBUG)
403
+ ```
376
404
 
377
- | Variable | Purpose |
378
- |----------|---------|
379
- | `TASKI_PROGRESS_DISABLE=1` | Disable progress display |
380
- | `TASKI_PROGRESS_MODE=tree\|simple\|plain` | Set progress display mode (default: tree) |
381
- | `TASKI_DEBUG=1` | Enable debug output |
405
+ Set `Taski.logger` to a Ruby `Logger` instance to enable structured logging of execution events.
382
406
 
383
407
  ### Dependency Tree Visualization
384
408
 
@@ -405,4 +429,4 @@ end
405
429
 
406
430
  **Static Analysis Requirements**
407
431
 
408
- Tasks must be defined in source files (not dynamically with `Class.new`) because static analysis uses Prism AST parsing which requires actual source files.
432
+ Tasks must be defined in source files (not dynamically with `Class.new`) because static analysis uses Prism AST parsing which requires actual source files. Static analysis is used for dependency tree visualization, circular dependency detection, and optimizing dependency resolution (determining when lazy proxy resolution is safe vs. when synchronous resolution is required).
data/examples/README.md CHANGED
@@ -19,22 +19,7 @@ ruby examples/quick_start.rb
19
19
 
20
20
  ---
21
21
 
22
- ### 2. section_demo.rb - Runtime Implementation Selection
23
-
24
- Switch implementations based on environment using the Section API.
25
-
26
- ```bash
27
- ruby examples/section_demo.rb
28
- ```
29
-
30
- **Covers:**
31
- - `interfaces` for defining contracts
32
- - Environment-specific implementations
33
- - Dependency tree visualization with `.tree`
34
-
35
- ---
36
-
37
- ### 3. args_demo.rb - Runtime Args and Options
22
+ ### 2. args_demo.rb - Runtime Args and Options
38
23
 
39
24
  Access execution args and pass custom options to tasks.
40
25
 
@@ -52,22 +37,21 @@ ruby examples/args_demo.rb
52
37
 
53
38
  ---
54
39
 
55
- ### 4. reexecution_demo.rb - Scope-Based Execution
40
+ ### 3. reexecution_demo.rb - Scope-Based Execution
56
41
 
57
42
  Understand scope-based execution and caching behavior.
58
43
 
59
44
  ```bash
60
- TASKI_PROGRESS_DISABLE=1 ruby examples/reexecution_demo.rb
45
+ ruby examples/reexecution_demo.rb
61
46
  ```
62
47
 
63
48
  **Covers:**
64
49
  - Fresh execution for each class method call
65
- - Instance-level caching with `Task.new`
66
50
  - Scope-based dependency caching
67
51
 
68
52
  ---
69
53
 
70
- ### 5. clean_demo.rb - Lifecycle Management
54
+ ### 4. clean_demo.rb - Lifecycle Management
71
55
 
72
56
  Demonstrates resource cleanup with clean methods.
73
57
 
@@ -79,10 +63,11 @@ ruby examples/clean_demo.rb
79
63
  - Defining `clean` methods for resource cleanup
80
64
  - Reverse dependency order execution
81
65
  - `run_and_clean` combined operation
66
+ - `run_and_clean` with block support
82
67
 
83
68
  ---
84
69
 
85
- ### 6. group_demo.rb - Task Output Grouping
70
+ ### 5. group_demo.rb - Task Output Grouping
86
71
 
87
72
  Organize task output into logical phases with groups.
88
73
 
@@ -97,7 +82,7 @@ ruby examples/group_demo.rb
97
82
 
98
83
  ---
99
84
 
100
- ### 7. message_demo.rb - User-Facing Messages
85
+ ### 6. message_demo.rb - User-Facing Messages
101
86
 
102
87
  Output messages that bypass the progress display capture.
103
88
 
@@ -112,14 +97,12 @@ ruby examples/message_demo.rb
112
97
 
113
98
  ---
114
99
 
115
- ### 8. progress_demo.rb - Progress Display Modes
100
+ ### 7. progress_demo.rb - Progress Display Modes
116
101
 
117
102
  Real-time progress visualization during parallel execution.
118
103
 
119
104
  ```bash
120
- ruby examples/progress_demo.rb # Tree mode
121
- TASKI_PROGRESS_MODE=simple ruby examples/progress_demo.rb # Simple mode
122
- TASKI_PROGRESS_DISABLE=1 ruby examples/progress_demo.rb # Disabled
105
+ ruby examples/progress_demo.rb # Simple mode (default)
123
106
  ```
124
107
 
125
108
  **Covers:**
@@ -136,7 +119,6 @@ TASKI_PROGRESS_DISABLE=1 ruby examples/progress_demo.rb # Disabled
136
119
  | Example | Feature | Complexity |
137
120
  |---------|---------|------------|
138
121
  | quick_start | Exports API | Basic |
139
- | section_demo | Section API | Intermediate |
140
122
  | args_demo | Args/Env API | Intermediate |
141
123
  | reexecution_demo | Scope-Based Execution | Intermediate |
142
124
  | clean_demo | Lifecycle Management | Intermediate |
@@ -150,8 +132,7 @@ TASKI_PROGRESS_DISABLE=1 ruby examples/progress_demo.rb # Disabled
150
132
  # Run each example
151
133
  for f in examples/*.rb; do echo "=== $f ===" && ruby "$f" && echo; done
152
134
 
153
- # Disable progress display if needed
154
- TASKI_PROGRESS_DISABLE=1 ruby examples/progress_demo.rb
135
+ # Disable progress display if needed (add Taski.progress_display = nil in script)
155
136
  ```
156
137
 
157
138
  ## Next Steps