docs: begin generating docs for defs
diff --git a/MODULE.bazel b/MODULE.bazel
index b0e8a88..aa9a96e 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -8,6 +8,7 @@
 bazel_dep(name = "bazel_skylib", version = "1.3.0")
 
 # Dependencies needed in tests
+bazel_dep(name = "stardoc", version = "0.5.6", dev_dependency = True, repo_name = "io_bazel_stardoc")
 bazel_dep(name = "rules_cc", version = "0.0.1", dev_dependency = True)
 bazel_dep(name = "googletest", version = "1.11.0", dev_dependency = True, repo_name = "com_google_googletest")
 bazel_dep(name = "protobuf", version = "21.7", dev_dependency = True, repo_name = "com_google_protobuf")
diff --git a/WORKSPACE b/WORKSPACE
index ff6744f..75a3c95 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -7,6 +7,7 @@
 rules_proto_toolchains()
 
 # Dev-only dependencies. USERS SHOULD NOT INSTALL THESE!
+
 load(":dev_deps.bzl", "rules_proto_dev_deps")
 
 rules_proto_dev_deps()
diff --git a/dev_deps.bzl b/dev_deps.bzl
index f89fa59..ad07c1e 100644
--- a/dev_deps.bzl
+++ b/dev_deps.bzl
@@ -18,6 +18,15 @@
 
 def rules_proto_dev_deps():
     http_archive(
+        name = "io_bazel_stardoc",
+        sha256 = "dfbc364aaec143df5e6c52faf1f1166775a5b4408243f445f44b661cfdc3134f",
+        urls = [
+            "https://mirror.bazel.build/github.com/bazelbuild/stardoc/releases/download/0.5.6/stardoc-0.5.6.tar.gz",
+            "https://github.com/bazelbuild/stardoc/releases/download/0.5.6/stardoc-0.5.6.tar.gz",
+        ],
+    )
+
+    http_archive(
         name = "rules_cc",
         sha256 = "4aeb102efbcfad509857d7cb9c5456731e8ce566bfbf2960286a2ec236796cc3",
         strip_prefix = "rules_cc-2f8c04c04462ab83c545ab14c0da68c3b4c96191",
diff --git a/docs/BUILD b/docs/BUILD
new file mode 100644
index 0000000..9a797c9
--- /dev/null
+++ b/docs/BUILD
@@ -0,0 +1,12 @@
+load(":stardoc_with_diff_test.bzl", "stardoc_with_diff_test", "update_docs")
+
+stardoc_with_diff_test(
+    name = "defs",
+    bzl_library_target = "//proto:defs",
+    out_label = "//docs:defs.md",
+)
+
+update_docs(
+    name = "update",
+    docs_folder = "docs",
+)
\ No newline at end of file
diff --git a/docs/defs.md b/docs/defs.md
new file mode 100644
index 0000000..c229743
--- /dev/null
+++ b/docs/defs.md
@@ -0,0 +1,72 @@
+<!-- Generated with Stardoc: http://skydoc.bazel.build -->
+
+Starlark rules for building protocol buffers.
+
+<a id="proto_descriptor_set"></a>
+
+## proto_descriptor_set
+
+<pre>
+proto_descriptor_set(<a href="#proto_descriptor_set-name">name</a>, <a href="#proto_descriptor_set-deps">deps</a>)
+</pre>
+
+Collects all `FileDescriptorSet`s from `deps` and combines them into a single
+`FileDescriptorSet` containing all the `FileDescriptorProto`.
+
+**ATTRIBUTES**
+
+
+| Name  | Description | Type | Mandatory | Default |
+| :------------- | :------------- | :------------- | :------------- | :------------- |
+| <a id="proto_descriptor_set-name"></a>name |  A unique name for this target.   | <a href="https://bazel.build/concepts/labels#target-names">Name</a> | required |  |
+| <a id="proto_descriptor_set-deps"></a>deps |  Sequence of <code>ProtoInfo</code>s to collect <code>FileDescriptorSet</code>s from.   | <a href="https://bazel.build/concepts/labels">List of labels</a> | optional | <code>[]</code> |
+
+
+<a id="proto_lang_toolchain"></a>
+
+## proto_lang_toolchain
+
+<pre>
+proto_lang_toolchain(<a href="#proto_lang_toolchain-name">name</a>, <a href="#proto_lang_toolchain-toolchain_type">toolchain_type</a>, <a href="#proto_lang_toolchain-exec_compatible_with">exec_compatible_with</a>, <a href="#proto_lang_toolchain-target_compatible_with">target_compatible_with</a>, <a href="#proto_lang_toolchain-attrs">attrs</a>)
+</pre>
+
+Creates a proto_lang_toolchain and corresponding toolchain target.
+
+Toolchain target is only created when toolchain_type is set.
+
+https://docs.bazel.build/versions/master/be/protocol-buffer.html#proto_lang_toolchain
+
+
+**PARAMETERS**
+
+
+| Name  | Description | Default Value |
+| :------------- | :------------- | :------------- |
+| <a id="proto_lang_toolchain-name"></a>name |  name of the toolchain   |  none |
+| <a id="proto_lang_toolchain-toolchain_type"></a>toolchain_type |  The toolchain type   |  <code>None</code> |
+| <a id="proto_lang_toolchain-exec_compatible_with"></a>exec_compatible_with |  ([constraints]) List of constraints the prebuild binaries is compatible with.   |  <code>[]</code> |
+| <a id="proto_lang_toolchain-target_compatible_with"></a>target_compatible_with |  ([constraints]) List of constraints the target libraries are compatible with.   |  <code>[]</code> |
+| <a id="proto_lang_toolchain-attrs"></a>attrs |  Rule attributes   |  none |
+
+
+<a id="proto_library"></a>
+
+## proto_library
+
+<pre>
+proto_library(<a href="#proto_library-attrs">attrs</a>)
+</pre>
+
+Bazel proto_library rule.
+
+https://docs.bazel.build/versions/master/be/protocol-buffer.html#proto_library
+
+
+**PARAMETERS**
+
+
+| Name  | Description | Default Value |
+| :------------- | :------------- | :------------- |
+| <a id="proto_library-attrs"></a>attrs |  Rule attributes   |  none |
+
+
diff --git a/docs/stardoc_with_diff_test.bzl b/docs/stardoc_with_diff_test.bzl
new file mode 100644
index 0000000..355d6f4
--- /dev/null
+++ b/docs/stardoc_with_diff_test.bzl
@@ -0,0 +1,111 @@
+# Copyright 2022 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Helpers for keeping stardoc documentation up-to-date.
+
+These are currently a private API in bazel-skylib.
+See discussion:
+https://github.com/bazelbuild/bazel-skylib/pull/321#issuecomment-1081166216
+
+If you need a similar feature, you can consider using a similar rule
+available from a third-party:
+https://github.com/aspect-build/bazel-lib/blob/main/docs/docs.md
+"""
+
+load("@bazel_skylib//rules:diff_test.bzl", "diff_test")
+load("@bazel_skylib//rules:write_file.bzl", "write_file")
+load("@io_bazel_stardoc//stardoc:stardoc.bzl", "stardoc")
+
+def stardoc_with_diff_test(
+        name,
+        bzl_library_target,
+        out_label):
+    """Creates a stardoc target coupled with a `diff_test` for a given `bzl_library`.
+
+    This is helpful for minimizing boilerplate in repos with lots of stardoc targets.
+
+    Args:
+        name: the stardoc target name
+        bzl_library_target: the label of the `bzl_library` target to generate documentation for
+        out_label: the label of the output MD file
+    """
+    out_file = out_label.replace("//", "").replace(":", "/")
+
+    # Generate MD from .bzl
+    stardoc(
+        name = name,
+        out = out_file.replace(".md", "-docgen.md"),
+        input = bzl_library_target + ".bzl",
+        deps = [bzl_library_target],
+    )
+
+    # Ensure that the generated MD has been updated in the local source tree
+    diff_test(
+        name = out_file.replace("/", "_").replace(".md", "-difftest"),
+        failure_message = "Please run \"bazel run //docs:update\"",
+        # Source file
+        file1 = out_label,
+        # Output from stardoc rule above
+        file2 = out_file.replace(".md", "-docgen.md"),
+        tags = ["no_windows"],
+    )
+
+def update_docs(
+        name = "update",
+        docs_folder = "docs"):
+    """Creates a `sh_binary` target which copies over generated doc files to the local source tree.
+
+    This is to be used in tandem with `stardoc_with_diff_test()` to produce a convenient workflow
+    for generating, testing, and updating all doc files as follows:
+
+    ``` bash
+    bazel build //{docs_folder}/... && bazel test //{docs_folder}/... && bazel run //{docs_folder}:update
+    ```
+
+    eg.
+
+    ``` bash
+    bazel build //docs/... && bazel test //docs/... && bazel run //docs:update
+    ```
+
+    The `update_docs()` invocation must come after/below any `stardoc_with_diff_test()` invocations
+    in the BUILD file.
+
+    Args:
+        name: the name of the `sh_binary` target
+        docs_folder: the name of the folder containing the doc files in the local source tree
+    """
+    content = ["#!/usr/bin/env bash", "cd ${BUILD_WORKSPACE_DIRECTORY}"]
+    data = []
+    for r in native.existing_rules().values():
+        if r["kind"] == "stardoc":
+            doc_gen = r["out"]
+            if doc_gen.startswith(":"):
+                doc_gen = doc_gen[1:]
+            doc_dest = doc_gen.replace("-docgen.md", ".md")
+            data.append(doc_gen)
+            content.append("cp -fv bazel-bin/{0}/{1} {2}".format(docs_folder, doc_gen, doc_dest))
+
+    update_script = name + ".sh"
+    write_file(
+        name = "gen_" + name,
+        out = update_script,
+        content = content,
+    )
+
+    native.sh_binary(
+        name = name,
+        srcs = [update_script],
+        data = data,
+    )
\ No newline at end of file
diff --git a/proto/BUILD b/proto/BUILD
index 4cbb2a0..cb6ad70 100644
--- a/proto/BUILD
+++ b/proto/BUILD
@@ -1,5 +1,11 @@
 load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
 
+
+exports_files(
+    glob(["*.bzl"]),
+    visibility = ["//docs:__pkg__"]
+)
+
 bzl_library(
     name = "defs",
     srcs = [
@@ -9,10 +15,28 @@
     deps = [
         "//proto/private:native",
         "//proto/private/rules:proto_descriptor_set",
+        ":proto_lang_toolchain"
     ],
 )
 
 bzl_library(
+    name = "proto_lang_toolchain",
+    srcs = [
+        "proto_lang_toolchain.bzl",
+    ],
+    deps = [
+        ":proto_common"
+    ]
+)
+
+bzl_library(
+    name = "proto_common",
+    srcs = [
+        "proto_common.bzl",
+    ]
+)
+
+bzl_library(
     name = "proto_toolchain_bzl",
     srcs = [
         "proto_toolchain.bzl",