From 4ab1152588ba2d47f27cb7c26bace5eafca64bb5 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Date: Fri, 21 Aug 2026 00:02:39 -0400 Subject: [PATCH 1/5] Add GlobalUsagePrinter: the usage view for help outside a project Co-authored-by: Cursor --- src/dev/cli/global_usage_printer.rb | 33 +++++++++++++++ test/dev/cli/global_usage_printer_test.rb | 50 +++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 src/dev/cli/global_usage_printer.rb create mode 100644 test/dev/cli/global_usage_printer_test.rb diff --git a/src/dev/cli/global_usage_printer.rb b/src/dev/cli/global_usage_printer.rb new file mode 100644 index 0000000..45acac2 --- /dev/null +++ b/src/dev/cli/global_usage_printer.rb @@ -0,0 +1,33 @@ +# typed: strict +# frozen_string_literal: true + +require "stringio" + +module Dev + module Cli + # The usage view for help outside a project (bare `dev`, `--help`, `-h`, + # `help` with no dev.yml in the cwd's ancestry): the global builtins that + # work from any directory, plus the hint that project commands need a + # dev.yml. A dedicated view rather than a UsagePrinter variant — that + # printer is shaped around a project catalog (project name, sections), + # and this listing is a flat, fixed set. + class GlobalUsagePrinter + extend T::Sig + + # @param commands [Hash{String => String}] global command name => description + # @param out [IO, StringIO] + # @return [void] + sig { params(commands: T::Hash[String, String], out: T.any(IO, StringIO)).void } + def print(commands:, out:) + out.puts "Usage: dev [args...]" + out.puts "" + out.puts "Global commands (available anywhere):" + commands.sort.each do |name, desc| + out.puts " #{name.ljust(12)} #{desc}" + end + out.puts "" + out.puts "Run dev inside a project that defines a dev.yml to see its commands." + end + end + end +end diff --git a/test/dev/cli/global_usage_printer_test.rb b/test/dev/cli/global_usage_printer_test.rb new file mode 100644 index 0000000..bded2dc --- /dev/null +++ b/test/dev/cli/global_usage_printer_test.rb @@ -0,0 +1,50 @@ +# typed: false +# frozen_string_literal: true + +require "test_helper" +require "dev/cli/global_usage_printer" +require "stringio" + +transform!(RSpock::AST::Transformation) +class Dev::Cli::GlobalUsagePrinterTest < Minitest::Test + test "print renders the usage line, each global command, and the project hint" do + Given "a name-to-description catalog" + printer = Dev::Cli::GlobalUsagePrinter.new + commands = { + "cd" => "Jump to a checkout", + "plan" => "Sync plans", + } + out = StringIO.new + + When "printing the global usage" + printer.print(commands: commands, out: out) + + Then "the header, rows, and hint all render" + out.string.include?("Usage: dev [args...]") + out.string.include?("Global commands (available anywhere):") + out.string.include?(" cd Jump to a checkout") + out.string.include?(" plan Sync plans") + out.string.include?("Run dev inside a project that defines a dev.yml to see its commands.") + end + + test "commands list alphabetically regardless of registration order" do + Given "a catalog registered out of alphabetical order" + printer = Dev::Cli::GlobalUsagePrinter.new + commands = { + "plan" => "Sync plans", + "cd" => "Jump to a checkout", + "learnings" => "Learnings read path", + } + out = StringIO.new + + When "printing the global usage" + printer.print(commands: commands, out: out) + + Then "rows appear alphabetically" + lines = out.string.lines.map(&:chomp) + lines.index(" cd Jump to a checkout") < + lines.index(" learnings Learnings read path") + lines.index(" learnings Learnings read path") < + lines.index(" plan Sync plans") + end +end From 1d78b6c954b20726c07b4ee504f61d0a35904491 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Date: Fri, 21 Aug 2026 00:03:19 -0400 Subject: [PATCH 2/5] Hoist global builtin descriptions to DESC constants The global usage listing needs the descriptions without instantiating the builtins (whose default constructors build real accessors); one constant serves both views so they cannot drift. Co-authored-by: Cursor --- src/dev/builtins/cd_command.rb | 6 +++++- src/dev/builtins/clone_command.rb | 6 +++++- src/dev/builtins/cred_command.rb | 6 +++++- src/dev/builtins/learnings_command.rb | 10 ++++++---- src/dev/builtins/plan_command.rb | 6 +++++- 5 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/dev/builtins/cd_command.rb b/src/dev/builtins/cd_command.rb index 0cf8207..af41a17 100644 --- a/src/dev/builtins/cd_command.rb +++ b/src/dev/builtins/cd_command.rb @@ -12,6 +12,10 @@ module Builtins class CdCommand < BuiltinCommand extend T::Sig + # Shared with the global usage listing (GlobalDispatch), which reads + # descriptions without instantiating the builtin. + DESC = "Jump to a checkout under $DEV_CD_ROOT (default ~/src) by fuzzy name" + sig { params(accessor: Dev::Cd::Accessor).void } def initialize(accessor: Dev::Cd::Accessor.new) super() @@ -19,7 +23,7 @@ def initialize(accessor: Dev::Cd::Accessor.new) end sig { override.returns(String) } - def desc = "Jump to a checkout under $DEV_CD_ROOT (default ~/src) by fuzzy name" + def desc = DESC sig { override.returns(Command::Category) } def category = Command::Category::Workflow diff --git a/src/dev/builtins/clone_command.rb b/src/dev/builtins/clone_command.rb index 713ad85..be6f2bc 100644 --- a/src/dev/builtins/clone_command.rb +++ b/src/dev/builtins/clone_command.rb @@ -12,6 +12,10 @@ module Builtins class CloneCommand < BuiltinCommand extend T::Sig + # Shared with the global usage listing (GlobalDispatch), which reads + # descriptions without instantiating the builtin. + DESC = "Clone a GitHub repo (via gh auth) into $DEV_CD_ROOT (default ~/src), org defaults to d3mlabs" + sig { params(accessor: Dev::Clone::Accessor).void } def initialize(accessor: Dev::Clone::Accessor.new) super() @@ -19,7 +23,7 @@ def initialize(accessor: Dev::Clone::Accessor.new) end sig { override.returns(String) } - def desc = "Clone a GitHub repo (via gh auth) into $DEV_CD_ROOT (default ~/src), org defaults to d3mlabs" + def desc = DESC sig { override.returns(Command::Category) } def category = Command::Category::Workflow diff --git a/src/dev/builtins/cred_command.rb b/src/dev/builtins/cred_command.rb index e419a19..6ccc891 100644 --- a/src/dev/builtins/cred_command.rb +++ b/src/dev/builtins/cred_command.rb @@ -12,6 +12,10 @@ module Builtins class CredCommand < BuiltinCommand extend T::Sig + # Shared with the global usage listing (GlobalDispatch), which reads + # descriptions without instantiating the builtin. + DESC = "Resolve a stored credential (e.g. cred get )" + sig { params(accessor: Dev::CredentialAccessor).void } def initialize(accessor: Dev::CredentialAccessor.new) super() @@ -19,7 +23,7 @@ def initialize(accessor: Dev::CredentialAccessor.new) end sig { override.returns(String) } - def desc = "Resolve a stored credential (e.g. cred get )" + def desc = DESC sig { override.returns(Command::Category) } def category = Command::Category::Workflow diff --git a/src/dev/builtins/learnings_command.rb b/src/dev/builtins/learnings_command.rb index 6b35a0a..cb9733f 100644 --- a/src/dev/builtins/learnings_command.rb +++ b/src/dev/builtins/learnings_command.rb @@ -13,6 +13,11 @@ module Builtins class LearningsCommand < BuiltinCommand extend T::Sig + # Shared with the global usage listing (GlobalDispatch), which reads + # descriptions without instantiating the builtin. + DESC = "Learnings read path (sync: refresh now, status: what's linked, invariants: Tier-0 block, " \ + "init: scaffold the index)" + # Builds the accessor for the enclosing project (per-call root). AccessorFactory = T.type_alias do T.proc.params(project_root: Pathname).returns(Dev::Learnings::Accessor) @@ -25,10 +30,7 @@ def initialize(accessor_factory: ->(project_root) { Dev::Learnings::Accessor.new end sig { override.returns(String) } - def desc - "Learnings read path (sync: refresh now, status: what's linked, invariants: Tier-0 block, " \ - "init: scaffold the index)" - end + def desc = DESC sig { override.returns(Command::Category) } def category = Command::Category::Workflow diff --git a/src/dev/builtins/plan_command.rb b/src/dev/builtins/plan_command.rb index bc9a696..b62101e 100644 --- a/src/dev/builtins/plan_command.rb +++ b/src/dev/builtins/plan_command.rb @@ -13,6 +13,10 @@ module Builtins class PlanCommand < BuiltinCommand extend T::Sig + # Shared with the global usage listing (GlobalDispatch), which reads + # descriptions without instantiating the builtin. + DESC = "Sync Cursor plans with GitHub issues (new/link/pull/push/status/init)" + # Builds the accessor for the enclosing project (per-call root). AccessorFactory = T.type_alias do T.proc.params(project_root: Pathname).returns(Dev::Plan::Accessor) @@ -25,7 +29,7 @@ def initialize(accessor_factory: ->(project_root) { Dev::Plan::Accessor.new(proj end sig { override.returns(String) } - def desc = "Sync Cursor plans with GitHub issues (new/link/pull/push/status/init)" + def desc = DESC sig { override.returns(Command::Category) } def category = Command::Category::Workflow From 64b58261bec6d9dac3ddd495344e5582cc591099 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Date: Fri, 21 Aug 2026 00:07:19 -0400 Subject: [PATCH 3/5] Render a global usage for help outside a project Bare dev / --help / -h / help hit the Runner's manifest load and died with the no-dev.yml refusal even though five global builtins work anywhere. GlobalDispatch now claims the help spellings when no dev.yml encloses the cwd and renders the global command listing; inside a project, help still routes to the Runner and lists the project catalog. Co-authored-by: Cursor --- bin/dev | 5 ++- src/dev/global_dispatch.rb | 76 +++++++++++++++++++++++++++++--- test/dev/global_dispatch_test.rb | 73 ++++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+), 9 deletions(-) diff --git a/bin/dev b/bin/dev index 3691f0d..9112b00 100755 --- a/bin/dev +++ b/bin/dev @@ -41,8 +41,9 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require "dev" require "dev/global_dispatch" -# Global builtins (cd/plan/cred/learnings) dispatch before any dev.yml -# lookup — they are host- or workspace-global, not project commands. +# Global builtins (cd/clone/plan/cred/learnings) dispatch before any dev.yml +# lookup — they are host- or workspace-global, not project commands. Help +# spellings join them when no dev.yml encloses the cwd (global usage). global_dispatch = Dev::GlobalDispatch.new if global_dispatch.global_command?(ARGV) global_dispatch.run(ARGV) diff --git a/src/dev/global_dispatch.rb b/src/dev/global_dispatch.rb index e570dae..6f686ae 100644 --- a/src/dev/global_dispatch.rb +++ b/src/dev/global_dispatch.rb @@ -2,7 +2,13 @@ # frozen_string_literal: true require "pathname" +require "dev/builtins/cd_command" +require "dev/builtins/clone_command" +require "dev/builtins/cred_command" +require "dev/builtins/learnings_command" +require "dev/builtins/plan_command" require "dev/cd" +require "dev/cli/global_usage_printer" require "dev/clone" require "dev/plan" require "dev/learnings" @@ -26,10 +32,27 @@ module Dev # Runs before Dev::Runner is constructed, so these commands work from any # directory. Project commands (`up`, yaml-declared names) keep the existing # "must find dev.yml" failure in the Runner path. + # + # Help is a conditional citizen here: outside any dev.yml project, the help + # spellings (bare `dev`, `--help`, `-h`, `help`) render the global usage — + # inside a project they stay with the Runner, which lists the project's + # catalog. class GlobalDispatch extend T::Sig - GLOBAL_COMMANDS = T.let(%w[cd clone plan cred learnings].freeze, T::Array[String]) + # Global command name => description. One hash serves both dispatch + # membership and the global usage listing; descriptions alias the + # builtins' canonical DESC constants so the two help views cannot drift. + GLOBAL_COMMANDS = T.let( + { + "cd" => Builtins::CdCommand::DESC, + "clone" => Builtins::CloneCommand::DESC, + "cred" => Builtins::CredCommand::DESC, + "learnings" => Builtins::LearningsCommand::DESC, + "plan" => Builtins::PlanCommand::DESC, + }.freeze, + T::Hash[String, String], + ) # Candidates shown in an ambiguous `dev cd` error before truncating. AMBIGUOUS_CANDIDATE_CAP = 10 @@ -37,27 +60,35 @@ class GlobalDispatch # @param cd_accessor [Dev::Cd::Accessor] # @param clone_accessor [Dev::Clone::Accessor] # @param cred_accessor [Dev::CredentialAccessor] + # @param usage_printer [Dev::Cli::GlobalUsagePrinter] sig do params( cd_accessor: Dev::Cd::Accessor, clone_accessor: Dev::Clone::Accessor, cred_accessor: Dev::CredentialAccessor, + usage_printer: Dev::Cli::GlobalUsagePrinter, ).void end def initialize(cd_accessor: Dev::Cd::Accessor.new, clone_accessor: Dev::Clone::Accessor.new, - cred_accessor: Dev::CredentialAccessor.new) + cred_accessor: Dev::CredentialAccessor.new, + usage_printer: Dev::Cli::GlobalUsagePrinter.new) @cd_accessor = T.let(cd_accessor, Dev::Cd::Accessor) @clone_accessor = T.let(clone_accessor, Dev::Clone::Accessor) @cred_accessor = T.let(cred_accessor, Dev::CredentialAccessor) + @usage_printer = T.let(usage_printer, Dev::Cli::GlobalUsagePrinter) end - # Whether the argv names a global builtin this dispatcher owns. + # Whether the argv is dispatched here, before any dev.yml lookup: a + # global builtin from anywhere, or a help spelling outside any project + # (inside one, the Runner's help lists the project catalog instead). # # @param argv [Array] # @return [Boolean] sig { params(argv: T::Array[String]).returns(T::Boolean) } def global_command?(argv) - GLOBAL_COMMANDS.include?(argv.first) + return true if GLOBAL_COMMANDS.key?(argv.first) + + help_argv?(argv) && nearest_dev_yaml_root.nil? end # Run a global builtin. Clean failures (usage errors, unresolved repos) @@ -67,6 +98,11 @@ def global_command?(argv) # @return [void] sig { params(argv: T::Array[String]).void } def run(argv) + if help_argv?(argv) + @usage_printer.print(commands: GLOBAL_COMMANDS, out: $stdout) + return + end + args = T.let(argv.dup, T::Array[String]) cmd_name = T.must(args.shift) case cmd_name @@ -93,6 +129,17 @@ def run(argv) private + # Whether the argv is a help spelling. Mirrors the Runner's routing: + # bare `dev`, the exact conventional flags, and `help` as the command + # name (the help builtin ignores trailing args). + # + # @param argv [Array] + # @return [Boolean] + sig { params(argv: T::Array[String]).returns(T::Boolean) } + def help_argv?(argv) + argv.empty? || argv == ["--help"] || argv == ["-h"] || argv.first == "help" + end + # Print an ambiguous `dev cd` result: the candidates (capped, each at its # shortest-unique depth) and the escape hatch — refine or Tab-browse. # @@ -125,11 +172,26 @@ def workspace_root # @return [Pathname, nil] sig { returns(T.nilable(Pathname)) } def enclosing_project_root - cwd = Pathname.new(Dir.pwd) - cwd.ascend do |path| + nearest_dev_yaml_root || nearest_git_root + end + + # The nearest ancestor holding a dev.yml, or nil. This is the "inside a + # project?" test the help fallback uses: a plain git checkout with no + # dev.yml still gets the global usage. + # + # @return [Pathname, nil] + sig { returns(T.nilable(Pathname)) } + def nearest_dev_yaml_root + Pathname.new(Dir.pwd).ascend do |path| return path if (path / Dev::DEV_YAML_FILENAME).exist? end - cwd.ascend do |path| + nil + end + + # @return [Pathname, nil] the nearest ancestor holding a .git, or nil + sig { returns(T.nilable(Pathname)) } + def nearest_git_root + Pathname.new(Dir.pwd).ascend do |path| return path if (path / ".git").exist? end nil diff --git a/test/dev/global_dispatch_test.rb b/test/dev/global_dispatch_test.rb index 8af6207..9063091 100644 --- a/test/dev/global_dispatch_test.rb +++ b/test/dev/global_dispatch_test.rb @@ -233,6 +233,79 @@ class Dev::GlobalDispatchTest < Minitest::Test FileUtils.rm_rf(cwd) end + test "help argvs dispatch globally when no dev.yml encloses the cwd" do + Given "a cwd with a .git dir but no dev.yml above it (a plain checkout)" + cwd = Dir.mktmpdir("dispatch-help-") + FileUtils.mkdir_p(File.join(cwd, ".git")) + dispatch = Dev::GlobalDispatch.new(cred_accessor: RecordingCredAccessor.new) + + When "classifying every help spelling from that cwd" + classified = Dir.chdir(cwd) do + { + bare: dispatch.global_command?([]), + long_flag: dispatch.global_command?(["--help"]), + short_flag: dispatch.global_command?(["-h"]), + word: dispatch.global_command?(["help"]), + } + end + + Then "all classify as global" + classified.values.all? + + Cleanup + FileUtils.rm_rf(cwd) + end + + test "help argvs stay with the Runner when a dev.yml encloses the cwd" do + Given "a cwd whose parent holds a dev.yml" + root = Dir.mktmpdir("dispatch-help-") + File.write(File.join(root, "dev.yml"), "name: someproject\n") + cwd = File.join(root, "nested") + FileUtils.mkdir_p(cwd) + dispatch = Dev::GlobalDispatch.new(cred_accessor: RecordingCredAccessor.new) + + When "classifying every help spelling from that cwd" + classified = Dir.chdir(cwd) do + { + bare: dispatch.global_command?([]), + long_flag: dispatch.global_command?(["--help"]), + short_flag: dispatch.global_command?(["-h"]), + word: dispatch.global_command?(["help"]), + } + end + + Then "none classify as global — project help renders the project catalog" + classified.values.none? + + Cleanup + FileUtils.rm_rf(root) + end + + test "bare dev outside a project prints the global usage" do + Given "a cwd with no dev.yml anywhere above it" + cwd = Dir.mktmpdir("dispatch-help-") + dispatch = Dev::GlobalDispatch.new(cred_accessor: RecordingCredAccessor.new) + out = StringIO.new + old_stdout = $stdout + $stdout = out + + When "we dispatch the bare argv" + Dir.chdir(cwd) { dispatch.run([]) } + + Then "the global commands render with their canonical descriptions and the project hint" + out.string.include?("Global commands (available anywhere):") + out.string.include?(" cd #{Dev::Builtins::CdCommand::DESC}") + out.string.include?(" clone #{Dev::Builtins::CloneCommand::DESC}") + out.string.include?(" cred #{Dev::Builtins::CredCommand::DESC}") + out.string.include?(" learnings #{Dev::Builtins::LearningsCommand::DESC}") + out.string.include?(" plan #{Dev::Builtins::PlanCommand::DESC}") + out.string.include?("Run dev inside a project that defines a dev.yml to see its commands.") + + Cleanup + $stdout = old_stdout + FileUtils.rm_rf(cwd) + end + test "project commands still require a nearby dev.yml" do Given "a cwd with no dev.yml anywhere above it" cwd = Dir.mktmpdir("dispatch-cwd-") From 8e697a2a6d994c970833925c40b2b7b20ff3ef3a Mon Sep 17 00:00:00 2001 From: Jean-Philippe Date: Fri, 21 Aug 2026 00:08:15 -0400 Subject: [PATCH 4/5] Cover the help-outside-a-project routing at the bin/dev boundary Bare dev and --help in a dev.yml-less directory now exit 0 with the global usage; the hostile-env boot test routes through a project command since bare dev no longer reaches the no-dev.yml refusal. Co-authored-by: Cursor --- test/dev/bin_dev_test.rb | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/test/dev/bin_dev_test.rb b/test/dev/bin_dev_test.rb index daaa944..8a2c9a8 100644 --- a/test/dev/bin_dev_test.rb +++ b/test/dev/bin_dev_test.rb @@ -2,6 +2,7 @@ # frozen_string_literal: true require "test_helper" +require "dev/global_dispatch" require "open3" require "tmpdir" @@ -9,13 +10,17 @@ # (dev#94): a harness running under `bundle exec` leaks RUBYOPT/BUNDLE_* # into every child, and the Ruby interpreter acts on RUBYOPT before the # script's first line — so the defense lives in the sh layer, and only -# spawning the real shim can exercise it. Three tests, one property each: +# spawning the real shim can exercise it. Scrub tests, one property each: # # 1. dev still boots when the caller's env is hostile (the bug's symptom). # 2. The shim hands Ruby an env with every scrub key removed (the fix, # key by key). # 3. The scrub list keeps up with bundler: whatever the locked bundler # exports must be on it (the drift over time). +# +# The shim is also the one place the full argv routing (global dispatch, +# then Runner) is wired together, so its end-to-end routing behavior — +# help outside a project — is exercised here too. transform!(RSpock::AST::Transformation) class Dev::BinDevTest < Minitest::Test DEV_ROOT = File.expand_path("../..", __dir__) @@ -36,8 +41,8 @@ class Dev::BinDevTest < Minitest::Test "BUNDLE_GEMFILE" => "/nonexistent/harness/Gemfile", } - When "running bin/dev there" - _out, err, status = Open3.capture3(hostile, "sh", BIN_DEV, chdir: dir) + When "running a project command (bare dev renders the global usage instead) there" + _out, err, status = Open3.capture3(hostile, "sh", BIN_DEV, "up", chdir: dir) Then "dev reached its own no-dev.yml refusal — not a crash inside the caller's bundler" !status.success? @@ -48,6 +53,26 @@ class Dev::BinDevTest < Minitest::Test FileUtils.rm_rf(dir) end + test "bare dev and dev --help outside a project print the global usage and exit 0" do + Given "a directory with no dev.yml anywhere above it" + dir = Dir.mktmpdir("dev-bin-test-") + + When "running bin/dev bare and with --help there" + bare_out, _bare_err, bare_status = Open3.capture3("sh", BIN_DEV, chdir: dir) + help_out, _help_err, help_status = Open3.capture3("sh", BIN_DEV, "--help", chdir: dir) + + Then "both succeed with the global command listing and the project hint" + bare_status.success? + help_status.success? + bare_out.include?("Global commands (available anywhere):") + bare_out.include?("Run dev inside a project that defines a dev.yml to see its commands.") + Dev::GlobalDispatch::GLOBAL_COMMANDS.keys.all? { |name| bare_out.include?(name) } + help_out == bare_out + + Cleanup + FileUtils.rm_rf(dir) + end + test "the shim removes every scrub key from the env it hands to ruby" do Given "every scrub key planted hostile, and a stub ruby that prints the env it receives" dir = Dir.mktmpdir("dev-bin-test-") From e207e174a5cc28ff1a4bc2eb63f06f999b4d69f5 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Date: Fri, 21 Aug 2026 00:10:09 -0400 Subject: [PATCH 5/5] Guard the nilable argv head before the global-command lookup Sorbet rejects T.nilable(String) into Hash#key?; bare argv reaches the help fallback through the explicit nil check instead. Co-authored-by: Cursor --- src/dev/global_dispatch.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dev/global_dispatch.rb b/src/dev/global_dispatch.rb index 6f686ae..4841b37 100644 --- a/src/dev/global_dispatch.rb +++ b/src/dev/global_dispatch.rb @@ -86,7 +86,8 @@ def initialize(cd_accessor: Dev::Cd::Accessor.new, clone_accessor: Dev::Clone::A # @return [Boolean] sig { params(argv: T::Array[String]).returns(T::Boolean) } def global_command?(argv) - return true if GLOBAL_COMMANDS.key?(argv.first) + cmd_name = argv.first + return true if cmd_name && GLOBAL_COMMANDS.key?(cmd_name) help_argv?(argv) && nearest_dev_yaml_root.nil? end