Changelog¶
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[0.4.1] - 2026-10-02¶
Added¶
asgard schedule list --all(-a) — lists every project's scheduled entries, one table per project under aProject: name (/full/path/to/project)header. Other projects'.lokifiles are not loaded: their COMMAND and SCHEDULE columns, and the project path, are read back from the installed launchd plist or systemd unit files. Outside any.lokitree,asgard schedule listshows every project automatically instead of failing with "no .loki file found".stop,start,triggerandlogaccept entries of other projects, so they work from any directory. A bare name matches this project's entry first, then any other project's; when two other projects share a name, write it asproject/name.- A project-scoped
listwith nothing installed now ends with "Useasgard schedule list --allto see all currently scheduled tasks." - Backend API:
installed_entries(every project's entries),installed_columns(name)andinstalled_directory(name)(read from the job files) on both the launchd and systemd backends. - Runtime dependency:
tty-table(~> 0.12), which bringspastelandtty-screen.
Changed¶
asgard schedule listprints a table — NAME, COMMAND, SCHEDULE, STATE and LAST EXIT, with long cells wrapped to the terminal width, replacing one text line per entry. A run of hourly times collapses (10:00-17:00 hourly weekdays). Color is used on a terminal and turned off byNO_COLORor when piped.- The not-installed messages from
stop,start,triggerandlogpoint atasgard schedule list --all.
Fixed¶
- Scheduled entries were invisible outside the directory tree of the project
that installed them:
listand every per-entry command were scoped to the project found from the current directory, solaunchctl listshowed jobs thatasgard schedule listdid not.
0.4.0 - 2026-09-26¶
Added¶
- Scheduled tasks, built in —
schedule :task, at: "17:30", on: :weekdays(orevery: 3600) at class level in a.lokideclares a periodic run, andasgard schedule preview|install|list|start|stop|trigger|log|removemanages the entries under launchd (macOS) or systemd user timers (Linux). Missed calendar runs fire on wake; a project.envrcis loaded at run time throughdirenv exec. Promoted from the standalonedev/schedule.loki; job labels and paths are unchanged, so entries it installed are still recognized. ActiveSupport is no longer required:every:takes seconds or any object within_seconds. The command is registered as the gem-owned_scheduleand mapped toschedule. See Scheduled Tasks. Asgard::Schedule::Error(subclass ofAsgard::Error) — raised whenlaunchctl/systemctlfails;Asgard.run!reports it as a one-lineasgard: ...message.
Changed¶
- Upgrading from
dev/schedule.loki: removeimport_up "dev/schedule.loki"from your.loki. ItsTasks.scheduleoverrides the built-in one and records declarations whereasgard schedulenever looks. Asgard::ErrorandAsgard::CircularDependencyErrornow live inlib/asgard/errors.rb, loaded first. No change to the classes themselves.- The gem's own test run (
test_check,test_verbose) loads everytest/test_*.rbin one process, so coverage is measured across the whole suite.
Fixed¶
asgard <task>now exits 1 when the task returns:fail— quality-gate tasks (*_check) signal failure by returning:fail; previously that result was discarded and asgard always exited 0, so callers relying on the exit status (CI, cross-repo runners likews_quality.loki) saw every individual gate as passing. The aggregatequalitytask was unaffected (it aborts itself). Only a top-level result of exactly:failmaps to exit 1;:warnand:skipremain non-blocking.
0.3.3 - 2026-08-27¶
Added¶
-
helper(name, &block)DSL method onAsgard::Base— defines a method available in both class context (e.g. insideheaderorfooter) and as a private instance method inside task bodies, with a single declaration. Eliminates the manualdef self.name+no_commands { private def name = self.class.name }boilerplate. Supports positional arguments, keyword arguments, default values, and block arguments. -
header(text)andfooter(text)DSL methods onAsgard::Base— attach static text to the general help output.headerlines are printed above the commands list;footerlines are printed below the options block. Multiple calls accumulate: eachheadercall appends a line, eachfootercall prepends a line, so content from later-loaded files naturally wraps around content from earlier files. Neither appears whenasgard help <command>is called for per-command detail. no_negate(*names)DSL method onAsgard::Base— suppresses the[--no-name]and[--skip-name]negation variants from help output for boolean class options where negation is meaningless. Call it after theclass_optiondeclaration:
Changed¶
--versionreimplemented as aclass_option— the flag now appears in the "Options" section ofasgard helpalongside--debugand--verbose, rather than as a listed command. The_versionmethod and itsmap "--version" => :_versionregistration have been removed. The actual early-exit behaviour still lives inAsgard.run!(before the.lokifile is required), so--versionworks even when no.lokifile exists.no_negate :versionsuppresses the spurious[--no-version]/[--skip-version]variants.loki_upreturnsPathnameinstead ofString— the return value is now aPathnameinstance (ornilwhen not found).Pathnameis accepted everywhereloki_up's result is used:import,dotenv,load, and standard Ruby file methods all acceptPathnameviato_path/to_s. Code that passes the result directly to those methods is unaffected; code that performs string operations on the path should call.to_sfirst.
Fixed¶
- Help output showed
tasksprefix before every command —asgard helpdisplayedasgard tasks buildinstead ofasgard build. The cause was a keyword-vs-positional mismatch in theAsgard::Base#helpoverride: declaringsubcommand: falseas a keyword argument caused Ruby'ssuperto forward it as the hash{subcommand: false}. Thor'sbannermethod received this hash as the positionalsubcommandargument, treated it as truthy, and prepended the class namespace (tasks) to every command name. Fixed by changing the override signature to match Thor's positional signature:def help(command = nil, subcommand = false).
Added (continued)¶
loki_up(name = ".loki")Kernel method — searchesDir.pwdand each ancestor directory for a file with the given name; returns the absolute path of the first match ornil. Available everywhere in Ruby (task bodies,.lokifiles, top-level code) as amodule_functiononKernel.import(path)Kernel method — loads a.lokifile (or a glob of.lokifiles) withrequire-like idempotency via$LOADED_FEATURES. Accepts aStringorPathname. Relative paths are resolved relative to the caller's file (likerequire_relative). Glob patterns (*.loki,**/*.loki) expand viaDir.globand load all matches. Returnstrueif any file was newly loaded,falseif all were already loaded or no glob matches were found. RaisesArgumentErrorif the path does not end with.loki.import_up(name = ".loki")Kernel method — combinesloki_upandimport. 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. Returnsfalseif nothing is found.debug?andverbose?Kernel module functions — thin wrappers around$DEBUGand$VERBOSE, available everywhere in Ruby asmodule_functiononKernel. Set via--debug/--verboseCLI flags or directly via$DEBUG/$VERBOSE.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"whenPORTis unset;env(:api_key)raisesKeyErrorwhenAPI_KEYis missing and no default is provided. Accepts bothenv(:port)andenv("PORT")forms. Cleaner thanENV['PORT']in task bodies.- Verbose/debug feedback for
importandimport_up— whenverbose?is true, each file loaded is printed to stderr. Whendebug?is true, already-loaded files are also reported (with an "already loaded" suffix), andimport_upreports when a file is not found. - RuboCop lint gate — RuboCop is now a first-class quality gate alongside tests and Flog. Added
rubocopto the Gemfile, a.rubocop.ymltuned for this codebase (Ruby 3.2 target, relaxedMetricsthresholds consistent with Flog as the primary complexity gate,examples/excluded), andrake rubocop/rake rubocop_fixtasks backed by atmp/rubocop_cachedirectory for fast re-runs. - Expanded
rake qualitytask —qualitynow runs three independent gates (tests + coverage, RuboCop, Flog) in parallel usingdepends_on [:test, :rubocop, :flog_check]. Each gate captures its pass/fail result in an instance variable; output is suppressed on pass and filtered to failures only on fail, preventing interleaved output from concurrent subprocesses. A formatted pass/fail summary table is printed after all gates complete, so every failure is visible in a single run. rake flog_checktask — replaces the bareflog 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.- Single-argument
descshorthand —descnow 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: 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"). default_taskoverride warning —Asgard::Basenow overrides Thor'sdefault_taskto 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.examples/env_usage.lokiandexamples/.env— demonstrate theenv()Kernel helper, including the default-fallback form (env(:log_level, "info")) for variables absent from the environment. Usesloki_up(".env")sodotenvlocates the.envfile correctly regardless of which directoryasgardis invoked from.examples/subdir/— three-file demo showingimportandimport_upacross directory boundaries:subdir/.lokiimportsimport_up_demo.lokiby name;import_up_demo.lokicallsimport_up "env_usage.loki"to locate and load a file from an ancestor directory without a hardcoded path.statustask inkitchen_sink.loki— demonstratesdebug?andverbose?predicates with conditional output; shows--debugprinting$DEBUG,$VERBOSE, and the full options hash.- Computed value methods in
kitchen_sink.loki— three private methods (version,sha,branch) demonstrating the idiomatic Ruby replacement for the removedvarDSL, including memoization via@ivar ||=.
Removed¶
varDSL method — removed in favour of native Ruby class variables. Use@@name ||= "value".freezein 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 usingvar.importDSL method —import(mod)was a one-line alias for Ruby's built-ininclude. Callers can useincludedirectly.--auto-loadCLI flag — sibling*.lokiloading is now entirely user-controlled: placeimport "*.loki"(or any glob or explicit path) at the top of your.lokifile to load additional task files. Breaking change for projects that relied on--auto-load.Asgard.load_loki(dir)— replaced byimportwith glob support. Callers can useimport(File.join(dir, "*.loki"))directly.debug?/verbose?private methods onTasks— removed as redundant. The identicalmodule_functionversions onKernelare available everywhere, including inside task bodies.
Refactored¶
validate_deps!decomposed into focused private helpers — the method was performing four unrelated validations in one body (orphaneddepends_oncheck, 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.invoke_commanddispatch 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 declaredprivateonAsgard::Baserather than wrapped inno_commands. The_prefix was dropped — the underscore convention is reserved for gem-owned methods onTasks;privateis sufficient to exclude instance methods from Thor's command registry.invoke_commanditself stays inno_commandsso Thor does not warn about an undescribed public method.
Fixed¶
- 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
warnbefore a generalAsgard::Erroris raised. When only one dep fails, its exception is re-raised directly as before. - Subcommand deps not validated at startup —
run!only calledTasks.validate_deps!, so circular dependencies and undefined dep names in subcommand groups were silently ignored.run!now snapshotsAsgard::Base.subclassesbefore loading task files and validates every newly defined subclass alongsideTasks. - 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.
- Dep with required arguments gave a cryptic runtime error —
depends_on :buildwherebuild(name)has required parameters causedThor::InvocationErrorat task invocation time with no indication of where the problem was.validate_deps!now checks arity viainstance_method.parametersand raisesAsgard::Errorat startup with the task name and argument count. - Orphaned
depends_onsilently discarded — adepends_ondeclaration at the end of a class body with no followingdesc/defleft@_pending_depsnon-empty but was ignored byvalidate_deps!due to an earlyreturn if _deps.empty?guard.validate_deps!now checks@_pending_depsbefore that guard and raisesAsgard::Errornaming the orphaned dependencies. - Single-arg
descoptions silently dropped — whendesc "description", hide: truewas used, Ruby routed the options hash to thedescriptionpositional parameter. The override now detects aHashin the description position and treats it as options. - Single-arg
descstolen byno_commandsblocks — if ano_commandsblock appeared between a single-argdescand its method,method_addedconsumed@_pending_single_descfor the interstitial helper and leaked a stale@usageonto the next real command. The fix:@_pending_single_descis only consumed whenno_commands?is false. - 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/_doneSets and a per-taskConditionVariable. Threads that arrive at an already-running dep now wait for its completion. depends_onsilently dropped beforeno_commandsblocks — themethod_addedguard now checks@usageinstead of theno_commands?counter, sono_commandshelpers placed betweendepends_onanddefno longer silently steal the dependency.shebangignored itssilent:keyword argument — the parameter was accepted but never referenced; the script body is now echoed to stdout unlesssilent: trueis passed, matching the behavior ofsh.
Changed¶
validate_deps!detects undefined dependency names —depends_on :nonexistentpreviously passed validation silently and produced no error at runtime.validate_deps!now raisesAsgard::Errorlisting every dep name that does not correspond to a defined task.default_taskbehaviour documented —docs/tasks.mdnow notes that runningasgardwith no arguments displays the help message whendefault_taskis not set.loki_upscope clarified in docs —docs/task-files.mdanddocs/api.mdnow make explicit thatloki_uplocates any file by name, not just.lokifiles, with examples for.envandVERSION. Thedotenv loki_up(".env") || ".env"pattern is shown as the canonical way to load a.envfile from any subdirectory.examples/.loki— updated to use explicitimport "*.loki"(sibling files) andimport "subdir/import_demo.loki"(subdirectory file), with comments explainingimport,import_up, andloki_up.
Added (continued)¶
--doctorbuilt-in CLI flag — diagnoses.lokiresolution, import chains, and task definitions for the current directory, then exits. Handled directly inAsgard.run!before the.lokifile is loaded (same pattern as--version), so it keeps working in exactly the situations that would otherwise abort the whole process: a broken.lokifile, a circular or undefined dependency, or a task silently redefined by a laterdef. Backed by the newAsgard::Doctorclass. The report includes a "Tasks by file" listing: every command grouped by the file it's defined in, printed asrelative/path:lineso 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) asOVERRIDDEN by <file>:<line> — never callable, the winning (last) one asactive — redefines <file>:<line>— replacing the old flat "Tasks#x redefined" summary line with an annotation right where the problem is. See API Reference.- Flay and Reek quality gates —
flay_checkchecks for structural code duplication (mass ≥ 150);reekchecks code smells. Both run as part ofqualityalongsidetest,rubocop, andflog_check. A.reek.ymltunes several detectors to this codebase's conventions (no doc-comment requirement, short variable names, disabledTooManyStatements, etc), plus per-methodexclude:entries (generated withreek --todoand 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. test_verbosetask — runs the test suite with Minitest's verbose (-v) output.- 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 sharedprint_quality_summaryhelper. consoletask — opens an IRB console with the gem loaded (bin/consoleif present, otherwisebundle exec irb).git.loki— per-repo git tasks (push,pull,fetch), imported from.loki.
Changed (continued)¶
releasetask now prompts for confirmation (Release asgard vX.Y.Z to RubyGems? [y/N]) unless-y/--yesis passed, before tagging and pushing.
Fixed (continued)¶
bin/asgardcould silently run the wrongasgardversion — the executable didrequire "asgard", which (withoutbundle 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 ownlib/. Changed torequire_relative "../lib/asgard"so the executable always loads the library that ships alongside it, regardless of what else is installed.
Added (continued 2)¶
SKIPandWARNquality-gate statuses — alongsidePASS/FAIL. Both are non-blocking (qualityonly aborts onFAIL);SKIPis for a required external tool that isn't installed,WARNis for a check that ran successfully but has non-blocking suggestions.print_quality_summaryrenders all four with distinct colored badges and a combined tally.typos_check/typos_fixtasks — spell-checking via the externaltyposCLI (brew install typos-cli, not a gem dependency).typos_checkwrites full findings totypos_output.txtand participates inquality. Iftyposisn't installed, the gate prints a one-line notice recommendingbrew install typos-cliand reportsSKIPrather than failing.fasterer_checktask — runs thefasterergem (new dev dependency) againstlib/, reporting performance-idiom suggestions asWARN(non-blocking) — these are suggestions on working code, not correctness problems.asgard treenow shows the project header/footer —Base#treewraps Thor's built-in command tree the same wayBase#helpalready wraps the command list, so both example outputs are consistent.- 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.txtfile 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.
Changed (continued 2)¶
Asgard::BaseandAsgard::Doctorsplit into focused mixins —Asgard::Base(31 methods) is nowRegistry,DependencyGraph,TaskDSL, andDispatch(lib/asgard/base/*.rb), withmethod_added/header/footer/help/treeremaining directly on the class as the orchestrator.Asgard::Doctor(19 methods) is nowTaskSectionsandReport(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 --doctoroutput, and the public API are unchanged. Drops Reek'sTooManyMethods/TooManyInstanceVariableswarnings on both classes to zero.- Reek grandfathering made precise — replaced the per-file smell-count baseline (
.quality/reek_baseline.txt, thereek_baselinetask) with per-method, per-detectorexclude:entries in.reek.ymlitself, generated viareek --todoand 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. Thereek_baselineandensure_quality_dirtasks and the.quality/directory have been removed as no longer needed.
Added (continued 3)¶
depends_onaccepts a Proc/lambda in addition to a fixed list — a sole callable defers resolution tovalidate_deps!(once, right after every.lokifile has loaded) instead of resolving immediately whendepends_onitself 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 aSymbolorArray); it's written directly in the class body, so it lexically captures that class asselfand can callall_commandsbare. A Proc that raises is re-raised asAsgard::Errornaming 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.bundler_audit_checktask — runsbundle-audit check --updateagainstGemfile.lock(new dev dependencybundler-audit); reportsFAILon any known vulnerability, since this is a security gate, not a suggestion.quality_rails.loki— imported by.lokionly whenRailsis defined; currently shipsbrakeman_check, a Rails security-scan example. Needs no wiring intoquality's dependency list —quality'sdepends_onProc discovers it automatically once it's loaded.
Changed (continued 3)¶
test,rubocop,reekrenamed totest_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'sdepends_onProc finds every task whose name matches_check\zrather than naming them one by one.
Removed (continued)¶
dagwoodruntime dependency — replaced by stdlibTSortfor the one thing it was still doing (cycle detection); the parallel-execution plan itself was already derived directly fromdepends_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.
Changed (continued 4)¶
validate_deps!cycle detection now uses stdlibTSortinstead ofDagwood::DependencyGraph#order— a privateGraphStruct(edgesmember,include TSort) owns the task→dependency Hash and the traversal, raisingTSort::Cyclicon a cycle exactly as before (converted toAsgard::CircularDependencyError).run_deps_forno longer round-trips through a rebuilt DAG on every dispatch — it iterates_deps[target]'s stage groups directly, which is already the parallel-execution plandepends_onbuilt.
Fixed (continued 2)¶
quality.loki's parallel*_checktasks raced on shared instance state — each*_checktask wrote its pass/fail status to an ivar onself(@test_result,@rubocop_result, ...) from inside aThread.newspawned 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_groupnow collects each task's own return value into aHash(viaThread#value),run_deps_formerges per-stage results, and a newdep_result/dep_resultsinstance API (backed byThread.current, notself) hands them to the task body that depends on them.quality.lokiandquality_rails.loki's*_checktasks now return their status as a plain value instead of writing to an ivar;qualityreadsdep_result(name)instead ofinstance_variable_get.
Added (continued 4)¶
examples/bad.loki— a worked demonstration of the race the fix above addresses: 4 parallel workers read-modify-write a shared@hitscounter directly (the anti-patternquality.lokiused to have), reliably losing updates. Kept as a contrast example for whatdep_result/dep_resultsis for.
Added (continued 5)¶
sh(script, exec: true)— hands the command the asgard process itself viaKernel.execinstead 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'sdoc_servertask (sh "mkdocs serve", exec: true) is the motivating example. See Shell Helpers.bootstrapandenv_infotasks inkitchen_sink.loki— demonstrateshwith a multi-line heredoc (routed throughbash -c) and a single-line command, respectively.
Fixed (continued 3)¶
- Ctrl-C during a running
shcommand printed a rawInterruptbacktrace — SIGINT hits the whole foreground process group, so asgard's own ruby process raisedInterruptindependently 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 rescuesInterruptand exits with the conventional 130 status.
Added (continued 6)¶
depends_onaccepts a block in addition to a Proc/lambda —depends_on { ... }(ordepends_on do ... endfor a block spanning multiple statements) defers resolution tovalidate_deps!exactly like the existing sole-Proc/lambda form; the two are interchangeable.depends_onstill accepts task arguments or a block, never both — combining them raisesAsgard::Error. See Dynamic Dependencies.- The resolved Proc/lambda/block result is now shape-validated — once
validate_deps!calls it, the return value must be anArrayof stages, each aSymbol/String(sequential) or anArrayofSymbol/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 raisesAsgard::Errornaming the task and the offending value, instead of failing later with an opaqueNoMethodError. examples/depends_on_block/good/andexamples/depends_on_block/bad/— two self-contained example projects (each its own.lokiroot, isolated from the mainexamples/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 exactAsgard::Errormessage it produces.
0.2.0 - 2026-05-29¶
Changed¶
*.lokifiles are no longer auto-loaded by default. Pass--auto-loadtoasgardto load all*.lokifiles from the project root alphabetically before.loki. This is a breaking change for projects using the multi-file layout.- Added
--auto-loadas a built-in CLI flag inTasks, visible inasgard help
0.1.2 - 2026-05-29¶
Added¶
--versionbuilt-in CLI flag — printsAsgard::VERSIONand exits; implemented as a_-prefixed method inTasksper the gem-owned naming convention--debugand--verbosebuilt-inclass_optiondeclarations onTasks— set$DEBUG/$VERBOSEbefore any task runs via theinvoke_commandhook inAsgard::Basedebug?andverbose?private predicate helpers onTasks— thin wrappers around$DEBUGand$VERBOSEfor use inside task bodies_prefix convention for gem-owned methods inTasks— built-in methods use_prefix to distinguish them from user-defined tasksrun!guards against direct invocation of_-prefixed commands with a clean error message and exit 1examples/directory with working.lokifiles:kitchen_sink.loki— demonstrates the full Thor DSL (all option types,long_desc,class_option,default_task,map,depends_on,var,no_commands,private)server_subcommands.loki— subcommand group for server managementdb_subcommands.loki— subcommand group for database management withdepends_onchaining- README sections: Helper methods, Subcommands, Thor wrapper callout
Fixed¶
- Replaced
warn/exit 1withabortthroughoutrun!—Kernel#warnis silenced when$VERBOSE = nil, which is the default in Ruby 4.0;abortwrites to$stderrregardless
Changed¶
--debugand--verbosepromoted from mapped tasks toclass_option— they now work as modifiers alongside other commands (e.g.asgard build --debug) rather than as standalone commands- Removed all references to
justtask runner andrecipeterminology; Asgard uses "task" throughout depends_onparameter renamed from*recipesto*tasksfor consistency
0.1.1 - 2026-05-28¶
Added¶
- Parallel dependency execution — wrap deps in an array to run them concurrently:
depends_on [:build, :lint]ordepends_on :setup, [:build, :lint], :deploy Asgard.run!(argv)— single entry point encapsulating find, load, validate, and startAsgard.load_loki(dir)— auto-loads all*.lokifiles in a directory alphabeticallyTasksclass pre-defined by the gem (class Tasks < Asgard::Base) — task files reopen it without restating the superclasslib/asgard/tasks.rb— ships the pre-definedTasksclass
Changed¶
- Replaced
SimpleFlowdependency withDagwood— purpose-built DAG library with no extra dependencies and no Ruby 4 compatibility issues bin/asgardsimplified to two lines:require "asgard"+Asgard.run!(ARGV)- Task file convention:
.lokiis the project root marker and entry point;*.lokifiles each reopenclass Tasksand are auto-loaded before.loki Asgard.find_task_filesrenamed toAsgard.find_task_file(singular — only.lokiis the entry point)depends_onnow accepts mixed sequential/parallel stages; bare symbols run sequentially, arrays within the splat run in parallelrun!handles its own errors — missing.lokiand circular dependencies produce a clean one-line message and exit 1 rather than a backtrace- Thread-safe dep deduplication via class-level
_ran_tasksSet + Mutex replaces Thor's@_invocations - Removed
importmacro — task files use Ruby class reopening instead of modules
Removed¶
SimpleFlowdependency (replaced byDagwood)loggergem workaround (was only needed for SimpleFlow on Ruby 4)*.lokiglob fallback infind_task_file— only.lokiis the auto-discovered entry point
0.1.0 - 2026-05-28¶
Added¶
Asgard::Base— Thor subclass providing the task DSLdepends_on— declare task dependencies; dependencies run at most once per invocationvar— declare static or lazy-evaluated variables available to all tasksimport— flat-merge a task module into the current classdotenv— load a.envfile into the environmentsh— run a shell command or multiline heredoc script; exits with the command's status on failureshebang— write a script body to a tempfile and execute it with a given interpreter (:python3,:node,:ruby,:perl,:bash,:sh, or any custom interpreter)Asgard.find_task_files— search current directory and ancestors for task files- Task file resolution:
.lokitakes priority; falls back to all*.lokifiles sorted alphabetically asgardexecutable — finds task files, validates dependency graph, dispatches via Thor- Circular dependency detection via
SimpleFlow::DependencyGraphat startup - 100% test coverage enforced via SimpleCov (95% minimum threshold)
- Quality task in
.lokiruns flog after tests