asgard 0.3.1 → 0.3.3
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/.envrc +5 -0
- data/.loki +14 -1
- data/.reek.yml +115 -0
- data/.rubocop.yml +6 -0
- data/Archspec.rb +24 -0
- data/CHANGELOG.md +72 -1
- data/CLAUDE.md +4 -7
- data/README.md +51 -1
- data/bin/asgard +1 -1
- data/doc_tasks.loki +21 -0
- data/docs/api.md +125 -6
- data/docs/changelog.md +23 -1
- data/docs/dependencies.md +94 -2
- data/docs/getting-started.md +4 -2
- data/docs/index.md +3 -3
- data/docs/options.md +16 -6
- data/docs/shell.md +30 -1
- data/docs/tasks.md +40 -0
- data/examples/bad.loki +63 -0
- data/examples/depends_on_block/bad/.loki +51 -0
- data/examples/depends_on_block/good/.loki +83 -0
- data/examples/kitchen_sink.loki +13 -0
- data/gem_tasks.loki +17 -1
- data/git.loki +13 -0
- data/lib/asgard/base/dependency_graph.rb +160 -0
- data/lib/asgard/base/dispatch.rb +160 -0
- data/lib/asgard/base/registry.rb +38 -0
- data/lib/asgard/base/task_dsl.rb +64 -0
- data/lib/asgard/base.rb +36 -250
- data/lib/asgard/doctor/report.rb +73 -0
- data/lib/asgard/doctor/task_sections.rb +67 -0
- data/lib/asgard/doctor.rb +150 -0
- data/lib/asgard/kernel_methods.rb +6 -2
- data/lib/asgard/shell.rb +21 -8
- data/lib/asgard/tasks.rb +6 -0
- data/lib/asgard/version.rb +1 -1
- data/lib/asgard.rb +9 -1
- data/quality.loki +218 -32
- data/quality_rails.loki +47 -0
- data/xyzzy.loki +12 -0
- metadata +18 -16
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0f88b550d323b26b075a45f6a7d6e8781b7d54d062c1f724de25b2d253d2f3e4
|
|
4
|
+
data.tar.gz: 5c2a05803003793e14ad08f0453d0a549cb6f2f0202b31c38c600303a539d66b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d9f2d214246e0960b2a2a4ca790818a8f57edfd45c17f0fca6edf1bcf486cc096d0331818d899c5ca929f9fcec404329744403a0f03aa2fe0faafb70b13ae172
|
|
7
|
+
data.tar.gz: e35a52164438b5734cdf86ee4f7210e2169edb391e8478cddd1548698691d3bddea5d703ef113e34bcdd1caeb0041d9851ec94b8ee7840b9115a0ca821f03f49
|
data/.envrc
CHANGED
data/.loki
CHANGED
|
@@ -3,12 +3,25 @@
|
|
|
3
3
|
# Task is pre-defined by the gem — just reopen it to add tasks.
|
|
4
4
|
|
|
5
5
|
import "quality.loki"
|
|
6
|
+
# asgard runs as a separate process outside the app it's checking, so
|
|
7
|
+
# `defined?(Rails)` never sees the app's Rails constant. RAILS_ROOT — set in
|
|
8
|
+
# a Rails repo's own .envrc (`export RAILS_ROOT=$RR`, after RR is defined)
|
|
9
|
+
# — is the signal instead.
|
|
10
|
+
import "quality_rails.loki" if ENV["RAILS_ROOT"]
|
|
11
|
+
|
|
6
12
|
import "gem_tasks.loki"
|
|
13
|
+
import "git.loki"
|
|
14
|
+
import "doc_tasks.loki"
|
|
15
|
+
|
|
16
|
+
import "xyzzy.loki" # An example for the --doctor flag
|
|
7
17
|
|
|
8
18
|
class Tasks
|
|
9
19
|
@@project ||= "asgard".freeze
|
|
10
20
|
@@project_desc ||= "CLI-task runner"
|
|
11
21
|
|
|
22
|
+
desc "xyzzy"
|
|
23
|
+
def xyzzy = puts "everything in Ruby metaprogramming is magic"
|
|
24
|
+
|
|
12
25
|
helper(:project_version) do
|
|
13
26
|
@@project_version ||= File.read("lib/#{@@project}/version.rb").match(/VERSION\s*=\s*"([^"]+)"/)[1].freeze
|
|
14
27
|
end
|
|
@@ -16,7 +29,7 @@ class Tasks
|
|
|
16
29
|
default_task :quality
|
|
17
30
|
|
|
18
31
|
header <<~HEAD
|
|
19
|
-
|
|
32
|
+
Project: #{@@project} (v#{project_version}) - #{@@project_desc}
|
|
20
33
|
Root Dir: #{loki_up.parent}
|
|
21
34
|
Default task: #{default_task}
|
|
22
35
|
HEAD
|
data/.reek.yml
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
---
|
|
2
|
+
detectors:
|
|
3
|
+
|
|
4
|
+
# Disabled: conflicts with project no-comment coding style.
|
|
5
|
+
IrresponsibleModule:
|
|
6
|
+
enabled: false
|
|
7
|
+
|
|
8
|
+
# Disabled: bang methods without non-bang counterparts are common in internal APIs.
|
|
9
|
+
MissingSafeMethod:
|
|
10
|
+
enabled: false
|
|
11
|
+
|
|
12
|
+
# Disabled: explicit nil checks are idiomatic Ruby (nil? / unless nil, etc.).
|
|
13
|
+
NilCheck:
|
|
14
|
+
enabled: false
|
|
15
|
+
|
|
16
|
+
# Disabled: method size/complexity is already covered by Flog (the primary
|
|
17
|
+
# complexity gate) and RuboCop's Metrics/MethodLength.
|
|
18
|
+
TooManyStatements:
|
|
19
|
+
enabled: false
|
|
20
|
+
|
|
21
|
+
# Default is 4 — Doctor legitimately tracks several pieces of diagnostic state.
|
|
22
|
+
TooManyInstanceVariables:
|
|
23
|
+
max_instance_variables: 6
|
|
24
|
+
|
|
25
|
+
# Accept standard Ruby short-name conventions for limited-scope variables.
|
|
26
|
+
UncommunicativeVariableName:
|
|
27
|
+
accept:
|
|
28
|
+
- _ # intentionally ignored
|
|
29
|
+
- d # dep / dir / data
|
|
30
|
+
- e # rescue => e
|
|
31
|
+
- f # file / finding
|
|
32
|
+
- h # hash
|
|
33
|
+
- i # index
|
|
34
|
+
- k # key
|
|
35
|
+
- p # path
|
|
36
|
+
- t # task / thread
|
|
37
|
+
|
|
38
|
+
# Reviewed and accepted as false positives / intentional design, per method
|
|
39
|
+
# (generated with `reek --todo` and hand-curated — see project review notes).
|
|
40
|
+
# Unlike a count-based baseline, this is precise: it grandfathers exactly
|
|
41
|
+
# these smells at exactly these methods, so a genuinely new smell at a new
|
|
42
|
+
# or existing method still fails the gate even if it doesn't change the
|
|
43
|
+
# file's total warning count.
|
|
44
|
+
Attribute:
|
|
45
|
+
exclude:
|
|
46
|
+
- Asgard::Doctor::ImportTracer#doctor # needs to stay settable/resettable per doctor run
|
|
47
|
+
|
|
48
|
+
BooleanParameter:
|
|
49
|
+
exclude:
|
|
50
|
+
- Asgard::Base#help # must match Thor's positional super signature exactly
|
|
51
|
+
- Asgard::Shell#sh # silent: is the real public API contract
|
|
52
|
+
- Asgard::Shell#shebang # same
|
|
53
|
+
|
|
54
|
+
ControlParameter:
|
|
55
|
+
exclude:
|
|
56
|
+
- Asgard::Shell#sh
|
|
57
|
+
- Asgard::Shell#shebang
|
|
58
|
+
|
|
59
|
+
DuplicateMethodCall:
|
|
60
|
+
exclude:
|
|
61
|
+
- Asgard::Base::Dispatch#acquire_run_token # done.include?(target): check, then poll-until-true
|
|
62
|
+
- Asgard::Base::Dispatch#run_dep_group # errors.size: two branches of one guard
|
|
63
|
+
- Asgard::Doctor::Report#print_report # puts divider: intentional top+bottom rule
|
|
64
|
+
- Asgard::Doctor::Report#summary_line # errors.zero?: sequential guard clauses
|
|
65
|
+
- Asgard::Doctor#load_chain # Base.subclasses: before/after snapshot diff
|
|
66
|
+
- Kernel#import_up # "not found" warned from two independent branches
|
|
67
|
+
- Asgard#self.run! # Base.subclasses snapshot diff; e.message in separate rescues
|
|
68
|
+
- Asgard::Base#tree # say "\n": intentional blank line before and after the tree
|
|
69
|
+
|
|
70
|
+
FeatureEnvy:
|
|
71
|
+
exclude:
|
|
72
|
+
- Asgard::Base::Dispatch#acquire_run_token
|
|
73
|
+
- Asgard::Base::Dispatch#run_dep_group
|
|
74
|
+
- Asgard::Base::Dispatch#signal_done
|
|
75
|
+
- Asgard::Base::Registry#inherited # inherited(subclass) configuring subclass is the whole point
|
|
76
|
+
- Asgard::Base::DependencyGraph#_normalize_pending_deps # small transform of its own argument
|
|
77
|
+
- Asgard::Base::DependencyGraph#_call_dep_proc # rescue => e; e.class/e.message is inherent
|
|
78
|
+
- Asgard::Base::DependencyGraph#_validate_dep_stage! # pure shape check of its own argument
|
|
79
|
+
- Asgard::Base::DependencyGraph#_validate_dep_leaf! # pure shape check of its own argument
|
|
80
|
+
- Asgard::Doctor::TaskSections#class_task_file_map
|
|
81
|
+
- Asgard::Doctor::TaskSections#relative_path
|
|
82
|
+
- Asgard::Doctor::TaskSections#task_status
|
|
83
|
+
- Asgard::Shell#shebang # Tempfile.create(&block) idiom
|
|
84
|
+
|
|
85
|
+
ManualDispatch:
|
|
86
|
+
exclude:
|
|
87
|
+
- Asgard::Base::DependencyGraph#_normalize_pending_deps # respond_to?(:call) distinguishes a Proc from an Array/Symbol — no polymorphic alternative over Ruby's own built-in types
|
|
88
|
+
- Asgard::Base::DependencyGraph#_resolve_lazy_deps! # same check
|
|
89
|
+
|
|
90
|
+
InstanceVariableAssumption:
|
|
91
|
+
exclude:
|
|
92
|
+
- Asgard::Base # class-level DSL state built up across method_added/inherited, not object init
|
|
93
|
+
- Asgard::Doctor # multi-phase report builder; @ivars set across report_markers/load_chain/etc in `run`
|
|
94
|
+
|
|
95
|
+
NestedIterators:
|
|
96
|
+
exclude:
|
|
97
|
+
- Asgard::Base::DependencyGraph#_build_dep_graph
|
|
98
|
+
- Asgard::Base::DependencyGraph#_check_dep_arities!
|
|
99
|
+
- Asgard::Base::TaskDSL#no_negate # inner block defines a method, isn't really iteration
|
|
100
|
+
- Asgard::Doctor::Report#print_task_sections
|
|
101
|
+
- Asgard::Doctor::TaskSections#class_task_file_map
|
|
102
|
+
- Asgard::Doctor::TaskSections#override_count
|
|
103
|
+
|
|
104
|
+
UtilityFunction:
|
|
105
|
+
exclude:
|
|
106
|
+
- Asgard::Base::DependencyGraph#_build_dep_graph # genuinely pure, but tightly coupled to this graph model
|
|
107
|
+
- Asgard::Base::Dispatch#dep_results # thread-local by design, not self — that's the whole point
|
|
108
|
+
- Asgard::Base::Dispatch#with_dep_results # same: stashes results on Thread.current, not self
|
|
109
|
+
- Asgard::Shell#shell_argv # extracted pure on purpose so it's testable without invoking system/exec
|
|
110
|
+
|
|
111
|
+
exclude_paths:
|
|
112
|
+
- test
|
|
113
|
+
- docs
|
|
114
|
+
- coverage
|
|
115
|
+
- pkg
|
data/.rubocop.yml
CHANGED
|
@@ -6,6 +6,12 @@ AllCops:
|
|
|
6
6
|
- 'examples/**/*'
|
|
7
7
|
- 'vendor/**/*'
|
|
8
8
|
|
|
9
|
+
# Archspec.rb's capitalization is mandated by the archspec gem itself, not a
|
|
10
|
+
# style choice — it won't look for a snake_case config file.
|
|
11
|
+
Naming/FileName:
|
|
12
|
+
Exclude:
|
|
13
|
+
- 'Archspec.rb'
|
|
14
|
+
|
|
9
15
|
# ── Style: disabled cops ───────────────────────────────────────────────────
|
|
10
16
|
Style/StringLiterals:
|
|
11
17
|
Enabled: false
|
data/Archspec.rb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
source "lib/**/*.rb"
|
|
4
|
+
|
|
5
|
+
component :shell, in: "lib/asgard/shell.rb"
|
|
6
|
+
component :kernel_methods, in: "lib/asgard/kernel_methods.rb"
|
|
7
|
+
component :base, in: %w[lib/asgard/base.rb lib/asgard/base/**/*.rb]
|
|
8
|
+
component :tasks, in: "lib/asgard/tasks.rb"
|
|
9
|
+
component :doctor, in: %w[lib/asgard/doctor.rb lib/asgard/doctor/**/*.rb]
|
|
10
|
+
|
|
11
|
+
# The DSL engine must not depend upward on the classes built on top of it —
|
|
12
|
+
# Tasks and Doctor are consumers of Base, never the other way around.
|
|
13
|
+
base.cannot_use :tasks, :doctor
|
|
14
|
+
|
|
15
|
+
# Built-in/user tasks stay independent of the --doctor introspection feature.
|
|
16
|
+
tasks.cannot_use :doctor
|
|
17
|
+
|
|
18
|
+
# Shell (sh/shebang helpers, mixed into Base) and the Kernel additions (env,
|
|
19
|
+
# loki_up, import, ...) are leaf-level utilities — they must not depend on
|
|
20
|
+
# anything built on top of them.
|
|
21
|
+
shell.cannot_use :base, :tasks, :doctor
|
|
22
|
+
kernel_methods.cannot_use :base, :tasks, :doctor, :shell
|
|
23
|
+
|
|
24
|
+
no_cycles among: %i[base tasks doctor shell kernel_methods]
|
data/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [0.3.
|
|
8
|
+
## [0.3.3] - 2026-08-27
|
|
9
9
|
|
|
10
10
|
### Added
|
|
11
11
|
|
|
@@ -103,6 +103,77 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
103
103
|
- **`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.
|
|
104
104
|
- **`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`.
|
|
105
105
|
|
|
106
|
+
### Added (continued)
|
|
107
|
+
|
|
108
|
+
- **`--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](docs/api.md#asgarddoctor).
|
|
109
|
+
- **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.
|
|
110
|
+
- **`test_verbose` task** — runs the test suite with Minitest's verbose (`-v`) output.
|
|
111
|
+
- **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.
|
|
112
|
+
- **`console` task** — opens an IRB console with the gem loaded (`bin/console` if present, otherwise `bundle exec irb`).
|
|
113
|
+
- **`git.loki`** — per-repo git tasks (`push`, `pull`, `fetch`), imported from `.loki`.
|
|
114
|
+
|
|
115
|
+
### Changed (continued)
|
|
116
|
+
|
|
117
|
+
- **`release` task** now prompts for confirmation (`Release asgard vX.Y.Z to RubyGems? [y/N]`) unless `-y`/`--yes` is passed, before tagging and pushing.
|
|
118
|
+
|
|
119
|
+
### Fixed (continued)
|
|
120
|
+
|
|
121
|
+
- **`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.
|
|
122
|
+
|
|
123
|
+
### Added (continued 2)
|
|
124
|
+
|
|
125
|
+
- **`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.
|
|
126
|
+
- **`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.
|
|
127
|
+
- **`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.
|
|
128
|
+
- **`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.
|
|
129
|
+
- 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.
|
|
130
|
+
|
|
131
|
+
### Changed (continued 2)
|
|
132
|
+
|
|
133
|
+
- **`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.
|
|
134
|
+
- **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.
|
|
135
|
+
|
|
136
|
+
### Added (continued 3)
|
|
137
|
+
|
|
138
|
+
- **`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](docs/dependencies.md#dynamic-dependencies-proc-form).
|
|
139
|
+
- **`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.
|
|
140
|
+
- **`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.
|
|
141
|
+
|
|
142
|
+
### Changed (continued 3)
|
|
143
|
+
|
|
144
|
+
- **`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.
|
|
145
|
+
|
|
146
|
+
### Removed (continued)
|
|
147
|
+
|
|
148
|
+
- **`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.
|
|
149
|
+
|
|
150
|
+
### Changed (continued 4)
|
|
151
|
+
|
|
152
|
+
- **`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.
|
|
153
|
+
|
|
154
|
+
### Fixed (continued 2)
|
|
155
|
+
|
|
156
|
+
- **`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`.
|
|
157
|
+
|
|
158
|
+
### Added (continued 4)
|
|
159
|
+
|
|
160
|
+
- **`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.
|
|
161
|
+
|
|
162
|
+
### Added (continued 5)
|
|
163
|
+
|
|
164
|
+
- **`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](docs/shell.md#handing-off-with-exec).
|
|
165
|
+
- **`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.
|
|
166
|
+
|
|
167
|
+
### Fixed (continued 3)
|
|
168
|
+
|
|
169
|
+
- **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.
|
|
170
|
+
|
|
171
|
+
### Added (continued 6)
|
|
172
|
+
|
|
173
|
+
- **`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](docs/dependencies.md#dynamic-dependencies-proc-block-form).
|
|
174
|
+
- **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`.
|
|
175
|
+
- **`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.
|
|
176
|
+
|
|
106
177
|
## [0.2.0] - 2026-05-29
|
|
107
178
|
|
|
108
179
|
### Changed
|
data/CLAUDE.md
CHANGED
|
@@ -16,7 +16,7 @@ bundle exec rake build # build .gem into pkg/
|
|
|
16
16
|
bundle exec rake install # install locally
|
|
17
17
|
|
|
18
18
|
# or use the gem's own .loki file:
|
|
19
|
-
asgard
|
|
19
|
+
asgard test_check
|
|
20
20
|
asgard quality
|
|
21
21
|
asgard release
|
|
22
22
|
```
|
|
@@ -72,16 +72,13 @@ depends_on :a, [:b, :c], :d # stages: [[:a], [:b, :c], [:d]]
|
|
|
72
72
|
|
|
73
73
|
**`invoke_command`** (Thor dispatch hook):
|
|
74
74
|
1. Atomically check `@_ran_tasks` Set (with `@_ran_mutex`); return early if already run
|
|
75
|
-
2.
|
|
76
|
-
3. For each
|
|
75
|
+
2. Look up `@_deps[target]` — already the parallel-group stage list `depends_on` built
|
|
76
|
+
3. For each stage group: spawn one thread per task, join; single-task groups run inline
|
|
77
77
|
4. Execute the target task
|
|
78
78
|
|
|
79
|
-
**`_build_dep_graph(stages)`** converts stages to a DAG hash:
|
|
80
|
-
- `[[:a], [:b, :c], [:d]]` → `{ a: [], b: [:a], c: [:a], d: [:b, :c] }`
|
|
81
|
-
|
|
82
79
|
### Dependency Resolution
|
|
83
80
|
|
|
84
|
-
|
|
81
|
+
`depends_on`'s stage list (`[[:a], [:b, :c], [:d]]`) *is* the parallel-execution plan — no separate graph library is needed to run it. Cycle detection is a separate concern, handled once in `validate_deps!` via stdlib `TSort` over the full `@_deps` graph (raises `TSort::Cyclic`, converted to `Asgard::CircularDependencyError`). The thread-safe deduplication (`_ran_tasks` Set + Mutex) ensures each task runs exactly once even when multiple tasks share a common dependency.
|
|
85
82
|
|
|
86
83
|
### Shell Helpers
|
|
87
84
|
|
data/README.md
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
- <strong>Dotenv Support</strong> — load <code>.env</code> files into the environment with <code>dotenv</code><br>
|
|
24
24
|
- <strong>Auto-Discovery</strong> — <code>.loki</code> root marker searched from CWD upward through parent directories<br>
|
|
25
25
|
- <strong>Multi-File Tasks</strong> — split tasks across <code>*.loki</code> files, loaded via <code>import</code> from your <code>.loki</code><br>
|
|
26
|
-
- <strong>Built-in Flags</strong> — <code>--debug</code>, <code>--verbose</code>, and <code>--
|
|
26
|
+
- <strong>Built-in Flags</strong> — <code>--debug</code>, <code>--verbose</code>, <code>--version</code>, and <code>--doctor</code> built-in class options; header/footer DSL for static help text<br>
|
|
27
27
|
</td>
|
|
28
28
|
</tr>
|
|
29
29
|
</table>
|
|
@@ -221,6 +221,29 @@ asgard ci executes:
|
|
|
221
221
|
ci
|
|
222
222
|
```
|
|
223
223
|
|
|
224
|
+
`depends_on` also accepts a `Proc`/lambda (or, equivalently, a block) instead of a fixed list, resolved once every `.loki` file has finished loading rather than immediately — useful when the list can't be known upfront, e.g. "every task whose name ends in `_check`," discovered across several files:
|
|
225
|
+
|
|
226
|
+
```ruby
|
|
227
|
+
depends_on -> { [all_commands.keys.grep(/_check\z/).map(&:to_sym)] }
|
|
228
|
+
def quality = puts "running every *_check task..."
|
|
229
|
+
|
|
230
|
+
depends_on { [all_commands.keys.grep(/_check\z/).map(&:to_sym)] }
|
|
231
|
+
def quality2 = puts "same, as a block..."
|
|
232
|
+
|
|
233
|
+
# do...end for a block spanning multiple statements — braces are for
|
|
234
|
+
# single-line blocks like the two above. The last expression is still what
|
|
235
|
+
# gets returned and validated.
|
|
236
|
+
depends_on do
|
|
237
|
+
checks = all_commands.keys.grep(/_check\z/).sort.map(&:to_sym)
|
|
238
|
+
slow = %i[c_check]
|
|
239
|
+
|
|
240
|
+
[checks - slow]
|
|
241
|
+
end
|
|
242
|
+
def quality3 = puts "same, minus the slow checks, as a multi-line block..."
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
See [Dependencies](https://madbomber.github.io/asgard/dependencies/#dynamic-dependencies-proc-block-form) for the full explanation.
|
|
246
|
+
|
|
224
247
|
---
|
|
225
248
|
|
|
226
249
|
## Variables
|
|
@@ -480,6 +503,33 @@ end
|
|
|
480
503
|
|
|
481
504
|
---
|
|
482
505
|
|
|
506
|
+
## Abbreviated command matching
|
|
507
|
+
|
|
508
|
+
Every task is a Thor command, so you don't have to type the full name — Thor resolves any unambiguous prefix automatically, with no extra code:
|
|
509
|
+
|
|
510
|
+
```ruby
|
|
511
|
+
class Tasks
|
|
512
|
+
desc "Compile the project"
|
|
513
|
+
def build = sh "rake build"
|
|
514
|
+
|
|
515
|
+
desc "Deploy to production"
|
|
516
|
+
def deploy = sh "cap production deploy"
|
|
517
|
+
|
|
518
|
+
desc "Deploy to staging"
|
|
519
|
+
def deploy_staging = sh "cap staging deploy"
|
|
520
|
+
end
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
```bash
|
|
524
|
+
asgard b # same as: asgard build — only task starting with "b"
|
|
525
|
+
asgard depl # Ambiguous command depl matches [deploy, deploy_staging]
|
|
526
|
+
asgard deploy # runs deploy — an exact match always wins, even over a shorter ambiguous prefix
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
This is Thor's own dispatch behavior, not an Asgard feature — it applies to every task in every `.loki` file automatically. When a prefix matches more than one task, Thor lists the candidates instead of guessing; type enough of the name to disambiguate, or use `map` (below) to pin a short name that stays stable even if a later-added task would otherwise make it ambiguous.
|
|
530
|
+
|
|
531
|
+
---
|
|
532
|
+
|
|
483
533
|
## Command aliases
|
|
484
534
|
|
|
485
535
|
`map` creates alternative names for a task:
|
data/bin/asgard
CHANGED
data/doc_tasks.loki
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# dev/doc_tasks.loki
|
|
2
|
+
|
|
3
|
+
# TODO: create common tasks for documentation management
|
|
4
|
+
class Tasks
|
|
5
|
+
|
|
6
|
+
if File.exist?(ENV['RR']+'/.config/tocer/configuration.yml')
|
|
7
|
+
desc "Management table of contents"
|
|
8
|
+
def tocer = sh "tocer help"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
if File.exist?(ENV['RR']+'/mkdocs.yml')
|
|
13
|
+
desc "Documentation builder"
|
|
14
|
+
def doc_builder = sh "mkdocs build"
|
|
15
|
+
|
|
16
|
+
desc "Documentation server startup"
|
|
17
|
+
depends_on :doc_builder
|
|
18
|
+
def doc_server = sh "mkdocs serve", exec: true
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
end
|
data/docs/api.md
CHANGED
|
@@ -111,16 +111,62 @@ import_up "*.loki" # find the nearest ancestor with *.loki files
|
|
|
111
111
|
|
|
112
112
|
| Method | Signature | Description |
|
|
113
113
|
|---|---|---|
|
|
114
|
-
| `depends_on` | `depends_on(*tasks)` | Declare prerequisites for the next `def`. Bare symbols run sequentially; arrays within the splat run as a parallel group. |
|
|
114
|
+
| `depends_on` | `depends_on(*tasks, &block)` | Declare prerequisites for the next `def`. Bare symbols run sequentially; arrays within the splat run as a parallel group. A sole `Proc`/lambda, or a block in place of the splat args, defers resolution to `validate_deps!` (after every `.loki` file has loaded) instead of resolving immediately — see [Dynamic Dependencies](dependencies.md#dynamic-dependencies-proc-block-form). Passing both task arguments and a block raises `Asgard::Error`. |
|
|
115
115
|
| `dotenv` | `dotenv(path = ".env")` | Load the specified `.env` file into `ENV` using the dotenv gem. Silently skipped if the file does not exist. Called at class-load time. |
|
|
116
116
|
| `header` | `header(text)` | Append a line of text shown above the commands list in `asgard help`. Each call adds another line. No-op for per-command help. |
|
|
117
117
|
| `footer` | `footer(text)` | Prepend a line of text shown below the options block in `asgard help`. Each call inserts above the previous lines. No-op for per-command help. |
|
|
118
118
|
| `no_negate` | `no_negate(*names)` | Suppress `[--no-name]` / `[--skip-name]` help entries for one or more boolean class options. Call after the `class_option` declaration. |
|
|
119
|
-
| `sh` | `sh(script, silent: false)` | Instance method. Run a shell command or multiline heredoc. Single-line → `system(script)`; multiline → `system("bash", "-c", script)`. Exits with the command's status on failure. |
|
|
119
|
+
| `sh` | `sh(script, silent: false, exec: false)` | Instance method. Run a shell command or multiline heredoc. Single-line → `system(script)`; multiline → `system("bash", "-c", script)`. Exits with the command's status on failure. With `exec: true`, replaces the asgard process via `Kernel.exec` instead of forking — see [Shell Helpers](shell.md#handing-off-with-exec). |
|
|
120
120
|
| `shebang` | `shebang(interpreter, script, silent: false)` | Instance method. Write `script` to a tempfile and execute it with `interpreter`. See the [Shell Helpers](shell.md) page for the full interpreter table. |
|
|
121
|
-
| `validate_deps!` | `Tasks.validate_deps!` | Build and topologically sort the full dependency graph using
|
|
121
|
+
| `validate_deps!` | `Tasks.validate_deps!` | Build and topologically sort the full dependency graph using stdlib `TSort`. Raises `Asgard::CircularDependencyError` on cycles. Called by `run!` at startup. |
|
|
122
122
|
| `_reset_ran!` | `Tasks._reset_ran!` | Clear the per-invocation task deduplication set. Called by `run!` before dispatching. Thread-safe via Mutex. |
|
|
123
123
|
|
|
124
|
+
### `header` and `footer` Accumulation
|
|
125
|
+
|
|
126
|
+
Both `header` and `footer` accumulate across multiple calls and across multiple imported `.loki` files, but they accumulate in opposite directions by design.
|
|
127
|
+
|
|
128
|
+
**`header` appends** — each call adds to the bottom of the header block:
|
|
129
|
+
|
|
130
|
+
```ruby
|
|
131
|
+
# .loki
|
|
132
|
+
import "quality.loki"
|
|
133
|
+
import "gem_tasks.loki"
|
|
134
|
+
|
|
135
|
+
class Tasks
|
|
136
|
+
header "Project: myapp" # line 1
|
|
137
|
+
header "Root: #{loki_up.parent}" # line 2
|
|
138
|
+
end
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Result in `asgard help`:
|
|
142
|
+
```
|
|
143
|
+
Project: myapp
|
|
144
|
+
Root: /home/user/myapp
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**`footer` prepends** — each call inserts at the top of the footer block:
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
class Tasks
|
|
151
|
+
footer "Github: https://github.com/org/myapp" # ends up second
|
|
152
|
+
footer "Docs: https://myapp.example.com" # ends up first
|
|
153
|
+
end
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Result in `asgard help`:
|
|
157
|
+
```
|
|
158
|
+
Docs: https://myapp.example.com
|
|
159
|
+
Github: https://github.com/org/myapp
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**Why the asymmetry?** When task files are split across multiple `.loki` files, the base `.loki` is loaded first and any imported files are loaded after. With `header`, earlier-loaded content appears first (the project banner stays at the top). With `footer`, later-loaded content appears first — this allows an imported file to inject a note that appears above the base footer rather than after it.
|
|
163
|
+
|
|
164
|
+
In practice: if only one file calls `header` and one file calls `footer`, the direction doesn't matter. The difference is visible only when multiple `.loki` files both call `header` or both call `footer`.
|
|
165
|
+
|
|
166
|
+
Neither `header` nor `footer` appears when running per-command help (`asgard help <task>`).
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
124
170
|
### `depends_on` Argument Shapes
|
|
125
171
|
|
|
126
172
|
```ruby
|
|
@@ -128,8 +174,16 @@ depends_on :build # single sequential dep
|
|
|
128
174
|
depends_on :clean, :build # two sequential deps
|
|
129
175
|
depends_on [:lint, :typecheck] # lint and typecheck run in parallel
|
|
130
176
|
depends_on :setup, [:lint, :build], :test # setup, then lint+build concurrently, then test
|
|
177
|
+
|
|
178
|
+
# A sole Proc/lambda (or, equivalently, a block) defers resolution to
|
|
179
|
+
# validate_deps!, after every .loki file has loaded — must return the same
|
|
180
|
+
# shape the splat form above would; the shape is validated when it resolves.
|
|
181
|
+
depends_on -> { [all_commands.keys.grep(/_check\z/).map(&:to_sym)] }
|
|
182
|
+
depends_on { [all_commands.keys.grep(/_check\z/).map(&:to_sym)] }
|
|
131
183
|
```
|
|
132
184
|
|
|
185
|
+
See [Dynamic Dependencies](dependencies.md#dynamic-dependencies-proc-block-form) for the full explanation of the Proc/block form — why it exists, when it runs, and how errors are reported.
|
|
186
|
+
|
|
133
187
|
---
|
|
134
188
|
|
|
135
189
|
## `Tasks` Built-ins
|
|
@@ -141,11 +195,78 @@ depends_on :setup, [:lint, :build], :test # setup, then lint+build concurrently
|
|
|
141
195
|
| `class_option :debug` | class option | `--debug` flag. Sets `$DEBUG = true` before any task runs. Boolean, default `false`. |
|
|
142
196
|
| `class_option :verbose` | class option | `--verbose` flag. Sets `$VERBOSE = true` before any task runs. Boolean, default `false`. |
|
|
143
197
|
| `class_option :version` | class option | `--version` flag. Handled by `Asgard.run!` before the `.loki` file is loaded — prints `Asgard::VERSION` and exits. `no_negate :version` suppresses the `[--no-version]` / `[--skip-version]` help entries. |
|
|
198
|
+
| `class_option :doctor` | class option | `--doctor` flag. Handled by `Asgard.run!` before the `.loki` file is loaded — runs `Asgard::Doctor.new.run` and exits. `no_negate :doctor` suppresses the `[--no-doctor]` / `[--skip-doctor]` help entries. |
|
|
144
199
|
| `debug?` | Kernel module function | Returns `$DEBUG`. Available everywhere via `Kernel`. |
|
|
145
200
|
| `verbose?` | Kernel module function | Returns `$VERBOSE`. Available everywhere via `Kernel`. |
|
|
146
201
|
|
|
147
202
|
---
|
|
148
203
|
|
|
204
|
+
## `Asgard::Doctor`
|
|
205
|
+
|
|
206
|
+
`asgard --doctor` diagnoses `.loki` resolution, import chains, and task definitions for the current directory. It deliberately bypasses the normal `Tasks` boot sequence used by `run!`, so it can still report findings in 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`.
|
|
207
|
+
|
|
208
|
+
| Method | Signature | Description |
|
|
209
|
+
|---|---|---|
|
|
210
|
+
| `new` | `Asgard::Doctor.new(dir = Dir.pwd)` | Builds a doctor scoped to `dir`. |
|
|
211
|
+
| `run` | `doctor.run` | Runs the full diagnostic pass and prints a report to stdout. Does not raise or exit — callers (like `Asgard.run!`) decide what to do afterward. |
|
|
212
|
+
| `ancestor_markers` | `Asgard::Doctor.ancestor_markers(dir) → Array<String>` | Every `.loki` marker from `dir` up to the filesystem root, nearest first. Unlike `loki_up`, it collects every match instead of stopping at the first, so shadowed ancestor markers can be reported. |
|
|
213
|
+
| `duplicate_methods` | `Asgard::Doctor.duplicate_methods(method_log) → Hash` | Given an `Asgard::Base` subclass's `_method_log`, returns the subset of entries defined at more than one location — same file or different — which is exactly what a silent `def` override looks like. |
|
|
214
|
+
|
|
215
|
+
Findings fall into three levels: `:info` (what was found — the marker used, each import's result), `:warn` (shadowed markers that are never reached), and `:error` (load failures, redefined methods, dependency graph problems).
|
|
216
|
+
|
|
217
|
+
After the findings, the report prints a **Tasks by file** section: every Thor command, grouped by the `.loki` file it's defined in, as `relative/path:line` — a format an editor can jump straight to. A task name defined at more than one location gets every definition annotated inline: the earlier one(s) are marked `OVERRIDDEN by <file>:<line> — never callable`, and the winning (last) definition is marked `active — redefines <file>:<line>`. This is the exact class of bug the flag was built to catch — a later `def` silently replacing an earlier one, with no error anywhere else in the toolchain. The report ends with a one-line summary and a count of problems/warnings, where each overridden task counts as one problem.
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
asgard --doctor
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
The asgard repo ships its own live example of this: `xyzzy.loki` defines a `xyzzy` task, and the top-level `.loki` (which imports it) reopens `Tasks` and defines `xyzzy` again — so the one from `xyzzy.loki` is silently dead. Clone the repo and run `asgard --doctor` from its root to see this for yourself:
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
asgard doctor -- /path/to/asgard
|
|
227
|
+
============================================================
|
|
228
|
+
[INFO] using .loki marker: /path/to/asgard/.loki
|
|
229
|
+
[INFO] import "quality.loki" -> /path/to/asgard/quality.loki
|
|
230
|
+
[INFO] import "gem_tasks.loki" -> /path/to/asgard/gem_tasks.loki
|
|
231
|
+
[INFO] import "git.loki" -> /path/to/asgard/git.loki
|
|
232
|
+
[INFO] import "xyzzy.loki" -> /path/to/asgard/xyzzy.loki
|
|
233
|
+
|
|
234
|
+
Tasks by file:
|
|
235
|
+
|
|
236
|
+
.loki
|
|
237
|
+
xyzzy .loki:15 active — redefines xyzzy.loki:6
|
|
238
|
+
|
|
239
|
+
quality.loki
|
|
240
|
+
test quality.loki:6
|
|
241
|
+
test_verbose quality.loki:15
|
|
242
|
+
quality quality.loki:21
|
|
243
|
+
rubocop quality.loki:34
|
|
244
|
+
rubocop_fix quality.loki:43
|
|
245
|
+
flog_check quality.loki:48
|
|
246
|
+
flay_check quality.loki:82
|
|
247
|
+
reek quality.loki:104
|
|
248
|
+
|
|
249
|
+
gem_tasks.loki
|
|
250
|
+
console gem_tasks.loki:6
|
|
251
|
+
build gem_tasks.loki:16
|
|
252
|
+
install gem_tasks.loki:24
|
|
253
|
+
release gem_tasks.loki:31
|
|
254
|
+
|
|
255
|
+
git.loki
|
|
256
|
+
push git.loki:6
|
|
257
|
+
pull git.loki:9
|
|
258
|
+
fetch git.loki:12
|
|
259
|
+
|
|
260
|
+
xyzzy.loki
|
|
261
|
+
xyzzy xyzzy.loki:6 OVERRIDDEN by .loki:15 — never callable
|
|
262
|
+
============================================================
|
|
263
|
+
1 problem(s), 1 warning(s).
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
The warning is a shadowed ancestor `.loki` marker one directory further up the tree — unrelated to the override, and something you may or may not see depending on what's above the repo on your own machine.
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
149
270
|
## `Asgard::Base` Internal Class Methods
|
|
150
271
|
|
|
151
272
|
These are implementation details exposed for extensibility. Prefer the DSL methods above in normal use.
|
|
@@ -157,7 +278,6 @@ These are implementation details exposed for extensibility. Prefer the DSL metho
|
|
|
157
278
|
| `_running` | `Set` of task name symbols currently executing (started but not yet finished). |
|
|
158
279
|
| `_cond` | Hash of `ConditionVariable` objects keyed by task name; threads wait here when a dep is in-flight. |
|
|
159
280
|
| `_ran_mutex` | `Mutex` protecting `_done`, `_running`, and `_cond` for thread-safe access. |
|
|
160
|
-
| `_build_dep_graph(stages)` | Translates the stage array (from `_deps`) into a Dagwood-compatible hash. |
|
|
161
281
|
|
|
162
282
|
---
|
|
163
283
|
|
|
@@ -167,7 +287,7 @@ These are implementation details exposed for extensibility. Prefer the DSL metho
|
|
|
167
287
|
|
|
168
288
|
1. Sets `$DEBUG` / `$VERBOSE` from `options` if the corresponding flags are present.
|
|
169
289
|
2. Tries to acquire a run token (`acquire_run_token`): if the task is already in `_done`, returns immediately (skip); if it is in `_running`, waits on the `_cond` ConditionVariable until it finishes, then returns (skip); otherwise adds the task to `_running` and continues.
|
|
170
|
-
3. Resolves dependency stages from `_deps
|
|
290
|
+
3. Resolves dependency stages from `_deps` — already the parallel-group execution plan `depends_on` built — and executes each group in order (parallel groups in threads, sequential groups one at a time).
|
|
171
291
|
4. Calls `command.run(self, *args)` to execute the task itself.
|
|
172
292
|
5. In an `ensure` block, adds the task to `_done` and broadcasts on its `_cond` to wake any waiting threads.
|
|
173
293
|
|
|
@@ -197,7 +317,6 @@ end
|
|
|
197
317
|
| Gem | Version | Purpose |
|
|
198
318
|
|---|---|---|
|
|
199
319
|
| [thor](https://github.com/rails/thor) | `~> 1.0` | CLI framework; provides the full task DSL |
|
|
200
|
-
| [dagwood](https://rubygems.org/gems/dagwood) | `~> 1.0` | DAG library for dependency graph resolution and topological sort |
|
|
201
320
|
| [dotenv](https://github.com/bkeepers/dotenv) | `~> 3.0` | `.env` file loading |
|
|
202
321
|
|
|
203
322
|
---
|