diff --git a/commands/core/archive.drush.inc b/commands/core/archive.drush.inc
index dd2cbac..7b3465e 100644
--- a/commands/core/archive.drush.inc
+++ b/commands/core/archive.drush.inc
@@ -235,6 +235,16 @@ function drush_archive_dump($sites_subdirs = '@self') {
 }
 
 /**
+ * Command argument complete callback.
+ *
+ * @return
+ *   List of site names/aliases for archival.
+ */
+function archive_archive_dump_complete() {
+  return array('values' => array_keys(_drush_sitealias_all_list()));
+}
+
+/**
  * Command callback. Restore web site(s) from a site archive file.
  */
 function drush_archive_restore($file, $site_id = NULL) {
@@ -316,3 +326,24 @@ function drush_archive_restore($file, $site_id = NULL) {
   }
   return $destination;
 }
+
+
+/**
+ * Command argument complete callback.
+ *
+ * @return
+ *   Strong glob of files to complete on.
+ */
+function archive_archive_restore_complete() {
+  return array(
+           'files' => array(
+             'directories' => array(
+               'pattern' => '*',
+               'flags' => GLOB_ONLYDIR,
+             ),
+             'tar' => array(
+               'pattern' => '*.tar.gz',
+             ),
+           ),
+         );
+}
diff --git a/commands/core/core.drush.inc b/commands/core/core.drush.inc
index 45b6d00..0a84a33 100644
--- a/commands/core/core.drush.inc
+++ b/commands/core/core.drush.inc
@@ -535,18 +535,7 @@ function drush_core_cron() {
  * Command callback. Edit drushrc and alias files.
  */
 function drush_core_config($filter = NULL) {
-  $rcs = $aliases = $drupal = array();
-  drush_sitealias_load_all();
-  if ($rcs = drush_get_context_options('context-path', TRUE)) {
-    $rcs = array_merge(array('drushrc' => '-- Drushrc --'), $rcs);
-  }
-  if ($aliases = drush_get_context('drush-alias-files')) {
-    $aliases = array_merge(array('aliases' => '-- Aliases --'), $aliases);
-  }
-  if ($site_root = drush_get_context('DRUSH_DRUPAL_SITE_ROOT')) {
-    $drupal = array_merge(array('drupal' => '-- Drupal --'), array(realpath($site_root . '/settings.php'), realpath(DRUPAL_ROOT . '/.htaccess')));
-  }
-  $all = array_merge($rcs, $aliases, $drupal);
+  $all = drush_core_config_load();
 
   // Apply any filter that was supplied.
   if ($filter) {
@@ -570,6 +559,38 @@ function drush_core_config($filter = NULL) {
 }
 
 /**
+ * Command argument complete callback.
+ *
+ * @return
+ *   Array of available configuration files for editing.
+ */
+function core_core_config_complete() {
+  return array('values' => drush_core_config_load(FALSE));
+}
+
+function drush_core_config_load($headers = TRUE) {
+  $rcs_header = $rcs = $aliases_header = $aliases = $drupal_header = $drupal = array();
+  drush_sitealias_load_all();
+  if ($rcs = drush_get_context_options('context-path', TRUE)) {
+    if ($headers) {
+      $rcs_header = array('drushrc' => '-- Drushrc --');
+    }
+  }
+  if ($aliases = drush_get_context('drush-alias-files')) {
+    if ($headers) {
+      $aliases_header = array('aliases' => '-- Aliases --');
+    }
+  }
+  if ($site_root = drush_get_context('DRUSH_DRUPAL_SITE_ROOT')) {
+    $drupal = array_merge(array(realpath($site_root . '/settings.php'), realpath(DRUPAL_ROOT . '/.htaccess')));
+    if ($headers) {
+      $drupal_header = array('drupal' => '-- Drupal --');
+    }
+  }
+  return array_merge($rcs_header, $rcs, $aliases_header, $aliases, $drupal_header, $drupal);
+}
+
+/**
  * Command callback. Provides a birds-eye view of the current Drupal
  * installation.
  */
diff --git a/commands/core/topic.drush.inc b/commands/core/topic.drush.inc
index cda52fe..e4c3d6e 100644
--- a/commands/core/topic.drush.inc
+++ b/commands/core/topic.drush.inc
@@ -72,6 +72,16 @@ function drush_topic_core_topic($topic_name = NULL) {
 }
 
 /**
+ * A command argument complete callback.
+ *
+ * @return
+ *   Available topic keys.
+ */
+function topic_core_topic_complete() {
+  return array('values' => array_keys(drush_get_topics()));
+}
+
+/**
  * Retrieve all defined topics
  */
 function drush_get_topics() {
diff --git a/drush.complete.sh b/drush.complete.sh
new file mode 100644
index 0000000..0072e25
--- /dev/null
+++ b/drush.complete.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+# BASH completion script for Drush.
+#
+# Place this in your /etc/bash_completion.d/ directory or source it from your
+# ~/.bash_completion file.
+
+# Ensure drush is available.
+have drush || return
+
+# Completion function, uses the "drush complete" command to retrieve
+# completions for a specific command line COMP_WORDS.
+_drush_completion() {
+  # Set IFS to newline (locally), since we only use newline separators, and
+  # need to retain spaces (or not) after completions.
+  local IFS=$'\n'
+  # The '< /dev/null' is a work around for a bug in php libedit stdin handling.
+  # Note that libedit in place of libreadline in some distributions. See:
+  # https://bugs.launchpad.net/ubuntu/+source/php5/+bug/322214
+  COMPREPLY=( $(drush complete "${COMP_WORDS[@]}" < /dev/null) )
+}
+
+# Register our completion function. We include common short aliases for Drush.
+complete -o nospace -F _drush_completion d dr drush drush.php
diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc
index 07abe16..e75e6e0 100644
--- a/includes/bootstrap.inc
+++ b/includes/bootstrap.inc
@@ -440,9 +440,6 @@ function _drush_bootstrap_drush() {
   // Statically define a way to call drush again.
   define('DRUSH_COMMAND', drush_find_drush());
 
-  $drush_info = drush_read_drush_info();
-  define('DRUSH_VERSION', $drush_info['drush_version']);
-
   // prime the CWD cache
   drush_cwd();
 
@@ -895,6 +892,7 @@ function drush_bootstrap_prepare() {
   require_once DRUSH_BASE_PATH . '/includes/cache.inc';
   require_once DRUSH_BASE_PATH . '/includes/filesystem.inc';
   require_once DRUSH_BASE_PATH . '/includes/dbtng.inc';
+  require_once DRUSH_BASE_PATH . '/includes/complete.inc';
 
   // Terminate immediately unless invoked as a command line script
   if (!drush_verify_cli()) {
@@ -907,6 +905,9 @@ function drush_bootstrap_prepare() {
     die('Your command line PHP installation is too old. Drush requires at least PHP ' . DRUSH_MINIMUM_PHP . "\n");
   }
 
+  $drush_info = drush_read_drush_info();
+  define('DRUSH_VERSION', $drush_info['drush_version']);
+
   define('DRUSH_REQUEST_TIME', microtime(TRUE));
 
   drush_set_context('argc', $GLOBALS['argc']);
@@ -918,8 +919,16 @@ function drush_bootstrap_prepare() {
 
   drush_set_context('DRUSH_BOOTSTRAP_PHASE', DRUSH_BOOTSTRAP_NONE);
 
-  // We need some global options processed at this early stage. Namely --debug.
-  drush_parse_args();
+  // We need some global options/arguments processed at this early stage.
+  $arguments = drush_parse_args();
+
+  // Because the complete argument is added by an autocomplete shell script,
+  // it should always be first.
+  if ($arguments[0] == 'complete') {
+    drush_complete();
+  }
+  
+  // Process early global options such as --debug. 
   _drush_bootstrap_global_options();
 }
 
diff --git a/includes/cache.inc b/includes/cache.inc
index 717615b..0119b9d 100644
--- a/includes/cache.inc
+++ b/includes/cache.inc
@@ -180,7 +180,8 @@ function drush_get_cid($prefix, $contexts = array(), $params = array()) {
   $cid = array();
 
   foreach ($contexts as $context) {
-    if ($c = drush_get_context($context) && !empty($c)) {
+    $c = drush_get_context($context);
+    if (!empty($c)) {
       $cid[] = is_scalar($c) ? $c : serialize($c);
     }
   }
diff --git a/includes/command.inc b/includes/command.inc
index f28d73c..6b5e256 100644
--- a/includes/command.inc
+++ b/includes/command.inc
@@ -342,9 +342,8 @@ function drush_parse_args() {
         $opt = substr($opt, 1);
         // Check if the current opt is in $arg_opts (= has to be followed by an argument).
         if ((in_array($opt, $arg_opts))) {
-          if (($args[$i+1] == NULL) || ($args[$i+1] == "") || ($args[$i + 1]{0} == "-")) {
-            drush_set_error('DRUSH_INVALID_INPUT', "Invalid input: -$opt needs to be followed by an argument.");
-          }
+          // Raising errors for missing option values should be handled by the
+          // bootstrap or specific command, so we no longer do this here.
           $options[$opt] = $args[$i + 1];
           $i++;
         }
@@ -374,6 +373,7 @@ function drush_parse_args() {
 
   drush_set_arguments($arguments);
   drush_set_context('cli', $options);
+  return $arguments;
 }
 
 /**
diff --git a/includes/complete.inc b/includes/complete.inc
new file mode 100644
index 0000000..775051d
--- /dev/null
+++ b/includes/complete.inc
@@ -0,0 +1,560 @@
+<?php
+
+/**
+ * @file
+ *
+ * Provide completion output for shells.
+ *
+ * This is not called directly, but by shell completion scripts specific to
+ * each shell (bash, csh etc). These run whenever the user triggers completion,
+ * typically when pressing <tab>. The shell completion scripts should call
+ * "drush complete <text>", where <text> is the full command line, which we take
+ * as input and use to produce a list of possible completions for the
+ * current/next word, separated by newlines. Typically, when multiple
+ * completions are returned the shell will display them to the user in a concise
+ * format - but when a single completion is returned it will autocomplete.
+ *
+ * We provide completion for site aliases, commands, shell aliases, options,
+ * engines and arguments. Displaying all of these when the last word has no
+ * characters yet is not useful, as there are too many items. Instead we filter
+ * the possible completions based on context, in a similar way to git.
+ * For example:
+ * - We only display site aliases and commands if one is not already present.
+ * - We only display options if the user has already entered a hyphen.
+ * - We only display global options before a command is entered, and we only
+ *   display command specific options after the command (Drush itself does not
+ *   care about option placement, but this approach keeps things more concise).
+ *
+ * Below is typical output of complete in different situations. Tokens in square
+ * brackets are optional, and [word] will filter available options that start
+ * with the same characters, or display all listed options if empty.
+ * drush --[word] : Output global options
+ * drush [word] : Output aliases, local sites and commands
+ * drush [@alias] [word] : Output commands
+ * drush [@alias] command [word] : Output command specific arguments
+ * drush [@alias] command --[word] : Output command specific options
+ *
+ * Because the purpose of autocompletion is to make command line work efficient,
+ * it is very important that the list of completions is returned quickly.
+ * To do this, we call drush_complete() early in the Drush bootstrap, and
+ * implement a simple caching system.
+ *
+ * To generate the list of completions, we set up the Drush environment as if
+ * the command was called on it's own, parse the command using the standard
+ * Drush functions, bootstrap the site (if any) and collect available
+ * completions from various sources. Because this can be somewhat slow, we cache
+ * the results. The cache strategy aims to balance accuracy and responsiveness:
+ * - We cache global and command specific options globally, since these are
+ *   almost always identical between sites - the cache can be used even if drush
+ *   complete has never run on a site before and does not require any bootstrap.
+ * - We cache command names and aliases per site, since they can vary per-site.
+ * - We generate (and cache) everything except arguments at the same time, so
+ *   subsequent completions on the site don't need any bootstrap.
+ * - We generate and cache arguments on-demand, since these can often be
+ *   expensive to generate. Arguments are also cached per-site.
+ *
+ * For argument completions, commandfiles can implement
+ * COMMANDFILE_COMMAND_complete() returning an array of argument completions for
+ * that command. For example, return array('aardvark', 'aardwolf') will offer
+ * the words 'aardvark' and 'aardwolf', or will complete to 'aardwolf' if the
+ * letters 'aardw' are already present. Since command arguments are cached,
+ * commandfiles can bootstrap a site or perform other somewhat time consuming
+ * activities to retrieve the list of possible arguments. Commands can also
+ * clear the cache (or just the "arguments" cache for their command) when they
+ * the list of arguments has likely changed - see drush_complete_cache_clear().
+ *
+ * Commandfiles can also return a special optional element in their array with
+ * the key 'files' that contains an array of patterns/flags for the glob()
+ * function. These are used to produce file and directory completions (the
+ * results of these are not cached, since this is a fast operation).
+ * See http://php.net/glob for details of valid patterns and flags.
+ * For example the following will complete the command arguments on all
+ * directories, as well as files ending in tar.gz:
+ *   return array(
+ *         'files' => array(
+ *           'directories' => array(
+ *             'pattern' => '*',
+ *             'flags' => GLOB_ONLYDIR,
+ *           ),
+ *           'tar' => array(
+ *             'pattern' => '*.tar.gz',
+ *           ),
+ *         ),
+ *       );
+ */
+
+/**
+ * Produce autocomplete output.
+ *
+ * Determine context (is there a site-alias or command set, and are we trying
+ * to complete an option). Then produce a list of completions for the last word
+ * and output them separated by newlines.
+ */
+function drush_complete() {
+  // We use a distinct --complete-debug option to avoid unwanted debug messages
+  // being printed when users use this option for other purposes in the command
+  // they are trying to complete.
+  drush_set_option('debug', FALSE);
+  if (drush_get_option('complete-debug', FALSE)) {
+    drush_set_context('DRUSH_DEBUG', TRUE);
+  }
+  // Set up as if we were running the command, and attempt to parse.
+  $argv = drush_complete_process_argv();
+  $set_sitealias = drush_sitealias_get_record('@self');
+  $set_sitealias_name = NULL;
+  if (!empty($set_sitealias['name'])) {
+    $set_sitealias_name = $set_sitealias['name'];
+  }
+  // Arguments have now had site-aliases and options removed, so we take the
+  // first item as our command. We need to know if the command is valid, so that
+  // we know if we are supposed to complete an in-progress command name, or
+  // arguments for a command. We do this by checking against our per-site cache
+  // of command names (which will only bootstrap if the cache needs to be
+  // regenerated), rather than drush_parse_command() which always requires a
+  // site bootstrap.
+  $arguments = drush_get_arguments();
+  $set_command_name = NULL;
+  if (isset($arguments[0]) && in_array($arguments[0] . ' ', drush_complete_get('command-names'))) {
+    $set_command_name = $arguments[0];
+  }
+  // We unset the command if it is "help" but that is not explicitly found in
+  // args, since Drush sets the command to "help" if no command is specified,
+  // which prevents completion of global options.
+  if ($set_command_name == 'help' && !array_search('help', $argv)) {
+    $set_command_name = NULL;
+  }
+
+  // Determine the word we are trying to complete, and if it is an option.
+  $last_word = end($argv);
+  $word_is_option = FALSE;
+  if (!empty($last_word) && $last_word[0] == '-') {
+    $word_is_option = TRUE;
+    $last_word = ltrim($last_word, '-');
+  }
+
+  $completions = array();
+
+  if (!$set_command_name) {
+    // We have no command yet.
+    if ($word_is_option) {
+      // Include global option completions.
+      $completions += drush_hyphenate_options(drush_complete_match($last_word, drush_complete_get('options')));
+    }
+    else {
+      if (!$set_sitealias_name) {
+        // Include site alias completions.
+        $completions += drush_complete_match($last_word, drush_complete_get('site-aliases'));
+      }
+      // Include command completions.
+      $completions += drush_complete_match($last_word, drush_complete_get('command-names'));
+    }
+  }
+  else {
+    if ($last_word == $set_command_name) {
+      // The user typed a valid command name, include in in the completion list
+      // so they get a space inserted, confirming it is valid.
+      $completions[] = $set_command_name;
+    }
+    else if ($word_is_option) {
+      // Include command option completions.
+      $completions += drush_hyphenate_options(drush_complete_match($last_word, drush_complete_get('options', $set_command_name)));
+    }
+    else {
+      // Include command argument completions.
+      $argument_completion = drush_complete_get('arguments', $set_command_name);
+      if (isset($argument_completion['values'])) {
+        $completions += drush_complete_match($last_word, $argument_completion['values']);
+      }
+      if (isset($argument_completion['files'])) {
+        $completions += drush_complete_match_file($last_word, $argument_completion['files']);
+      }
+    }
+  }
+
+  // Print the final output.
+  if (!empty($completions)) {
+    drush_print(implode("\n", $completions));
+  }
+
+  // We complete execution here, since our work is done, and continuing would
+  // trigger valid commands occurring later in the bootstrap.
+  drush_bootstrap_finish();
+  exit(1);
+}
+
+/**
+ * This function resets the raw arguments so that Drush can parse the command as
+ * if it was run directly.
+ *
+ * When using a complete script (i.e. pressing tab) the script passes in the
+ * entire command line as an argument, so argv looks something like this:
+ * array (
+ *  '0' => '/home/owen/workspace/drush/drush.php',
+ *  '1' => 'complete',
+ *  '2' => 'drush',
+ *  '3' => 'COMMAND',
+ *  '4' => '',
+ *  '5' => '--php=/usr/bin/php',
+ * );
+ *
+ * When calling "complete" manually (for testing) the script passes in the
+ * arguments more normally (although including the "complete" command, and not
+ * including any empty last argument), so argv looks like this:
+ * array (
+ *  '0' => '/home/owen/workspace/drush/drush.php',
+ *  '1' => 'complete',
+ *  '2' => 'COMMAND',
+ *  '3' => '--php=/usr/bin/php',
+ * );
+ *
+ * @return $args
+ *   Array of arguments (argv), excluding the "complete" command:
+ *   array (
+ *    '0' => 'drush',
+ *    '1' => 'COMMAND',
+ *    '2' => '',
+ *   );
+ */
+function drush_complete_process_argv() {
+  $argv = drush_get_context('argv');
+  if (getenv('ETC_PREFIX')) drush_set_context('ETC_PREFIX', getenv('ETC_PREFIX'));
+  // Remove everything up to and including the "complete" command (running now).
+  $argv = array_slice($argv, array_search('complete', $argv) + 1);
+  // Remove the --php option at the end if exists (added by the "drush" shell
+  // script that is called when completion is requested).
+  if (substr(end($argv), 0, 6) == '--php=') {
+    array_pop($argv);
+  }
+  // Ensure the initial argument is "drush" so that drush_parse_args() works
+  // correctly. This is only neccessary when calling "complete" manually.
+  if (strpos(reset($argv), 'drush') === FALSE) {
+    array_unshift($argv, 'drush');
+  }
+  drush_set_context('argv', $argv);
+  drush_set_command(NULL);
+  // Reparse arguments, site alias, and command.
+  drush_parse_args();
+  drush_sitealias_check_arg();
+
+  // Return the new argv for easy reference.
+  return $argv;
+}
+
+/**
+ * Retrieves the appropriate list of candidate completions, then filters this
+ * list using the last word that we are trying to complete.
+ *
+ * @param string $last_word
+ *   The last word in the argument list (i.e. the subject of completion).
+ * @param array $values
+ *   Array of possible completion values to filter.
+ *
+ * @return array
+ *   Array of candidate completions that start with the same characters as the
+ *   last word. If the last word is empty, return all candidates.
+ */
+function drush_complete_match($last_word, $values) {
+  // Using preg_grep appears to be faster that strpos with array_filter/loop.
+  return preg_grep('/^' . preg_quote($last_word, '/') . '/', $values);
+}
+
+/**
+ * Retrieves the appropriate list of candidate file/directory completions,
+ * filtered by the last word that we are trying to complete.
+ *
+ * @param string $last_word
+ *   The last word in the argument list (i.e. the subject of completion).
+ * @param array $files
+ *   Array of file specs, each with a pattern and flags subarray.
+ *
+ * @return array
+ *   Array of candidate file/directory completions that start with the same
+ *   characters as the last word. If the last word is empty, return all
+ *   candidates.
+ */
+function drush_complete_match_file($last_word, $files) {
+  $return = array();
+  $firstchar = '';
+  $full_paths = TRUE;
+  if (isset($last_word) && $last_word[0] == '~') {
+    // Complete does not do tilde expansion, so we do it here.
+    $parts = explode('/', $last_word);
+    // We shell out (unquoted) to expand the tilde.
+    drush_shell_exec('echo ' . $parts[0]);
+    $output = drush_shell_exec_output();
+    $parts[0] = $output[0];
+    $last_word = implode('/', $parts);
+  }
+  foreach ($files as $spec) {
+    // We always include GLOB_MARK, as an easy way to detect directories.
+    $flags = GLOB_MARK;
+    if (isset($spec['flags'])) {
+      $flags = $spec['flags'] | GLOB_MARK;
+    }
+    $listing = glob($last_word . $spec['pattern'], $flags);
+    foreach ($listing as $item) {
+      // Detect if the initial characters of the file/dirs to be listing differ.
+      // If they do, we return a list of just their names. If they all have the
+      // same first character we return full paths, to prevent the shell
+      // replacing the current path with just the matching character(s).
+      $char = $item[strrpos($last_word, '/') + 1];
+      if (empty($firstchar)) {
+        $firstchar = $char;
+      }
+      else if ($firstchar !== $char) {
+        $full_paths = FALSE;
+      }
+      $return[] = $item;
+    }
+  }
+  // If we don't need to return full paths, shorten them appropriately.
+  if ($full_paths == FALSE) {
+    foreach ($return as $id => $item) {
+      $return[$id] = substr($return[$id], strrpos($last_word, '/') + 1);
+    }
+  }
+  // If we are returning a single item (which will become part of the final
+  // command), we need to use the full path, and we need to escape it
+  // appropriately.
+  if (count($return) == 1) {
+    // Escape shell metacharacters (we don't use escapeshellarg as it
+    // single quotes everything, even when unnecessary).
+    $item = preg_replace('/[ |&;()<>]/', "\\\\$0", $item);
+    if (substr($item, -1) !== '/') {
+      // Insert a space after files, since the argument is complete.
+      $item = $item . ' ';
+    }
+    $return = array($item);
+  }
+  return $return;
+}
+
+/**
+ * Simple helper function to ensure options are properly hyphenated before we
+ * return them to the user (we match against the non-hyphenated versions
+ * internally).
+ *
+ * @param array $options
+ *   Array of unhyphenated option names.
+ *
+ * @return array
+ *   Array of hyphenated option names.
+ */
+function drush_hyphenate_options($options) {
+  foreach ($options as $key => $option) {
+    $options[$key] = '--' . ltrim($option, '--');
+  }
+  return $options;
+}
+
+/**
+ * Retrieves from cache, or generates a listing of completion candidates of a
+ * specific type (and optionally, command).
+ *
+ * @param string $type
+ *   String indicating type of completions to return.
+ *   See drush_complete_rebuild() for possible keys.
+ * @param string $command
+ *   An optional command name if command specific completion is needed.
+ *
+ * @return array
+ *   List of candidate completions.
+ */
+function drush_complete_get($type, $command = NULL) {
+  if (empty($command)) {
+    // Retrieve global items from a non-command specific cache, or rebuild cache
+    // if needed.
+    $cache = drush_cache_get(drush_complete_cache_cid($type), 'complete');
+    if (isset($cache->data)) {
+      return $cache->data;
+    }
+    $complete = drush_complete_rebuild();
+    return $complete[$type];
+  }
+  // Retrieve items from a command specific cache.
+  $cache = drush_cache_get(drush_complete_cache_cid($type, $command), 'complete');
+  if (isset($cache->data)) {
+    return $cache->data;
+  }
+  // Build argument cache - built only on demand.
+  if ($type == 'arguments') {
+    return drush_complete_rebuild_arguments($command);
+  }
+  // Rebuild cache of general command specific items.
+  $complete = drush_complete_rebuild();
+  if (!empty($complete['commands'][$command][$type])) {
+    return $complete['commands'][$command][$type];
+  }
+  return array();
+}
+
+/**
+ * Rebuild and cache completions for everything except command arguments.
+ *
+ * @return array
+ *   Structured array of completion types, commands and candidate completions.
+ */
+function drush_complete_rebuild() {
+  $complete = array();
+  // Bootstrap to the site level (if possible) - commands may need to check
+  // the bootstrap level, and perhaps bootstrap higher in extraordinary cases.
+  drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_SITE);
+  $commands = drush_get_commands();
+  foreach ($commands as $command_name => $command) {
+    // Add command options - we don't currently complete on option values (with
+    // the exception of engine names).
+    $options = array_keys($command['options']);
+    // Add engine types and sub-options from engines, if any.
+    // This could potentially be improved to only show options associated with
+    // an active engine.
+    foreach ($command['engines'] as $type => $description) {
+      $all_engines = drush_get_engines($type);
+      foreach ($all_engines as $name => $engine) {
+        $options[] = $name;
+        if (!empty($engine['sub-options'])) {
+          foreach ($engine['sub-options'] as $sub_option => $sub_option_values) {
+            $options = array_merge($options, array_keys($sub_option_values));
+          }
+        }
+      }
+    }
+    $complete['commands'][$command_name]['options'] = $options;
+  }
+  // We treat shell aliases as commands for the purposes of completion.
+  $complete['command-names'] = array_merge(array_keys($commands), array_keys(drush_get_option('shell-aliases', array())));
+  $site_aliases = _drush_sitealias_all_list();
+  // TODO: Figure out where this dummy @0 alias is introduced.
+  unset($site_aliases['@0']);
+  $complete['site-aliases'] = array_keys($site_aliases);
+  $complete['options'] = array_keys(drush_get_global_options());
+
+  // We add a space following all completes. Eventually there may be some
+  // items (e.g. options that we know need values) where we don't add a space.
+  array_walk_recursive($complete, 'drush_complete_trailing_space');
+  drush_complete_cache_set($complete);
+  return $complete;
+}
+
+/**
+ * Helper callback function that adds a trailing space to completes in an array.
+ */
+function drush_complete_trailing_space(&$item, $key) {
+  if (!is_array($item)) {
+    $item = (string)$item . ' ';
+  }
+}
+
+/**
+ * Rebuild and cache completions for command arguments.
+ *
+ */
+
+/**
+ * Rebuild and cache completions for command arguments.
+ *
+ * @param string $command
+ *   A specific command to retrieve and cache arguments for.
+ *
+ * @return array
+ *   Structured array of candidate completion arguments, keyed by the command.
+ */
+function drush_complete_rebuild_arguments($command) {
+  // Bootstrap to the site level (if possible) - commands may need to check
+  // the bootstrap level, and perhaps bootstrap higher in extraordinary cases.
+  drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_SITE);
+  $commands = drush_get_commands();
+  $hook = str_replace("-", "_", $commands[$command]['command-hook']);
+  $result = drush_command_invoke_all($hook . '_complete');
+  if (isset($result['values'])) {
+    // We add a space following all completes. Eventually there may be some
+    // items (e.g. comma separated arguments) where we don't add a space.
+    array_walk($result['values'], 'drush_complete_trailing_space');
+  }
+
+  $complete = array(
+    'commands' => array(
+      $command => array(
+        'arguments' => $result,
+      )
+    )
+  );
+  drush_complete_cache_set($complete);
+  return $complete['commands'][$command]['arguments'];
+}
+
+/**
+ * Stores caches for completions.
+ *
+ * @param $complete
+ *   A structured array of completions, keyed by type, including a 'commands'
+ *   type that contains all commands with command specific completions keyed by
+ *   type. The array does not need to include all types - used by
+ *   drush_complete_rebuild_arguments().
+ */
+function drush_complete_cache_set($complete) {
+  foreach ($complete as $type => $values) {
+    if ($type == 'commands') {
+      foreach ($values as $command_name => $command) {
+        foreach ($command as $command_type => $command_values) {
+          drush_cache_set(drush_complete_cache_cid($command_type, $command_name), $command_values, 'complete', DRUSH_CACHE_TEMPORARY);
+        }
+      }
+    }
+    else {
+      drush_cache_set(drush_complete_cache_cid($type), $values, 'complete', DRUSH_CACHE_TEMPORARY);
+    }
+  }
+}
+
+/**
+ * Clears completion caches.
+ *
+ * If called with no parameters the entire complete cache will be cleared.
+ * If called with just the $type parameter the global cache for that type will
+ * be cleared (in the site context, if any). If called with both $type and
+ * $command parameters the command cache of that type will be cleared  (in the
+ * site context, if any).
+ *
+ * @param $type
+ *   The completion type (optional).
+ * @param $command
+ *   The command name (optional), if command specific cache is to be cleared.
+ *   If specifying a command, $type is not optional.
+ */
+function drush_complete_cache_clear($type = NULL, $command = NULL) {
+  if ($type) {
+    drush_cache_clear_all(drush_complete_cache_cid($type, $command), 'complete');
+    return;
+  }
+  // No type or command, so clear the entire complete cache.
+  drush_cache_clear_all('*', 'complete', TRUE);
+}
+
+/**
+ * Generate a cache id.
+ *
+ * @param $type
+ *   The completion type.
+ * @param $command
+ *   The command name (optional), if completions are command specific.
+ *
+ * @return string
+ *   Cache id.
+ */
+function drush_complete_cache_cid($type, $command = NULL) {
+  // Everything is cached per-site, except global options.
+  $root = NULL;
+  $site = NULL;
+  if ($type !== 'options') {
+    // For per-site caches (everything except options), we include the site root
+    // and uri/path in the cache id hash. These are quick to determine, and
+    // prevents a bootstrap to site just to get a validated root and URI.
+    // Because these are not validated, there is the possibility of cache misses
+    // but they should be rare, since sites are normally referred to the same
+    // way (e.g. a site alias, or using the current way), at least within a
+    // single command completion session.
+    $root = drush_get_option(array('r', 'root'), drush_locate_root());
+    $site = drush_get_option(array('l', 'uri'), drush_site_path());
+  }
+  return drush_get_cid('complete', array(), array($type, $command, $root, $site));
+}
diff --git a/tests/completeTest.php b/tests/completeTest.php
new file mode 100644
index 0000000..9ea120c
--- /dev/null
+++ b/tests/completeTest.php
@@ -0,0 +1,101 @@
+<?php
+
+class completeCase extends Drush_CommandTestCase {
+  public function testComplete() {
+    $sites = $this->setUpDrupal(2);
+    $env = key($sites);
+    $root = $this->webroot();
+    // We copy our test command into our dev site, so we have a difference we
+    // can detect for cache correctness. We cannot use --include since complete
+    // deliberately avoids drush command dispatch.
+    mkdir("$root/sites/$env/modules");
+    copy(dirname(__FILE__) . '/unit.drush.inc', "$root/sites/$env/modules/unit.drush.inc");
+    // Clear the cache, so it finds our test command.
+    $this->drush('php-eval', array('drush_cache_clear_all();'), array(), '@' . $env);
+
+    // Create a sample directory and file to test file/directory completion.
+    mkdir("aardvark");
+    touch('aard wolf.tar.gz');
+
+    // Create directory for temporary debug logs.
+    mkdir(UNISH_SANDBOX . '/complete-debug');
+
+    // Test cache clearing for global cache, which should affect all
+    // environments.
+    $this->drush('php-eval', array('drush_complete_cache_clear();'));
+    $this->verifyComplete('@dev uni', 'uninstall', 'unit-invoke', FALSE);
+    $this->verifyComplete('uni', 'uninstall', 'uninstall', FALSE);
+    $this->verifyComplete('@dev uni', 'uninstall', 'unit-invoke');
+    $this->verifyComplete('uni', 'uninstall', 'uninstall');
+    // Test cache clearing for a completion type, which should be effective
+    // only for current environment.
+    $this->drush('php-eval', array('drush_complete_cache_clear("command-names");'));
+    $this->verifyComplete('@dev uni', 'uninstall', 'unit-invoke');
+    $this->verifyComplete('uni', 'uninstall', 'uninstall', FALSE);
+    // Test cache clearing for a command specific completion type, which should
+    // be effective only for current environment. Prime caches first.
+    $this->verifyComplete('@dev topic docs-c', 'docs-configuration', 'docs-context');
+    $this->verifyComplete('topic docs-c', 'docs-configuration', 'docs-context');
+    $this->drush('php-eval', array('drush_complete_cache_clear("arguments", "topic");'));
+    $this->verifyComplete('@dev topic docs-c', 'docs-configuration', 'docs-context');
+    $this->verifyComplete('topic docs-c', 'docs-configuration', 'docs-context', FALSE);
+
+    // Test overall context sensitivity - all of these should be cache hits.
+    // Site alias alone.
+    $this->verifyComplete('@', '@stage', '@dev');
+    // Command alone.
+    $this->verifyComplete('d', 'drupal-directory', 'download');
+    // Command with single result.
+    $this->verifyComplete('core-t', 'core-topic', 'core-topic');
+    // Global option alone.
+    $this->verifyComplete('--n', '--no', '--nocolor');
+    // Site alias + command.
+    $this->verifyComplete('@dev d', 'drupal-directory', 'download');
+    // Site alias + command, should allow no further site aliases or commands.
+    $this->verifyComplete('@dev topic @', '', '');
+    $this->verifyComplete('@dev topic topi', '', '');
+    // Command + command option.
+    $this->verifyComplete('dl --', '--destination', '--gitsubmoduleaddparams');
+    // Site alias + command + command option.
+    $this->verifyComplete('@dev dl --', '--destination', '--gitsubmoduleaddparams');
+    // Command + argument.
+    $this->verifyComplete('topic docs-c', 'docs-configuration', 'docs-context');
+    // Site alias + command + regular argument.
+    $this->verifyComplete('@dev topic docs-c', 'docs-configuration', 'docs-context');
+    // Site alias + command + file/directory argument.
+    $this->verifyComplete('archive-restore aard', 'aardvark/', 'aard wolf.tar.gz');
+    // Site alias + command + file/directory argument with quoting.
+    $this->verifyComplete('archive-restore aard\ w', 'aard\ wolf.tar.gz', 'aard\ wolf.tar.gz');
+  }
+
+  /**
+   * Helper function to call completion and make common checks.
+   *
+   * @param $command
+   *   The command line to attempt to complete.
+   * @param $first
+   *   String indicating the expected first completion suggestion.
+   * @param $last
+   *   String indicating the expected last completion suggestion.
+   * @param bool $cache_hit
+   *   Optional parameter, if TRUE or ommitted the debug log is checked to
+   *   ensure a cache hit, if FALSE then a cache miss is checked for.
+   */
+  function verifyComplete($command, $first, $last, $cache_hit = TRUE) {
+    // We capture debug output to a separate file, so we can check for cache
+    // hits/misses.
+    $debug_file = tempnam(UNISH_SANDBOX . '/complete-debug', 'complete-debug');
+    // We expect a return code of 1 so just call execute() directly.
+    $exec = sprintf('%s complete --complete-debug %s 2> %s', UNISH_DRUSH, $command, $debug_file);
+    $this->execute($exec, self::EXIT_ERROR);
+    $result = $this->getOutputAsList();
+    $this->assertEquals(reset($result), $first);
+    $this->assertEquals(end($result), $last);
+    $cache = 'HIT';
+    if (!$cache_hit) {
+      $cache = 'MISS';
+    }
+    $this->assertTrue(strpos(file_get_contents($debug_file), 'Cache ' . $cache . ' cid') !== FALSE);
+    unlink($debug_file);
+  }
+}
\ No newline at end of file
