feat: generate markdown docs, assert they're up-to-date
diff --git a/WORKSPACE b/WORKSPACE
index 4d2f257..78da1a6 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -3,6 +3,15 @@
 load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
 
 http_archive(
+    name = "bazel_skylib",
+    sha256 = "58f558d04a936cade1d4744d12661317e51f6a21e3dd7c50b96dc14f3fa3b87d",
+    strip_prefix = "bazel-skylib-df3c9e2735f02a7fe8cd80db4db00fec8e13d25f",
+    urls = [
+        "https://github.com/bazelbuild/bazel-skylib/archive/df3c9e2735f02a7fe8cd80db4db00fec8e13d25f.tar.gz",
+    ],
+)
+
+http_archive(
     name = "io_bazel_rules_go",
     sha256 = "8e968b5fcea1d2d64071872b12737bbb5514524ee5f0a4f54f5920266c261acb",
     urls = [
diff --git a/cmd/aspect/root/root.go b/cmd/aspect/root/root.go
index 9478de7..1f90641 100644
--- a/cmd/aspect/root/root.go
+++ b/cmd/aspect/root/root.go
@@ -22,7 +22,7 @@
 
 var (
 	boldCyan = color.New(color.FgCyan, color.Bold)
-	faint = color.New(color.Faint)
+	faint    = color.New(color.Faint)
 )
 
 func NewDefaultRootCmd() *cobra.Command {
@@ -62,6 +62,7 @@
 	}
 
 	// ### Child commands
+	// IMPORTANT: when adding a new command, also update the _DOCS list in /docs/BUILD.bazel
 	cmd.AddCommand(version.NewDefaultVersionCmd())
 
 	// ### "Additional help topic commands" which are not runnable
diff --git a/docs/BUILD.bazel b/docs/BUILD.bazel
new file mode 100644
index 0000000..b07454f
--- /dev/null
+++ b/docs/BUILD.bazel
@@ -0,0 +1,46 @@
+load("@bazel_skylib//rules:diff_test.bzl", "diff_test")
+load("@bazel_skylib//rules:write_file.bzl", "write_file")
+
+# This list must be updated when we add a new command
+_DOCS = [
+    "aspect.md",
+    "aspect_version.md",
+]
+
+genrule(
+    name = "docgen",
+    outs = ["gen/" + d for d in _DOCS],
+    cmd = "$(execpath //cmd/docgen) $(@D)/gen",
+    tools = ["//cmd/docgen"],
+)
+
+# Help developers who get a red CI result by telling them how to fix it
+_failure_message = "\nPlease update the docs by running\n    bazel run //docs:update"
+
+[
+    diff_test(
+        name = "check_" + file,
+        failure_message = _failure_message,
+        file1 = "gen/" + file,
+        file2 = file,
+    )
+    for file in _DOCS
+]
+
+write_file(
+    name = "gen_update",
+    out = "update.sh",
+    content = [
+        "#!/usr/bin/env bash",
+        "cd $BUILD_WORKSPACE_DIRECTORY",
+    ] + [
+        "cp -fv bazel-bin/docs/gen/{0} docs/{0}".format(file)
+        for file in _DOCS
+    ],
+)
+
+sh_binary(
+    name = "update",
+    srcs = ["update.sh"],
+    data = ["gen/" + d for d in _DOCS],
+)
diff --git a/docs/aspect.md b/docs/aspect.md
new file mode 100644
index 0000000..15fea1a
--- /dev/null
+++ b/docs/aspect.md
@@ -0,0 +1,21 @@
+## aspect
+
+Aspect.build bazel wrapper
+
+### Synopsis
+
+Aspect CLI is a better frontend for running bazel
+
+### Options
+
+```
+      --config string   config file (default is $HOME/.aspect.yaml)
+  -h, --help            help for aspect
+      --interactive     Interactive mode (e.g. prompts for user input)
+```
+
+### SEE ALSO
+
+* [aspect version](aspect_version.md)	 - Print the version of aspect CLI as well as tools it invokes
+
+###### Auto generated by spf13/cobra on 4-Sep-2021
diff --git a/docs/aspect_version.md b/docs/aspect_version.md
new file mode 100644
index 0000000..78e9616
--- /dev/null
+++ b/docs/aspect_version.md
@@ -0,0 +1,31 @@
+## aspect version
+
+Print the version of aspect CLI as well as tools it invokes
+
+### Synopsis
+
+Prints version info on colon-separated lines, just like bazel does
+
+```
+aspect version [flags]
+```
+
+### Options
+
+```
+      --gnu_format   format space-separated following GNU convention
+  -h, --help         help for version
+```
+
+### Options inherited from parent commands
+
+```
+      --config string   config file (default is $HOME/.aspect.yaml)
+      --interactive     Interactive mode (e.g. prompts for user input)
+```
+
+### SEE ALSO
+
+* [aspect](aspect.md)	 - Aspect.build bazel wrapper
+
+###### Auto generated by spf13/cobra on 4-Sep-2021