asgard 0.5.1 → 0.6.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 +4 -4
- data/CHANGELOG.md +179 -99
- data/README.md +141 -46
- data/bin/asgard +10 -0
- data/lib/asgard/base/dispatch.rb +257 -31
- data/lib/asgard/base/registry.rb +7 -3
- data/lib/asgard/base.rb +140 -9
- data/lib/asgard/declarations.rb +21 -0
- data/lib/asgard/doctor/task_sections.rb +11 -3
- data/lib/asgard/doctor.rb +12 -8
- data/lib/asgard/jobs/commands.rb +152 -0
- data/lib/asgard/jobs/declaration.rb +149 -0
- data/lib/asgard/jobs/launcher.rb +109 -0
- data/lib/asgard/jobs/state.rb +190 -0
- data/lib/asgard/jobs/stopper.rb +141 -0
- data/lib/asgard/jobs.rb +25 -0
- data/lib/asgard/kernel_methods.rb +19 -14
- data/lib/asgard/leading_flag.rb +10 -0
- data/lib/asgard/pid.rb +130 -0
- data/lib/asgard/schedule/commands.rb +39 -11
- data/lib/asgard/schedule/declaration.rb +139 -12
- data/lib/asgard/schedule/job_files.rb +24 -0
- data/lib/asgard/schedule/launchd.rb +55 -18
- data/lib/asgard/schedule/systemd.rb +69 -27
- data/lib/asgard/schedule/table.rb +34 -16
- data/lib/asgard/schedule.rb +49 -9
- data/lib/asgard/shell.rb +372 -44
- data/lib/asgard/state_dir.rb +84 -0
- data/lib/asgard/tasks.rb +8 -0
- data/lib/asgard/version.rb +1 -1
- data/lib/asgard/watch/changes.rb +26 -1
- data/lib/asgard/watch/commands.rb +52 -12
- data/lib/asgard/watch/daemon.rb +334 -43
- data/lib/asgard/watch/declaration.rb +135 -40
- data/lib/asgard/watch/dispatcher.rb +61 -10
- data/lib/asgard/watch/mailbox.rb +50 -0
- data/lib/asgard/watch/reporter.rb +50 -34
- data/lib/asgard/watch/spawn.rb +41 -16
- data/lib/asgard/watch/state.rb +93 -27
- data/lib/asgard/watch/worker.rb +44 -9
- data/lib/asgard/watch.rb +4 -11
- data/lib/asgard.rb +80 -14
- metadata +167 -186
- data/.envrc +0 -6
- data/.github/workflows/deploy-github-pages.yml +0 -52
- data/.loki +0 -39
- data/.reek.yml +0 -166
- data/.rubocop.yml +0 -163
- data/Archspec.rb +0 -24
- data/CLAUDE.md +0 -139
- data/COMMITS.md +0 -196
- data/bin/console +0 -11
- data/bin/setup +0 -8
- data/doc_tasks.loki +0 -21
- data/docs/api.md +0 -368
- data/docs/assets/css/custom.css +0 -93
- data/docs/assets/images/asgard.jpg +0 -0
- data/docs/changelog.md +0 -1
- data/docs/dependencies.md +0 -313
- data/docs/environment.md +0 -129
- data/docs/examples.md +0 -194
- data/docs/getting-started.md +0 -181
- data/docs/helpers.md +0 -262
- data/docs/index.md +0 -87
- data/docs/options.md +0 -224
- data/docs/schedule.md +0 -106
- data/docs/shell.md +0 -237
- data/docs/subcommands.md +0 -181
- data/docs/task-files.md +0 -407
- data/docs/tasks.md +0 -326
- data/docs/variables.md +0 -338
- data/docs/watch.md +0 -123
- data/examples/.env +0 -4
- data/examples/.envrc +0 -3
- data/examples/.loki +0 -24
- data/examples/bad.loki +0 -63
- data/examples/concurrent.loki +0 -58
- data/examples/confirm.loki +0 -48
- data/examples/db_subcommands.loki +0 -74
- data/examples/depends_on_block/bad/.loki +0 -51
- data/examples/depends_on_block/good/.loki +0 -83
- data/examples/do.loki +0 -45
- data/examples/env_usage.loki +0 -27
- data/examples/kitchen_sink.loki +0 -210
- data/examples/server_subcommands.loki +0 -65
- data/examples/subdir/.loki +0 -12
- data/examples/subdir/import_demo.loki +0 -14
- data/examples/subdir/import_up_demo.loki +0 -18
- data/examples/watcher_examples/.envrc +0 -3
- data/examples/watcher_examples/.loki +0 -54
- data/examples/watcher_examples/README.md +0 -112
- data/examples/watcher_examples/bin/hello +0 -3
- data/examples/watcher_examples/data/records.json +0 -1
- data/examples/watcher_examples/docs/drafts/ideas.md +0 -5
- data/examples/watcher_examples/docs/index.md +0 -4
- data/examples/watcher_examples/lib/greeter.rb +0 -7
- data/gem_tasks.loki +0 -51
- data/git.loki +0 -13
- data/mkdocs.yml +0 -166
- data/quality.loki +0 -266
- data/quality_rails.loki +0 -47
- data/xyzzy.loki +0 -12
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0ce55713997e41bcc3531ce3b34765e872184ab435aaa310eecc45625240c840
|
|
4
|
+
data.tar.gz: 0913ae51128056486619cf7655479dd28c6298300699ee307e6ec88a0c5e6d6d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 416c0a47d231bc39c1b14351d48edaa75a00538f3ee5a9f6e0b2aeb78ca63badc72e77f1d6010f4e50e7235d22f436e93ae5b6c7a9719851ca82bfe43a8338bb
|
|
7
|
+
data.tar.gz: 7426f8fdd9fc2f56655f2584685b91634890cc8ccb84ef0c0a01ec25d8e7085770f2e0ce5fbedfe6da89ac01c552b938b5dc87fa0efc9ec5242acbac1f963348
|
data/CHANGELOG.md
CHANGED
|
@@ -2,9 +2,142 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.6.0] - 2026-10-31
|
|
9
|
+
|
|
10
|
+
Upgrading from 0.5.x? Read
|
|
11
|
+
[Breaking Changes](https://madbomber.github.io/asgard/breaking-changes/)
|
|
12
|
+
first: schedules and watchers must be removed before you upgrade and
|
|
13
|
+
reinstalled after.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **Background jobs.** `sh "mkdocs serve", background: true` starts a job
|
|
18
|
+
that outlives asgard and logs its output; `asgard jobs list`, `stop`,
|
|
19
|
+
`log`, `restart` and `clean` manage them.
|
|
20
|
+
- **Shells registry.** `shell` registers an interpreter
|
|
21
|
+
(`shell :strict, "bash -eu -o pipefail"`), `default_shell` picks the one
|
|
22
|
+
`sh` uses, and `shebang :name, script` runs any of them. Each executable
|
|
23
|
+
is checked as the `.loki` loads.
|
|
24
|
+
- **`asgard schedule remove [NAME]` and `asgard schedule prune
|
|
25
|
+
[--dry-run]`.**
|
|
26
|
+
- **Watcher**: triggers that arrive while a task is busy merge into one
|
|
27
|
+
run, and `watcher.log` rotates at 10 MB (`ASGARD_LOG_MAX`).
|
|
28
|
+
`asgard watcher stop --force` ends running tasks instead of waiting for
|
|
29
|
+
them, as a second Ctrl-C does in `watcher run`. Globs accept `[ab]`,
|
|
30
|
+
`[!ab]` and nested braces.
|
|
31
|
+
- **`asgard help jobs`, `help schedule` and `help watcher`** list their
|
|
32
|
+
subcommands.
|
|
33
|
+
- **CI** on Linux and macOS, Ruby 4.0.9 and 4.1.0-preview1.
|
|
34
|
+
- **Runtime dependencies are bounded** to the versions asgard is built
|
|
35
|
+
against: `thor ~> 1.5`, `listen ~> 3.10`, `ractor_queue ~> 0.2`,
|
|
36
|
+
`dotenv ~> 3.2`, `tty-table ~> 0.12` and `tsort ~> 0.2`.
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- **BREAKING: Ruby 4.0 or later.** On Ruby 3.4 the file watcher hangs at
|
|
41
|
+
shutdown.
|
|
42
|
+
- **BREAKING: project slugs carry a hash of the path** (`api-9f3a1c2b`),
|
|
43
|
+
so two projects in directories of the same name no longer share
|
|
44
|
+
schedules or a watcher. Nothing migrates: run `asgard schedule remove`
|
|
45
|
+
and `asgard watcher stop` in each project before upgrading, then
|
|
46
|
+
reinstall and restart. `list --all` marks a project whose directory is
|
|
47
|
+
gone with `[root missing]`.
|
|
48
|
+
- **`sh` runs every script under the default shell.** A script given no
|
|
49
|
+
shell name, one line or many, runs under the shell set with
|
|
50
|
+
`default_shell`, or `bash` when none is set. A one-liner used to go
|
|
51
|
+
through `/bin/sh` while a heredoc ran under bash. Fixes #1.
|
|
52
|
+
- **`sh` is `shebang`**: one method under two names, so
|
|
53
|
+
`sh :python3, "..."` works.
|
|
54
|
+
- **A public method without a `desc` is a hidden task.** It runs from the
|
|
55
|
+
command line and as a dependency, and `help` leaves it out. Make helper
|
|
56
|
+
methods `private`, put them in `no_commands`, or use `helper`.
|
|
57
|
+
- **Parallel dependencies each run on their own instance**, so they no
|
|
58
|
+
longer race on instance variables; pass values with `dep_result`.
|
|
59
|
+
- **A `.loki` that fails to load says so in one line**, with file and
|
|
60
|
+
line; set `ASGARD_TRACE=1` for the backtrace.
|
|
61
|
+
- **`--version` and `--doctor` are asgard's only before the task name**,
|
|
62
|
+
and `--debug` and `--verbose` show one spelling in help.
|
|
63
|
+
- **State is private**: state directories are created 0700 and schedule
|
|
64
|
+
job files 0600, since logs, scripts and `env:` values can hold secrets.
|
|
65
|
+
- **asgard's helpers are reserved names.** A task or helper named `sh`,
|
|
66
|
+
`shebang`, `env`, `import`, `import_up`, `loki_up`, `changes`,
|
|
67
|
+
`dep_result`, `dep_results` or `current_task` is refused at load with
|
|
68
|
+
file and line; it used to replace the helper silently.
|
|
69
|
+
- **Schedule entry names from `options:` can carry a hash.** When an
|
|
70
|
+
option is not plain lowercase words, or the name passes 64 characters,
|
|
71
|
+
an 8-character hash is added, so `--period=week` and `--period week` no
|
|
72
|
+
longer collide. Two different commands whose names still slug alike
|
|
73
|
+
(`--period week` and `period week`) both take a hashed name.
|
|
74
|
+
`asgard schedule install` moves such entries to their
|
|
75
|
+
new names. `every:` together with `on:` is now an error instead of
|
|
76
|
+
ignoring `on:`.
|
|
77
|
+
- **Watcher ignores and patterns.** `.git`, `.hg`, `.svn` and
|
|
78
|
+
`node_modules` are ignored at any depth; `coverage`, `pkg`, `tmp`,
|
|
79
|
+
`log`, `.bundle`, `bundle`, `vendor/bundle` and `vendor/ruby` only at
|
|
80
|
+
the project root, so `app/log/` is watched. A path pattern applies only
|
|
81
|
+
to its own directory, and a bad `env:` is an error when the `.loki`
|
|
82
|
+
loads.
|
|
83
|
+
|
|
84
|
+
### Removed
|
|
85
|
+
|
|
86
|
+
- **BREAKING: `exec:` removed from `sh` and `shebang`.** Passing it raises
|
|
87
|
+
`ArgumentError (unknown keyword: :exec)`; delete the keyword.
|
|
88
|
+
|
|
89
|
+
### Fixed
|
|
90
|
+
|
|
91
|
+
- **Dependencies**: a failing parallel dependency waits for its siblings
|
|
92
|
+
and every failure is named; a task waiting on a shared dependency that
|
|
93
|
+
failed no longer runs; a raising dependency no longer prints Ruby's
|
|
94
|
+
thread report. A dependency's own option defaults apply when it runs
|
|
95
|
+
for another task; a task run inside a caller's `rescue` is no longer
|
|
96
|
+
recorded as failed; a runtime `invoke` cycle is reported as a circular
|
|
97
|
+
dependency instead of crashing; methods every object has (`to_s`,
|
|
98
|
+
`inspect`) are not tasks.
|
|
99
|
+
- **`sh`**: a command ended by a signal exits 128 plus the signal number
|
|
100
|
+
instead of crashing; a missing interpreter is named and exits 127. INT
|
|
101
|
+
or TERM sent to asgard alone (`timeout`, a cancelled CI job) reaches the
|
|
102
|
+
running script and everything it started, instead of leaving them
|
|
103
|
+
running.
|
|
104
|
+
- **Loading**: `import_up` from an imported file no longer recurses until
|
|
105
|
+
the stack overflows, and neither do two files that import each other;
|
|
106
|
+
`import` works in a directory whose name contains `[`, `{`, `?` or `*`;
|
|
107
|
+
task and helper names Thor relies on (`args`, `shell`, `options`, ...)
|
|
108
|
+
are rejected with file and line; a `.loki` directory is not a task file.
|
|
109
|
+
- **Subcommand groups** list only their own tasks in help, and
|
|
110
|
+
`asgard tree` shows the name the group was registered under.
|
|
111
|
+
- **`--doctor`** credits a subcommand group to the `.loki` line that
|
|
112
|
+
registered it instead of a file inside Thor, and lists only `.loki`
|
|
113
|
+
files as imports, not every library a task file requires.
|
|
114
|
+
- **Watcher**: it reloads on a `.loki` change instead of silently watching
|
|
115
|
+
nothing; a pidfile is trusted only while its pid is still the watcher; a
|
|
116
|
+
failed spawn no longer kills a worker; an idle daemon no longer spins;
|
|
117
|
+
two starts at once cannot leave two daemons. Tasks no longer inherit the
|
|
118
|
+
watcher's lock, so a task that leaves a process behind cannot block the
|
|
119
|
+
next `start`; a `.loki` edit that drops a watched task keeps the running
|
|
120
|
+
watcher instead of killing it; changes saved during a reload are not
|
|
121
|
+
lost; the watcher's own log lines survive rotation and `log -f` follows
|
|
122
|
+
across it; ignored directories are not scanned; plus smaller path,
|
|
123
|
+
glob, environment and shutdown fixes.
|
|
124
|
+
- **Background jobs**: concurrent starts of one name start one job;
|
|
125
|
+
`jobs stop` can never signal asgard's own process group; a reused pid
|
|
126
|
+
shows as `gone`. A malformed record is skipped instead of crashing every
|
|
127
|
+
listing; processes left in a job's group after its script exits are
|
|
128
|
+
shown and stopped; a job killed by `stop` shows `exited 137`; `clean`
|
|
129
|
+
cannot remove a job that a concurrent start just recorded; a `ps` that
|
|
130
|
+
cannot list processes (BusyBox) is a clear error.
|
|
131
|
+
- **Schedules**: a `$` in a systemd `env:` value stays one `$`; empty
|
|
132
|
+
`on:`/`at:`, control characters and invalid env names are rejected; an
|
|
133
|
+
unchanged install leaves a running job alone; `status` reports running
|
|
134
|
+
jobs and signal exits. `remove`, `prune` and `install` stop with an
|
|
135
|
+
error instead of reporting success when the scheduler will not unload a
|
|
136
|
+
job; a long `options:` string no longer makes a file name too long to
|
|
137
|
+
write; on Linux, background jobs a scheduled task starts survive it.
|
|
138
|
+
- **Packaging**: the gem ships only `lib/`, `bin/asgard`, `sig/`, and the
|
|
139
|
+
README, LICENSE and CHANGELOG.
|
|
140
|
+
|
|
8
141
|
## [0.5.1] - 2026-10-19
|
|
9
142
|
|
|
10
143
|
### Added
|
|
@@ -156,7 +289,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
156
289
|
def show_version = puts version # instance context
|
|
157
290
|
end
|
|
158
291
|
```
|
|
159
|
-
|
|
160
292
|
- **`header(text)` and `footer(text)` DSL methods on `Asgard::Base`** — attach static text to the general help output. `header` lines are printed above the commands list; `footer` lines are printed below the options block. Multiple calls accumulate: each `header` call appends a line, each `footer` call prepends a line, so content from later-loaded files naturally wraps around content from earlier files. Neither appears when `asgard help <command>` is called for per-command detail.
|
|
161
293
|
```ruby
|
|
162
294
|
class Tasks
|
|
@@ -169,18 +301,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
169
301
|
class_option :version, type: :boolean, default: false, desc: "Show version and exit"
|
|
170
302
|
no_negate :version
|
|
171
303
|
```
|
|
172
|
-
|
|
173
|
-
### Changed
|
|
174
|
-
|
|
175
|
-
- **`--version` reimplemented as a `class_option`** — the flag now appears in the "Options" section of `asgard help` alongside `--debug` and `--verbose`, rather than as a listed command. The `_version` method and its `map "--version" => :_version` registration have been removed. The actual early-exit behaviour still lives in `Asgard.run!` (before the `.loki` file is required), so `--version` works even when no `.loki` file exists. `no_negate :version` suppresses the spurious `[--no-version]` / `[--skip-version]` variants.
|
|
176
|
-
- **`loki_up` returns `Pathname` instead of `String`** — the return value is now a `Pathname` instance (or `nil` when not found). `Pathname` is accepted everywhere `loki_up`'s result is used: `import`, `dotenv`, `load`, and standard Ruby file methods all accept `Pathname` via `to_path` / `to_s`. Code that passes the result directly to those methods is unaffected; code that performs string operations on the path should call `.to_s` first.
|
|
177
|
-
|
|
178
|
-
### Fixed
|
|
179
|
-
|
|
180
|
-
- **Help output showed `tasks` prefix before every command** — `asgard help` displayed `asgard tasks build` instead of `asgard build`. The cause was a keyword-vs-positional mismatch in the `Asgard::Base#help` override: declaring `subcommand: false` as a keyword argument caused Ruby's `super` to forward it as the hash `{subcommand: false}`. Thor's `banner` method received this hash as the positional `subcommand` argument, treated it as truthy, and prepended the class namespace (`tasks`) to every command name. Fixed by changing the override signature to match Thor's positional signature: `def help(command = nil, subcommand = false)`.
|
|
181
|
-
|
|
182
|
-
### Added (continued)
|
|
183
|
-
|
|
184
304
|
- **`loki_up(name = ".loki")` Kernel method** — searches `Dir.pwd` and each ancestor directory for a file with the given name; returns the absolute path of the first match or `nil`. Available everywhere in Ruby (task bodies, `.loki` files, top-level code) as a `module_function` on `Kernel`.
|
|
185
305
|
- **`import(path)` Kernel method** — loads a `.loki` file (or a glob of `.loki` files) with `require`-like idempotency via `$LOADED_FEATURES`. Accepts a `String` or `Pathname`. Relative paths are resolved relative to the caller's file (like `require_relative`). Glob patterns (`*.loki`, `**/*.loki`) expand via `Dir.glob` and load all matches. Returns `true` if any file was newly loaded, `false` if all were already loaded or no glob matches were found. Raises `ArgumentError` if the path does not end with `.loki`.
|
|
186
306
|
- **`import_up(name = ".loki")` Kernel method** — combines `loki_up` and `import`. For exact names, finds the first ancestor directory containing that file and loads it. For glob names, finds the first ancestor directory containing any matching files and loads them all — stopping at that level rather than aggregating across multiple ancestors. Returns `false` if nothing is found.
|
|
@@ -201,111 +321,69 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
201
321
|
- **`examples/subdir/`** — three-file demo showing `import` and `import_up` across directory boundaries: `subdir/.loki` imports `import_up_demo.loki` by name; `import_up_demo.loki` calls `import_up "env_usage.loki"` to locate and load a file from an ancestor directory without a hardcoded path.
|
|
202
322
|
- **`status` task in `kitchen_sink.loki`** — demonstrates `debug?` and `verbose?` predicates with conditional output; shows `--debug` printing `$DEBUG`, `$VERBOSE`, and the full options hash.
|
|
203
323
|
- **Computed value methods in `kitchen_sink.loki`** — three private methods (`version`, `sha`, `branch`) demonstrating the idiomatic Ruby replacement for the removed `var` DSL, including memoization via `@ivar ||=`.
|
|
204
|
-
|
|
205
|
-
### Removed
|
|
206
|
-
|
|
207
|
-
- **`var` DSL method** — removed in favour of native Ruby class variables. Use `@@name ||= "value".freeze` in the class body. Class variables are visible in all task instance methods and in subcommand subclasses, making them the correct tool for shared configuration in a Thor-based task runner. Breaking change for projects using `var`.
|
|
208
|
-
- **`import` DSL method** — `import(mod)` was a one-line alias for Ruby's built-in `include`. Callers can use `include` directly.
|
|
209
|
-
- **`--auto-load` CLI flag** — sibling `*.loki` loading is now entirely user-controlled: place `import "*.loki"` (or any glob or explicit path) at the top of your `.loki` file to load additional task files. Breaking change for projects that relied on `--auto-load`.
|
|
210
|
-
- **`Asgard.load_loki(dir)`** — replaced by `import` with glob support. Callers can use `import(File.join(dir, "*.loki"))` directly.
|
|
211
|
-
- **`debug?` / `verbose?` private methods on `Tasks`** — removed as redundant. The identical `module_function` versions on `Kernel` are available everywhere, including inside task bodies.
|
|
212
|
-
|
|
213
|
-
### Refactored
|
|
214
|
-
|
|
215
|
-
- **`validate_deps!` decomposed into focused private helpers** — the method was performing four unrelated validations in one body (orphaned `depends_on` check, undefined dep name check, dep arity check, cycle detection). Each concern is now a dedicated `_`-prefixed private method (`_check_orphaned_deps!`, `_check_undefined_deps!`, `_check_dep_arities!`, `_build_and_sort_graph`). `validate_deps!` is now a sequencer of ~8 lines. Flog score dropped from 87.3 to 24.4.
|
|
216
|
-
- **`invoke_command` dispatch helpers made private with descriptive names** — the dispatch hook was decomposed into focused helpers. Those helpers (`acquire_run_token`, `run_deps_for`, `run_dep_group`, `signal_done`, `run_dep`) are now declared `private` on `Asgard::Base` rather than wrapped in `no_commands`. The `_` prefix was dropped — the underscore convention is reserved for gem-owned methods on `Tasks`; `private` is sufficient to exclude instance methods from Thor's command registry. `invoke_command` itself stays in `no_commands` so Thor does not warn about an undescribed public method.
|
|
217
|
-
|
|
218
|
-
### Fixed
|
|
219
|
-
|
|
220
|
-
- **Multiple parallel dep failures now all surfaced** — when two or more parallel deps raised, only the first exception was re-raised; the rest were silently discarded. All errors are now printed to stderr via `warn` before a general `Asgard::Error` is raised. When only one dep fails, its exception is re-raised directly as before.
|
|
221
|
-
- **Subcommand deps not validated at startup** — `run!` only called `Tasks.validate_deps!`, so circular dependencies and undefined dep names in subcommand groups were silently ignored. `run!` now snapshots `Asgard::Base.subclasses` before loading task files and validates every newly defined subclass alongside `Tasks`.
|
|
222
|
-
- **Parallel dep thread orphaned on exception** — when a parallel dep group contained one fast-failing task and one slow task, the join loop re-raised the first thread's exception and abandoned the remaining threads. The join loop now collects all thread exceptions before re-raising, ensuring every thread completes before execution exits the group.
|
|
223
|
-
- **Dep with required arguments gave a cryptic runtime error** — `depends_on :build` where `build(name)` has required parameters caused `Thor::InvocationError` at task invocation time with no indication of where the problem was. `validate_deps!` now checks arity via `instance_method.parameters` and raises `Asgard::Error` at startup with the task name and argument count.
|
|
224
|
-
- **Orphaned `depends_on` silently discarded** — a `depends_on` declaration at the end of a class body with no following `desc`/`def` left `@_pending_deps` non-empty but was ignored by `validate_deps!` due to an early `return if _deps.empty?` guard. `validate_deps!` now checks `@_pending_deps` before that guard and raises `Asgard::Error` naming the orphaned dependencies.
|
|
225
|
-
- **Single-arg `desc` options silently dropped** — when `desc "description", hide: true` was used, Ruby routed the options hash to the `description` positional parameter. The override now detects a `Hash` in the description position and treats it as options.
|
|
226
|
-
- **Single-arg `desc` stolen by `no_commands` blocks** — if a `no_commands` block appeared between a single-arg `desc` and its method, `method_added` consumed `@_pending_single_desc` for the interstitial helper and leaked a stale `@usage` onto the next real command. The fix: `@_pending_single_desc` is only consumed when `no_commands?` is false.
|
|
227
|
-
- **Parallel dep race condition** — when two parallel tasks shared a common dependency, the second thread could start before the shared dep finished. `_ran_tasks` (a single Set) has been replaced with `_running` / `_done` Sets and a per-task `ConditionVariable`. Threads that arrive at an already-running dep now wait for its completion.
|
|
228
|
-
- **`depends_on` silently dropped before `no_commands` blocks** — the `method_added` guard now checks `@usage` instead of the `no_commands?` counter, so `no_commands` helpers placed between `depends_on` and `def` no longer silently steal the dependency.
|
|
229
|
-
- **`shebang` ignored its `silent:` keyword argument** — the parameter was accepted but never referenced; the script body is now echoed to stdout unless `silent: true` is passed, matching the behavior of `sh`.
|
|
230
|
-
|
|
231
|
-
### Changed
|
|
232
|
-
|
|
233
|
-
- **`validate_deps!` detects undefined dependency names** — `depends_on :nonexistent` previously passed validation silently and produced no error at runtime. `validate_deps!` now raises `Asgard::Error` listing every dep name that does not correspond to a defined task.
|
|
234
|
-
- **`default_task` behaviour documented** — `docs/tasks.md` now notes that running `asgard` with no arguments displays the help message when `default_task` is not set.
|
|
235
|
-
- **`loki_up` scope clarified in docs** — `docs/task-files.md` and `docs/api.md` now make explicit that `loki_up` locates any file by name, not just `.loki` files, with examples for `.env` and `VERSION`. The `dotenv loki_up(".env") || ".env"` pattern is shown as the canonical way to load a `.env` file from any subdirectory.
|
|
236
|
-
- **`examples/.loki`** — updated to use explicit `import "*.loki"` (sibling files) and `import "subdir/import_demo.loki"` (subdirectory file), with comments explaining `import`, `import_up`, and `loki_up`.
|
|
237
|
-
|
|
238
|
-
### Added (continued)
|
|
239
|
-
|
|
240
324
|
- **`--doctor` built-in CLI flag** — diagnoses `.loki` resolution, import chains, and task definitions for the current directory, then exits. Handled directly in `Asgard.run!` before the `.loki` file is loaded (same pattern as `--version`), so it keeps working in exactly the situations that would otherwise abort the whole process: a broken `.loki` file, a circular or undefined dependency, or a task silently redefined by a later `def`. Backed by the new `Asgard::Doctor` class. The report includes a "Tasks by file" listing: every command grouped by the file it's defined in, printed as `relative/path:line` so an editor can jump straight to it. A task name defined at more than one location gets every definition annotated inline — the earlier one(s) as `OVERRIDDEN by <file>:<line> — never callable`, the winning (last) one as `active — redefines <file>:<line>` — replacing the old flat "Tasks#x redefined" summary line with an annotation right where the problem is. See [API Reference](https://madbomber.github.io/asgard/api/#asgarddoctor).
|
|
241
325
|
- **Flay and Reek quality gates** — `flay_check` checks for structural code duplication (mass ≥ 150); `reek` checks code smells. Both run as part of `quality` alongside `test`, `rubocop`, and `flog_check`. A `.reek.yml` tunes several detectors to this codebase's conventions (no doc-comment requirement, short variable names, disabled `TooManyStatements`, etc), plus per-method `exclude:` entries (generated with `reek --todo` and hand-curated) that grandfather specific reviewed smells at specific methods — precise enough that a genuinely new smell still fails the gate even at an already-reviewed method, unlike a per-file count.
|
|
242
326
|
- **`test_verbose` task** — runs the test suite with Minitest's verbose (`-v`) output.
|
|
243
327
|
- **Colorized quality gate summary** — `quality`'s final report now prints a green/red PASS/FAIL badge per gate plus a passed/failed tally, via a shared `print_quality_summary` helper.
|
|
244
328
|
- **`console` task** — opens an IRB console with the gem loaded (`bin/console` if present, otherwise `bundle exec irb`).
|
|
245
329
|
- **`git.loki`** — per-repo git tasks (`push`, `pull`, `fetch`), imported from `.loki`.
|
|
246
|
-
|
|
247
|
-
### Changed (continued)
|
|
248
|
-
|
|
249
|
-
- **`release` task** now prompts for confirmation (`Release asgard vX.Y.Z to RubyGems? [y/N]`) unless `-y`/`--yes` is passed, before tagging and pushing.
|
|
250
|
-
|
|
251
|
-
### Fixed (continued)
|
|
252
|
-
|
|
253
|
-
- **`bin/asgard` could silently run the wrong `asgard` version** — the executable did `require "asgard"`, which (without `bundle exec`) is resolved by RubyGems independently of where the script itself lives, so it could load a separately-installed gem version instead of this repo's own `lib/`. Changed to `require_relative "../lib/asgard"` so the executable always loads the library that ships alongside it, regardless of what else is installed.
|
|
254
|
-
|
|
255
|
-
### Added (continued 2)
|
|
256
|
-
|
|
257
330
|
- **`SKIP` and `WARN` quality-gate statuses** — alongside `PASS`/`FAIL`. Both are non-blocking (`quality` only aborts on `FAIL`); `SKIP` is for a required external tool that isn't installed, `WARN` is for a check that ran successfully but has non-blocking suggestions. `print_quality_summary` renders all four with distinct colored badges and a combined tally.
|
|
258
331
|
- **`typos_check` / `typos_fix` tasks** — spell-checking via the external `typos` CLI (`brew install typos-cli`, not a gem dependency). `typos_check` writes full findings to `typos_output.txt` and participates in `quality`. If `typos` isn't installed, the gate prints a one-line notice recommending `brew install typos-cli` and reports `SKIP` rather than failing.
|
|
259
332
|
- **`fasterer_check` task** — runs the `fasterer` gem (new dev dependency) against `lib/`, reporting performance-idiom suggestions as `WARN` (non-blocking) — these are suggestions on working code, not correctness problems.
|
|
260
333
|
- **`asgard tree` now shows the project header/footer** — `Base#tree` wraps Thor's built-in command tree the same way `Base#help` already wraps the command list, so both example outputs are consistent.
|
|
261
334
|
- Every quality gate (`test_check`, `rubocop_check`, `flog_check`, `flay_check`, `reek_check`, `typos_check`, `fasterer_check`, `bundler_audit_check`) now writes its full detailed output to a `<gate>_output.txt` file at the repo root (gitignored via `*_output.txt`) and prints only a one-line summary to stdout — full detail stays on disk without cluttering the terminal.
|
|
262
|
-
|
|
263
|
-
### Changed (continued 2)
|
|
264
|
-
|
|
265
|
-
- **`Asgard::Base` and `Asgard::Doctor` split into focused mixins** — `Asgard::Base` (31 methods) is now `Registry`, `DependencyGraph`, `TaskDSL`, and `Dispatch` (`lib/asgard/base/*.rb`), with `method_added`/`header`/`footer`/`help`/`tree` remaining directly on the class as the orchestrator. `Asgard::Doctor` (19 methods) is now `TaskSections` and `Report` (`lib/asgard/doctor/*.rb`), with the diagnostic flow (`run`, `report_markers`, `load_chain`, etc.) staying on the class itself. Purely a file-organization change — behavior, `asgard help`/`asgard --doctor` output, and the public API are unchanged. Drops Reek's `TooManyMethods`/`TooManyInstanceVariables` warnings on both classes to zero.
|
|
266
|
-
- **Reek grandfathering made precise** — replaced the per-file smell-count baseline (`.quality/reek_baseline.txt`, the `reek_baseline` task) with per-method, per-detector `exclude:` entries in `.reek.yml` itself, generated via `reek --todo` and hand-curated. Unlike a count, this still catches a genuinely new smell at an already-reviewed method, without relying on the file's total count staying the same. The `reek_baseline` and `ensure_quality_dir` tasks and the `.quality/` directory have been removed as no longer needed.
|
|
267
|
-
|
|
268
|
-
### Added (continued 3)
|
|
269
|
-
|
|
270
335
|
- **`depends_on` accepts a Proc/lambda in addition to a fixed list** — a sole callable defers resolution to `validate_deps!` (once, right after every `.loki` file has loaded) instead of resolving immediately when `depends_on` itself is evaluated. This solves the "load order matters" problem for a dependency list that can't be known upfront — e.g. "every task whose name ends in `_check`," discovered across several files including ones imported conditionally (`import "quality_rails.loki" if defined?(Rails)`). The Proc must return the same shape the splat form would receive (an array of stages, each a `Symbol` or `Array`); it's written directly in the class body, so it lexically captures that class as `self` and can call `all_commands` bare. A Proc that raises is re-raised as `Asgard::Error` naming the task it was declared for; a Proc that resolves to an undefined task or a cycle is still caught by the existing startup validation, since the resolved result is checked exactly like a plain array. See [Dynamic Dependencies](https://madbomber.github.io/asgard/dependencies/#dynamic-dependencies-proc-block-form).
|
|
271
336
|
- **`bundler_audit_check` task** — runs `bundle-audit check --update` against `Gemfile.lock` (new dev dependency `bundler-audit`); reports `FAIL` on any known vulnerability, since this is a security gate, not a suggestion.
|
|
272
337
|
- **`quality_rails.loki`** — imported by `.loki` only when `Rails` is defined; currently ships `brakeman_check`, a Rails security-scan example. Needs no wiring into `quality`'s dependency list — `quality`'s `depends_on` Proc discovers it automatically once it's loaded.
|
|
338
|
+
- **`examples/bad.loki`** — a worked demonstration of the race the fix above addresses: 4 parallel workers read-modify-write a shared `@hits` counter directly (the anti-pattern `quality.loki` used to have), reliably losing updates. Kept as a contrast example for what `dep_result`/`dep_results` is for.
|
|
339
|
+
- **`sh(script, exec: true)`** — hands the command the asgard process itself via `Kernel.exec` instead of forking. For a task's final, long-running command (a dev server, a REPL) this replaces the ruby process outright, so nothing sits resident in memory behind it and Ctrl-C is handled directly by the command instead of unwinding back through asgard. `doc_tasks.loki`'s `doc_server` task (`sh "mkdocs serve", exec: true`) is the motivating example. See [Shell Helpers](https://madbomber.github.io/asgard/shell/#handing-off-with-exec).
|
|
340
|
+
- **`bootstrap` and `env_info` tasks in `kitchen_sink.loki`** — demonstrate `sh` with a multi-line heredoc (routed through `bash -c`) and a single-line command, respectively.
|
|
341
|
+
- **`depends_on` accepts a block in addition to a Proc/lambda** — `depends_on { ... }` (or `depends_on do ... end` for a block spanning multiple statements) defers resolution to `validate_deps!` exactly like the existing sole-Proc/lambda form; the two are interchangeable. `depends_on` still accepts task arguments *or* a block, never both — combining them raises `Asgard::Error`. See [Dynamic Dependencies](https://madbomber.github.io/asgard/dependencies/#dynamic-dependencies-proc-block-form).
|
|
342
|
+
- **The resolved Proc/lambda/block result is now shape-validated** — once `validate_deps!` calls it, the return value must be an `Array` of stages, each a `Symbol`/`String` (sequential) or an `Array` of `Symbol`/`String` (parallel group), nested no deeper than that. A bad shape (wrong type, an invalid stage, a non-Symbol/String leaf, or nesting more than one level deep) now raises `Asgard::Error` naming the task and the offending value, instead of failing later with an opaque `NoMethodError`.
|
|
343
|
+
- **`examples/depends_on_block/good/` and `examples/depends_on_block/bad/`** — two self-contained example projects (each its own `.loki` root, isolated from the main `examples/` tree) demonstrating the block form: `good/` covers single-line `{ ... }`, `do...end`, and a mixed sequential+parallel shape; `bad/` demonstrates the double-wrapped-array mistake that the new shape validation catches, with the exact `Asgard::Error` message it produces.
|
|
273
344
|
|
|
274
|
-
### Changed
|
|
345
|
+
### Changed
|
|
275
346
|
|
|
347
|
+
- **`--version` reimplemented as a `class_option`** — the flag now appears in the "Options" section of `asgard help` alongside `--debug` and `--verbose`, rather than as a listed command. The `_version` method and its `map "--version" => :_version` registration have been removed. The actual early-exit behaviour still lives in `Asgard.run!` (before the `.loki` file is required), so `--version` works even when no `.loki` file exists. `no_negate :version` suppresses the spurious `[--no-version]` / `[--skip-version]` variants.
|
|
348
|
+
- **`loki_up` returns `Pathname` instead of `String`** — the return value is now a `Pathname` instance (or `nil` when not found). `Pathname` is accepted everywhere `loki_up`'s result is used: `import`, `dotenv`, `load`, and standard Ruby file methods all accept `Pathname` via `to_path` / `to_s`. Code that passes the result directly to those methods is unaffected; code that performs string operations on the path should call `.to_s` first.
|
|
349
|
+
- **`validate_deps!` decomposed into focused private helpers** — the method was performing four unrelated validations in one body (orphaned `depends_on` check, undefined dep name check, dep arity check, cycle detection). Each concern is now a dedicated `_`-prefixed private method (`_check_orphaned_deps!`, `_check_undefined_deps!`, `_check_dep_arities!`, `_build_and_sort_graph`). `validate_deps!` is now a sequencer of ~8 lines. Flog score dropped from 87.3 to 24.4.
|
|
350
|
+
- **`invoke_command` dispatch helpers made private with descriptive names** — the dispatch hook was decomposed into focused helpers. Those helpers (`acquire_run_token`, `run_deps_for`, `run_dep_group`, `signal_done`, `run_dep`) are now declared `private` on `Asgard::Base` rather than wrapped in `no_commands`. The `_` prefix was dropped — the underscore convention is reserved for gem-owned methods on `Tasks`; `private` is sufficient to exclude instance methods from Thor's command registry. `invoke_command` itself stays in `no_commands` so Thor does not warn about an undescribed public method.
|
|
351
|
+
- **`validate_deps!` detects undefined dependency names** — `depends_on :nonexistent` previously passed validation silently and produced no error at runtime. `validate_deps!` now raises `Asgard::Error` listing every dep name that does not correspond to a defined task.
|
|
352
|
+
- **`default_task` behaviour documented** — `docs/tasks.md` now notes that running `asgard` with no arguments displays the help message when `default_task` is not set.
|
|
353
|
+
- **`loki_up` scope clarified in docs** — `docs/task-files.md` and `docs/api.md` now make explicit that `loki_up` locates any file by name, not just `.loki` files, with examples for `.env` and `VERSION`. The `dotenv loki_up(".env") || ".env"` pattern is shown as the canonical way to load a `.env` file from any subdirectory.
|
|
354
|
+
- **`examples/.loki`** — updated to use explicit `import "*.loki"` (sibling files) and `import "subdir/import_demo.loki"` (subdirectory file), with comments explaining `import`, `import_up`, and `loki_up`.
|
|
355
|
+
- **`release` task** now prompts for confirmation (`Release asgard vX.Y.Z to RubyGems? [y/N]`) unless `-y`/`--yes` is passed, before tagging and pushing.
|
|
356
|
+
- **`Asgard::Base` and `Asgard::Doctor` split into focused mixins** — `Asgard::Base` (31 methods) is now `Registry`, `DependencyGraph`, `TaskDSL`, and `Dispatch` (`lib/asgard/base/*.rb`), with `method_added`/`header`/`footer`/`help`/`tree` remaining directly on the class as the orchestrator. `Asgard::Doctor` (19 methods) is now `TaskSections` and `Report` (`lib/asgard/doctor/*.rb`), with the diagnostic flow (`run`, `report_markers`, `load_chain`, etc.) staying on the class itself. Purely a file-organization change — behavior, `asgard help`/`asgard --doctor` output, and the public API are unchanged. Drops Reek's `TooManyMethods`/`TooManyInstanceVariables` warnings on both classes to zero.
|
|
357
|
+
- **Reek grandfathering made precise** — replaced the per-file smell-count baseline (`.quality/reek_baseline.txt`, the `reek_baseline` task) with per-method, per-detector `exclude:` entries in `.reek.yml` itself, generated via `reek --todo` and hand-curated. Unlike a count, this still catches a genuinely new smell at an already-reviewed method, without relying on the file's total count staying the same. The `reek_baseline` and `ensure_quality_dir` tasks and the `.quality/` directory have been removed as no longer needed.
|
|
276
358
|
- **`test`, `rubocop`, `reek` renamed to `test_check`, `rubocop_check`, `reek_check`** — for consistency with the other gates, all of which already ended in `_check`. This convention is what makes automatic discovery possible: `quality`'s `depends_on` Proc finds every task whose name matches `_check\z` rather than naming them one by one.
|
|
359
|
+
- **`validate_deps!` cycle detection now uses stdlib `TSort`** instead of `Dagwood::DependencyGraph#order` — a private `Graph` `Struct` (`edges` member, `include TSort`) owns the task→dependency Hash and the traversal, raising `TSort::Cyclic` on a cycle exactly as before (converted to `Asgard::CircularDependencyError`). `run_deps_for` no longer round-trips through a rebuilt DAG on every dispatch — it iterates `_deps[target]`'s stage groups directly, which is already the parallel-execution plan `depends_on` built.
|
|
277
360
|
|
|
278
|
-
### Removed
|
|
361
|
+
### Removed
|
|
279
362
|
|
|
363
|
+
- **`var` DSL method** — removed in favour of native Ruby class variables. Use `@@name ||= "value".freeze` in the class body. Class variables are visible in all task instance methods and in subcommand subclasses, making them the correct tool for shared configuration in a Thor-based task runner. Breaking change for projects using `var`.
|
|
364
|
+
- **`import` DSL method** — `import(mod)` was a one-line alias for Ruby's built-in `include`. Callers can use `include` directly.
|
|
365
|
+
- **`--auto-load` CLI flag** — sibling `*.loki` loading is now entirely user-controlled: place `import "*.loki"` (or any glob or explicit path) at the top of your `.loki` file to load additional task files. Breaking change for projects that relied on `--auto-load`.
|
|
366
|
+
- **`Asgard.load_loki(dir)`** — replaced by `import` with glob support. Callers can use `import(File.join(dir, "*.loki"))` directly.
|
|
367
|
+
- **`debug?` / `verbose?` private methods on `Tasks`** — removed as redundant. The identical `module_function` versions on `Kernel` are available everywhere, including inside task bodies.
|
|
280
368
|
- **`dagwood` runtime dependency** — replaced by stdlib `TSort` for the one thing it was still doing (cycle detection); the parallel-execution plan itself was already derived directly from `depends_on`'s stage list, not from a rebuilt DAG. `_build_dep_graph` (dead code — its return value was already unused) is deleted along with the gemspec entry.
|
|
281
369
|
|
|
282
|
-
###
|
|
283
|
-
|
|
284
|
-
- **`validate_deps!` cycle detection now uses stdlib `TSort`** instead of `Dagwood::DependencyGraph#order` — a private `Graph` `Struct` (`edges` member, `include TSort`) owns the task→dependency Hash and the traversal, raising `TSort::Cyclic` on a cycle exactly as before (converted to `Asgard::CircularDependencyError`). `run_deps_for` no longer round-trips through a rebuilt DAG on every dispatch — it iterates `_deps[target]`'s stage groups directly, which is already the parallel-execution plan `depends_on` built.
|
|
285
|
-
|
|
286
|
-
### Fixed (continued 2)
|
|
370
|
+
### Fixed
|
|
287
371
|
|
|
372
|
+
- **Help output showed `tasks` prefix before every command** — `asgard help` displayed `asgard tasks build` instead of `asgard build`. The cause was a keyword-vs-positional mismatch in the `Asgard::Base#help` override: declaring `subcommand: false` as a keyword argument caused Ruby's `super` to forward it as the hash `{subcommand: false}`. Thor's `banner` method received this hash as the positional `subcommand` argument, treated it as truthy, and prepended the class namespace (`tasks`) to every command name. Fixed by changing the override signature to match Thor's positional signature: `def help(command = nil, subcommand = false)`.
|
|
373
|
+
- **Multiple parallel dep failures now all surfaced** — when two or more parallel deps raised, only the first exception was re-raised; the rest were silently discarded. All errors are now printed to stderr via `warn` before a general `Asgard::Error` is raised. When only one dep fails, its exception is re-raised directly as before.
|
|
374
|
+
- **Subcommand deps not validated at startup** — `run!` only called `Tasks.validate_deps!`, so circular dependencies and undefined dep names in subcommand groups were silently ignored. `run!` now snapshots `Asgard::Base.subclasses` before loading task files and validates every newly defined subclass alongside `Tasks`.
|
|
375
|
+
- **Parallel dep thread orphaned on exception** — when a parallel dep group contained one fast-failing task and one slow task, the join loop re-raised the first thread's exception and abandoned the remaining threads. The join loop now collects all thread exceptions before re-raising, ensuring every thread completes before execution exits the group.
|
|
376
|
+
- **Dep with required arguments gave a cryptic runtime error** — `depends_on :build` where `build(name)` has required parameters caused `Thor::InvocationError` at task invocation time with no indication of where the problem was. `validate_deps!` now checks arity via `instance_method.parameters` and raises `Asgard::Error` at startup with the task name and argument count.
|
|
377
|
+
- **Orphaned `depends_on` silently discarded** — a `depends_on` declaration at the end of a class body with no following `desc`/`def` left `@_pending_deps` non-empty but was ignored by `validate_deps!` due to an early `return if _deps.empty?` guard. `validate_deps!` now checks `@_pending_deps` before that guard and raises `Asgard::Error` naming the orphaned dependencies.
|
|
378
|
+
- **Single-arg `desc` options silently dropped** — when `desc "description", hide: true` was used, Ruby routed the options hash to the `description` positional parameter. The override now detects a `Hash` in the description position and treats it as options.
|
|
379
|
+
- **Single-arg `desc` stolen by `no_commands` blocks** — if a `no_commands` block appeared between a single-arg `desc` and its method, `method_added` consumed `@_pending_single_desc` for the interstitial helper and leaked a stale `@usage` onto the next real command. The fix: `@_pending_single_desc` is only consumed when `no_commands?` is false.
|
|
380
|
+
- **Parallel dep race condition** — when two parallel tasks shared a common dependency, the second thread could start before the shared dep finished. `_ran_tasks` (a single Set) has been replaced with `_running` / `_done` Sets and a per-task `ConditionVariable`. Threads that arrive at an already-running dep now wait for its completion.
|
|
381
|
+
- **`depends_on` silently dropped before `no_commands` blocks** — the `method_added` guard now checks `@usage` instead of the `no_commands?` counter, so `no_commands` helpers placed between `depends_on` and `def` no longer silently steal the dependency.
|
|
382
|
+
- **`shebang` ignored its `silent:` keyword argument** — the parameter was accepted but never referenced; the script body is now echoed to stdout unless `silent: true` is passed, matching the behavior of `sh`.
|
|
383
|
+
- **`bin/asgard` could silently run the wrong `asgard` version** — the executable did `require "asgard"`, which (without `bundle exec`) is resolved by RubyGems independently of where the script itself lives, so it could load a separately-installed gem version instead of this repo's own `lib/`. Changed to `require_relative "../lib/asgard"` so the executable always loads the library that ships alongside it, regardless of what else is installed.
|
|
288
384
|
- **`quality.loki`'s parallel `*_check` tasks raced on shared instance state** — each `*_check` task wrote its pass/fail status to an ivar on `self` (`@test_result`, `@rubocop_result`, ...) from inside a `Thread.new` spawned by the same parallel-dependency group — an unsynchronized write across threads that MRI's GVL happens to hide today but would not on JRuby/TruffleRuby. Fixed at the framework level: `Dispatch#run_dep_group` now collects each task's own return value into a `Hash` (via `Thread#value`), `run_deps_for` merges per-stage results, and a new `dep_result`/`dep_results` instance API (backed by `Thread.current`, not `self`) hands them to the task body that depends on them. `quality.loki` and `quality_rails.loki`'s `*_check` tasks now return their status as a plain value instead of writing to an ivar; `quality` reads `dep_result(name)` instead of `instance_variable_get`.
|
|
289
|
-
|
|
290
|
-
### Added (continued 4)
|
|
291
|
-
|
|
292
|
-
- **`examples/bad.loki`** — a worked demonstration of the race the fix above addresses: 4 parallel workers read-modify-write a shared `@hits` counter directly (the anti-pattern `quality.loki` used to have), reliably losing updates. Kept as a contrast example for what `dep_result`/`dep_results` is for.
|
|
293
|
-
|
|
294
|
-
### Added (continued 5)
|
|
295
|
-
|
|
296
|
-
- **`sh(script, exec: true)`** — hands the command the asgard process itself via `Kernel.exec` instead of forking. For a task's final, long-running command (a dev server, a REPL) this replaces the ruby process outright, so nothing sits resident in memory behind it and Ctrl-C is handled directly by the command instead of unwinding back through asgard. `doc_tasks.loki`'s `doc_server` task (`sh "mkdocs serve", exec: true`) is the motivating example. See [Shell Helpers](https://madbomber.github.io/asgard/shell/#handing-off-with-exec).
|
|
297
|
-
- **`bootstrap` and `env_info` tasks in `kitchen_sink.loki`** — demonstrate `sh` with a multi-line heredoc (routed through `bash -c`) and a single-line command, respectively.
|
|
298
|
-
|
|
299
|
-
### Fixed (continued 3)
|
|
300
|
-
|
|
301
385
|
- **Ctrl-C during a running `sh` command printed a raw `Interrupt` backtrace** — SIGINT hits the whole foreground process group, so asgard's own ruby process raised `Interrupt` independently of whatever the shelled-out command did with the signal, and it went uncaught, unwinding through Thor and printing a stack trace before exiting. `Asgard.run!` now rescues `Interrupt` and exits with the conventional 130 status.
|
|
302
386
|
|
|
303
|
-
### Added (continued 6)
|
|
304
|
-
|
|
305
|
-
- **`depends_on` accepts a block in addition to a Proc/lambda** — `depends_on { ... }` (or `depends_on do ... end` for a block spanning multiple statements) defers resolution to `validate_deps!` exactly like the existing sole-Proc/lambda form; the two are interchangeable. `depends_on` still accepts task arguments *or* a block, never both — combining them raises `Asgard::Error`. See [Dynamic Dependencies](https://madbomber.github.io/asgard/dependencies/#dynamic-dependencies-proc-block-form).
|
|
306
|
-
- **The resolved Proc/lambda/block result is now shape-validated** — once `validate_deps!` calls it, the return value must be an `Array` of stages, each a `Symbol`/`String` (sequential) or an `Array` of `Symbol`/`String` (parallel group), nested no deeper than that. A bad shape (wrong type, an invalid stage, a non-Symbol/String leaf, or nesting more than one level deep) now raises `Asgard::Error` naming the task and the offending value, instead of failing later with an opaque `NoMethodError`.
|
|
307
|
-
- **`examples/depends_on_block/good/` and `examples/depends_on_block/bad/`** — two self-contained example projects (each its own `.loki` root, isolated from the main `examples/` tree) demonstrating the block form: `good/` covers single-line `{ ... }`, `do...end`, and a mixed sequential+parallel shape; `bad/` demonstrates the double-wrapped-array mistake that the new shape validation catches, with the exact `Asgard::Error` message it produces.
|
|
308
|
-
|
|
309
387
|
## [0.2.0] - 2026-05-29
|
|
310
388
|
|
|
311
389
|
### Changed
|
|
@@ -328,17 +406,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
328
406
|
- `db_subcommands.loki` — subcommand group for database management with `depends_on` chaining
|
|
329
407
|
- README sections: Helper methods, Subcommands, Thor wrapper callout
|
|
330
408
|
|
|
331
|
-
### Fixed
|
|
332
|
-
|
|
333
|
-
- Replaced `warn`/`exit 1` with `abort` throughout `run!` — `Kernel#warn` is silenced when `$VERBOSE = nil`, which is the default in Ruby 4.0; `abort` writes to `$stderr` regardless
|
|
334
|
-
|
|
335
409
|
### Changed
|
|
336
410
|
|
|
337
411
|
- `--debug` and `--verbose` promoted from mapped tasks to `class_option` — they now work as modifiers alongside other commands (e.g. `asgard build --debug`) rather than as standalone commands
|
|
338
412
|
- Removed all references to `just` task runner and `recipe` terminology; Asgard uses "task" throughout
|
|
339
413
|
- `depends_on` parameter renamed from `*recipes` to `*tasks` for consistency
|
|
340
414
|
|
|
415
|
+
### Fixed
|
|
416
|
+
|
|
417
|
+
- Replaced `warn`/`exit 1` with `abort` throughout `run!` — `Kernel#warn` is silenced when `$VERBOSE = nil`, which is the default in Ruby 4.0; `abort` writes to `$stderr` regardless
|
|
418
|
+
|
|
341
419
|
## [0.1.1] - 2026-05-28
|
|
420
|
+
|
|
342
421
|
### Added
|
|
343
422
|
|
|
344
423
|
- Parallel dependency execution — wrap deps in an array to run them concurrently:
|
|
@@ -383,7 +462,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
383
462
|
- 100% test coverage enforced via SimpleCov (95% minimum threshold)
|
|
384
463
|
- Quality task in `.loki` runs flog after tests
|
|
385
464
|
|
|
386
|
-
[0.
|
|
465
|
+
[0.6.0]: https://github.com/MadBomber/asgard/compare/v0.5.1...v0.6.0
|
|
466
|
+
[0.5.1]: https://github.com/MadBomber/asgard/compare/v0.4.1...v0.5.1
|
|
387
467
|
[0.4.1]: https://github.com/MadBomber/asgard/compare/v0.4.0...v0.4.1
|
|
388
468
|
[0.4.0]: https://github.com/MadBomber/asgard/compare/v0.3.3...v0.4.0
|
|
389
469
|
[0.3.3]: https://github.com/MadBomber/asgard/compare/v0.2.0...v0.3.3
|