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.
data/docs/variables.md CHANGED
@@ -1,122 +1,338 @@
1
1
  # Variables
2
2
 
3
- `var` declares a named value that is available to all tasks in the class as a method call. Values can be static or lazily evaluated.
3
+ Asgard task files are plain Ruby. Shared configuration values are declared using Ruby class variables (`@@name`) at the top of the `Tasks` class body.
4
4
 
5
5
  ---
6
6
 
7
- ## Static Value
7
+ ## Ruby Variable Types
8
8
 
9
- Pass the value directly as the second argument:
9
+ Ruby has four kinds of variables plus constants, each with a distinct prefix and scope. Understanding the differences matters because tasks are instance methods — the wrong variable type will simply not be visible where you expect it.
10
+
11
+ | Kind | Prefix | Example | Scope |
12
+ |------|--------|---------|-------|
13
+ | Local | none | `count = 0` | The method or block it is defined in only |
14
+ | Instance | `@` | `@name = "myapp"` | One specific object instance |
15
+ | Class | `@@` | `@@name = "myapp"` | The class and all its subclasses |
16
+ | Global | `$` | `$DEBUG = true` | Everywhere in the process |
17
+ | Constant | uppercase first letter | `APP = "myapp"` | Everywhere (namespaced to where defined) |
18
+
19
+ ### Local variables
20
+
21
+ ```ruby
22
+ def build
23
+ output_dir = "dist" # only visible inside this method
24
+ sh "rake build OUTDIR=#{output_dir}"
25
+ end
26
+ ```
27
+
28
+ `output_dir` disappears when the method returns. It cannot be seen by any other task.
29
+
30
+ ### Instance variables (`@`)
10
31
 
11
32
  ```ruby
12
33
  class Tasks
13
- var :app, "myapp"
14
- var :env, "production"
15
- var :port, 3000
34
+ @app = "myapp" # class instance variable — lives on the Tasks class object
16
35
 
17
- desc "info", "Print app info"
18
- def info
19
- puts "#{app} running on port #{port} in #{env}"
36
+ def build
37
+ puts @app # nil — this @app is on the instance, not the class
20
38
  end
21
39
  end
22
40
  ```
23
41
 
42
+ `@` in the class body sets a variable on the class object itself, not on the instances that run tasks. Inside a task method body, `@name` refers to the instance, which is a different object. They do not share state.
43
+
44
+ `@` inside a method is useful for memoization within a single task invocation:
45
+
46
+ ```ruby
47
+ def version
48
+ @version ||= `git describe --tags`.strip # computed once, cached for this run
49
+ end
50
+ ```
51
+
52
+ ### Class variables (`@@`)
53
+
54
+ ```ruby
55
+ class Tasks
56
+ @@app = "myapp" # visible in every task method and every subclass
57
+
58
+ def build
59
+ puts @@app # "myapp"
60
+ end
61
+ end
62
+ ```
63
+
64
+ `@@` is shared across the class body, all instance methods, and all subclasses (including Thor subcommand classes). This makes it the right choice for configuration values in Asgard task files.
65
+
66
+ ### Global variables (`$`)
67
+
68
+ ```ruby
69
+ $DEBUG = true # visible everywhere in the Ruby process
70
+ $VERBOSE = true
71
+ ```
72
+
73
+ Asgard uses `$DEBUG` and `$VERBOSE` internally — they are set by the `--debug` and `--verbose` CLI flags. Avoid declaring your own `$` variables in `.loki` files; they affect the entire Ruby process including all loaded gems.
74
+
75
+ Several important Ruby globals you may encounter in task files:
76
+
77
+ | Variable | Purpose |
78
+ |----------|---------|
79
+ | `$stdout` / `$STDOUT` | Standard output stream — `puts` writes here |
80
+ | `$stderr` / `$STDERR` | Standard error stream — `warn` writes here |
81
+ | `$DEBUG` | Enables debug mode when `true` |
82
+ | `$VERBOSE` | Enables verbose warnings when `true` |
83
+ | `$PROGRAM_NAME` / `$0` | The name of the running script |
84
+
85
+ The uppercase versions (`$STDOUT`, `$STDERR`) are the original stream objects. The lowercase versions (`$stdout`, `$stderr`) are reassignable aliases — libraries sometimes redirect them temporarily to capture output. In task files, use `$stdout.puts` or `$stderr.puts` when you need explicit stream control; use plain `puts` and `warn` for normal output.
86
+
87
+ ### Constants
88
+
89
+ Any name that begins with an uppercase letter is a constant in Ruby. Constants are available everywhere — inside methods, across files, and across classes — without any prefix:
90
+
91
+ ```ruby
92
+ APP_NAME = "myapp".freeze
93
+ MAX_RETRIES = 3
94
+ BASE_URL = "https://example.com".freeze
95
+
96
+ class Tasks
97
+ desc "Deploy the app"
98
+ def deploy
99
+ puts "Deploying #{APP_NAME} to #{BASE_URL}"
100
+ sh "cap deploy"
101
+ end
102
+ end
103
+ ```
104
+
105
+ The convention is `ALL_CAPS_WITH_UNDERSCORES` for values that are truly fixed. Class and module names are also constants — `Tasks`, `Asgard`, `String`, `Integer` all start with an uppercase letter.
106
+
107
+ Ruby will issue a warning if you reassign a constant but will not prevent it. Use `.freeze` to make the value itself immutable:
108
+
109
+ ```ruby
110
+ APP_NAME = "myapp".freeze # value cannot be mutated; reassignment still warns
111
+ ```
112
+
113
+ **Constants vs `@@` class variables in task files:**
114
+
115
+ | | Constant | `@@` class variable |
116
+ |---|---|---|
117
+ | Accessible in task methods | Yes | Yes |
118
+ | Accessible in subcommand subclasses | Yes | Yes |
119
+ | Visible outside the class | Yes — anywhere | Only within the class hierarchy |
120
+ | Reassignment warning | Yes | No |
121
+ | Convention | `ALL_CAPS` | `snake_case` |
122
+
123
+ For fixed values that will never change — app names, version strings, URLs, port numbers — constants are often the clearest choice. For values that might reasonably vary across environments or be overridden in a different `.loki` file, `@@` with `||=` is more flexible.
124
+
24
125
  ---
25
126
 
26
- ## Lazy Lambda
127
+ ## Strings and Interpolation
128
+
129
+ ### Always use double quotes
27
130
 
28
- Pass a lambda (or proc) to defer evaluation until the variable is first accessed. The lambda is called once and its return value is used for all subsequent accesses:
131
+ Ruby supports both single and double quoted strings. In Asgard task files, always use double quotes:
132
+
133
+ ```ruby
134
+ @@app ||= "myapp".freeze # correct
135
+ sh "bundle exec rake test" # correct
136
+ ```
137
+
138
+ Single-quoted strings look similar but behave differently — they do not support interpolation or escape sequences. Mixing the two styles adds confusion for no benefit. Double quotes work everywhere single quotes do, and more.
139
+
140
+ ### String interpolation
141
+
142
+ Embedding a variable's value inside a string uses the `#{}` syntax. Everything inside the braces is Ruby code — the result is converted to a string and inserted in place:
29
143
 
30
144
  ```ruby
31
145
  class Tasks
32
- var :version, -> { `git describe --tags`.strip }
33
- var :sha, -> { `git rev-parse --short HEAD`.strip }
146
+ @@app ||= "myapp".freeze
147
+ @@env ||= "production".freeze
34
148
 
35
- desc "tag", "Create a release tag"
36
- def tag = sh "git tag v#{version}"
149
+ desc "Deploy the app"
150
+ def deploy
151
+ sh "cap #{@@env} deploy APP=#{@@app}"
152
+ end
153
+ end
154
+ ```
37
155
 
38
- desc "info", "Show version info"
39
- def info = puts "#{version} (#{sha})"
156
+ Any Ruby expression works inside `#{}`:
157
+
158
+ ```ruby
159
+ puts "build started at #{Time.now}"
160
+ sh "puma -p #{env(:port, '3000').to_i + 1}"
161
+ sh "git tag #{@@app}-#{`git describe --tags`.strip}"
162
+ ```
163
+
164
+ Interpolation only works inside double-quoted strings. This is the primary reason Asgard tasks use double quotes exclusively — shell commands almost always need to embed variable values.
165
+
166
+ ### Multi-line strings
167
+
168
+ For shell scripts with multiple lines, use a heredoc. The `~` modifier strips leading indentation so the script aligns with your code:
169
+
170
+ ```ruby
171
+ desc "Bootstrap the project"
172
+ def bootstrap
173
+ sh <<~SHELL
174
+ bundle install
175
+ rails db:create db:migrate
176
+ echo "#{@@app} ready on #{env(:port, '3000')}"
177
+ SHELL
40
178
  end
41
179
  ```
42
180
 
43
- !!! tip
44
- Lazy lambdas are ideal for values that require a shell call or file read — they only pay the cost if the variable is actually used in the task being run.
181
+ Interpolation works inside heredocs the same as in double-quoted strings.
45
182
 
46
183
  ---
47
184
 
48
- ## Block Syntax
185
+ ## System Environment Variables
49
186
 
50
- You can also use a block instead of a lambda:
187
+ System environment variables are set outside Ruby — in the shell, a CI environment, or a `.env` file — and are accessed inside tasks via the `env` Kernel method:
51
188
 
52
189
  ```ruby
53
190
  class Tasks
54
- var(:build_dir) { "builds/#{app}" }
55
- var(:app) { "myapp" }
191
+ desc "Start the server"
192
+ def start
193
+ sh "puma -p #{env(:port, '3000')} -e #{env(:rack_env, 'development')}"
194
+ end
56
195
 
57
- desc "build", "Compile into build_dir"
58
- def build = sh "rake build OUTDIR=#{build_dir}"
196
+ desc "Deploy the app"
197
+ def deploy
198
+ sh "cap #{env(:deploy_target)} deploy" # raises KeyError if DEPLOY_TARGET is not set
199
+ end
59
200
  end
60
201
  ```
61
202
 
203
+ `env` accepts a symbol or string and converts it to an uppercase `ENV` key automatically:
204
+
205
+ | Call | Equivalent | Behaviour |
206
+ |------|-----------|-----------|
207
+ | `env(:port, "3000")` | `ENV.fetch("PORT", "3000")` | Returns `"3000"` if `PORT` is unset |
208
+ | `env(:api_key)` | `ENV.fetch("API_KEY")` | Raises `KeyError` if `API_KEY` is unset |
209
+ | `env("DATABASE_URL")` | `ENV.fetch("DATABASE_URL")` | Raises `KeyError` if unset |
210
+ | `env("database_url")` | `ENV.fetch("DATABASE_URL")` | Same — name is always upcased |
211
+
62
212
  !!! note
63
- The block form and the lambda form behave identically both are stored as callables and invoked on first access.
213
+ All environment variable values are strings. Convert to other types explicitly: `env(:port, "3000").to_i`, `env(:debug, "false") == "true"`.
214
+
215
+ Use `dotenv` to load a `.env` file before tasks run — see [Environment](environment.md).
64
216
 
65
217
  ---
66
218
 
67
- ## Accessing Variables from Tasks
219
+ ## The Pattern
68
220
 
69
- Variables are available as method calls from within any task body (or other method) in the same class. They are defined using `no_commands`, so they appear neither in `--help` output nor as CLI commands:
221
+ ```ruby
222
+ class Tasks
223
+ @@app ||= "myapp".freeze
224
+ @@port ||= 3000
225
+ @@env ||= "production".freeze
226
+
227
+ desc "Print app info"
228
+ def info
229
+ puts "#{@@app} running on port #{@@port} in #{@@env}"
230
+ end
231
+ end
232
+ ```
233
+
234
+ **Why `||=` instead of `=`?**
235
+ Because multiple `.loki` files reopen the same `class Tasks`. Using `||=` means the first file to declare a value wins, and subsequent files that reopen `Tasks` won't accidentally overwrite it.
236
+
237
+ **Why `.freeze`?**
238
+ It prevents mutation of the value (e.g. `@@app << "-extra"` raises a `FrozenError`). Numbers and symbols are already frozen. Use `.freeze` on strings, arrays, and hashes.
239
+
240
+ **Why `@@` instead of `@`?**
241
+ A single `@` in the class body sets a class instance variable — it lives on the `Tasks` class object and is **not** accessible inside task method bodies. `@@` is a class variable and is visible everywhere: in all instance methods, and in any subclass (including Thor subcommand classes).
242
+
243
+ ---
244
+
245
+ ## Sharing Values Across Subcommands
246
+
247
+ Class variables are visible in subclasses, which makes them the right choice when you have Thor subcommands defined in separate classes:
70
248
 
71
249
  ```ruby
250
+ # config.loki
72
251
  class Tasks
73
- var :app, "myapp"
74
- var :version, -> { `git describe --tags`.strip }
75
- var :pkg, -> { "pkg/#{app}-#{version}.gem" }
252
+ @@app ||= "myapp".freeze
253
+ end
76
254
 
77
- desc "build", "Build the gem"
78
- def build = sh "gem build #{app}.gemspec"
255
+ # deploy.loki
256
+ class DeployCommands < Tasks
257
+ desc "Deploy to production"
258
+ def production
259
+ sh "cap production deploy APP=#{@@app}" # @@app is visible here
260
+ end
261
+ end
79
262
 
80
- desc "push", "Push the gem to RubyGems"
81
- def push = sh "gem push #{pkg}"
263
+ class Tasks
264
+ desc "deploy SUBCOMMAND", "Deployment tasks"
265
+ subcommand "deploy", DeployCommands
82
266
  end
83
267
  ```
84
268
 
85
- Variables can reference other variables in their lambdas as long as the referenced variable is also defined with `var` on the same class.
269
+ ---
270
+
271
+ ## Computed Values
272
+
273
+ For values that require a shell call, file read, or any runtime computation, define a method instead:
274
+
275
+ ```ruby
276
+ class Tasks
277
+ def version = `git describe --tags`.strip
278
+ def sha = `git rev-parse --short HEAD`.strip
279
+
280
+ desc "Show version info"
281
+ def info = puts "#{version} (#{sha})"
282
+ end
283
+ ```
284
+
285
+ Methods defined without `desc` do not appear in `--help` output or as CLI commands. If you need memoization (the computation is expensive and called multiple times), use `||=` on an instance variable inside the method:
286
+
287
+ ```ruby
288
+ class Tasks
289
+ def version
290
+ @version ||= `git describe --tags`.strip
291
+ end
292
+ end
293
+ ```
86
294
 
87
295
  ---
88
296
 
89
- ## Sharing Variables Across Files
297
+ ## Sharing Values Across Files
90
298
 
91
- Because all `.loki` files reopen the same `class Tasks`, variables declared in one file are available in all other files loaded in the same session:
299
+ Because all `.loki` files reopen the same `class Tasks`, a `@@` variable declared in one file is available in all other files loaded in the same session:
92
300
 
93
301
  ```ruby
94
302
  # config.loki
95
303
  class Tasks
96
- var :app, "myapp"
97
- var :port, 8080
304
+ @@app ||= "myapp".freeze
305
+ @@port ||= 8080
98
306
  end
99
307
 
100
308
  # deploy.loki
101
309
  class Tasks
102
- desc "deploy", "Deploy the app"
103
- def deploy = sh "cap deploy APP=#{app} PORT=#{port}"
310
+ desc "Deploy the app"
311
+ def deploy = sh "cap deploy APP=#{@@app} PORT=#{@@port}"
104
312
  end
105
313
  ```
106
314
 
107
315
  ---
108
316
 
109
- ## Naming Caution
317
+ ## Naming Conventions
110
318
 
111
- !!! warning
112
- Do not use `var` names that conflict with built-in Ruby method names, Thor DSL method names, or Asgard's own built-in methods. In particular, avoid naming a variable `version` — `Tasks` already defines `_version` (the `--version` flag handler), and a `var :version` would collide with that namespace and produce confusing behavior. Use a more specific name like `app_version` or `gem_version` instead.
319
+ Class variable names use `snake_case` — the standard Ruby convention for variables and methods:
113
320
 
114
321
  ```ruby
115
- # Avoid this — conflicts with the built-in version infrastructure:
116
- # var :version, -> { "1.0.0" }
117
-
118
- # Use this instead:
119
- var :app_version, -> { `git describe --tags`.strip }
322
+ class Tasks
323
+ @@app_name ||= "myapp".freeze
324
+ @@deploy_host ||= "production.example.com".freeze
325
+ @@max_workers ||= 4
326
+ end
120
327
  ```
121
328
 
122
- Other names to avoid: `options`, `class_options`, `shell`, `invoke`, `invoke_command`.
329
+ Multi-word names are separated by underscores, not camelCase or hyphens.
330
+
331
+ ---
332
+
333
+ ## Naming Caution
334
+
335
+ !!! warning
336
+ Avoid `@@` names that conflict with built-in Ruby or Thor internals. Safe practice: use descriptive names that are unlikely to clash.
337
+
338
+ Names to avoid: `options`, `shell`, `invoke`, `command`, `args`.
data/examples/.env ADDED
@@ -0,0 +1,4 @@
1
+ APP_NAME=my_app
2
+ PORT=4000
3
+ API_KEY=secret-key-abc123
4
+ DATABASE_URL=postgres://localhost/my_app_dev
data/examples/.loki CHANGED
@@ -1,2 +1,24 @@
1
- # The .loki file can be empty if there are *.loki files define the tasks
2
- # they will be auto loaded.
1
+ # examples/.loki root task file for the examples directory.
2
+ #
3
+ # Asgard loads only this file by default. Everything else must be
4
+ # explicitly pulled in with import.
5
+ #
6
+ # import(path)
7
+ # Load a specific file or a glob of files, idempotently. Absolute
8
+ # paths, relative paths, and globs are all accepted.
9
+ #
10
+ # import_up(name)
11
+ # Walk CWD and every ancestor directory looking for `name`, then
12
+ # import it. Useful inside a nested task file that needs to pull in
13
+ # a shared config from the project root without knowing how deep it is.
14
+ #
15
+ # loki_up(name)
16
+ # Same walk as import_up but just returns the absolute path (or nil)
17
+ # without loading it — handy when you need the path for another purpose.
18
+
19
+ # Load all *.loki files in this directory (kitchen_sink, concurrent, etc.)
20
+ import "*.loki"
21
+
22
+ # Load a task file from a subdirectory.
23
+ # import also accepts a direct path, not just a glob.
24
+ import "subdir/import_demo.loki"
@@ -21,12 +21,12 @@ $stdout.sync = true # flush every print immediately across all threads
21
21
  CONCURRENT_REPS = 10 # how many times each worker prints its character
22
22
 
23
23
  class Tasks
24
- desc "start", "Print start marker"
24
+ desc "Print start marker"
25
25
  def start
26
26
  puts "starting demo of concurrent task execution ..."
27
27
  end
28
28
 
29
- desc "worker_a", "Print 'A' repeatedly with random delays"
29
+ desc "Print 'A' repeatedly with random delays"
30
30
  def worker_a
31
31
  CONCURRENT_REPS.times do
32
32
  print "A"
@@ -34,7 +34,7 @@ class Tasks
34
34
  end
35
35
  end
36
36
 
37
- desc "worker_b", "Print 'B' repeatedly with random delays"
37
+ desc "Print 'B' repeatedly with random delays"
38
38
  def worker_b
39
39
  CONCURRENT_REPS.times do
40
40
  print "B"
@@ -42,7 +42,7 @@ class Tasks
42
42
  end
43
43
  end
44
44
 
45
- desc "worker_c", "Print 'C' repeatedly with random delays"
45
+ desc "Print 'C' repeatedly with random delays"
46
46
  def worker_c
47
47
  CONCURRENT_REPS.times do
48
48
  print "C"
@@ -51,7 +51,7 @@ class Tasks
51
51
  end
52
52
 
53
53
  depends_on :start, [:worker_a, :worker_b, :worker_c]
54
- desc "finish", "Print end marker after all workers complete"
54
+ desc "Print end marker after all workers complete"
55
55
  def finish
56
56
  puts "\nfini - the end of concurrent task demo"
57
57
  end
@@ -39,7 +39,7 @@ class DBCommands < Tasks
39
39
  # depends_on chains within the subcommand group:
40
40
  # rollback → migrate → seed → reset
41
41
  depends_on :rollback, :migrate, :seed
42
- desc "reset", "Rollback all migrations, re-migrate, and reseed"
42
+ desc "Rollback all migrations, re-migrate, and reseed"
43
43
  def reset
44
44
  puts "Database reset complete."
45
45
  end
@@ -54,7 +54,7 @@ class DBCommands < Tasks
54
54
  asgard db console\x5
55
55
  asgard db console --env staging
56
56
  DESC
57
- desc "console", "Open an interactive database console"
57
+ desc "Open an interactive database console"
58
58
  option :env, type: :string, default: "development",
59
59
  enum: %w[development staging production],
60
60
  desc: "Environment to connect to"
@@ -62,7 +62,7 @@ class DBCommands < Tasks
62
62
  puts "Opening #{options[:env]} database console..."
63
63
  end
64
64
 
65
- desc "status", "Show applied and pending migrations"
65
+ desc "Show applied and pending migrations"
66
66
  def status
67
67
  puts "Checking migration status..."
68
68
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+ # Demonstrates the env() Kernel helper for reading system environment variables.
3
+ #
4
+ # env() is cleaner than ENV[] — it accepts symbols or strings, upcases
5
+ # automatically, and raises KeyError when a required variable is missing
6
+ # rather than silently returning nil.
7
+ #
8
+ # Run from examples/ or any subdirectory — loki_up locates .env automatically:
9
+ # asgard env_demo
10
+
11
+ class Tasks
12
+ # loki_up walks CWD and every ancestor until it finds .env, returning
13
+ # its absolute path. This works regardless of which directory asgard
14
+ # is run from — no hardcoded relative path needed.
15
+ dotenv loki_up(".env") || ".env"
16
+
17
+ desc "Print environment variable values resolved via env()"
18
+ def env_demo
19
+ puts <<~OUT
20
+ APP_NAME : #{env(:app_name)}
21
+ PORT : #{env(:port)}
22
+ API_KEY : #{env(:api_key)}
23
+ DATABASE_URL : #{env(:database_url)}
24
+ LOG_LEVEL : #{env(:log_level, "info")}
25
+ OUT
26
+ end
27
+ end
@@ -5,9 +5,20 @@
5
5
  # install, release) so this file can be loaded alongside them without conflict.
6
6
 
7
7
  class Tasks
8
- # ── Asgard: varstatic value and lazy lambda ─────────────────────────────
9
- var :app_name, "my_app"
10
- var :build_dir, -> { "builds/#{app_name}" }
8
+ # ── Ruby class variables shared across all tasks and subcommands ──────────
9
+ @@app_name ||= "my_app".freeze
10
+ @@build_dir ||= "builds/#{@@app_name}".freeze
11
+
12
+ # ── Computed values — plain private methods replace the removed var DSL ──────
13
+ # Declare as private so Thor does not try to register them as commands.
14
+ # Use @ivar ||= inside the method body to memoize expensive calls.
15
+ private
16
+
17
+ def version = `git describe --tags --always`.strip
18
+ def sha = `git rev-parse --short HEAD`.strip
19
+ def branch = @branch ||= `git rev-parse --abbrev-ref HEAD`.strip
20
+
21
+ public
11
22
 
12
23
  # ── Asgard: dotenv — load environment variables ────────────────────────────
13
24
  # Uncomment to activate:
@@ -37,9 +48,9 @@ class Tasks
37
48
  map "pl" => :pipeline
38
49
 
39
50
  # ── Basic task — no parameters ─────────────────────────────────────────────
40
- desc "greet", "Say hello (default task when no command is given)"
51
+ desc "Say hello (default task when no command is given)"
41
52
  def greet
42
- puts "Hello from #{app_name} (#{options[:env]})!"
53
+ puts "Hello from #{@@app_name} #{version} (#{branch}) in #{options[:env]} mode!"
43
54
  end
44
55
 
45
56
  # ── Positional parameter with default ──────────────────────────────────────
@@ -72,14 +83,14 @@ class Tasks
72
83
  end
73
84
 
74
85
  # ── method_option — all five option types ──────────────────────────────────
75
- desc "compile", "Compile the project"
86
+ desc "Compile the project"
76
87
  option :output, aliases: "-o", type: :string, default: "dist/", desc: "Output directory"
77
88
  option :verbose, aliases: "-v", type: :boolean, default: false, desc: "Enable verbose output"
78
89
  option :jobs, aliases: "-j", type: :numeric, default: 1, desc: "Number of parallel jobs"
79
90
  option :tags, type: :array, desc: "Build tags to apply"
80
91
  option :defines, type: :hash, desc: "Preprocessor defines (KEY:VALUE)"
81
92
  def compile
82
- puts "Compiling #{app_name} → #{options[:output]}"
93
+ puts "Compiling #{@@app_name} → #{options[:output]}"
83
94
  end
84
95
 
85
96
  # ── required option + enum + banner ────────────────────────────────────────
@@ -99,7 +110,7 @@ class Tasks
99
110
  default: "main",
100
111
  desc: "Git branch to deploy"
101
112
  def deploy(env = "staging")
102
- puts "Deploying #{app_name}@#{options[:branch]} to #{env}..."
113
+ puts "Deploying #{@@app_name}@#{options[:branch]} to #{env}..."
103
114
  end
104
115
 
105
116
  # ── long_desc — extended help shown by `asgard help report` ────────────────
@@ -115,7 +126,7 @@ class Tasks
115
126
  asgard report --format json --output report.json\x5
116
127
  asgard rp --format text
117
128
  LONGDESC
118
- desc "report", "Generate a project report"
129
+ desc "Generate a project report"
119
130
  option :format, type: :string, default: "text", enum: %w[text html json], desc: "Output format"
120
131
  option :since, type: :string, banner: "DATE", desc: "Limit to changes after DATE"
121
132
  option :output, type: :string, banner: "FILE", desc: "Write output to FILE"
@@ -124,30 +135,52 @@ class Tasks
124
135
  end
125
136
 
126
137
  # ── Asgard depends_on: sequential — analyze runs before spec ───────────────
127
- desc "analyze", "Check code style and complexity"
138
+ desc "Check code style and complexity"
128
139
  def analyze = puts "Analyzing..."
129
140
 
130
141
  depends_on :analyze
131
- desc "spec", "Run the test suite (depends on: analyze)"
142
+ desc "Run the test suite (depends on: analyze)"
132
143
  def spec = puts "Running specs..."
133
144
 
134
145
  # ── Asgard depends_on: parallel — analyze and typecheck run concurrently ───
135
- desc "typecheck", "Run the type checker"
146
+ desc "Run the type checker"
136
147
  def typecheck = puts "Type checking..."
137
148
 
138
149
  depends_on [:analyze, :typecheck]
139
- desc "check", "Run analyze and typecheck in parallel"
150
+ desc "Run analyze and typecheck in parallel"
140
151
  def check = puts "All checks passed."
141
152
 
142
153
  # ── Asgard depends_on: mixed sequential + parallel ─────────────────────────
143
- desc "pack", "Create distribution archive"
154
+ desc "Create distribution archive"
144
155
  def pack = puts "Packing..."
145
156
 
146
157
  # check → compile+spec (parallel) → pack → pipeline
147
158
  depends_on :check, [:compile, :spec], :pack
148
- desc "pipeline", "Full pipeline: check → compile+spec → pack"
159
+ desc "Full pipeline: check → compile+spec → pack"
149
160
  def pipeline = puts "Pipeline complete."
150
161
 
162
+ # ── Asgard: debug? / verbose? — Kernel predicates for conditional output ──────
163
+ # debug? returns true when --debug is passed (sets $DEBUG)
164
+ # verbose? returns true when --verbose is passed (sets $VERBOSE)
165
+ # Both are set by Asgard before invoke_command runs, so they are safe
166
+ # to read inside any task body.
167
+ #
168
+ # Run with:
169
+ # asgard status --verbose
170
+ # asgard status --debug
171
+ # asgard status --verbose --debug
172
+ desc "Show application status"
173
+ def status
174
+ puts "#{@@app_name} is running in #{options[:env]} mode."
175
+ puts " build dir : #{@@build_dir}" if verbose?
176
+ puts " sha : #{current_sha}" if verbose?
177
+ if debug?
178
+ puts " $DEBUG : #{$DEBUG.inspect}"
179
+ puts " $VERBOSE : #{$VERBOSE.inspect}"
180
+ puts " options : #{options.inspect}"
181
+ end
182
+ end
183
+
151
184
  # ── Thor: no_commands — public helper excluded from CLI and --help ──────────
152
185
  no_commands do
153
186
  def current_sha