taski 0.5.0 → 0.7.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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -0
  3. data/README.md +168 -21
  4. data/docs/GUIDE.md +394 -0
  5. data/examples/README.md +65 -17
  6. data/examples/{context_demo.rb → args_demo.rb} +27 -27
  7. data/examples/clean_demo.rb +204 -0
  8. data/examples/data_pipeline_demo.rb +1 -1
  9. data/examples/group_demo.rb +113 -0
  10. data/examples/large_tree_demo.rb +519 -0
  11. data/examples/reexecution_demo.rb +93 -80
  12. data/examples/simple_progress_demo.rb +80 -0
  13. data/examples/system_call_demo.rb +56 -0
  14. data/lib/taski/{context.rb → args.rb} +3 -3
  15. data/lib/taski/execution/base_progress_display.rb +348 -0
  16. data/lib/taski/execution/execution_context.rb +383 -0
  17. data/lib/taski/execution/executor.rb +405 -134
  18. data/lib/taski/execution/plain_progress_display.rb +76 -0
  19. data/lib/taski/execution/registry.rb +17 -1
  20. data/lib/taski/execution/scheduler.rb +308 -0
  21. data/lib/taski/execution/simple_progress_display.rb +173 -0
  22. data/lib/taski/execution/task_output_pipe.rb +42 -0
  23. data/lib/taski/execution/task_output_router.rb +287 -0
  24. data/lib/taski/execution/task_wrapper.rb +215 -52
  25. data/lib/taski/execution/tree_progress_display.rb +349 -212
  26. data/lib/taski/execution/worker_pool.rb +104 -0
  27. data/lib/taski/section.rb +16 -3
  28. data/lib/taski/static_analysis/visitor.rb +3 -0
  29. data/lib/taski/task.rb +218 -37
  30. data/lib/taski/test_helper/errors.rb +13 -0
  31. data/lib/taski/test_helper/minitest.rb +38 -0
  32. data/lib/taski/test_helper/mock_registry.rb +51 -0
  33. data/lib/taski/test_helper/mock_wrapper.rb +46 -0
  34. data/lib/taski/test_helper/rspec.rb +38 -0
  35. data/lib/taski/test_helper.rb +214 -0
  36. data/lib/taski/version.rb +1 -1
  37. data/lib/taski.rb +211 -23
  38. data/sig/taski.rbs +207 -27
  39. metadata +25 -8
  40. data/docs/advanced-features.md +0 -625
  41. data/docs/api-guide.md +0 -509
  42. data/docs/error-handling.md +0 -684
  43. data/examples/section_progress_demo.rb +0 -78
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: afd6114f6d0814be687181ddd8e0acfa45af1d1064f14247e2864be53c3103f0
4
- data.tar.gz: 5b76552b037943f8b9555f01eb6b98d5a515be95b8f75e63f6f42314cf388294
3
+ metadata.gz: 0ca85800083318a5bc4d40151f14e5c55d6474a81a89a095de6d2433a8609043
4
+ data.tar.gz: d68b0a3f9bf5bab689882df7cdf22fe02a169008a3d4bd64a24eaea2699dce33
5
5
  SHA512:
6
- metadata.gz: 6c37d3650e3a1af652e469bad4627964fa62763ad7acc93e54b07bdaa577777dda65f0e34265d7974209ab3b3ab5f5ef5299d4675f6a8810fcdcd5dcbad19306
7
- data.tar.gz: f2d5295608ee67c5a2ecebdfb0416f73fe16569534a4f798252cf5bd101bec3c43507e1c0560be102cfb37c44fcdf603ff2a7cf9a6b0afdfb9b74c3624ff7553
6
+ metadata.gz: 3705a29cdf6e5fbe4a244b9431c35bc326e2c383f71e00fe7223a1e87f98728e565e1faebbd081111ab8127197e39294c89d05c6e35511b8405425f21b6a8af3
7
+ data.tar.gz: 87d334c0e053bbd164c260e304f642a26305f14021de17bf10bb8d50180fd0160338efbca0821a3fc3af996b8212e3a10c52d5e533c54b6141b069e17f21af7a
data/CHANGELOG.md ADDED
@@ -0,0 +1,50 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.7.0] - 2025-12-23
11
+
12
+ ### Added
13
+ - Group block for organizing progress display messages (`Taski.group`) ([#105](https://github.com/ahogappa/taski/pull/105))
14
+ - Scope-based execution with thread-local registry for independent task execution ([#103](https://github.com/ahogappa/taski/pull/103))
15
+ - `TaskClass::Error` auto-generation for task-specific error handling ([#95](https://github.com/ahogappa/taski/pull/95))
16
+ - `AggregateAware` module for transparent rescue matching with `AggregateError` ([#95](https://github.com/ahogappa/taski/pull/95))
17
+ - `AggregateError#includes?` and `AggregateError#find` methods for searching aggregated errors ([#95](https://github.com/ahogappa/taski/pull/95))
18
+ - Aggregation of multiple errors in parallel execution ([#95](https://github.com/ahogappa/taski/pull/95))
19
+ - `workers` parameter to `Task.run`, `Task.clean`, and `Task.run_and_clean` for configurable parallelism ([#92](https://github.com/ahogappa/taski/pull/92))
20
+
21
+ ### Changed
22
+ - Renamed `context` to `args` for API clarity (BREAKING CHANGE) ([#94](https://github.com/ahogappa/taski/pull/94))
23
+
24
+ ### Fixed
25
+ - Thread-safety improvements in `Registry#get_or_create` ([#90](https://github.com/ahogappa/taski/pull/90))
26
+
27
+ ## [0.6.0] - 2025-12-21
28
+
29
+ ### Added
30
+ - `Task#system` override for capturing subprocess output in progress display
31
+ - `ExecutionContext` with observer pattern for managing execution state
32
+ - Inline task output display in progress tree
33
+ - Clean execution with reverse dependency order via `run_and_clean`
34
+ - Comprehensive documentation for `TaskAbortException`
35
+ - Unit tests for `ExecutionContext`, `WorkerPool`, and `Scheduler`
36
+
37
+ ### Changed
38
+ - Replaced `ThreadOutputCapture` with pipe-based `TaskOutputRouter` for more reliable output capture
39
+ - Split `Executor` into separate `Scheduler` and `WorkerPool` classes for better separation of concerns
40
+ - Centralized tree building logic in `TreeProgressDisplay`
41
+ - Improved `run_and_clean` implementation and display
42
+
43
+ ### Fixed
44
+ - Added mutex protection to `impl_call_order` accessor for thread safety
45
+ - Fixed `ExecutionContext` passing to `TaskWrapper` resolving progress display garbage
46
+ - Improved output capture reliability for progress display
47
+
48
+ ## [0.5.0] - 2025-11-30
49
+
50
+ - Initial release with core task execution functionality
data/README.md CHANGED
@@ -113,9 +113,90 @@ end
113
113
 
114
114
  > **Note**: Nested implementation classes automatically inherit Section's `interfaces` as `exports`.
115
115
 
116
+ ## Best Practices
117
+
118
+ ### Keep Tasks Small and Focused
119
+
120
+ Each task should do **one thing only**. While Taski allows you to write complex logic within a single task, keeping tasks small and focused provides significant benefits:
121
+
122
+ ```ruby
123
+ # ✅ Good: Small, focused tasks
124
+ class FetchData < Taski::Task
125
+ exports :data
126
+ def run
127
+ @data = API.fetch
128
+ end
129
+ end
130
+
131
+ class TransformData < Taski::Task
132
+ exports :result
133
+ def run
134
+ @result = FetchData.data.transform
135
+ end
136
+ end
137
+
138
+ class SaveData < Taski::Task
139
+ def run
140
+ Database.save(TransformData.result)
141
+ end
142
+ end
143
+
144
+ # ❌ Avoid: Monolithic task doing everything
145
+ class DoEverything < Taski::Task
146
+ def run
147
+ data = API.fetch
148
+ result = data.transform
149
+ Database.save(result)
150
+ end
151
+ end
152
+ ```
153
+
154
+ **Why small tasks matter:**
155
+
156
+ - **Parallel Execution**: Independent tasks run concurrently. Large monolithic tasks can't be parallelized
157
+ - **Easier Cleanup**: `Task.clean` works per-task. Smaller tasks mean more granular cleanup control
158
+ - **Better Reusability**: Small tasks can be composed into different workflows
159
+ - **Clearer Dependencies**: The dependency graph becomes explicit and visible with `Task.tree`
160
+
161
+ **Note:** Complex internal logic is perfectly fine. "One thing" means one responsibility, not one line of code. Other tasks only care about the exported results, not how they were computed.
162
+
163
+ ```ruby
164
+ class RawData < Taski::Task
165
+ exports :data
166
+ def run
167
+ @data = API.fetch
168
+ end
169
+ end
170
+
171
+ class ProcessedData < Taski::Task
172
+ exports :result
173
+
174
+ def run
175
+ # Complex internal logic is OK - this task has one responsibility:
176
+ # producing the processed result
177
+ validated = validate_and_clean(RawData.data)
178
+ enriched = enrich_with_metadata(validated)
179
+ normalized = normalize_format(enriched)
180
+ @result = apply_business_rules(normalized)
181
+ end
182
+
183
+ private
184
+
185
+ def validate_and_clean(data)
186
+ # Complex validation logic...
187
+ end
188
+
189
+ def enrich_with_metadata(data)
190
+ # Complex enrichment logic...
191
+ end
192
+
193
+ # ... other private methods
194
+ end
195
+ ```
196
+
116
197
  ## Advanced Usage
117
198
 
118
- ### Context - Runtime Information and Options
199
+ ### Args - Runtime Information and Options
119
200
 
120
201
  Pass custom options and access execution context from any task:
121
202
 
@@ -123,43 +204,62 @@ Pass custom options and access execution context from any task:
123
204
  class DeployTask < Taski::Task
124
205
  def run
125
206
  # User-defined options
126
- env = Taski.context[:env]
127
- debug = Taski.context.fetch(:debug, false)
207
+ env = Taski.args[:env]
208
+ debug = Taski.args.fetch(:debug, false)
128
209
 
129
210
  # Runtime information
130
- puts "Working directory: #{Taski.context.working_directory}"
131
- puts "Started at: #{Taski.context.started_at}"
132
- puts "Root task: #{Taski.context.root_task}"
211
+ puts "Working directory: #{Taski.args.working_directory}"
212
+ puts "Started at: #{Taski.args.started_at}"
213
+ puts "Root task: #{Taski.args.root_task}"
133
214
  puts "Deploying to: #{env}"
134
215
  end
135
216
  end
136
217
 
137
218
  # Pass options when running
138
- DeployTask.run(context: { env: "production", debug: true })
219
+ DeployTask.run(args: { env: "production", debug: true })
139
220
  ```
140
221
 
141
- Context API:
142
- - `Taski.context[:key]` - Get option value (nil if not set)
143
- - `Taski.context.fetch(:key, default)` - Get with default value
144
- - `Taski.context.key?(:key)` - Check if option exists
145
- - `Taski.context.working_directory` - Execution directory
146
- - `Taski.context.started_at` - Execution start time
147
- - `Taski.context.root_task` - First task class called
222
+ Args API:
223
+ - `Taski.args[:key]` - Get option value (nil if not set)
224
+ - `Taski.args.fetch(:key, default)` - Get with default value
225
+ - `Taski.args.key?(:key)` - Check if option exists
226
+ - `Taski.args.working_directory` - Execution directory
227
+ - `Taski.args.started_at` - Execution start time
228
+ - `Taski.args.root_task` - First task class called
148
229
 
149
- ### Re-execution
230
+ ### Execution Model
150
231
 
151
232
  ```ruby
152
- # Cached execution (default)
233
+ # Each class method call creates fresh execution
153
234
  RandomTask.value # => 42
154
- RandomTask.value # => 42 (cached)
235
+ RandomTask.value # => 99 (different value - fresh execution)
155
236
 
156
- # Fresh execution
157
- RandomTask.new.run # => 123 (new instance)
237
+ # Instance-level caching
238
+ instance = RandomTask.new
239
+ instance.run # => 42
240
+ instance.run # => 42 (cached within instance)
241
+ instance.value # => 42
158
242
 
159
- # Reset all caches
160
- RandomTask.reset!
243
+ # Dependencies within same execution share results
244
+ DoubleConsumer.run # RandomTask runs once, both accesses get same value
245
+ ```
246
+
247
+ ### Aborting Execution
248
+
249
+ Stop all pending tasks when a critical error occurs:
250
+
251
+ ```ruby
252
+ class CriticalTask < Taski::Task
253
+ def run
254
+ if fatal_error?
255
+ raise Taski::TaskAbortException, "Cannot continue"
256
+ end
257
+ end
258
+ end
161
259
  ```
162
260
 
261
+ When `TaskAbortException` is raised, no new tasks will start. Already running tasks will complete, then execution stops.
262
+
163
263
  ### Progress Display
164
264
 
165
265
  Tree-based progress visualization is enabled by default:
@@ -172,6 +272,23 @@ WebServer (Task)
172
272
  └── ◻ Server (Task)
173
273
  ```
174
274
 
275
+ **Simple mode** provides a compact single-line display:
276
+
277
+ ```
278
+ ⠹ [3/5] DeployTask | Uploading files...
279
+ ✓ [5/5] All tasks completed (1234ms)
280
+ ```
281
+
282
+ **Configuration:**
283
+
284
+ ```ruby
285
+ # Via API
286
+ Taski.progress_mode = :simple # or :tree (default)
287
+
288
+ # Via environment variable
289
+ TASKI_PROGRESS_MODE=simple ruby your_script.rb
290
+ ```
291
+
175
292
  To disable: `TASKI_PROGRESS_DISABLE=1 ruby your_script.rb`
176
293
 
177
294
  ### Tree Visualization
@@ -184,6 +301,36 @@ puts WebServer.tree
184
301
  # └── Cache (Task)
185
302
  ```
186
303
 
304
+ ## Testing
305
+
306
+ ### Test Helper for Mocking Dependencies
307
+
308
+ Taski provides a test helper to mock task dependencies in your unit tests:
309
+
310
+ ```ruby
311
+ require 'taski/test_helper/minitest'
312
+
313
+ class BuildReportTest < Minitest::Test
314
+ include Taski::TestHelper::Minitest
315
+
316
+ def test_builds_report
317
+ # Mock direct dependencies - their run methods won't execute
318
+ mock_task(FetchData, users: [1, 2, 3])
319
+
320
+ # Task under test uses mocked values
321
+ assert_equal 3, BuildReport.user_count
322
+ end
323
+ end
324
+ ```
325
+
326
+ **Key features:**
327
+ - Mock only direct dependencies; indirect dependencies are automatically isolated
328
+ - Verify which dependencies were accessed with `assert_task_accessed` / `refute_task_accessed`
329
+ - Automatic cleanup after each test
330
+ - Supports both Minitest and RSpec
331
+
332
+ For RSpec, use `include Taski::TestHelper::RSpec` instead.
333
+
187
334
  ## Development
188
335
 
189
336
  ```bash
data/docs/GUIDE.md ADDED
@@ -0,0 +1,394 @@
1
+ # Taski Guide
2
+
3
+ This guide provides detailed documentation beyond the basics covered in the README.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Error Handling](#error-handling)
8
+ - [Lifecycle Management](#lifecycle-management)
9
+ - [Progress Display](#progress-display)
10
+ - [Debugging](#debugging)
11
+
12
+ ---
13
+
14
+ ## Error Handling
15
+
16
+ Taski provides comprehensive error handling for parallel task execution.
17
+
18
+ ### Error Types
19
+
20
+ | Exception | Purpose |
21
+ |-----------|---------|
22
+ | `Taski::AggregateError` | Multiple tasks failed during parallel execution |
23
+ | `Taski::TaskError` | Base class for task-specific errors |
24
+ | `Taski::TaskAbortException` | Intentional abort (stops all tasks immediately) |
25
+ | `Taski::CircularDependencyError` | Circular dependency detected |
26
+ | `TaskClass::Error` | Auto-generated error class for each Task subclass |
27
+
28
+ ### AggregateError
29
+
30
+ When multiple tasks fail during parallel execution, errors are collected into an `AggregateError`:
31
+
32
+ ```ruby
33
+ class DatabaseTask < Taski::Task
34
+ exports :connection
35
+ def run
36
+ raise "Database connection failed"
37
+ end
38
+ end
39
+
40
+ class CacheTask < Taski::Task
41
+ exports :redis_client
42
+ def run
43
+ raise "Cache connection failed"
44
+ end
45
+ end
46
+
47
+ class AppTask < Taski::Task
48
+ def run
49
+ db = DatabaseTask.connection
50
+ cache = CacheTask.redis_client
51
+ end
52
+ end
53
+
54
+ begin
55
+ AppTask.run
56
+ rescue Taski::AggregateError => e
57
+ puts "#{e.errors.size} tasks failed:"
58
+ e.errors.each do |failure|
59
+ puts " - #{failure.task_class.name}: #{failure.error.message}"
60
+ end
61
+ end
62
+ # Output:
63
+ # 2 tasks failed:
64
+ # - DatabaseTask: Database connection failed
65
+ # - CacheTask: Cache connection failed
66
+ ```
67
+
68
+ ### Task-Specific Error Classes
69
+
70
+ Each Task subclass automatically gets an `::Error` class for targeted rescue:
71
+
72
+ ```ruby
73
+ class DatabaseTask < Taski::Task
74
+ exports :connection
75
+ def run
76
+ raise "Connection failed"
77
+ end
78
+ end
79
+
80
+ # Rescue errors from a specific task
81
+ begin
82
+ AppTask.run
83
+ rescue DatabaseTask::Error => e
84
+ puts "Database task failed: #{e.message}"
85
+ # e.task_class returns DatabaseTask
86
+ # e.cause returns the original error
87
+ end
88
+ ```
89
+
90
+ This works transparently with `AggregateError` - when you rescue `DatabaseTask::Error`, it matches an `AggregateError` that contains a `DatabaseTask::Error`.
91
+
92
+ ### TaskAbortException
93
+
94
+ Use `TaskAbortException` to immediately stop all task execution:
95
+
96
+ ```ruby
97
+ class CriticalTask < Taski::Task
98
+ def run
99
+ if critical_condition_met?
100
+ raise Taski::TaskAbortException, "Critical error - aborting"
101
+ end
102
+ end
103
+ end
104
+ ```
105
+
106
+ `TaskAbortException` takes priority over regular errors. Already running tasks will complete, but no new tasks will start.
107
+
108
+ ### Error Handling Best Practices
109
+
110
+ ```ruby
111
+ # 1. Handle errors within the task when recovery is possible
112
+ class ResilientTask < Taski::Task
113
+ exports :data
114
+ def run
115
+ @data = fetch_from_primary
116
+ rescue Timeout::Error
117
+ @data = fetch_from_fallback
118
+ end
119
+ end
120
+
121
+ # 2. Use task-specific errors for clarity
122
+ begin
123
+ AppTask.run
124
+ rescue DatabaseTask::Error => e
125
+ handle_database_failure(e)
126
+ rescue CacheTask::Error => e
127
+ handle_cache_failure(e)
128
+ end
129
+
130
+ # 3. Fail fast with clear messages
131
+ class ValidatingTask < Taski::Task
132
+ def run
133
+ missing = %w[DATABASE_URL API_KEY].select { |v| ENV[v].nil? }
134
+ raise "Missing: #{missing.join(', ')}" if missing.any?
135
+ end
136
+ end
137
+ ```
138
+
139
+ ---
140
+
141
+ ## Lifecycle Management
142
+
143
+ Taski supports resource cleanup with `run`, `clean`, and `run_and_clean` methods.
144
+
145
+ ### Basic Lifecycle
146
+
147
+ ```ruby
148
+ class DatabaseSetup < Taski::Task
149
+ exports :connection
150
+
151
+ def run
152
+ @connection = "postgresql://localhost:5432/myapp"
153
+ puts "Database connected"
154
+ end
155
+
156
+ def clean
157
+ puts "Database disconnected"
158
+ end
159
+ end
160
+
161
+ class WebServer < Taski::Task
162
+ def run
163
+ puts "Server started with #{DatabaseSetup.connection}"
164
+ end
165
+
166
+ def clean
167
+ puts "Server stopped"
168
+ end
169
+ end
170
+
171
+ # Start
172
+ WebServer.run
173
+ # => Database connected
174
+ # => Server started with postgresql://localhost:5432/myapp
175
+
176
+ # Clean (reverse dependency order)
177
+ WebServer.clean
178
+ # => Server stopped
179
+ # => Database disconnected
180
+ ```
181
+
182
+ ### run_and_clean
183
+
184
+ Execute run followed by clean in a single operation:
185
+
186
+ ```ruby
187
+ WebServer.run_and_clean
188
+ # => Database connected
189
+ # => Server started
190
+ # => Server stopped
191
+ # => Database disconnected
192
+ ```
193
+
194
+ ### Idempotent Clean Methods
195
+
196
+ Clean methods should be safe to call multiple times:
197
+
198
+ ```ruby
199
+ class SafeFileTask < Taski::Task
200
+ exports :data_file
201
+
202
+ def run
203
+ @data_file = '/tmp/data.txt'
204
+ File.write(@data_file, 'data')
205
+ end
206
+
207
+ def clean
208
+ # Check before delete
209
+ if @data_file && File.exist?(@data_file)
210
+ File.delete(@data_file)
211
+ end
212
+ end
213
+ end
214
+ ```
215
+
216
+ ---
217
+
218
+ ## Progress Display
219
+
220
+ Taski provides real-time progress visualization during task execution.
221
+
222
+ ### Features
223
+
224
+ - **Spinner Animation**: Animated spinner during execution
225
+ - **Output Capture**: Real-time display of task output (last line)
226
+ - **Status Indicators**: Success/failure icons with execution time
227
+ - **Group Blocks**: Organize output messages into logical phases
228
+ - **TTY Detection**: Clean output when redirected to files
229
+
230
+ ### Group Blocks
231
+
232
+ Use `group` blocks to organize output within a task into logical phases. The current group name is displayed alongside the task's output in the progress display.
233
+
234
+ ```ruby
235
+ class DeployTask < Taski::Task
236
+ def run
237
+ group("Preparing environment") do
238
+ puts "Checking dependencies..."
239
+ puts "Validating config..."
240
+ end
241
+
242
+ group("Building application") do
243
+ puts "Compiling source..."
244
+ puts "Running tests..."
245
+ end
246
+
247
+ group("Deploying") do
248
+ puts "Uploading files..."
249
+ puts "Restarting server..."
250
+ end
251
+ end
252
+ end
253
+ ```
254
+
255
+ Progress display output:
256
+
257
+ ```text
258
+ During execution:
259
+ ⠋ DeployTask (Task) | Deploying: Uploading files...
260
+
261
+ After completion:
262
+ ✓ DeployTask (Task) 520ms
263
+ ```
264
+
265
+ The group name appears as a prefix to the output message: `| GroupName: output...`
266
+
267
+ Groups are useful for:
268
+ - **Logical organization**: Group related operations together
269
+ - **Progress visibility**: See which phase is currently executing
270
+ - **Error context**: Know which phase failed when errors occur
271
+
272
+ ### Example Output
273
+
274
+ ```
275
+ During execution:
276
+ WebServer (Task)
277
+ ├── Config (Task) ...
278
+ │ ├── Database (Task) 45.2ms
279
+ │ └── Cache (Task) ...
280
+ └── Server (Task)
281
+
282
+ After completion:
283
+ WebServer (Task) 120.5ms
284
+ ├── Config (Task) 50.3ms
285
+ │ ├── Database (Task) 45.2ms
286
+ │ └── Cache (Task) 48.1ms
287
+ └── Server (Task) 70.2ms
288
+ ```
289
+
290
+ ### Display Modes
291
+
292
+ Taski supports two progress display modes:
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
307
+
308
+ Compact single-line display showing current progress:
309
+
310
+ ```
311
+ ⠹ [3/5] DeployTask | Uploading files...
312
+ ✓ [5/5] All tasks completed (1234ms)
313
+ ```
314
+
315
+ Format: `[spinner] [completed/total] TaskName | last output...`
316
+
317
+ When multiple tasks run in parallel:
318
+ ```
319
+ ⠹ [2/5] DownloadLayer1, DownloadLayer2 | Downloading...
320
+ ```
321
+
322
+ On failure:
323
+ ```
324
+ ✗ [3/5] DeployTask failed: Connection refused
325
+ ```
326
+
327
+ ### Configuring Progress Mode
328
+
329
+ **Via API:**
330
+
331
+ ```ruby
332
+ Taski.progress_mode = :simple # Use simple mode
333
+ Taski.progress_mode = :tree # Use tree mode (default)
334
+ ```
335
+
336
+ **Via environment variable:**
337
+
338
+ ```bash
339
+ TASKI_PROGRESS_MODE=simple ruby your_script.rb
340
+ TASKI_PROGRESS_MODE=tree ruby your_script.rb
341
+ ```
342
+
343
+ ### Disabling Progress Display
344
+
345
+ ```bash
346
+ TASKI_PROGRESS_DISABLE=1 ruby your_script.rb
347
+ ```
348
+
349
+ ### File Output Mode
350
+
351
+ When output is redirected, interactive spinners are automatically disabled:
352
+
353
+ ```bash
354
+ ruby build.rb > build.log 2>&1
355
+ ```
356
+
357
+ ---
358
+
359
+ ## Debugging
360
+
361
+ ### Environment Variables
362
+
363
+ | Variable | Purpose |
364
+ |----------|---------|
365
+ | `TASKI_PROGRESS_DISABLE=1` | Disable progress display |
366
+ | `TASKI_PROGRESS_MODE=simple\|tree` | Set progress display mode (default: tree) |
367
+ | `TASKI_DEBUG=1` | Enable debug output |
368
+
369
+ ### Dependency Tree Visualization
370
+
371
+ ```ruby
372
+ puts MyTask.tree
373
+ # MyTask (Task)
374
+ # ├── DatabaseTask (Task)
375
+ # └── CacheTask (Task)
376
+ # └── ConfigTask (Task)
377
+ ```
378
+
379
+ ### Common Issues
380
+
381
+ **Circular Dependencies**
382
+
383
+ ```ruby
384
+ # Detected before execution
385
+ begin
386
+ TaskA.run
387
+ rescue Taski::CircularDependencyError => e
388
+ puts e.cyclic_tasks # [[TaskA, TaskB]]
389
+ end
390
+ ```
391
+
392
+ **Static Analysis Requirements**
393
+
394
+ Tasks must be defined in source files (not dynamically with `Class.new`) because static analysis uses Prism AST parsing which requires actual source files.