Repository: yargs/yargs
Pull request #806 (2017-02-26 15:50:36)

chore: get yargs 7.x over the finish line
let's try this again, landing 7.x branch without squashing.


Patch:
diff --git a/.gitignore b/.gitignore
index 2f3aff6..90d6638 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,4 @@ node_modules/
 .nyc_output/
 *.swp
 test.js
+coverage
diff --git a/README.md b/README.md
index c14cb29..44262f5 100644
--- a/README.md
+++ b/README.md
@@ -408,7 +408,7 @@ explicitly set.
 
 If `key` is an array, interpret all the elements as booleans.
 
-.check(fn)
+.check(fn, [global=true])
 ----------
 
 Check that certain conditions are met in the provided arguments.
@@ -418,6 +418,9 @@ Check that certain conditions are met in the provided arguments.
 If `fn` throws or returns a non-truthy value, show the thrown error, usage information, and
 exit.
 
+`global` indicates whether `check()` should be enabled both
+at the top-level and for each sub-command.
+
 <a name="choices"></a>.choices(key, choices)
 ----------------------
 
@@ -551,14 +554,6 @@ yargs
   .argv
 ```
 
-Note that commands will not automatically inherit configuration _or_ options
-of their parent context. This means you'll have to re-apply configuration
-if necessary, and make options global manually using the [global](#global) method.
-
-Additionally, the [`help`](#help) and [`version`](#version)
-options (if used) **always** apply globally, just like the
-[`.wrap()`](#wrap) configuration.
-
 `builder` can also be a function. This function is executed
 with a `yargs` instance, and can be used to provide _advanced_ command specific help:
 
@@ -596,6 +591,34 @@ yargs
   .argv
 ```
 
+### Default Commands
+
+To specify a default command use the character `*`. A default command
+will be run if the positional arguments provided match no known
+commands:
+
+```js
+const argv = require('yargs')
+  .command('*', 'the default command', () => {}, (argv) => {
+    console.log('this command will be run by default')
+  })
+```
+
+The command defined above will be executed if the program
+is run with `./my-cli.js --x=22`.
+
+Default commands can also be used as a command alias, like so:
+
+```js
+const argv = require('yargs')
+  .command(['serve', '*'], 'the serve command', () => {}, (argv) => {
+    console.log('this command will be run by default')
+  })
+```
+
+The command defined above will be executed if the program
+is run with `./my-cli.js --x=22`, or with `./my-cli.js serve --x=22`.
+
 ### Positional Arguments
 
 Commands can accept _optional_ and _required_ positional arguments. Required
@@ -678,7 +701,7 @@ require('yargs')
       console.log(`setting ${argv.key} to ${argv.value}`)
     }
   })
-  .demandCommand(1)
+  .demandCommand()
   .help()
   .wrap(72)
   .argv
@@ -824,7 +847,7 @@ cli.js:
 #!/usr/bin/env node
 require('yargs')
   .commandDir('cmds')
-  .demandCommand(1)
+  .demandCommand()
   .help()
   .argv
 ```
@@ -985,6 +1008,8 @@ $ node test.js
   '$0': 'test.js' }
 ```
 
+Note that a configuration object may extend from a JSON file using the `"extends"` property. When doing so, the `"extends"` value should be a path (relative or absolute) to the extended JSON file.
+
 <a name="conflicts"></a>.conflicts(x, y)
 ----------------------------------------------
 
@@ -1086,12 +1111,12 @@ require('yargs')
     'run': {
       alias: 'r',
       describe: 'run your program',
-      demand: true
+      demandOption: true
     },
     'path': {
       alias: 'p',
       describe: 'provide a path to file',
-      demand: true
+      demandOption: true
     },
     'spec': {
       alias: 's',
@@ -1112,9 +1137,9 @@ Options:
 Missing required arguments: run, path
 ```
 
-<a name="demandCommand"></a>.demandCommand(min, [minMsg])
+<a name="demandCommand"></a>.demandCommand([min=1], [minMsg])
 ------------------------------
-.demandCommand(min, [max], [minMsg], [maxMsg])
+.demandCommand([min=1], [max], [minMsg], [maxMsg])
 ------------------------------
 
 Demand in context of commands. You can demand a minimum and a maximum number a user can have within your program, as well as provide corresponding error messages if either of the demands is not met.
@@ -1134,7 +1159,9 @@ require('yargs')
   .help()
   .argv
 ```
+
 which will provide the following output:
+
 ```bash
 Commands:
   configure <key> [value]  Set a config variable         [aliases: config, cfg]
@@ -1293,7 +1320,7 @@ require('yargs')
 
 Outputs the same completion choices as `./test.js --foo`<kbd>TAB</kbd>: `--foobar` and `--foobaz`
 
-<a name="global"></a>.global(globals)
+<a name="global"></a>.global(globals, [global=true])
 ------------
 
 Indicate that an option (or group of options) should not be reset when a command
@@ -1303,11 +1330,13 @@ is executed, as an example:
 var argv = require('yargs')
   .option('a', {
     alias: 'all',
-    default: true
+    default: true,
+    global: false
   })
   .option('n', {
     alias: 'none',
-    default: true
+    default: true,
+    global: false
   })
   .command('foo', 'foo command', function (yargs) {
     return yargs.option('b', {
@@ -1322,7 +1351,7 @@ var argv = require('yargs')
 If the `foo` command is executed the `all` option will remain, but the `none`
 option will have been eliminated.
 
-`help`, `version`, and `completion` options default to being global.
+Options default to being global.
 
 <a name="group"></a>.group(key(s), groupName)
 --------------------
@@ -1413,7 +1442,7 @@ var argv = require('yargs')
   .option('option', {
     alias: 'o',
     describe: "'tis a mighty fine option",
-    demand: true
+    demandOption: true
   })
   .command('run', "Arrr, ya best be knowin' what yer doin'")
   .example('$0 run foo', "shiver me timbers, here's an example for ye")
@@ -1530,7 +1559,7 @@ For example:
 var argv = require('yargs')
     .option('f', {
         alias: 'file',
-        demand: true,
+        demandOption: true,
         default: '/etc/passwd',
         describe: 'x marks the spot',
         type: 'string'
@@ -1559,7 +1588,7 @@ var argv = require('yargs')
     .options({
       'f': {
         alias: 'file',
-        demand: true,
+        demandOption: true,
         default: '/etc/passwd',
         describe: 'x marks the spot',
         type: 'string'
@@ -1650,6 +1679,8 @@ as a configuration object.
 `cwd` can optionally be provided, the package.json will be read
 from this location.
 
+Note that a configuration stanza in package.json may extend from an identically keyed stanza in another package.json file using the `"extends"` property. When doing so, the `"extends"` value should be a path (relative or absolute) to the extended package.json file.
+
 .recommendCommands()
 ---------------------------
 
@@ -1778,12 +1809,15 @@ Specify --help for available options
 Specifies either a single option key (string), or an array of options.
 If any of the options is present, yargs validation is skipped.
 
-.strict()
+.strict([global=true])
 ---------
 
 Any command-line argument given that is not demanded, or does not have a
 corresponding description, will be reported as an error.
 
+`global` indicates whether `strict()` should be enabled both
+at the top-level and for each sub-command.
+
 <a name="string"></a>.string(key)
 ------------
 
diff --git a/lib/apply-extends.js b/lib/apply-extends.js
new file mode 100644
index 0000000..ec751b0
--- /dev/null
+++ b/lib/apply-extends.js
@@ -0,0 +1,41 @@
+var fs = require('fs')
+var path = require('path')
+var assign = require('./assign')
+var YError = require('./yerror')
+
+var previouslyVisitedConfigs = []
+
+function checkForCircularExtends (path) {
+  if (previouslyVisitedConfigs.indexOf(path) > -1) {
+    throw new YError("Circular extended configurations: '" + path + "'.")
+  }
+}
+
+function getPathToDefaultConfig (cwd, pathToExtend) {
+  return path.resolve(cwd, pathToExtend)
+}
+
+function applyExtends (config, cwd, subKey) {
+  var defaultConfig = {}
+
+  if (config.hasOwnProperty('extends')) {
+    var pathToDefault = getPathToDefaultConfig(cwd, config.extends)
+
+    checkForCircularExtends(pathToDefault)
+
+    previouslyVisitedConfigs.push(pathToDefault)
+    delete config.extends
+
+    defaultConfig = JSON.parse(fs.readFileSync(pathToDefault, 'utf8'))
+    if (subKey) {
+      defaultConfig = defaultConfig[subKey] || {}
+    }
+    defaultConfig = applyExtends(defaultConfig, path.dirname(pathToDefault), subKey)
+  }
+
+  previouslyVisitedConfigs = []
+
+  return assign(defaultConfig, config)
+}
+
+module.exports = applyExtends
diff --git a/lib/argsert.js b/lib/argsert.js
new file mode 100644
index 0000000..d3e72fc
--- /dev/null
+++ b/lib/argsert.js
@@ -0,0 +1,72 @@
+const command = require('./command')()
+const YError = require('./yerror')
+
+const positionName = ['first', 'second', 'third', 'fourth', 'fifth', 'sixth']
+
+module.exports = function (expected, callerArguments, length) {
+  // TODO: should this eventually raise an exception.
+  try {
+    // preface the argument description with "cmd", so
+    // that we can run it through yargs' command parser.
+    var position = 0
+    var parsed = {demanded: [], optional: []}
+    if (typeof expected === 'object') {
+      length = callerArguments
+      callerArguments = expected
+    } else {
+      parsed = command.parseCommand('cmd ' + expected)
+    }
+    const args = [].slice.call(callerArguments)
+
+    while (args.length && args[args.length - 1] === undefined) args.pop()
+    length = length || args.length
+
+    if (length < parsed.demanded.length) {
+      throw new YError('Not enough arguments provided. Expected ' + parsed.demanded.length +
+        ' but received ' + args.length + '.')
+    }
+
+    const totalCommands = parsed.demanded.length + parsed.optional.length
+    if (length > totalCommands) {
+      throw new YError('Too many arguments provided. Expected max ' + totalCommands +
+        ' but received ' + length + '.')
+    }
+
+    parsed.demanded.forEach(function (demanded) {
+      const arg = args.shift()
+      const observedType = guessType(arg)
+      const matchingTypes = demanded.cmd.filter(function (type) {
+        return type === observedType || type === '*'
+      })
+      if (matchingTypes.length === 0) argumentTypeError(observedType, demanded.cmd, position, false)
+      position += 1
+    })
+
+    parsed.optional.forEach(function (optional) {
+      if (args.length === 0) return
+      const arg = args.shift()
+      const observedType = guessType(arg)
+      const matchingTypes = optional.cmd.filter(function (type) {
+        return type === observedType || type === '*'
+      })
+      if (matchingTypes.length === 0) argumentTypeError(observedType, optional.cmd, position, true)
+      position += 1
+    })
+  } catch (err) {
+    console.warn(err.stack)
+  }
+}
+
+function guessType (arg) {
+  if (Array.isArray(arg)) {
+    return 'array'
+  } else if (arg === null) {
+    return 'null'
+  }
+  return typeof arg
+}
+
+function argumentTypeError (observedType, allowedTypes, position, optional) {
+  throw new YError('Invalid ' + (positionName[position] || 'manyith') + ' argument.' +
+    ' Expected ' + allowedTypes.join(' or ') + ' but received ' + observedType + '.')
+}
diff --git a/lib/command.js b/lib/command.js
index c1afc1c..c49555a 100644
--- a/lib/command.js
+++ b/lib/command.js
@@ -2,6 +2,8 @@ const path = require('path')
 const inspect = require('util').inspect
 const camelCase = require('camelcase')
 
+const DEFAULT_MARKER = '*'
+
 // handles parsing positional arguments,
 // and populating argv with said positional
 // arguments.
@@ -10,6 +12,7 @@ module.exports = function (yargs, usage, validation) {
 
   var handlers = {}
   var aliasMap = {}
+  var defaultCommand
   self.addHandler = function (cmd, description, builder, handler) {
     var aliases = []
     if (Array.isArray(cmd)) {
@@ -28,15 +31,50 @@ module.exports = function (yargs, usage, validation) {
       return
     }
 
-    var parsedCommand = parseCommand(cmd)
+    // parse positionals out of cmd string
+    var parsedCommand = self.parseCommand(cmd)
+
+    // remove positional args from aliases only
     aliases = aliases.map(function (alias) {
-      alias = parseCommand(alias).cmd // remove positional args
+      return self.parseCommand(alias).cmd
+    })
+
+    // check for default and filter out '*''
+    var isDefault = false
+    var parsedAliases = [parsedCommand.cmd].concat(aliases).filter(function (c) {
+      if (c === DEFAULT_MARKER) {
+        isDefault = true
+        return false
+      }
+      return true
+    })
+
+    // short-circuit if default with no aliases
+    if (isDefault && parsedAliases.length === 0) {
+      defaultCommand = {
+        original: cmd.replace(DEFAULT_MARKER, '').trim(),
+        handler: handler,
+        builder: builder || {},
+        demanded: parsedCommand.demanded,
+        optional: parsedCommand.optional
+      }
+      return
+    }
+
+    // shift cmd and aliases after filtering out '*'
+    if (isDefault) {
+      parsedCommand.cmd = parsedAliases[0]
+      aliases = parsedAliases.slice(1)
+      cmd = cmd.replace(DEFAULT_MARKER, parsedCommand.cmd)
+    }
+
+    // populate aliasMap
+    aliases.forEach(function (alias) {
       aliasMap[alias] = parsedCommand.cmd
-      return alias
     })
 
     if (description !== false) {
-      usage.command(cmd, description, aliases)
+      usage.command(cmd, description, isDefault, aliases)
     }
 
     handlers[parsedCommand.cmd] = {
@@ -46,6 +84,8 @@ module.exports = function (yargs, usage, validation) {
       demanded: parsedCommand.demanded,
       optional: parsedCommand.optional
     }
+
+    if (isDefault) defaultCommand = handlers[parsedCommand.cmd]
   }
 
   self.addDirectory = function (dir, context, req, callerFile, opts) {
@@ -94,7 +134,7 @@ module.exports = function (yargs, usage, validation) {
     return false
   }
 
-  function parseCommand (cmd) {
+  self.parseCommand = function (cmd) {
     var extraSpacesStrippedCommand = cmd.replace(/\s{2,}/g, ' ')
     var splitCommand = extraSpacesStrippedCommand.split(/\s+(?![^[]*]|[^<]*>)/)
     var bregex = /\.*[\][<>]/g
@@ -130,48 +170,71 @@ module.exports = function (yargs, usage, validation) {
     return handlers
   }
 
+  self.hasDefaultCommand = function () {
+    return !!defaultCommand
+  }
+
   self.runCommand = function (command, yargs, parsed) {
-    var argv = parsed.argv
-    var commandHandler = handlers[command] || handlers[aliasMap[command]]
-    var innerArgv = argv
+    var aliases = parsed.aliases
+    var commandHandler = handlers[command] || handlers[aliasMap[command]] || defaultCommand
     var currentContext = yargs.getContext()
     var numFiles = currentContext.files.length
     var parentCommands = currentContext.commands.slice()
-    currentContext.commands.push(command)
+
+    // what does yargs look like after the buidler is run?
+    var innerArgv = parsed.argv
+    var innerYargs = null
+    var positionalMap = {}
+
+    if (command) currentContext.commands.push(command)
     if (typeof commandHandler.builder === 'function') {
       // a function can be provided, which builds
       // up a yargs chain and possibly returns it.
-      innerArgv = commandHandler.builder(yargs.reset(parsed.aliases))
+      innerYargs = commandHandler.builder(yargs.reset(parsed.aliases))
       // if the builder function did not yet parse argv with reset yargs
       // and did not explicitly set a usage() string, then apply the
       // original command string as usage() for consistent behavior with
-      // options object below
+      // options object below.
       if (yargs.parsed === false) {
         if (typeof yargs.getUsageInstance().getUsage() === 'undefined') {
           yargs.usage('$0 ' + (parentCommands.length ? parentCommands.join(' ') + ' ' : '') + commandHandler.original)
         }
-        innerArgv = innerArgv ? innerArgv.argv : yargs.argv
+        innerArgv = innerYargs ? innerYargs._parseArgs(null, null, true) : yargs._parseArgs(null, null, true)
       } else {
         innerArgv = yargs.parsed.argv
       }
+
+      if (innerYargs && yargs.parsed === false) aliases = innerYargs.parsed.aliases
+      else aliases = yargs.parsed.aliases
     } else if (typeof commandHandler.builder === 'object') {
       // as a short hand, an object can instead be provided, specifying
       // the options that a command takes.
-      innerArgv = yargs.reset(parsed.aliases)
-      innerArgv.usage('$0 ' + (parentCommands.length ? parentCommands.join(' ') + ' ' : '') + commandHandler.original)
+      innerYargs = yargs.reset(parsed.aliases)
+      innerYargs.usage('$0 ' + (parentCommands.length ? parentCommands.join(' ') + ' ' : '') + commandHandler.original)
       Object.keys(commandHandler.builder).forEach(function (key) {
-        innerArgv.option(key, commandHandler.builder[key])
+        innerYargs.option(key, commandHandler.builder[key])
       })
-      innerArgv = innerArgv.argv
+      innerArgv = innerYargs._parseArgs(null, null, true)
+      aliases = innerYargs.parsed.aliases
+    }
+
+    if (!yargs._hasOutput()) {
+      positionalMap = populatePositionals(commandHandler, innerArgv, currentContext, yargs)
     }
-    if (!yargs._hasOutput()) populatePositionals(commandHandler, innerArgv, currentContext, yargs)
+
+    // we apply validation post-hoc, so that custom
+    // checks get passed populated positional arguments.
+    if (!yargs._hasOutput()) yargs._runValidation(innerArgv, aliases, positionalMap, yargs.parsed.error)
 
     if (commandHandler.handler && !yargs._hasOutput()) {
+      yargs._setHasOutput()
       commandHandler.handler(innerArgv)
     }
-    currentContext.commands.pop()
+
+    if (command) currentContext.commands.pop()
     numFiles = currentContext.files.length - numFiles
     if (numFiles > 0) currentContext.files.splice(numFiles * -1, numFiles)
+
     return innerArgv
   }
 
@@ -181,25 +244,27 @@ module.exports = function (yargs, usage, validation) {
     argv._ = argv._.slice(context.commands.length) // nuke the current commands
     var demanded = commandHandler.demanded.slice(0)
     var optional = commandHandler.optional.slice(0)
+    var positionalMap = {}
 
     validation.positionalCount(demanded.length, argv._.length)
 
     while (demanded.length) {
       var demand = demanded.shift()
-      populatePositional(demand, argv, yargs)
+      populatePositional(demand, argv, yargs, positionalMap)
     }
 
     while (optional.length) {
       var maybe = optional.shift()
-      populatePositional(maybe, argv, yargs)
+      populatePositional(maybe, argv, yargs, positionalMap)
     }
 
     argv._ = context.commands.concat(argv._)
+    return positionalMap
   }
 
   // populate a single positional argument and its
   // aliases onto argv.
-  function populatePositional (positional, argv, yargs) {
+  function populatePositional (positional, argv, yargs, positionalMap) {
     // "positional" consists of the positional.cmd, an array representing
     // the positional's name and aliases, and positional.variadic
     // indicating whether or not it is a variadic array.
@@ -214,6 +279,7 @@ module.exports = function (yargs, usage, validation) {
         if (value) argv[cmd] = value
         else argv[cmd] = value = argv._.shift()
       }
+      positionalMap[cmd] = true
       postProcessPositional(yargs, argv, cmd)
       addCamelCaseExpansions(argv, cmd)
     }
@@ -242,6 +308,7 @@ module.exports = function (yargs, usage, validation) {
   self.reset = function () {
     handlers = {}
     aliasMap = {}
+    defaultCommand = undefined
     return self
   }
 
@@ -254,10 +321,12 @@ module.exports = function (yargs, usage, validation) {
     frozen = {}
     frozen.handlers = handlers
     frozen.aliasMap = aliasMap
+    frozen.defaultCommand = defaultCommand
   }
   self.unfreeze = function () {
     handlers = frozen.handlers
     aliasMap = frozen.aliasMap
+    defaultCommand = frozen.defaultCommand
     frozen = undefined
   }
 
diff --git a/lib/completion.js b/lib/completion.js
index 93a936c..5cd9a18 100644
--- a/lib/completion.js
+++ b/lib/completion.js
@@ -44,7 +44,12 @@ module.exports = function (yargs, usage, command) {
     var handlers = command.getCommandHandlers()
     for (var i = 0, ii = args.length; i < ii; ++i) {
       if (handlers[args[i]] && handlers[args[i]].builder) {
-        return handlers[args[i]].builder(yargs.reset()).argv
+        const builder = handlers[args[i]].builder
+        if (typeof builder === 'function') {
+          const y = yargs.reset()
+          builder(y)
+          return y.argv
+        }
       }
     }
 
diff --git a/lib/usage.js b/lib/usage.js
index 765c271..43f7122 100644
--- a/lib/usage.js
+++ b/lib/usage.js
@@ -3,6 +3,7 @@
 const stringWidth = require('string-width')
 const objFilter = require('./obj-filter')
 const setBlocking = require('set-blocking')
+const YError = require('./yerror')
 
 module.exports = function (yargs, y18n) {
   const __ = y18n.__
@@ -50,7 +51,7 @@ module.exports = function (yargs, y18n) {
         }
       }
 
-      err = err || new Error(msg)
+      err = err || new YError(msg)
       if (yargs.getExitProcess()) {
         return yargs.exit(1)
       } else if (yargs._hasParseCallback()) {
@@ -76,8 +77,15 @@ module.exports = function (yargs, y18n) {
   }
 
   var commands = []
-  self.command = function (cmd, description, aliases) {
-    commands.push([cmd, description || '', aliases])
+  self.command = function (cmd, description, isDefault, aliases) {
+    // the last default wins, so cancel out any previously set default
+    if (isDefault) {
+      commands = commands.map(function (cmdArray) {
+        cmdArray[2] = false
+        return cmdArray
+      })
+    }
+    commands.push([cmd, description || '', isDefault, aliases])
   }
   self.getCommands = function () {
     return commands
@@ -165,8 +173,13 @@ module.exports = function (yargs, y18n) {
           {text: command[0], padding: [0, 2, 0, 2], width: maxWidth(commands, theWrap) + 4},
           {text: command[1]}
         )
-        if (command[2] && command[2].length) {
-          ui.div({text: '[' + __('aliases:') + ' ' + command[2].join(', ') + ']', padding: [0, 0, 0, 2], align: 'right'})
+        var hints = []
+        if (command[2]) hints.push('[' + __('default:').slice(0, -1) + ']') // TODO hacking around i18n here
+        if (command[3] && command[3].length) {
+          hints.push('[' + __('aliases:') + ' ' + command[3].join(', ') + ']')
+        }
+        if (hints.length) {
+          ui.div({text: hints.join(' '), padding: [0, 0, 0, 2], align: 'right'})
         } else {
           ui.div()
         }
@@ -234,7 +247,7 @@ module.exports = function (yargs, y18n) {
 
         var extra = [
           type,
-          demandedOptions[key] ? '[' + __('required') + ']' : null,
+          (key in demandedOptions) ? '[' + __('required') + ']' : null,
           options.choices && options.choices[key] ? '[' + __('choices:') + ' ' +
             self.stringifiedValues(options.choices[key]) + ']' : null,
           defaultString(options.default[key], options.defaultDescription[key])
@@ -329,7 +342,7 @@ module.exports = function (yargs, y18n) {
         // copy descriptions.
         if (descriptions[alias]) self.describe(key, descriptions[alias])
         // copy demanded.
-        if (demandedOptions[alias]) yargs.demandOption(key, demandedOptions[alias].msg)
+        if (alias in demandedOptions) yargs.demandOption(key, demandedOptions[alias])
         // type messages.
         if (~options.boolean.indexOf(alias)) yargs.boolean(key)
         if (~options.count.indexOf(alias)) yargs.count(key)
@@ -435,7 +448,7 @@ module.exports = function (yargs, y18n) {
     else logger.log(version)
   }
 
-  self.reset = function (globalLookup) {
+  self.reset = function (localLookup) {
     // do not reset wrap here
     // do not reset fails here
     failMessage = null
@@ -445,7 +458,7 @@ module.exports = function (yargs, y18n) {
     examples = []
     commands = []
     descriptions = objFilter(descriptions, function (k, v) {
-      return globalLookup[k]
+      return !localLookup[k]
     })
     return self
   }
diff --git a/lib/validation.js b/lib/validation.js
index 328d83a..3d3dbf5 100644
--- a/lib/validation.js
+++ b/lib/validation.js
@@ -98,7 +98,7 @@ module.exports = function (yargs, usage, y18n) {
     if (missing) {
       const customMsgs = []
       Object.keys(missing).forEach(function (key) {
-        const msg = missing[key].msg
+        const msg = missing[key]
         if (msg && customMsgs.indexOf(msg) < 0) {
           customMsgs.push(msg)
         }
@@ -116,7 +116,7 @@ module.exports = function (yargs, usage, y18n) {
   }
 
   // check for unknown arguments (strict-mode).
-  self.unknownArguments = function (argv, aliases) {
+  self.unknownArguments = function (argv, aliases, positionalMap) {
     const aliasLookup = {}
     const descriptions = usage.getDescriptions()
     const demandedOptions = yargs.getDemandedOptions()
@@ -134,6 +134,8 @@ module.exports = function (yargs, usage, y18n) {
       if (key !== '$0' && key !== '_' &&
         !descriptions.hasOwnProperty(key) &&
         !demandedOptions.hasOwnProperty(key) &&
+        !positionalMap.hasOwnProperty(key) &&
+        !yargs._getParseContext().hasOwnProperty(key) &&
         !aliasLookup.hasOwnProperty(key)) {
         unknown.push(key)
       }
@@ -194,22 +196,26 @@ module.exports = function (yargs, usage, y18n) {
 
   // custom checks, added using the `check` option on yargs.
   var checks = []
-  self.check = function (f) {
-    checks.push(f)
+  self.check = function (f, global) {
+    checks.push({
+      func: f,
+      global: global
+    })
   }
 
   self.customChecks = function (argv, aliases) {
     for (var i = 0, f; (f = checks[i]) !== undefined; i++) {
+      var func = f.func
       var result = null
       try {
-        result = f(argv, aliases)
+        result = func(argv, aliases)
       } catch (err) {
         usage.fail(err.message ? err.message : err, err)
         continue
       }
 
       if (!result) {
-        usage.fail(__('Argument check failed: %s', f.toString()))
+        usage.fail(__('Argument check failed: %s', func.toString()))
       } else if (typeof result === 'string' || result instanceof Error) {
         usage.fail(result.toString(), result)
       }
@@ -224,6 +230,7 @@ module.exports = function (yargs, usage, y18n) {
         self.implies(k, key[k])
       })
     } else {
+      yargs.global(key)
       implied[key] = value
     }
   }
@@ -290,6 +297,7 @@ module.exports = function (yargs, usage, y18n) {
         self.conflicts(k, key[k])
       })
     } else {
+      yargs.global(key)
       conflicting[key] = value
     }
   }
@@ -324,12 +332,16 @@ module.exports = function (yargs, usage, y18n) {
     if (recommended) usage.fail(__('Did you mean %s?', recommended))
   }
 
-  self.reset = function (globalLookup) {
+  self.reset = function (localLookup) {
     implied = objFilter(implied, function (k, v) {
-      return globalLookup[k]
+      return !localLookup[k]
+    })
+    conflicting = objFilter(conflicting, function (k, v) {
+      return !localLookup[k]
+    })
+    checks = checks.filter(function (c) {
+      return c.global
     })
-    checks = []
-    conflicting = {}
     return self
   }
 
diff --git a/lib/yerror.js b/lib/yerror.js
new file mode 100644
index 0000000..ad96a87
--- /dev/null
+++ b/lib/yerror.js
@@ -0,0 +1,10 @@
+function YError (msg) {
+  this.name = 'YError'
+  this.message = msg || 'yargs error'
+  Error.captureStackTrace(this, YError)
+}
+
+YError.prototype = Object.create(Error.prototype)
+YError.prototype.constructor = YError
+
+module.exports = YError
diff --git a/package.json b/package.json
index 0911614..ec5eb96 100644
--- a/package.json
+++ b/package.json
@@ -24,7 +24,7 @@
     "string-width": "^1.0.2",
     "which-module": "^1.0.0",
     "y18n": "^3.2.1",
-    "yargs-parser": "^4.2.1"
+    "yargs-parser": "^5.0.0"
   },
   "devDependencies": {
     "chai": "^3.4.1",
diff --git a/yargs.js b/yargs.js
index 53a8774..2b911d9 100644
--- a/yargs.js
+++ b/yargs.js
@@ -1,3 +1,4 @@
+const argsert = require('./lib/argsert')
 const assign = require('./lib/assign')
 const Command = require('./lib/command')
 const Completion = require('./lib/completion')
@@ -8,6 +9,8 @@ const Validation = require('./lib/validation')
 const Y18n = require('y18n')
 const objFilter = require('./lib/obj-filter')
 const setBlocking = require('set-blocking')
+const applyExtends = require('./lib/apply-extends')
+const YError = require('./lib/yerror')
 
 var exports = module.exports = Yargs
 function Yargs (processArgs, cwd, parentRequire) {
@@ -65,23 +68,23 @@ function Yargs (processArgs, cwd, parentRequire) {
     // logic is used to build a nested command
     // hierarchy.
     var tmpOptions = {}
-    tmpOptions.global = options.global ? options.global : []
+    tmpOptions.local = options.local ? options.local : []
     tmpOptions.configObjects = options.configObjects ? options.configObjects : []
 
-    // if a key has been set as a global, we
-    // do not want to reset it or its aliases.
-    var globalLookup = {}
-    tmpOptions.global.forEach(function (g) {
-      globalLookup[g] = true
-      ;(aliases[g] || []).forEach(function (a) {
-        globalLookup[a] = true
+    // if a key has been explicitly set as local,
+    // we should reset it before passing options to command.
+    var localLookup = {}
+    tmpOptions.local.forEach(function (l) {
+      localLookup[l] = true
+      ;(aliases[l] || []).forEach(function (a) {
+        localLookup[a] = true
       })
     })
 
-    // preserve groups containing global keys
+    // preserve all groups not set to local.
     preservedGroups = Object.keys(groups).reduce(function (acc, groupName) {
       var keys = groups[groupName].filter(function (key) {
-        return key in globalLookup
+        return !(key in localLookup)
       })
       if (keys.length > 0) {
         acc[groupName] = keys
@@ -103,13 +106,13 @@ function Yargs (processArgs, cwd, parentRequire) {
 
     arrayOptions.forEach(function (k) {
       tmpOptions[k] = (options[k] || []).filter(function (k) {
-        return globalLookup[k]
+        return !localLookup[k]
       })
     })
 
     objectOptions.forEach(function (k) {
       tmpOptions[k] = objFilter(options[k], function (k, v) {
-        return globalLookup[k]
+        return !localLookup[k]
       })
     })
 
@@ -118,12 +121,12 @@ function Yargs (processArgs, cwd, parentRequire) {
 
     // if this is the first time being executed, create
     // instances of all our helpers -- otherwise just reset.
-    usage = usage ? usage.reset(globalLookup) : Usage(self, y18n)
-    validation = validation ? validation.reset(globalLookup) : Validation(self, usage, y18n)
+    usage = usage ? usage.reset(localLookup) : Usage(self, y18n)
+    validation = validation ? validation.reset(localLookup) : Validation(self, usage, y18n)
     command = command ? command.reset() : Command(self, usage, validation)
     if (!completion) completion = Completion(self, usage, command)
 
-    strict = false
+    if (!strictGlobal) strict = false
     completionCommand = null
     output = ''
     exitError = null
@@ -171,56 +174,143 @@ function Yargs (processArgs, cwd, parentRequire) {
     frozen = undefined
   }
 
-  self.boolean = function (bools) {
-    options.boolean.push.apply(options.boolean, [].concat(bools))
+  self.boolean = function (keys) {
+    argsert('<array|string>', [keys], arguments.length)
+    populateParserHintArray('boolean', keys)
     return self
   }
 
-  self.array = function (arrays) {
-    options.array.push.apply(options.array, [].concat(arrays))
+  self.array = function (keys) {
+    argsert('<array|string>', [keys], arguments.length)
+    populateParserHintArray('array', keys)
     return self
   }
 
-  self.nargs = function (key, n) {
-    if (typeof key === 'object') {
-      Object.keys(key).forEach(function (k) {
-        self.nargs(k, key[k])
-      })
-    } else {
-      options.narg[key] = n
-    }
+  self.number = function (keys) {
+    argsert('<array|string>', [keys], arguments.length)
+    populateParserHintArray('number', keys)
     return self
   }
 
-  self.number = function (numbers) {
-    options.number.push.apply(options.number, [].concat(numbers))
+  self.normalize = function (keys) {
+    argsert('<array|string>', [keys], arguments.length)
+    populateParserHintArray('normalize', keys)
     return self
   }
 
-  self.choices = function (key, values) {
-    if (typeof key === 'object') {
-      Object.keys(key).forEach(function (k) {
-        self.choices(k, key[k])
-      })
-    } else {
-      options.choices[key] = (options.choices[key] || []).concat(values)
+  self.count = function (keys) {
+    argsert('<array|string>', [keys], arguments.length)
+    populateParserHintArray('count', keys)
+    return self
+  }
+
+  self.string = function (keys) {
+    argsert('<array|string>', [keys], arguments.length)
+    populateParserHintArray('string', keys)
+    return self
+  }
+
+  self.requiresArg = function (keys) {
+    argsert('<array|string>', [keys], arguments.length)
+    populateParserHintArray('requiresArg', keys)
+    return self
+  }
+
+  self.skipValidation = function (keys) {
+    argsert('<array|string>', [keys], arguments.length)
+    populateParserHintArray('skipValidation', keys)
+    return self
+  }
+
+  function populateParserHintArray (type, keys, value) {
+    keys = [].concat(keys)
+    keys.forEach(function (key) {
+      options[type].push(key)
+    })
+  }
+
+  self.nargs = function (key, value) {
+    argsert('<string|object|array> [number]', [key, value], arguments.length)
+    populateParserHintObject(self.nargs, false, 'narg', key, value)
+    return self
+  }
+
+  self.choices = function (key, value) {
+    argsert('<object|string|array> [string|array]', [key, value], arguments.length)
+    populateParserHintObject(self.choices, true, 'choices', key, value)
+    return self
+  }
+
+  self.alias = function (key, value) {
+    argsert('<object|string|array> [string|array]', [key, value], arguments.length)
+    populateParserHintObject(self.alias, true, 'alias', key, value)
+    return self
+  }
+
+  // TODO: actually deprecate self.defaults.
+  self.default = self.defaults = function (key, value, defaultDescription) {
+    argsert('<object|string|array> [*] [string]', [key, value, defaultDescription], arguments.length)
+    if (defaultDescription) options.defaultDescription[key] = defaultDescription
+    if (typeof value === 'function') {
+      if (!options.defaultDescription[key]) options.defaultDescription[key] = usage.functionDescription(value)
+      value = value.call()
     }
+    populateParserHintObject(self.default, false, 'default', key, value)
+    return self
+  }
+
+  self.describe = function (key, desc) {
+    argsert('<object|string|array> [string]', [key, desc], arguments.length)
+    populateParserHintObject(self.describe, false, 'key', key, true)
+    usage.describe(key, desc)
     return self
   }
 
-  self.normalize = function (strings) {
-    options.normalize.push.apply(options.normalize, [].concat(strings))
+  self.demandOption = function (keys, msg) {
+    argsert('<object|string|array> [string]', [keys, msg], arguments.length)
+    populateParserHintObject(self.demandOption, false, 'demandedOptions', keys, msg)
     return self
   }
 
+  self.coerce = function (keys, value) {
+    argsert('<object|string|array> [function]', [keys, value], arguments.length)
+    populateParserHintObject(self.coerce, false, 'coerce', keys, value)
+    return self
+  }
+
+  function populateParserHintObject (builder, isArray, type, key, value) {
+    if (Array.isArray(key)) {
+      // an array of keys with one value ['x', 'y', 'z'], function parse () {}
+      var temp = {}
+      key.forEach(function (k) {
+        temp[k] = value
+      })
+      builder(temp)
+    } else if (typeof key === 'object') {
+      // an object of key value pairs: {'x': parse () {}, 'y': parse() {}}
+      Object.keys(key).forEach(function (k) {
+        builder(k, key[k])
+      })
+    } else {
+      // a single key value pair 'x', parse() {}
+      if (isArray) {
+        options[type][key] = (options[type][key] || []).concat(value)
+      } else {
+        options[type][key] = value
+      }
+    }
+  }
+
   self.config = function (key, msg, parseFn) {
-    // allow to pass a configuration object
+    argsert('[object|string] [string|function] [function]', [key, msg, parseFn], arguments.length)
+    // allow a config object to be provided directly.
     if (typeof key === 'object') {
+      key = applyExtends(key, cwd)
       options.configObjects = (options.configObjects || []).concat(key)
       return self
     }
 
-    // allow to provide a parsing function
+    // allow for a custom parsing function.
     if (typeof msg === 'function') {
       parseFn = msg
       msg = null
@@ -231,78 +321,31 @@ function Yargs (processArgs, cwd, parentRequire) {
     ;(Array.isArray(key) ? key : [key]).forEach(function (k) {
       options.config[k] = parseFn || true
     })
+
     return self
   }
 
   self.example = function (cmd, description) {
+    argsert('<string> [string]', [cmd, description], arguments.length)
     usage.example(cmd, description)
     return self
   }
 
   self.command = function (cmd, description, builder, handler) {
+    argsert('<string|array|object> [string|boolean] [function|object] [function]', [cmd, description, builder, handler], arguments.length)
     command.addHandler(cmd, description, builder, handler)
     return self
   }
 
   self.commandDir = function (dir, opts) {
+    argsert('<string> [object]', [dir, opts], arguments.length)
     const req = parentRequire || require
     command.addDirectory(dir, self.getContext(), req, require('get-caller-file')(), opts)
     return self
   }
 
-  self.string = function (strings) {
-    options.string.push.apply(options.string, [].concat(strings))
-    return self
-  }
-
-  // The 'defaults' alias is deprecated. It will be removed in the next major version.
-  self.default = self.defaults = function (key, value, defaultDescription) {
-    if (typeof key === 'object') {
-      Object.keys(key).forEach(function (k) {
-        self.default(k, key[k])
-      })
-    } else {
-      if (defaultDescription) options.defaultDescription[key] = defaultDescription
-      if (typeof value === 'function') {
-        if (!options.defaultDescription[key]) options.defaultDescription[key] = usage.functionDescription(value)
-        value = value.call()
-      }
-      options.default[key] = value
-    }
-    return self
-  }
-
-  self.alias = function (x, y) {
-    if (typeof x === 'object') {
-      Object.keys(x).forEach(function (key) {
-        self.alias(key, x[key])
-      })
-    } else {
-      options.alias[x] = (options.alias[x] || []).concat(y)
-    }
-    return self
-  }
-
-  self.coerce = function (key, fn) {
-    if (typeof key === 'object' && !Array.isArray(key)) {
-      Object.keys(key).forEach(function (k) {
-        self.coerce(k, key[k])
-      })
-    } else {
-      [].concat(key).forEach(function (k) {
-        options.coerce[k] = fn
-      })
-    }
-    return self
-  }
-
-  self.count = function (counts) {
-    options.count.push.apply(options.count, [].concat(counts))
-    return self
-  }
-
-  // deprecated: the demand API is too overloaded, and is being
-  // deprecated in favor of .demandCommand() .demandOption().
+  // TODO: deprecate self.demand in favor of
+  // .demandCommand() .demandOption().
   self.demand = self.required = self.require = function (keys, max, msg) {
     // you can optionally provide a 'max' key,
     // which will raise an exception if too many '_'
@@ -334,29 +377,18 @@ function Yargs (processArgs, cwd, parentRequire) {
     return self
   }
 
-  self.demandOption = function (key, msg) {
-    if (Array.isArray(key)) {
-      key.forEach(function (key) {
-        self.demandOption(key, msg)
-      })
-    } else {
-      if (typeof msg === 'string') {
-        options.demandedOptions[key] = { msg: msg }
-      // allow edge-case of options: {a: {demand: true}, b: {demand: false}}
-      } else if (msg === true || typeof msg === 'undefined') {
-        options.demandedOptions[key] = { msg: undefined }
-      }
-    }
+  self.demandCommand = function (min, max, minMsg, maxMsg) {
+    argsert('[number] [number|string] [string|null] [string|null]', [min, max, minMsg, maxMsg], arguments.length)
 
-    return self
-  }
+    if (typeof min === 'undefined') min = 1
 
-  self.demandCommand = function (min, max, minMsg, maxMsg) {
     if (typeof max !== 'number') {
       minMsg = max
       max = Infinity
     }
 
+    self.global('_', false)
+
     options.demandedCommands._ = {
       min: min,
       max: max,
@@ -368,34 +400,30 @@ function Yargs (processArgs, cwd, parentRequire) {
   }
 
   self.getDemandedOptions = function () {
+    argsert([], 0)
     return options.demandedOptions
   }
 
   self.getDemandedCommands = function () {
+    argsert([], 0)
     return options.demandedCommands
   }
 
-  self.requiresArg = function (requiresArgs) {
-    options.requiresArg.push.apply(options.requiresArg, [].concat(requiresArgs))
-    return self
-  }
-
-  self.skipValidation = function (skipValidations) {
-    options.skipValidation.push.apply(options.skipValidation, [].concat(skipValidations))
-    return self
-  }
-
   self.implies = function (key, value) {
+    argsert('<string|object> [string]', [key, value], arguments.length)
     validation.implies(key, value)
     return self
   }
 
   self.conflicts = function (key1, key2) {
+    argsert('<string|object> [string]', [key1, key2], arguments.length)
     validation.conflicts(key1, key2)
     return self
   }
 
   self.usage = function (msg, opts) {
+    argsert('<string|null|object> [object]', [msg, opts], arguments.length)
+
     if (!opts && typeof msg === 'object') {
       opts = msg
       msg = null
@@ -409,45 +437,49 @@ function Yargs (processArgs, cwd, parentRequire) {
   }
 
   self.epilogue = self.epilog = function (msg) {
+    argsert('<string>', [msg], arguments.length)
     usage.epilog(msg)
     return self
   }
 
   self.fail = function (f) {
+    argsert('<function>', [f], arguments.length)
     usage.failFn(f)
     return self
   }
 
-  self.check = function (f) {
-    validation.check(f)
+  self.check = function (f, _global) {
+    argsert('<function> [boolean]', [f, _global], arguments.length)
+    validation.check(f, _global !== false)
     return self
   }
 
-  self.describe = function (key, desc) {
-    if (typeof key === 'object') {
-      Object.keys(key).forEach(function (k) {
-        options.key[k] = true
+  self.global = function (globals, global) {
+    argsert('<string|array> [boolean]', [globals, global], arguments.length)
+    globals = [].concat(globals)
+    if (global !== false) {
+      options.local = options.local.filter(function (l) {
+        return globals.indexOf(l) === -1
       })
     } else {
-      options.key[key] = true
+      globals.forEach(function (g) {
+        if (options.local.indexOf(g) === -1) options.local.push(g)
+      })
     }
-    usage.describe(key, desc)
-    return self
-  }
-
-  self.global = function (globals) {
-    options.global.push.apply(options.global, [].concat(globals))
     return self
   }
 
   self.pkgConf = function (key, path) {
+    argsert('<string> [string]', [key, path], arguments.length)
     var conf = null
-
-    var obj = pkgUp(path)
+    // prefer cwd to require-main-filename in this method
+    // since we're looking for e.g. "nyc" config in nyc consumer
+    // rather than "yargs" config in nyc (where nyc is the main filename)
+    var obj = pkgUp(path || cwd)
 
     // If an object exists in the key, add it to options.configObjects
     if (obj[key] && typeof obj[key] === 'object') {
-      conf = obj[key]
+      conf = applyExtends(obj[key], path || cwd, key)
       options.configObjects = (options.configObjects || []).concat(conf)
     }
 
@@ -475,6 +507,8 @@ function Yargs (processArgs, cwd, parentRequire) {
   var parseFn = null
   var parseContext = null
   self.parse = function (args, shortCircuit, _parseFn) {
+    argsert('<string|array> [function|boolean|object] [function]', [args, shortCircuit, _parseFn], arguments.length)
+
     // a context object can optionally be provided, this allows
     // additional information to be passed to a command handler.
     if (typeof shortCircuit === 'object') {
@@ -496,18 +530,23 @@ function Yargs (processArgs, cwd, parentRequire) {
     freeze()
     if (parseFn) exitProcess = false
 
-    var parsed = parseArgs(args, shortCircuit)
+    var parsed = self._parseArgs(args, shortCircuit)
     if (parseFn) parseFn(exitError, parsed, output)
     unfreeze()
 
     return parsed
   }
 
+  self._getParseContext = function () {
+    return parseContext || {}
+  }
+
   self._hasParseCallback = function () {
     return !!parseFn
   }
 
   self.option = self.options = function (key, opt) {
+    argsert('<string|object> [object]', [key, opt], arguments.length)
     if (typeof key === 'object') {
       Object.keys(key).forEach(function (k) {
         self.options(k, key[k])
@@ -523,12 +562,13 @@ function Yargs (processArgs, cwd, parentRequire) {
 
       var demand = opt.demand || opt.required || opt.require
 
+      // deprecated, use 'demandOption' instead
       if (demand) {
         self.demand(key, demand)
       }
 
       if ('demandOption' in opt) {
-        self.demandOption(key, opt.demandOption)
+        self.demandOption(key, typeof opt.demandOption === 'string' ? opt.demandOption : undefined)
       }
 
       if ('config' in opt) {
@@ -567,10 +607,6 @@ function Yargs (processArgs, cwd, parentRequire) {
         self.group(key, opt.group)
       }
 
-      if (opt.global) {
-        self.global(key)
-      }
-
       if (opt.boolean || opt.type === 'boolean') {
         self.boolean(key)
         if (opt.alias) self.boolean(opt.alias)
@@ -595,6 +631,10 @@ function Yargs (processArgs, cwd, parentRequire) {
         self.count(key)
       }
 
+      if (typeof opt.global === 'boolean') {
+        self.global(key, opt.global)
+      }
+
       if (opt.defaultDescription) {
         options.defaultDescription[key] = opt.defaultDescription
       }
@@ -620,10 +660,10 @@ function Yargs (processArgs, cwd, parentRequire) {
   }
 
   self.group = function (opts, groupName) {
+    argsert('<string|array> <string>', [opts, groupName], arguments.length)
     var existing = preservedGroups[groupName] || groups[groupName]
     if (preservedGroups[groupName]) {
-      // the preserved group will be moved to the set of explicitly declared
-      // groups
+      // we now only need to track this group name in groups.
       delete preservedGroups[groupName]
     }
 
@@ -642,19 +682,24 @@ function Yargs (processArgs, cwd, parentRequire) {
   // as long as options.envPrefix is not undefined,
   // parser will apply env vars matching prefix to argv
   self.env = function (prefix) {
+    argsert('[string|boolean]', [prefix], arguments.length)
     if (prefix === false) options.envPrefix = undefined
     else options.envPrefix = prefix || ''
     return self
   }
 
   self.wrap = function (cols) {
+    argsert('<number|null>', [cols], arguments.length)
     usage.wrap(cols)
     return self
   }
 
   var strict = false
-  self.strict = function () {
+  var strictGlobal = false
+  self.strict = function (global) {
+    argsert('[boolean]', [global], arguments.length)
     strict = true
+    strictGlobal = global !== false
     return self
   }
   self.getStrict = function () {
@@ -662,13 +707,15 @@ function Yargs (processArgs, cwd, parentRequire) {
   }
 
   self.showHelp = function (level) {
-    if (!self.parsed) parseArgs(processArgs) // run parser, if it has not already been executed.
+    argsert('[string|function]', [level], arguments.length)
+    if (!self.parsed) self._parseArgs(processArgs) // run parser, if it has not already been executed.
     usage.showHelp(level)
     return self
   }
 
   var versionOpt = null
   self.version = function (opt, msg, ver) {
+    argsert('[string|function] [string|function] [string]', [opt, msg, ver], arguments.length)
     if (arguments.length === 0) {
       ver = guessVersion()
       opt = 'version'
@@ -677,6 +724,7 @@ function Yargs (processArgs, cwd, parentRequire) {
       opt = 'version'
     } else if (arguments.length === 2) {
       ver = msg
+      msg = null
     }
 
     versionOpt = opt
@@ -684,7 +732,6 @@ function Yargs (processArgs, cwd, parentRequire) {
 
     usage.version(ver || undefined)
     self.boolean(versionOpt)
-    self.global(versionOpt)
     self.describe(versionOpt, msg)
     return self
   }
@@ -698,6 +745,8 @@ function Yargs (processArgs, cwd, parentRequire) {
   var helpOpt = null
   var useHelpOptAsCommand = false // a call to .help() will enable this
   self.addHelpOpt = self.help = function (opt, msg, addImplicitCmd) {
+    argsert('[string|boolean] [string|boolean] [boolean]', [opt, msg, addImplicitCmd], arguments.length)
+
     // argument shuffle
     if (arguments.length === 0) {
       useHelpOptAsCommand = true
@@ -721,18 +770,19 @@ function Yargs (processArgs, cwd, parentRequire) {
     // use arguments, fallback to defaults for opt and msg
     helpOpt = opt || 'help'
     self.boolean(helpOpt)
-    self.global(helpOpt)
     self.describe(helpOpt, msg || usage.deferY18nLookup('Show help'))
     return self
   }
 
   self.showHelpOnFail = function (enabled, message) {
+    argsert('[boolean|string] [string]', [enabled, message], arguments.length)
     usage.showHelpOnFail(enabled, message)
     return self
   }
 
   var exitProcess = true
   self.exitProcess = function (enabled) {
+    argsert('[boolean]', [enabled], arguments.length)
     if (typeof enabled !== 'boolean') {
       enabled = true
     }
@@ -745,6 +795,8 @@ function Yargs (processArgs, cwd, parentRequire) {
 
   var completionCommand = null
   self.completion = function (cmd, desc, fn) {
+    argsert('[string] [string|boolean|function] [function]', [cmd, desc, fn], arguments.length)
+
     // a function to execute when generating
     // completions can be provided as the second
     // or third argument to completion.
@@ -767,16 +819,19 @@ function Yargs (processArgs, cwd, parentRequire) {
   }
 
   self.showCompletionScript = function ($0) {
+    argsert('[string]', [$0], arguments.length)
     $0 = $0 || self.$0
     _logger.log(completion.generateCompletionScript($0))
     return self
   }
 
   self.getCompletion = function (args, done) {
+    argsert('<array> <function>', [args, done], arguments.length)
     completion.getCompletion(args, done)
   }
 
   self.locale = function (locale) {
+    argsert('[string]', [locale], arguments.length)
     if (arguments.length === 0) {
       guessLocale()
       return y18n.getLocale()
@@ -787,6 +842,7 @@ function Yargs (processArgs, cwd, parentRequire) {
   }
 
   self.updateStrings = self.updateLocale = function (obj) {
+    argsert('<object>', [obj], arguments.length)
     detectLocale = false
     y18n.updateLocale(obj)
     return self
@@ -794,6 +850,7 @@ function Yargs (processArgs, cwd, parentRequire) {
 
   var detectLocale = true
   self.detectLocale = function (detect) {
+    argsert('<boolean>', [detect], arguments.length)
     detectLocale = detect
     return self
   }
@@ -815,14 +872,16 @@ function Yargs (processArgs, cwd, parentRequire) {
   // so that we can print to non-CLIs, e.g., chat-bots.
   var _logger = {
     log: function () {
-      var args = Array.prototype.slice.call(arguments)
+      const args = []
+      for (var i = 0; i < arguments.length; i++) args.push(arguments[i])
       if (!self._hasParseCallback()) console.log.apply(console, args)
       hasOutput = true
       if (output.length) output += '\n'
       output += args.join(' ')
     },
     error: function () {
-      var args = Array.prototype.slice.call(arguments)
+      const args = []
+      for (var i = 0; i < arguments.length; i++) args.push(arguments[i])
       if (!self._hasParseCallback()) console.error.apply(console, args)
       hasOutput = true
       if (output.length) output += '\n'
@@ -838,9 +897,14 @@ function Yargs (processArgs, cwd, parentRequire) {
     return hasOutput
   }
 
+  self._setHasOutput = function () {
+    hasOutput = true
+  }
+
   var recommendCommands
-  self.recommendCommands = function () {
-    recommendCommands = true
+  self.recommendCommands = function (recommend) {
+    argsert('[boolean]', [recommend], arguments.length)
+    recommendCommands = typeof recommend === 'boolean' ? recommend : true
     return self
   }
 
@@ -857,161 +921,174 @@ function Yargs (processArgs, cwd, parentRequire) {
   }
 
   self.terminalWidth = function () {
+    argsert([], 0)
     return process.stdout.columns
   }
 
   Object.defineProperty(self, 'argv', {
     get: function () {
-      var args = null
-
-      try {
-        args = parseArgs(processArgs)
-      } catch (err) {
-        usage.fail(err.message, err)
-      }
-
-      return args
+      return self._parseArgs(processArgs)
     },
     enumerable: true
   })
 
-  function parseArgs (args, shortCircuit) {
+  self._parseArgs = function (args, shortCircuit, _skipValidation) {
+    var skipValidation = !!_skipValidation
+    args = args || processArgs
+
     options.__ = y18n.__
     options.configuration = pkgUp()['yargs'] || {}
     const parsed = Parser.detailed(args, options)
     var argv = parsed.argv
-    if (parseContext) argv = assign(parseContext, argv)
+    if (parseContext) argv = assign(argv, parseContext)
     var aliases = parsed.aliases
 
     argv.$0 = self.$0
     self.parsed = parsed
 
-    guessLocale() // guess locale lazily, so that it can be turned off in chain.
+    try {
+      guessLocale() // guess locale lazily, so that it can be turned off in chain.
 
-    // while building up the argv object, there
-    // are two passes through the parser. If completion
-    // is being performed short-circuit on the first pass.
-    if (shortCircuit) {
-      return argv
-    }
+      // while building up the argv object, there
+      // are two passes through the parser. If completion
+      // is being performed short-circuit on the first pass.
+      if (shortCircuit) {
+        return argv
+      }
 
-    if (argv._.length) {
-      // check for helpOpt in argv._ before running commands
-      // assumes helpOpt must be valid if useHelpOptAsCommand is true
-      if (useHelpOptAsCommand) {
-        // consider any multi-char helpOpt alias as a valid help command
-        // unless all helpOpt aliases are single-char
-        // note that parsed.aliases is a normalized bidirectional map :)
-        var helpCmds = [helpOpt].concat(aliases[helpOpt])
-        var multiCharHelpCmds = helpCmds.filter(function (k) {
-          return k.length > 1
-        })
-        if (multiCharHelpCmds.length) helpCmds = multiCharHelpCmds
-        // look for and strip any helpCmds from argv._
-        argv._ = argv._.filter(function (cmd) {
-          if (~helpCmds.indexOf(cmd)) {
-            argv[helpOpt] = true
-            return false
+      if (argv._.length) {
+        // check for helpOpt in argv._ before running commands
+        // assumes helpOpt must be valid if useHelpOptAsCommand is true
+        if (useHelpOptAsCommand) {
+          // consider any multi-char helpOpt alias as a valid help command
+          // unless all helpOpt aliases are single-char
+          // note that parsed.aliases is a normalized bidirectional map :)
+          var helpCmds = [helpOpt].concat(aliases[helpOpt] || [])
+          var multiCharHelpCmds = helpCmds.filter(function (k) {
+            return k.length > 1
+          })
+          if (multiCharHelpCmds.length) helpCmds = multiCharHelpCmds
+          // look for and strip any helpCmds from argv._
+          argv._ = argv._.filter(function (cmd) {
+            if (~helpCmds.indexOf(cmd)) {
+              argv[helpOpt] = true
+              return false
+            }
+            return true
+          })
+        }
+
+        // if there's a handler associated with a
+        // command defer processing to it.
+        var handlerKeys = command.getCommands()
+        if (handlerKeys.length) {
+          var firstUnknownCommand
+          for (var i = 0, cmd; (cmd = argv._[i]) !== undefined; i++) {
+            if (~handlerKeys.indexOf(cmd) && cmd !== completionCommand) {
+              setPlaceholderKeys(argv)
+              return command.runCommand(cmd, self, parsed)
+            } else if (!firstUnknownCommand && cmd !== completionCommand) {
+              firstUnknownCommand = cmd
+            }
           }
-          return true
-        })
-      }
 
-      // if there's a handler associated with a
-      // command defer processing to it.
-      var handlerKeys = command.getCommands()
-      if (handlerKeys.length) {
-        var firstUnknownCommand
-        for (var i = 0, cmd; (cmd = argv._[i]) !== undefined; i++) {
-          if (~handlerKeys.indexOf(cmd) && cmd !== completionCommand) {
+          // run the default command, if defined
+          if (command.hasDefaultCommand() && !argv[helpOpt]) {
             setPlaceholderKeys(argv)
-            return command.runCommand(cmd, self, parsed)
-          } else if (!firstUnknownCommand && cmd !== completionCommand) {
-            firstUnknownCommand = cmd
+            return command.runCommand(null, self, parsed)
+          }
+
+          // recommend a command if recommendCommands() has
+          // been enabled, and no commands were found to execute
+          if (recommendCommands && firstUnknownCommand) {
+            validation.recommendCommands(firstUnknownCommand, handlerKeys)
           }
         }
 
-        // recommend a command if recommendCommands() has
-        // been enabled, and no commands were found to execute
-        if (recommendCommands && firstUnknownCommand) {
-          validation.recommendCommands(firstUnknownCommand, handlerKeys)
+        // generate a completion script for adding to ~/.bashrc.
+        if (completionCommand && ~argv._.indexOf(completionCommand) && !argv[completion.completionKey]) {
+          if (exitProcess) setBlocking(true)
+          self.showCompletionScript()
+          self.exit(0)
         }
+      } else if (command.hasDefaultCommand()) {
+        setPlaceholderKeys(argv)
+        return command.runCommand(null, self, parsed)
       }
 
-      // generate a completion script for adding to ~/.bashrc.
-      if (completionCommand && ~argv._.indexOf(completionCommand) && !argv[completion.completionKey]) {
+      // we must run completions first, a user might
+      // want to complete the --help or --version option.
+      if (completion.completionKey in argv) {
         if (exitProcess) setBlocking(true)
-        self.showCompletionScript()
-        self.exit(0)
-      }
-    }
 
-    // we must run completions first, a user might
-    // want to complete the --help or --version option.
-    if (completion.completionKey in argv) {
-      if (exitProcess) setBlocking(true)
-
-      // we allow for asynchronous completions,
-      // e.g., loading in a list of commands from an API.
-      var completionArgs = args.slice(args.indexOf('--' + completion.completionKey) + 1)
-      completion.getCompletion(completionArgs, function (completions) {
-        ;(completions || []).forEach(function (completion) {
-          _logger.log(completion)
-        })
+        // we allow for asynchronous completions,
+        // e.g., loading in a list of commands from an API.
+        var completionArgs = args.slice(args.indexOf('--' + completion.completionKey) + 1)
+        completion.getCompletion(completionArgs, function (completions) {
+          ;(completions || []).forEach(function (completion) {
+            _logger.log(completion)
+          })
 
-        self.exit(0)
-      })
-      return setPlaceholderKeys(argv)
-    }
+          self.exit(0)
+        })
+        return setPlaceholderKeys(argv)
+      }
 
-    var skipValidation = false
+      // Handle 'help' and 'version' options
+      Object.keys(argv).forEach(function (key) {
+        if (key === helpOpt && argv[key]) {
+          if (exitProcess) setBlocking(true)
 
-    // Handle 'help' and 'version' options
-    Object.keys(argv).forEach(function (key) {
-      if (key === helpOpt && argv[key]) {
-        if (exitProcess) setBlocking(true)
+          skipValidation = true
+          self.showHelp('log')
+          self.exit(0)
+        } else if (key === versionOpt && argv[key]) {
+          if (exitProcess) setBlocking(true)
 
-        skipValidation = true
-        self.showHelp('log')
-        self.exit(0)
-      } else if (key === versionOpt && argv[key]) {
-        if (exitProcess) setBlocking(true)
+          skipValidation = true
+          usage.showVersion()
+          self.exit(0)
+        }
+      })
 
-        skipValidation = true
-        usage.showVersion()
-        self.exit(0)
+      // Check if any of the options to skip validation were provided
+      if (!skipValidation && options.skipValidation.length > 0) {
+        skipValidation = Object.keys(argv).some(function (key) {
+          return options.skipValidation.indexOf(key) >= 0 && argv[key] === true
+        })
       }
-    })
 
-    // Check if any of the options to skip validation were provided
-    if (!skipValidation && options.skipValidation.length > 0) {
-      skipValidation = Object.keys(argv).some(function (key) {
-        return options.skipValidation.indexOf(key) >= 0 && argv[key] === true
-      })
-    }
+      // If the help or version options where used and exitProcess is false,
+      // or if explicitly skipped, we won't run validations.
+      if (!skipValidation) {
+        if (parsed.error) throw new YError(parsed.error.message)
 
-    // If the help or version options where used and exitProcess is false,
-    // or if explicitly skipped, we won't run validations
-    if (!skipValidation) {
-      if (parsed.error) throw parsed.error
-
-      // if we're executed via bash completion, don't
-      // bother with validation.
-      if (!argv[completion.completionKey]) {
-        validation.nonOptionCount(argv)
-        validation.missingArgumentValue(argv)
-        validation.requiredArguments(argv)
-        if (strict) validation.unknownArguments(argv, aliases)
-        validation.customChecks(argv, aliases)
-        validation.limitedChoices(argv)
-        validation.implications(argv)
-        validation.conflicting(argv)
+        // if we're executed via bash completion, don't
+        // bother with validation.
+        if (!argv[completion.completionKey]) {
+          self._runValidation(argv, aliases, {}, parsed.error)
+        }
       }
+    } catch (err) {
+      if (err instanceof YError) usage.fail(err.message, err)
+      else throw err
     }
 
     return setPlaceholderKeys(argv)
   }
 
+  self._runValidation = function (argv, aliases, positionalMap, parseErrors) {
+    if (parseErrors) throw new YError(parseErrors.message)
+    validation.nonOptionCount(argv)
+    validation.missingArgumentValue(argv)
+    validation.requiredArguments(argv)
+    if (strict) validation.unknownArguments(argv, aliases, positionalMap)
+    validation.customChecks(argv, aliases)
+    validation.limitedChoices(argv)
+    validation.implications(argv)
+    validation.conflicting(argv)
+  }
+
   function guessLocale () {
     if (!detectLocale) return
 


Test patch:
diff --git a/test/argsert.js b/test/argsert.js
new file mode 100644
index 0000000..9139d0d
--- /dev/null
+++ b/test/argsert.js
@@ -0,0 +1,143 @@
+/* global describe, it */
+
+const argsert = require('../lib/argsert')
+const checkOutput = require('./helpers/utils').checkOutput
+
+require('chai').should()
+
+describe('Argsert', function () {
+  it('does not warn if optional argument is not provided', function () {
+    var o = checkOutput(function () {
+      argsert('[object]', [].slice.call(arguments))
+    })
+
+    o.warnings.length.should.equal(0)
+  })
+
+  it('warn if wrong type is provided for optional argument', function () {
+    var o = checkOutput(function () {
+      function foo (opts) {
+        argsert('[object|number]', [].slice.call(arguments))
+      }
+
+      foo('hello')
+    })
+
+    o.warnings[0].should.match(/Invalid first argument. Expected object or number but received string./)
+  })
+
+  it('does not warn if optional argument is valid', function () {
+    var o = checkOutput(function () {
+      function foo (opts) {
+        argsert('[object]', [].slice.call(arguments))
+      }
+
+      foo({foo: 'bar'})
+    })
+
+    o.warnings.length.should.equal(0)
+  })
+
+  it('warns if required argument is not provided', function () {
+    var o = checkOutput(function () {
+      argsert('<object>', [].slice.call(arguments))
+    })
+
+    o.warnings[0].should.match(/Not enough arguments provided. Expected 1 but received 0./)
+  })
+
+  it('warns if required argument is of wrong type', function () {
+    var o = checkOutput(function () {
+      function foo (opts) {
+        argsert('<object>', [].slice.call(arguments))
+      }
+
+      foo('bar')
+    })
+
+    o.warnings[0].should.match(/Invalid first argument. Expected object but received string./)
+  })
+
+  it('supports a combination of required and optional arguments', function () {
+    var o = checkOutput(function () {
+      function foo (opts) {
+        argsert('<array> <string|object> [string|object]', [].slice.call(arguments))
+      }
+
+      foo([], 'foo', {})
+    })
+
+    o.warnings.length.should.equal(0)
+  })
+
+  it('warns if too many arguments are provided', function () {
+    var o = checkOutput(function () {
+      function foo (expected) {
+        argsert('<array> [batman]', [].slice.call(arguments))
+      }
+
+      foo([], 33, 99)
+    })
+
+    o.warnings[0].should.match(/Too many arguments provided. Expected max 2 but received 3./)
+  })
+
+  it('configures function to accept 0 parameters, if only arguments object is provided', function () {
+    var o = checkOutput(function () {
+      function foo (expected) {
+        argsert([].slice.call(arguments))
+      }
+
+      foo(99)
+    })
+
+    o.warnings[0].should.match(/Too many arguments provided. Expected max 0 but received 1./)
+  })
+
+  it('allows for any type if * is provided', function () {
+    var o = checkOutput(function () {
+      function foo (opts) {
+        argsert('<*>', [].slice.call(arguments))
+      }
+
+      foo('bar')
+    })
+
+    o.warnings.length.should.equal(0)
+  })
+
+  it('should ignore trailing undefined values', function () {
+    var o = checkOutput(function () {
+      function foo (opts) {
+        argsert('<*>', [].slice.call(arguments))
+      }
+
+      foo('bar', undefined, undefined)
+    })
+
+    o.warnings.length.should.equal(0)
+  })
+
+  it('should not ignore undefined values that are not trailing', function () {
+    var o = checkOutput(function () {
+      function foo (opts) {
+        argsert('<*>', [].slice.call(arguments))
+      }
+
+      foo('bar', undefined, undefined, 33)
+    })
+
+    o.warnings[0].should.match(/Too many arguments provided. Expected max 1 but received 4./)
+  })
+
+  it('supports null as special type', function () {
+    var o = checkOutput(function () {
+      function foo (arg) {
+        argsert('<null>', [].slice.call(arguments))
+      }
+      foo(null)
+    })
+
+    o.warnings.length.should.equal(0)
+  })
+})
diff --git a/test/command.js b/test/command.js
index be4f0dc..a792653 100644
--- a/test/command.js
+++ b/test/command.js
@@ -181,21 +181,23 @@ describe('Command', function () {
     it('accepts string, string as first 2 arguments', function () {
       var cmd = 'foo'
       var desc = 'i\'m not feeling very creative at the moment'
+      var isDefault = false
       var aliases = []
 
       var y = yargs([]).command(cmd, desc)
       var commands = y.getUsageInstance().getCommands()
-      commands[0].should.deep.equal([cmd, desc, aliases])
+      commands[0].should.deep.equal([cmd, desc, isDefault, aliases])
     })
 
     it('accepts array, string as first 2 arguments', function () {
       var aliases = ['bar', 'baz']
       var cmd = 'foo <qux>'
       var desc = 'i\'m not feeling very creative at the moment'
+      var isDefault = false
 
       var y = yargs([]).command([cmd].concat(aliases), desc)
       var usageCommands = y.getUsageInstance().getCommands()
-      usageCommands[0].should.deep.equal([cmd, desc, aliases])
+      usageCommands[0].should.deep.equal([cmd, desc, isDefault, aliases])
       var cmdCommands = y.getCommandInstance().getCommands()
       cmdCommands.should.deep.equal(['foo', 'bar', 'baz'])
     })
@@ -284,6 +286,7 @@ describe('Command', function () {
         builder: function (yargs) { return yargs },
         handler: function (argv) {}
       }
+      var isDefault = false
       var aliases = []
 
       var y = yargs([]).command(module)
@@ -292,7 +295,7 @@ describe('Command', function () {
       handlers.foo.builder.should.equal(module.builder)
       handlers.foo.handler.should.equal(module.handler)
       var commands = y.getUsageInstance().getCommands()
-      commands[0].should.deep.equal([module.command, module.describe, aliases])
+      commands[0].should.deep.equal([module.command, module.describe, isDefault, aliases])
     })
 
     it('accepts module (description key, builder function) as 1st argument', function () {
@@ -302,6 +305,7 @@ describe('Command', function () {
         builder: function (yargs) { return yargs },
         handler: function (argv) {}
       }
+      var isDefault = false
       var aliases = []
 
       var y = yargs([]).command(module)
@@ -310,7 +314,7 @@ describe('Command', function () {
       handlers.foo.builder.should.equal(module.builder)
       handlers.foo.handler.should.equal(module.handler)
       var commands = y.getUsageInstance().getCommands()
-      commands[0].should.deep.equal([module.command, module.description, aliases])
+      commands[0].should.deep.equal([module.command, module.description, isDefault, aliases])
     })
 
     it('accepts module (desc key, builder function) as 1st argument', function () {
@@ -320,6 +324,7 @@ describe('Command', function () {
         builder: function (yargs) { return yargs },
         handler: function (argv) {}
       }
+      var isDefault = false
       var aliases = []
 
       var y = yargs([]).command(module)
@@ -328,7 +333,7 @@ describe('Command', function () {
       handlers.foo.builder.should.equal(module.builder)
       handlers.foo.handler.should.equal(module.handler)
       var commands = y.getUsageInstance().getCommands()
-      commands[0].should.deep.equal([module.command, module.desc, aliases])
+      commands[0].should.deep.equal([module.command, module.desc, isDefault, aliases])
     })
 
     it('accepts module (false describe, builder function) as 1st argument', function () {
@@ -375,6 +380,7 @@ describe('Command', function () {
         },
         handler: function (argv) {}
       }
+      var isDefault = false
       var aliases = []
 
       var y = yargs([]).command(module)
@@ -383,7 +389,7 @@ describe('Command', function () {
       handlers.foo.builder.should.equal(module.builder)
       handlers.foo.handler.should.equal(module.handler)
       var commands = y.getUsageInstance().getCommands()
-      commands[0].should.deep.equal([module.command, module.describe, aliases])
+      commands[0].should.deep.equal([module.command, module.describe, isDefault, aliases])
     })
 
     it('accepts module (missing handler function) as 1st argument', function () {
@@ -396,6 +402,7 @@ describe('Command', function () {
           }
         }
       }
+      var isDefault = false
       var aliases = []
 
       var y = yargs([]).command(module)
@@ -404,7 +411,7 @@ describe('Command', function () {
       handlers.foo.builder.should.equal(module.builder)
       expect(handlers.foo.handler).to.equal(undefined)
       var commands = y.getUsageInstance().getCommands()
-      commands[0].should.deep.equal([module.command, module.describe, aliases])
+      commands[0].should.deep.equal([module.command, module.describe, isDefault, aliases])
     })
 
     it('accepts module (with command array) as 1st argument', function () {
@@ -414,6 +421,7 @@ describe('Command', function () {
         builder: function (yargs) { return yargs },
         handler: function (argv) {}
       }
+      var isDefault = false
 
       var y = yargs([]).command(module)
       var handlers = y.getCommandInstance().getCommandHandlers()
@@ -421,7 +429,7 @@ describe('Command', function () {
       handlers.foo.builder.should.equal(module.builder)
       handlers.foo.handler.should.equal(module.handler)
       var usageCommands = y.getUsageInstance().getCommands()
-      usageCommands[0].should.deep.equal([module.command[0], module.describe, ['bar', 'baz']])
+      usageCommands[0].should.deep.equal([module.command[0], module.describe, isDefault, ['bar', 'baz']])
       var cmdCommands = y.getCommandInstance().getCommands()
       cmdCommands.should.deep.equal(['foo', 'bar', 'baz'])
     })
@@ -434,6 +442,7 @@ describe('Command', function () {
         builder: function (yargs) { return yargs },
         handler: function (argv) {}
       }
+      var isDefault = false
 
       var y = yargs([]).command(module)
       var handlers = y.getCommandInstance().getCommandHandlers()
@@ -441,7 +450,7 @@ describe('Command', function () {
       handlers.foo.builder.should.equal(module.builder)
       handlers.foo.handler.should.equal(module.handler)
       var usageCommands = y.getUsageInstance().getCommands()
-      usageCommands[0].should.deep.equal([module.command, module.describe, module.aliases])
+      usageCommands[0].should.deep.equal([module.command, module.describe, isDefault, module.aliases])
       var cmdCommands = y.getCommandInstance().getCommands()
       cmdCommands.should.deep.equal(['foo', 'bar', 'baz'])
     })
@@ -454,6 +463,7 @@ describe('Command', function () {
         builder: function (yargs) { return yargs },
         handler: function (argv) {}
       }
+      var isDefault = false
 
       var y = yargs([]).command(module)
       var handlers = y.getCommandInstance().getCommandHandlers()
@@ -461,7 +471,7 @@ describe('Command', function () {
       handlers.foo.builder.should.equal(module.builder)
       handlers.foo.handler.should.equal(module.handler)
       var usageCommands = y.getUsageInstance().getCommands()
-      usageCommands[0].should.deep.equal([module.command[0], module.describe, ['bar', 'baz', 'nat']])
+      usageCommands[0].should.deep.equal([module.command[0], module.describe, isDefault, ['bar', 'baz', 'nat']])
       var cmdCommands = y.getCommandInstance().getCommands()
       cmdCommands.should.deep.equal(['foo', 'bar', 'baz', 'nat'])
     })
@@ -474,6 +484,7 @@ describe('Command', function () {
         builder: function (yargs) { return yargs },
         handler: function (argv) {}
       }
+      var isDefault = false
 
       var y = yargs([]).command(module)
       var handlers = y.getCommandInstance().getCommandHandlers()
@@ -481,7 +492,7 @@ describe('Command', function () {
       handlers.foo.builder.should.equal(module.builder)
       handlers.foo.handler.should.equal(module.handler)
       var usageCommands = y.getUsageInstance().getCommands()
-      usageCommands[0].should.deep.equal([module.command, module.describe, ['bar']])
+      usageCommands[0].should.deep.equal([module.command, module.describe, isDefault, ['bar']])
       var cmdCommands = y.getCommandInstance().getCommands()
       cmdCommands.should.deep.equal(['foo', 'bar'])
     })
@@ -747,6 +758,21 @@ describe('Command', function () {
     counter.should.equal(2)
   })
 
+  // addresses: https://github.com/yargs/yargs/issues/776
+  it('allows command handler to be invoked repeatedly when help is enabled', function (done) {
+    var counter = 0
+    var y = yargs([])
+      .command('foo', 'the foo command', {}, function (argv) {
+        counter++
+      })
+      .help()
+    y.parse(['foo'], function () {})
+    y.parse(['foo'], function () {
+      counter.should.equal(2)
+      return done()
+    })
+  })
+
   // addresses https://github.com/yargs/yargs/issues/522
   it('does not require builder function to return', function () {
     var argv = yargs('yo')
@@ -839,4 +865,438 @@ describe('Command', function () {
       argv.sins.should.deep.equal([113993, 112888])
     })
   })
+
+  describe('global parsing hints', function () {
+    describe('config', function () {
+      it('does not load config for command if global is false', function (done) {
+        yargs('command --foo ./package.json')
+          .command('command', 'a command', {}, function (argv) {
+            expect(argv.license).to.equal(undefined)
+            return done()
+          })
+          .config('foo')
+          .global('foo', false)
+          .argv
+      })
+
+      it('loads config for command by default', function (done) {
+        yargs('command --foo ./package.json')
+          .command('command', 'a command', {}, function (argv) {
+            argv.license.should.equal('MIT')
+            return done()
+          })
+          .config('foo')
+          .argv
+      })
+    })
+
+    describe('validation', function () {
+      it('resets implies logic for command if global is false', function (done) {
+        yargs('command --foo 99')
+          .command('command', 'a command', {}, function (argv) {
+            argv.foo.should.equal(99)
+            return done()
+          })
+          .implies('foo', 'bar')
+          .global('foo', false)
+          .argv
+      })
+
+      it('applies conflicts logic for command by default', function (done) {
+        yargs('command --foo --bar')
+          .command('command', 'a command', {}, function (argv) {})
+          .fail(function (msg) {
+            msg.should.match(/mutually exclusive/)
+            return done()
+          })
+          .conflicts('foo', 'bar')
+          .argv
+      })
+
+      it('resets conflicts logic for command if global is false', function (done) {
+        yargs('command --foo --bar')
+          .command('command', 'a command', {}, function (argv) {
+            argv.foo.should.equal(true)
+            argv.bar.should.equal(true)
+            return done()
+          })
+          .conflicts('foo', 'bar')
+          .global('foo', false)
+          .argv
+      })
+
+      it('applies custom checks globally by default', function (done) {
+        yargs('command blerg --foo')
+          .command('command <snuh>', 'a command')
+          .check(function (argv) {
+            argv.snuh.should.equal('blerg')
+            argv.foo.should.equal(true)
+            argv._.should.include('command')
+            done()
+            return true
+          })
+          .argv
+      })
+
+      it('resets custom check if global is false', function () {
+        var checkCalled = false
+        yargs('command blerg --foo')
+          .command('command <snuh>', 'a command')
+          .check(function (argv) {
+            checkCalled = true
+            return true
+          }, false)
+          .argv
+        checkCalled.should.equal(false)
+      })
+
+      it('applies demandOption globally', function (done) {
+        yargs('command blerg --foo')
+          .command('command <snuh>', 'a command')
+          .fail(function (msg) {
+            msg.should.match(/Missing required argument: bar/)
+            return done()
+          })
+          .demandOption('bar')
+          .argv
+      })
+    })
+
+    describe('strict', function () {
+      it('defaults to false when not called', function () {
+        var commandCalled = false
+        yargs('hi')
+          .command('hi', 'The hi command', function (innerYargs) {
+            commandCalled = true
+            innerYargs.getStrict().should.be.false
+          })
+        yargs.getStrict().should.be.false
+        yargs.argv // parse and run command
+        commandCalled.should.be.true
+      })
+
+      it('can be enabled just for a command', function () {
+        var commandCalled = false
+        yargs('hi')
+          .command('hi', 'The hi command', function (innerYargs) {
+            commandCalled = true
+            innerYargs.strict().getStrict().should.be.true
+          })
+        yargs.getStrict().should.be.false
+        yargs.argv // parse and run command
+        commandCalled.should.be.true
+      })
+
+      it('applies strict globally by default', function () {
+        var commandCalled = false
+        yargs('hi')
+          .strict()
+          .command('hi', 'The hi command', function (innerYargs) {
+            commandCalled = true
+            innerYargs.getStrict().should.be.true
+          })
+        yargs.getStrict().should.be.true
+        yargs.argv // parse and run command
+        commandCalled.should.be.true
+      })
+
+      it('does not apply strict globally when passed value of `false`', function () {
+        var commandCalled = false
+        yargs('hi')
+          .strict(false)
+          .command('hi', 'The hi command', function (innerYargs) {
+            commandCalled = true
+            innerYargs.getStrict().should.be.false
+          })
+        yargs.getStrict().should.be.true
+        yargs.argv // parse and run command
+        commandCalled.should.be.true
+      })
+
+      // address regression introduced in #766, thanks @nexdrew!
+      it('does not fail strict check due to postional command arguments', function (done) {
+        yargs()
+          .strict()
+          .command('hi <name>', 'The hi command')
+          .parse('hi ben', function (err, argv, output) {
+            expect(err).to.equal(null)
+            return done()
+          })
+      })
+
+      // address https://github.com/yargs/yargs/issues/795
+      it('does not fail strict check due to postional command arguments in nested commands', function (done) {
+        yargs()
+          .strict()
+          .command('hi', 'The hi command', function (yargs) {
+            yargs.command('ben <age>', 'ben command', function () {}, function () {})
+          })
+          .parse('hi ben 99', function (err, argv, output) {
+            expect(err).to.equal(null)
+            return done()
+          })
+      })
+
+      it('does not fire command if validation fails', function (done) {
+        var commandRun = false
+        yargs()
+          .strict()
+          .command('hi <name>', 'The hi command', function () {}, function (argv) {
+            commandRun = true
+          })
+          .parse('hi ben --hello=world', function (err, argv, output) {
+            commandRun.should.equal(false)
+            err.message.should.equal('Unknown argument: hello')
+            return done()
+          })
+      })
+    })
+
+    describe('types', function () {
+      it('applies array type globally', function () {
+        const argv = yargs('command --foo 1 2')
+          .command('command', 'a command')
+          .array('foo')
+          .argv
+        argv.foo.should.eql([1, 2])
+      })
+
+      it('allows global setting to be disabled for array type', function () {
+        const argv = yargs('command --foo 1 2')
+          .command('command', 'a command')
+          .array('foo')
+          .global('foo', false)
+          .argv
+        argv.foo.should.eql(1)
+      })
+
+      it('applies choices type globally', function (done) {
+        yargs('command --foo 99')
+          .command('command', 'a command')
+          .choices('foo', [33, 88])
+          .fail(function (msg) {
+            msg.should.match(/Choices: 33, 88/)
+            return done()
+          })
+          .argv
+      })
+    })
+
+    describe('aliases', function () {
+      it('defaults to applying aliases globally', function (done) {
+        yargs('command blerg --foo 22')
+          .command('command <snuh>', 'a command', {}, function (argv) {
+            argv.foo.should.equal(22)
+            argv.bar.should.equal(22)
+            argv.snuh.should.equal('blerg')
+            return done()
+          })
+          .alias('foo', 'bar')
+          .argv
+      })
+
+      it('allows global application of alias to be disabled', function (done) {
+        yargs('command blerg --foo 22')
+          .command('command <snuh>', 'a command', {}, function (argv) {
+            argv.foo.should.equal(22)
+            expect(argv.bar).to.equal(undefined)
+            argv.snuh.should.equal('blerg')
+            return done()
+          })
+          .option('foo', {
+            alias: 'bar',
+            global: false
+          })
+          .argv
+      })
+    })
+
+    describe('coerce', function () {
+      it('defaults to applying coerce rules globally', function (done) {
+        yargs('command blerg --foo 22')
+          .command('command <snuh>', 'a command', {}, function (argv) {
+            argv.foo.should.equal(44)
+            argv.snuh.should.equal('blerg')
+            return done()
+          })
+          .coerce('foo', function (arg) {
+            return arg * 2
+          })
+          .argv
+      })
+
+      // addresses https://github.com/yargs/yargs/issues/794
+      it('should bubble errors thrown by coerce function inside commands', function (done) {
+        yargs
+          .command('foo', 'the foo command', function (yargs) {
+            yargs.coerce('x', function (arg) {
+              throw Error('yikes an error')
+            })
+          })
+          .parse('foo -x 99', function (err) {
+            err.message.should.match(/yikes an error/)
+            return done()
+          })
+      })
+    })
+
+    describe('defaults', function () {
+      it('applies defaults globally', function (done) {
+        yargs('command --foo 22')
+          .command('command [snuh]', 'a command', {}, function (argv) {
+            argv.foo.should.equal(22)
+            argv.snuh.should.equal(55)
+            return done()
+          })
+          .default('snuh', 55)
+          .argv
+      })
+    })
+
+    describe('describe', function () {
+      it('flags an option as global if a description is set', function (done) {
+        yargs()
+          .command('command [snuh]', 'a command')
+          .describe('foo', 'an awesome argument')
+          .help()
+          .parse('command --help', function (err, argv, output) {
+            if (err) return done(err)
+            output.should.not.match(/Commands:/)
+            output.should.match(/an awesome argument/)
+            return done()
+          })
+      })
+    })
+
+    describe('help', function () {
+      it('applies help globally', function (done) {
+        yargs()
+          .command('command [snuh]', 'a command')
+          .describe('foo', 'an awesome argument')
+          .help('hellllllp')
+          .parse('command --hellllllp', function (err, argv, output) {
+            if (err) return done(err)
+            output.should.match(/--hellllllp {2}Show help/)
+            return done()
+          })
+      })
+    })
+
+    describe('version', function () {
+      it('applies version globally', function (done) {
+        yargs()
+          .command('command [snuh]', 'a command')
+          .describe('foo', 'an awesome argument')
+          .version('ver', 'show version', '9.9.9')
+          .parse('command --ver', function (err, argv, output) {
+            if (err) return done(err)
+            output.should.equal('9.9.9')
+            return done()
+          })
+      })
+    })
+
+    describe('groups', function () {
+      it('should apply custom option groups globally', function (done) {
+        yargs()
+          .command('command [snuh]', 'a command')
+          .group('foo', 'Bad Variable Names:')
+          .group('snuh', 'Bad Variable Names:')
+          .describe('foo', 'foo option')
+          .describe('snuh', 'snuh positional')
+          .help()
+          .parse('command --help', function (err, argv, output) {
+            if (err) return done(err)
+            output.should.match(/Bad Variable Names:\W*--foo/)
+            return done()
+          })
+      })
+    })
+  })
+
+  describe('default commands', function () {
+    it('executes default command if no positional arguments given', function (done) {
+      yargs('--foo bar')
+        .command('*', 'default command', function () {}, function (argv) {
+          argv.foo.should.equal('bar')
+          return done()
+        })
+        .argv
+    })
+
+    it('does not execute default command if another command is provided', function (done) {
+      yargs('run bcoe --foo bar')
+        .command('*', 'default command', function () {}, function (argv) {})
+        .command('run <name>', 'run command', function () {}, function (argv) {
+          argv.name.should.equal('bcoe')
+          argv.foo.should.equal('bar')
+          return done()
+        })
+        .argv
+    })
+
+    it('allows default command to be set as alias', function (done) {
+      yargs('bcoe --foo bar')
+        .command(['start <name>', '*'], 'start command', function () {}, function (argv) {
+          argv._.should.eql([])
+          argv.name.should.equal('bcoe')
+          argv.foo.should.equal('bar')
+          return done()
+        })
+        .argv
+    })
+
+    it('allows command to be run when alias is default command', function (done) {
+      yargs('start bcoe --foo bar')
+        .command(['start <name>', '*'], 'start command', function () {}, function (argv) {
+          argv._.should.eql(['start'])
+          argv.name.should.equal('bcoe')
+          argv.foo.should.equal('bar')
+          return done()
+        })
+        .argv
+    })
+
+    it('the last default command set should take precedence', function (done) {
+      yargs('bcoe --foo bar')
+        .command(['first', '*'], 'override me', function () {}, function () {})
+        .command(['second <name>', '*'], 'start command', function () {}, function (argv) {
+          argv._.should.eql([])
+          argv.name.should.equal('bcoe')
+          argv.foo.should.equal('bar')
+          return done()
+        })
+        .argv
+    })
+
+    describe('strict', function () {
+      it('executes default command when strict mode is enabled', function (done) {
+        yargs('--foo bar')
+          .command('*', 'default command', function () {}, function (argv) {
+            argv.foo.should.equal('bar')
+            return done()
+          })
+          .option('foo', {
+            describe: 'a foo command'
+          })
+          .strict()
+          .argv
+      })
+
+      it('allows default command aliases, when strict mode is enabled', function (done) {
+        yargs('bcoe --foo bar')
+          .command(['start <name>', '*'], 'start command', function () {}, function (argv) {
+            argv._.should.eql([])
+            argv.name.should.equal('bcoe')
+            argv.foo.should.equal('bar')
+            return done()
+          })
+          .strict()
+          .option('foo', {
+            describe: 'a foo command'
+          })
+          .argv
+      })
+    })
+  })
 })
diff --git a/test/completion.js b/test/completion.js
index 4c52a8d..57c2d50 100644
--- a/test/completion.js
+++ b/test/completion.js
@@ -97,7 +97,6 @@ describe('Completion', function () {
                 describe: 'first option'
               }
             })
-            .argv
           })
           .command('cmd2', 'second command', function (subYargs) {
             subYargs.options({
@@ -105,7 +104,6 @@ describe('Completion', function () {
                 describe: 'second option'
               }
             })
-            .argv
           })
           .completion()
           .argv
diff --git a/test/fixtures/extends/circular_1.json b/test/fixtures/extends/circular_1.json
new file mode 100644
index 0000000..4cd6629
--- /dev/null
+++ b/test/fixtures/extends/circular_1.json
@@ -0,0 +1,4 @@
+{
+  "a": 44,
+  "extends": "./circular_2.json"
+}
\ No newline at end of file
diff --git a/test/fixtures/extends/circular_2.json b/test/fixtures/extends/circular_2.json
new file mode 100644
index 0000000..85b7de8
--- /dev/null
+++ b/test/fixtures/extends/circular_2.json
@@ -0,0 +1,4 @@
+{
+  "b": "any",
+  "extends": "./circular_1.json"
+}
\ No newline at end of file
diff --git a/test/fixtures/extends/config_1.json b/test/fixtures/extends/config_1.json
new file mode 100644
index 0000000..043fb87
--- /dev/null
+++ b/test/fixtures/extends/config_1.json
@@ -0,0 +1,5 @@
+{
+  "a": 30,
+  "b": 22,
+  "extends": "./config_2.json"
+}
\ No newline at end of file
diff --git a/test/fixtures/extends/config_2.json b/test/fixtures/extends/config_2.json
new file mode 100644
index 0000000..886f419
--- /dev/null
+++ b/test/fixtures/extends/config_2.json
@@ -0,0 +1,3 @@
+{
+  "z": 15
+}
\ No newline at end of file
diff --git a/test/fixtures/extends/packageA/package.json b/test/fixtures/extends/packageA/package.json
new file mode 100644
index 0000000..236e60f
--- /dev/null
+++ b/test/fixtures/extends/packageA/package.json
@@ -0,0 +1,6 @@
+{
+  "foo": {
+    "a": 80,
+    "extends": "../packageB/package.json"
+  }
+}
\ No newline at end of file
diff --git a/test/fixtures/extends/packageB/package.json b/test/fixtures/extends/packageB/package.json
new file mode 100644
index 0000000..473fda7
--- /dev/null
+++ b/test/fixtures/extends/packageB/package.json
@@ -0,0 +1,6 @@
+{
+  "foo": {
+    "a": 90,
+    "b": "riffiwobbles"
+  }
+}
\ No newline at end of file
diff --git a/test/helpers/utils.js b/test/helpers/utils.js
index d896dae..5a2ccc4 100644
--- a/test/helpers/utils.js
+++ b/test/helpers/utils.js
@@ -10,6 +10,7 @@ exports.checkOutput = function (f, argv, cb) {
   var _argv = process.argv
   var _error = console.error
   var _log = console.log
+  var _warn = console.warn
 
   process.exit = function () { exit = true }
   process.env = Hash.merge(process.env, { _: 'node' })
@@ -17,9 +18,11 @@ exports.checkOutput = function (f, argv, cb) {
 
   var errors = []
   var logs = []
+  var warnings = []
 
   console.error = function (msg) { errors.push(msg) }
   console.log = function (msg) { logs.push(msg) }
+  console.warn = function (msg) { warnings.push(msg) }
 
   var result
 
@@ -57,6 +60,7 @@ exports.checkOutput = function (f, argv, cb) {
 
     console.error = _error
     console.log = _log
+    console.warn = _warn
   }
 
   function done () {
@@ -65,6 +69,7 @@ exports.checkOutput = function (f, argv, cb) {
     return {
       errors: errors,
       logs: logs,
+      warnings: warnings,
       exit: exit,
       result: result
     }
diff --git a/test/usage.js b/test/usage.js
index 6b57722..87635aa 100644
--- a/test/usage.js
+++ b/test/usage.js
@@ -1,10 +1,11 @@
 /* global describe, it, beforeEach */
 
-var checkUsage = require('./helpers/utils').checkOutput
-var chalk = require('chalk')
-var path = require('path')
-var yargs = require('../')
-var rebase = require('../yargs').rebase
+const checkUsage = require('./helpers/utils').checkOutput
+const chalk = require('chalk')
+const path = require('path')
+const yargs = require('../')
+const rebase = require('../yargs').rebase
+const YError = require('../lib/yerror')
 
 require('chai').should()
 
@@ -83,16 +84,16 @@ describe('usage tests', function () {
         r.exit.should.be.ok
       })
 
-      it('no failure occurs if the required arguments and the required number of commands are provided.', function () {
+      it('no failure occurs if the required arguments and the required number of commands are provided', function () {
         var r = checkUsage(function () {
           return yargs('wombat -w 10 -m 10')
             .usage('Usage: $0 -w NUM -m NUM')
             .command('wombat', 'wombat handlers')
             .demand(1, ['w', 'm'])
-            .strict()
             .wrap(null)
             .argv
         })
+
         r.result.should.have.property('w', 10)
         r.result.should.have.property('m', 10)
         r.result.should.have.property('_').with.length(1)
@@ -169,24 +170,6 @@ describe('usage tests', function () {
         r.logs.should.have.length(0)
         r.exit.should.be.ok
       })
-
-      it('no failure occurs if the required arguments and the required number of commands are provided.', function () {
-        var r = checkUsage(function () {
-          return yargs('wombat -w 10 -m 10')
-            .usage('Usage: $0 -w NUM -m NUM')
-            .command('wombat', 'wombat handlers')
-            .require(1, ['w', 'm'])
-            .strict()
-            .wrap(null)
-            .argv
-        })
-        r.result.should.have.property('w', 10)
-        r.result.should.have.property('m', 10)
-        r.result.should.have.property('_').with.length(1)
-        r.should.have.property('errors').with.length(0)
-        r.should.have.property('logs').with.length(0)
-        r.should.have.property('exit', false)
-      })
     })
 
     it('should show an error along with a custom message on demand fail', function () {
@@ -482,7 +465,7 @@ describe('usage tests', function () {
                 describe: 'bar command'
               }
             }, function (argv) {
-              throw new Error('blah')
+              throw new YError('blah')
             })
             .fail(function (message, error, yargs) {
               yargs.showHelp()
@@ -534,14 +517,14 @@ describe('usage tests', function () {
                     })
                     .exitProcess(false)
                 }, function (argv) {
-                  throw new Error('foo')
+                  throw new YError('foo')
                 })
                 .argv
             } catch (error) {
 
             }
           })
-          r.logs.should.deep.equal([['Error', 'foo'], 'is triggered last'])
+          r.logs.should.deep.equal([['YError', 'foo'], 'is triggered last'])
           r.should.have.property('exit').and.be.false
         })
       })
@@ -1597,7 +1580,7 @@ describe('usage tests', function () {
       ])
     })
 
-    it('preserves groups with global keys', function () {
+    it('allows global option to be disabled', function () {
       var r = checkUsage(function () {
         return yargs(['upload', '-h'])
           .command('upload', 'upload something', function (yargs) {
diff --git a/test/validation.js b/test/validation.js
index f08080a..53c30bb 100644
--- a/test/validation.js
+++ b/test/validation.js
@@ -233,6 +233,43 @@ describe('validation tests', function () {
       argv._[0].should.equal('koala')
     })
 
+    // addresses: https://github.com/yargs/yargs/issues/791
+    it('should recognize context variables in strict mode', function (done) {
+      yargs()
+        .command('foo <y>')
+        .strict()
+        .parse('foo 99', {x: 33}, function (err, argv, output) {
+          expect(err).to.equal(null)
+          expect(output).to.equal('')
+          argv.y.should.equal(99)
+          argv.x.should.equal(33)
+          argv._.should.include('foo')
+          return done()
+        })
+    })
+
+    // addresses: https://github.com/yargs/yargs/issues/791
+    it('should recognize context variables in strict mode, when running sub-commands', function (done) {
+      yargs()
+        .command('request', 'request command', function (yargs) {
+          yargs
+            .command('get', 'sub-command')
+            .option('y', {
+              describe: 'y inner option'
+            })
+        })
+        .strict()
+        .parse('request get --y=22', {x: 33}, function (err, argv, output) {
+          expect(err).to.equal(null)
+          expect(output).to.equal('')
+          argv.y.should.equal(22)
+          argv.x.should.equal(33)
+          argv._.should.include('request')
+          argv._.should.include('get')
+          return done()
+        })
+    })
+
     it('fails when a required argument is missing', function (done) {
       yargs('-w 10 marsupial')
         .demand(1, ['w', 'b'])
@@ -589,5 +626,15 @@ describe('validation tests', function () {
         })
         .argv
     })
+
+    it('defaults to demanding 1 command', function (done) {
+      yargs('-a 10')
+        .demandCommand()
+        .fail(function (msg) {
+          msg.should.equal('Not enough non-option arguments: got 0, need at least 1')
+          return done()
+        })
+        .argv
+    })
   })
 })
diff --git a/test/yargs.js b/test/yargs.js
index b8777e7..45719d2 100644
--- a/test/yargs.js
+++ b/test/yargs.js
@@ -1,16 +1,21 @@
-/* global context, describe, it, beforeEach */
+/* global context, describe, it, beforeEach, afterEach */
 
 var expect = require('chai').expect
 var fs = require('fs')
 var path = require('path')
 var checkOutput = require('./helpers/utils').checkOutput
-var yargs = require('../')
+var yargs
+var YError = require('../lib/yerror')
 
 require('chai').should()
 
 describe('yargs dsl tests', function () {
   beforeEach(function () {
-    yargs.reset()
+    yargs = require('../')
+  })
+
+  afterEach(function () {
+    delete require.cache[require.resolve('../')]
   })
 
   it('should use bin name for $0, eliminating path', function () {
@@ -220,8 +225,10 @@ describe('yargs dsl tests', function () {
         .implies('foo', 'snuh')
         .conflicts('qux', 'xyzzy')
         .group('foo', 'Group:')
-        .strict()
+        .strict(false)
         .exitProcess(false)  // defaults to true.
+        .global('foo', false)
+        .global('qux', false)
         .env('YARGS')
         .reset()
 
@@ -244,9 +251,13 @@ describe('yargs dsl tests', function () {
         config: {},
         configObjects: [],
         envPrefix: 'YARGS', // preserved as global
-        global: ['help'],
         demandedCommands: {},
-        demandedOptions: {}
+        demandedOptions: {},
+        local: [
+          '_',
+          'foo',
+          'qux'
+        ]
       }
 
       expect(y.getOptions()).to.deep.equal(emptyOptions)
@@ -261,9 +272,10 @@ describe('yargs dsl tests', function () {
       expect(y.getGroups()).to.deep.equal({})
     })
 
-    it('does not invoke parse with an error if reset has been called', function (done) {
+    it('does not invoke parse with an error if reset has been called and option is not global', function (done) {
       var y = yargs()
         .demand('cake')
+        .global('cake', false)
 
       y.parse('hello', function (err) {
         err.message.should.match(/Missing required argument/)
@@ -761,6 +773,14 @@ describe('yargs dsl tests', function () {
       a1.foo.should.equal('bar')
       a1.context.should.equal('look at me go!')
     })
+
+    // see https://github.com/yargs/yargs/issues/724
+    it('overrides parsed value of argv with context object', function () {
+      var a1 = yargs.parse('-x=33', {
+        x: 42
+      })
+      a1.x.should.equal(42)
+    })
   })
 
   // yargs.parse(['foo', '--bar'], function (err, argv, output) {}
@@ -1123,6 +1143,7 @@ describe('yargs dsl tests', function () {
         .config('config', function (path) {
           return JSON.parse(fs.readFileSync(path))
         })
+        .global('config', false)
         .argv
 
       argv.foo.should.equal('baz')
@@ -1131,7 +1152,8 @@ describe('yargs dsl tests', function () {
     it('allows key to be specified with option shorthand', function () {
       var argv = yargs('--config ./test/fixtures/config.json')
         .option('config', {
-          config: true
+          config: true,
+          global: false
         })
         .argv
 
@@ -1146,6 +1168,42 @@ describe('yargs dsl tests', function () {
       argv.foo.should.equal(1)
       argv.bar.should.equal(2)
     })
+
+    describe('extends', function () {
+      it('applies default configurations when given config object', function () {
+        var argv = yargs
+          .config({
+            extends: './test/fixtures/extends/config_1.json',
+            a: 1
+          })
+          .argv
+
+        argv.a.should.equal(1)
+        argv.b.should.equal(22)
+        argv.z.should.equal(15)
+      })
+
+      it('protects against circular extended configurations', function () {
+        expect(function () {
+          yargs.config({extends: './test/fixtures/extends/circular_1.json'})
+        }).to.throw(YError)
+      })
+
+      it('handles aboslute paths', function () {
+        var absolutePath = path.join(process.cwd(), 'test', 'fixtures', 'extends', 'config_1.json')
+
+        var argv = yargs
+          .config({
+            a: 2,
+            extends: absolutePath
+          })
+          .argv
+
+        argv.a.should.equal(2)
+        argv.b.should.equal(22)
+        argv.z.should.equal(15)
+      })
+    })
   })
 
   describe('normalize', function () {
@@ -1218,7 +1276,8 @@ describe('yargs dsl tests', function () {
           nargs: 2
         })
         .option('bar', {
-          nargs: 2
+          nargs: 2,
+          global: false
         })
         .global('foo')
         .reset()
@@ -1238,7 +1297,8 @@ describe('yargs dsl tests', function () {
         .option('bar', {
           nargs: 2,
           string: true,
-          demand: true
+          demand: true,
+          global: false
         })
         .global('foo')
         .reset({
@@ -1259,14 +1319,14 @@ describe('yargs dsl tests', function () {
       var y = yargs('--foo')
         .help('help')
       var options = y.getOptions()
-      options.global.should.include('help')
+      options.local.should.not.include('help')
     })
 
     it('should set version to global option by default', function () {
       var y = yargs('--foo')
         .version()
       var options = y.getOptions()
-      options.global.should.include('version')
+      options.local.should.not.include('version')
     })
 
     it('should not reset usage descriptions of global options', function () {
@@ -1274,6 +1334,7 @@ describe('yargs dsl tests', function () {
         .describe('bar', 'my awesome bar option')
         .describe('foo', 'my awesome foo option')
         .global('foo')
+        .global('bar', false)
         .reset()
       var descriptions = y.getUsageInstance().getDescriptions()
       Object.keys(descriptions).should.include('foo')
@@ -1288,7 +1349,7 @@ describe('yargs dsl tests', function () {
         .implies({
           z: 'w'
         })
-        .global(['x'])
+        .global(['z'], false)
         .reset()
       var implied = y.getValidationInstance().getImplied()
       Object.keys(implied).should.include('x')
@@ -1298,11 +1359,11 @@ describe('yargs dsl tests', function () {
     it('should expose an options short-hand for declaring global options', function () {
       var y = yargs('--foo a b c')
         .option('foo', {
-          nargs: 2,
-          global: true
+          nargs: 2
         })
         .option('bar', {
-          nargs: 2
+          nargs: 2,
+          global: false
         })
         .reset()
       var options = y.getOptions()
@@ -1399,6 +1460,20 @@ describe('yargs dsl tests', function () {
 
       argv.foo.should.equal('a')
     })
+
+    it('should apply default configurations from extended packages', function () {
+      var argv = yargs().pkgConf('foo', 'test/fixtures/extends/packageA').argv
+
+      argv.a.should.equal(80)
+      argv.b.should.equals('riffiwobbles')
+    })
+
+    it('should apply extended configurations from cwd when no path is given', function () {
+      var argv = yargs('', 'test/fixtures/extends/packageA').pkgConf('foo').argv
+
+      argv.a.should.equal(80)
+      argv.b.should.equals('riffiwobbles')
+    })
   })
 
   describe('skipValidation', function () {