asgard 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 839554cd5b16759477245ef561d2769b6d09ea555aa592c8086c2de6bc54450b
4
- data.tar.gz: 419b813aaccbd3afbe5bc70be2ada0ba4b99ce3f5fdcc0b5b246b461bc3bd76f
3
+ metadata.gz: b6045b69a572bacdc07c1ae149f2978000fb690c180cf536512caf4714345317
4
+ data.tar.gz: defa5899f4e66143fe3118d835c60f128534075d2ce4a41652a0bbf54f61e790
5
5
  SHA512:
6
- metadata.gz: 8eb7270b4a6668ea0e111f739de651753e927347dd91d5a85275a6b2e014b01ba4c3fc84261174d97f221bf36ac23a4868f3b56ba3df80041a96bf9fa9115397
7
- data.tar.gz: 5fb697c0ac1dd087b1d8b521680a7a3ec277b633dfee7dab612dfce6da6ce4a7d1377b916049913f082c2f233567ef3a311cfe60b6dc1945c891ac122b1add43
6
+ metadata.gz: 8d27eb2c214be342a64561c8f2287cd05aa48a30e411676dbd69b20f36ec9b706456d7c805c3bfa3a4b64897b8f79831a0a55b96c38b77e3981de551c8737750
7
+ data.tar.gz: 6bbd01f8c90195fc9b7428dbe7b56721a70d9ffe4b881853497df84c68db7d96400cb279ab9006ff3b6841ab85bd5f54d6838f3d887469331dbe04873bd5dca5
data/.loki CHANGED
@@ -3,33 +3,31 @@
3
3
  # Task is pre-defined by the gem — just reopen it to add tasks.
4
4
 
5
5
  class Tasks
6
- var :gem_name, "asgard"
7
- var :version, -> { Asgard::VERSION }
6
+ @@gem_name ||= "asgard".freeze
8
7
 
9
- desc "test", "Run the test suite"
8
+ desc "Run the test suite"
10
9
  def test
11
10
  sh "bundle exec rake test"
12
11
  end
13
12
 
14
- depends_on :test
15
- desc "quality", "Run tests then flog"
13
+ desc "Run all quality gates (tests, RuboCop, Flog)"
16
14
  def quality
17
- sh "flog lib/"
15
+ sh "bundle exec rake quality"
18
16
  end
19
17
 
20
- desc "build", "Build the gem package"
18
+ desc "Build the gem package"
21
19
  def build
22
20
  sh "bundle exec rake build"
23
21
  end
24
22
 
25
23
  depends_on :test
26
- desc "install", "Build and install gem locally"
24
+ desc "Build and install gem locally"
27
25
  def install
28
26
  sh "bundle exec rake install"
29
27
  end
30
28
 
31
29
  depends_on :quality
32
- desc "release", "Release to RubyGems"
30
+ desc "Release to RubyGems"
33
31
  def release
34
32
  sh "bundle exec rake release"
35
33
  end
data/.rubocop.yml ADDED
@@ -0,0 +1,157 @@
1
+ AllCops:
2
+ NewCops: enable
3
+ SuggestExtensions: false
4
+ TargetRubyVersion: 3.2
5
+ Exclude:
6
+ - 'examples/**/*'
7
+ - 'vendor/**/*'
8
+
9
+ # ── Style: disabled cops ───────────────────────────────────────────────────
10
+ Style/StringLiterals:
11
+ Enabled: false
12
+
13
+ Style/StringLiteralsInInterpolation:
14
+ Enabled: false
15
+
16
+ Style/Documentation:
17
+ Enabled: false
18
+
19
+ Style/IfUnlessModifier:
20
+ Enabled: false
21
+
22
+ Style/RescueModifier:
23
+ Enabled: false
24
+
25
+ Style/TrivialAccessors:
26
+ Enabled: false
27
+
28
+ Style/MultilineTernaryOperator:
29
+ Enabled: false
30
+
31
+ Style/SafeNavigation:
32
+ Enabled: false
33
+
34
+ Style/ClassAndModuleChildren:
35
+ Enabled: false
36
+
37
+ Style/RescueStandardError:
38
+ Enabled: false
39
+
40
+ # Both % and format/sprintf are acceptable
41
+ Style/FormatString:
42
+ Enabled: false
43
+
44
+ # String concatenation and interpolation are both acceptable
45
+ Style/StringConcatenation:
46
+ Enabled: false
47
+
48
+ # ── Layout ─────────────────────────────────────────────────────────────────
49
+ Layout/LineLength:
50
+ Max: 140
51
+
52
+ Layout/ExtraSpacing:
53
+ Enabled: false
54
+
55
+ Layout/HashAlignment:
56
+ Enabled: false
57
+
58
+ Layout/FirstHashElementIndentation:
59
+ Enabled: false
60
+
61
+ Layout/EmptyLineAfterGuardClause:
62
+ Enabled: false
63
+
64
+ # ── Naming ─────────────────────────────────────────────────────────────────
65
+ # Single-char params (e, t) are acceptable throughout
66
+ Naming/MethodParameterName:
67
+ Enabled: false
68
+
69
+ Naming/VariableNumber:
70
+ Exclude:
71
+ - 'test/**/*'
72
+
73
+ Naming/RescuedExceptionsVariableName:
74
+ Enabled: false
75
+
76
+ Naming/AccessorMethodName:
77
+ Enabled: false
78
+
79
+ Naming/PredicatePrefix:
80
+ Enabled: false
81
+
82
+ Naming/PredicateMethod:
83
+ AllowedMethods:
84
+ - import
85
+ Exclude:
86
+ - 'test/**/*'
87
+
88
+ # ── Lint ───────────────────────────────────────────────────────────────────
89
+ Lint/EmptyBlock:
90
+ Exclude:
91
+ - 'test/**/*'
92
+
93
+ Lint/UnusedMethodArgument:
94
+ Enabled: false
95
+
96
+ Lint/ConstantDefinitionInBlock:
97
+ Exclude:
98
+ - 'Rakefile'
99
+ - 'test/**/*'
100
+
101
+ # ── Gemspec ────────────────────────────────────────────────────────────────
102
+ Gemspec/DevelopmentDependencies:
103
+ EnforcedStyle: Gemfile
104
+
105
+ Gemspec/RequiredRubyVersion:
106
+ Enabled: false
107
+
108
+ Gemspec/OrderedDependencies:
109
+ Enabled: false
110
+
111
+ # ── Metrics ────────────────────────────────────────────────────────────────
112
+ # Flog is the primary complexity gate — these thresholds catch only
113
+ # egregious outliers without false-positiving every dispatch method.
114
+
115
+ Metrics/MethodLength:
116
+ Max: 35
117
+ CountAsOne:
118
+ - heredoc
119
+ - array
120
+ - hash
121
+ Exclude:
122
+ - 'test/**/*'
123
+
124
+ Metrics/AbcSize:
125
+ Max: 40
126
+ Exclude:
127
+ - 'test/**/*'
128
+
129
+ Metrics/ClassLength:
130
+ Max: 300
131
+ Exclude:
132
+ - 'test/**/*'
133
+
134
+ Metrics/ModuleLength:
135
+ Max: 200
136
+ Exclude:
137
+ - 'test/**/*'
138
+
139
+ Metrics/CyclomaticComplexity:
140
+ Max: 20
141
+ Exclude:
142
+ - 'test/**/*'
143
+
144
+ Metrics/PerceivedComplexity:
145
+ Max: 20
146
+ Exclude:
147
+ - 'test/**/*'
148
+
149
+ Metrics/ParameterLists:
150
+ Enabled: false
151
+
152
+ Metrics/BlockLength:
153
+ Max: 60
154
+ Exclude:
155
+ - 'Rakefile'
156
+ - '*.gemspec'
157
+ - 'test/**/*'
data/CHANGELOG.md CHANGED
@@ -5,25 +5,63 @@ 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
- ## [Unreleased]
8
+ ## [0.3.0] - Unreleased
9
9
 
10
- ### Fixed (round 2)
10
+ ### Added
11
+
12
+ - **`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`.
13
+ - **`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`.
14
+ - **`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.
15
+ - **`debug?` and `verbose?` Kernel module functions** — thin wrappers around `$DEBUG` and `$VERBOSE`, available everywhere in Ruby as `module_function` on `Kernel`. Set via `--debug` / `--verbose` CLI flags or directly via `$DEBUG` / `$VERBOSE`.
16
+ - **`env(name, default = nil)` Kernel method** — fetches a system environment variable by symbol or string name, upcasing the key automatically. `env(:port, "3000")` returns `"3000"` when `PORT` is unset; `env(:api_key)` raises `KeyError` when `API_KEY` is missing and no default is provided. Accepts both `env(:port)` and `env("PORT")` forms. Cleaner than `ENV['PORT']` in task bodies.
17
+ - **Verbose/debug feedback for `import` and `import_up`** — when `verbose?` is true, each file loaded is printed to stderr. When `debug?` is true, already-loaded files are also reported (with an "already loaded" suffix), and `import_up` reports when a file is not found.
18
+ - **RuboCop lint gate** — RuboCop is now a first-class quality gate alongside tests and Flog. Added `rubocop` to the Gemfile, a `.rubocop.yml` tuned for this codebase (Ruby 3.2 target, relaxed `Metrics` thresholds consistent with Flog as the primary complexity gate, `examples/` excluded), and `rake rubocop` / `rake rubocop_fix` tasks backed by a `tmp/rubocop_cache` directory for fast re-runs.
19
+ - **Expanded `rake quality` task** — `quality` now runs three independent gates (tests + coverage, RuboCop, Flog) and prints a formatted pass/fail summary table after all gates complete, so every failure is visible in a single run rather than stopping at the first.
20
+ - **`rake flog_check` task** — replaces the bare `flog lib/` call with a structured task that enforces per-method thresholds (warn ≥20, fail ≥50), lists warnings and failures in separate sections, and exits non-zero only when the failure threshold is breached.
21
+ - **Single-argument `desc` shorthand** — `desc` now accepts one string (the description) with the usage string omitted. The usage defaults to the method name, eliminating the redundant first argument for the common case:
22
+ ```ruby
23
+ desc "Run the test suite" # usage defaults to "test"
24
+ def test = sh "bundle exec rake test"
25
+ ```
26
+ The two-argument form (`desc "usage", "description"`) still works unchanged and is still required when the usage string differs from the method name (e.g. `desc "build NAME", "Build an artifact"`).
27
+ - **`default_task` override warning** — `Asgard::Base` now overrides Thor's `default_task` to warn to stderr when a second call would silently replace the first. The warning includes the task name, file, and line number for both the original declaration and the override, making accidental cross-file clobbering visible immediately.
28
+ - **`examples/env_usage.loki` and `examples/.env`** — demonstrate the `env()` Kernel helper, including the default-fallback form (`env(:log_level, "info")`) for variables absent from the environment. Uses `loki_up(".env")` so `dotenv` locates the `.env` file correctly regardless of which directory `asgard` is invoked from.
29
+ - **`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.
30
+ - **`status` task in `kitchen_sink.loki`** — demonstrates `debug?` and `verbose?` predicates with conditional output; shows `--debug` printing `$DEBUG`, `$VERBOSE`, and the full options hash.
31
+ - **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 ||=`.
32
+
33
+ ### Removed
34
+
35
+ - **`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`.
36
+ - **`import` DSL method** — `import(mod)` was a one-line alias for Ruby's built-in `include`. Callers can use `include` directly.
37
+ - **`--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`.
38
+ - **`Asgard.load_loki(dir)`** — replaced by `import` with glob support. Callers can use `import(File.join(dir, "*.loki"))` directly.
39
+ - **`debug?` / `verbose?` private methods on `Tasks`** — removed as redundant. The identical `module_function` versions on `Kernel` are available everywhere, including inside task bodies.
40
+
41
+ ### Refactored
11
42
 
12
- - **Subcommand deps not validated at startup** — `run!` only called `Tasks.validate_deps!`, so circular dependencies and undefined dep names in subcommand groups (classes that inherit from `Asgard::Base` or `Tasks`) were silently ignored. `run!` now snapshots `Asgard::Base.subclasses` before loading task files and validates every newly defined subclass alongside `Tasks`.
13
- - **Parallel dep thread orphaned on exception** — when a parallel dep group contained one fast-failing task and one slow task, `threads.each(&:join)` re-raised the first thread's exception and abandoned the remaining threads. The slow thread continued running unsupervised after the caller saw the exception. The join loop now collects all thread exceptions before re-raising the first, ensuring every thread completes before execution exits the group.
14
- - **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 the arity of every dep task via `instance_method.parameters` and raises `Asgard::Error` at startup with the task name and required argument count.
15
- - **Orphaned `depends_on` silently discarded** — a `depends_on` declaration at the end of a class body (or `.loki` file) 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.
43
+ - **`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.
44
+ - **`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.
16
45
 
17
46
  ### Fixed
18
47
 
19
- - **Parallel dep race condition** — when two parallel tasks shared a common dependency, the second thread could start before the shared dep finished executing. `_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 rather than skipping it; the `ensure` block broadcasts completion whether the task succeeds or raises.
20
- - **`depends_on` silently dropped before `var` or `no_commands`** Thor uses an integer counter for `@no_commands` that resets to `0` (truthy in Ruby) after any `no_commands` block. The `method_added` guard now checks `@usage` instead: pending deps are only consumed when a command-defining method is added (one preceded by `desc`), so `var` declarations and `no_commands` helpers placed between `depends_on` and `def` no longer silently steal the dependency.
48
+ - **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.
49
+ - **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`.
50
+ - **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.
51
+ - **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.
52
+ - **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.
53
+ - **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.
54
+ - **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.
55
+ - **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.
56
+ - **`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.
21
57
  - **`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`.
22
- - **`var` lambdas re-evaluated on every access** — the accessor method now caches its result in a per-instance variable on first call. Lambdas used for computed values (e.g. `` -> { `git describe --tags`.strip } ``) now run exactly once per instance.
23
58
 
24
59
  ### Changed
25
60
 
26
- - **`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. `run!` catches this alongside `CircularDependencyError` and exits with a clean message.
61
+ - **`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.
62
+ - **`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.
63
+ - **`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.
64
+ - **`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`.
27
65
 
28
66
  ## [0.2.0] - 2026-05-29
29
67
 
@@ -102,7 +140,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
102
140
  - 100% test coverage enforced via SimpleCov (95% minimum threshold)
103
141
  - Quality task in `.loki` runs flog after tests
104
142
 
105
- [Unreleased]: https://github.com/MadBomber/asgard/compare/v0.2.0...HEAD
143
+ [0.3.0]: https://github.com/MadBomber/asgard/compare/v0.2.2...HEAD
106
144
  [0.2.0]: https://github.com/MadBomber/asgard/compare/v0.1.2...v0.2.0
107
145
  [0.1.2]: https://github.com/MadBomber/asgard/compare/v0.1.1...v0.1.2
108
146
  [0.1.1]: https://github.com/MadBomber/asgard/compare/v0.1.0...v0.1.1
data/CLAUDE.md CHANGED
@@ -29,11 +29,10 @@ Single test: `ruby -Ilib:test test/test_asgard.rb`
29
29
 
30
30
  `bin/asgard` → `Asgard.run!(ARGV)` (`lib/asgard.rb`):
31
31
  1. Walk CWD + ancestors for `.loki` (marker only, not a task file)
32
- 2. If `--auto-load` is in argv: glob + sort `*.loki` files and load each alphabetically
33
- 3. Load `.loki` itself last
34
- 4. `Tasks.validate_deps!` — build full dep graph, raise `CircularDependencyError` if cyclic
35
- 5. `Tasks._reset_ran!`clear execution tracking
36
- 6. `Tasks.start(argv)` — Thor dispatches the command
32
+ 2. Load `.loki` any sibling `*.loki` files are loaded only if `.loki` calls `import`
33
+ 3. `Tasks.validate_deps!` — build full dep graph, raise `CircularDependencyError` if cyclic
34
+ 4. `Tasks._reset_ran!` — clear execution tracking
35
+ 5. `Tasks.start(argv)`Thor dispatches the command
37
36
 
38
37
  ### Core Classes
39
38
 
@@ -71,8 +70,6 @@ Do not define `_`-prefixed methods in user `.loki` files — that namespace is r
71
70
  depends_on :a, [:b, :c], :d # stages: [[:a], [:b, :c], [:d]]
72
71
  ```
73
72
 
74
- **`var`** stores values or lambdas in `@_vars` and creates an instance method that evaluates the lambda once on first access.
75
-
76
73
  **`invoke_command`** (Thor dispatch hook):
77
74
  1. Atomically check `@_ran_tasks` Set (with `@_ran_mutex`); return early if already run
78
75
  2. Resolve `@_deps` stages → `_build_dep_graph` → `Dagwood::DependencyGraph#parallel_order`
@@ -91,6 +88,19 @@ Dagwood topologically sorts the DAG and returns parallel groups. The thread-safe
91
88
  - `sh(script, silent: false)` — single-line strings use `system(script)`; multi-line strings pipe through `bash -c`; exits with the command's status on failure
92
89
  - `shebang(interpreter, script)` — writes script to a tempfile and executes with the named interpreter (`:python3`, `:node`, `:ruby`, `:perl`, `:bash`, etc.)
93
90
 
91
+ ### Kernel Methods
92
+
93
+ Asgard adds the following `module_function` methods to `Kernel`, making them available everywhere in `.loki` files without any prefix or require:
94
+
95
+ | Method | Description |
96
+ |--------|-------------|
97
+ | `env(name, default = nil)` | Fetch a system environment variable by symbol or string; name is upcased automatically. Raises `KeyError` when missing and no default given. |
98
+ | `loki_up(name = ".loki")` | Walk CWD and ancestors for a file by name; returns absolute path or `nil`. |
99
+ | `import(path)` | Load a `.loki` file or glob of `.loki` files, idempotently. |
100
+ | `import_up(name = ".loki")` | Combine `loki_up` and `import` — find and load in one call. |
101
+ | `debug?` | Returns `$DEBUG`. |
102
+ | `verbose?` | Returns `$VERBOSE`. |
103
+
94
104
  ## Testing
95
105
 
96
106
  All tests are in `test/test_asgard.rb` (one file, ~11 named classes). SimpleCov minimum is 95%; the Rakefile configures this with a prelude that loads coverage before the library.
@@ -103,7 +113,7 @@ A `.loki` file is plain Ruby that reopens `Tasks`:
103
113
 
104
114
  ```ruby
105
115
  class Tasks
106
- var :gem_name, "asgard"
116
+ @@gem_name ||= "asgard".freeze
107
117
 
108
118
  desc "test", "Run tests"
109
119
  def test = sh "bundle exec rake test"
@@ -114,4 +124,4 @@ class Tasks
114
124
  end
115
125
  ```
116
126
 
117
- By default, only `.loki` is loaded. Pass `--auto-load` to `asgard` to also load all `*.loki` files in the same directory alphabetically before `.loki` is loaded. The bare `.loki` file serves as the project root marker its content is always loaded last.
127
+ Only `.loki` is loaded by default. The bare `.loki` file is the project root marker and always controls what else gets loaded call `import "*.loki"` (or any glob/path) at the top to pull in sibling task files.