selfdoc

command module
v0.48.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 10, 2026 License: MIT Imports: 4 Imported by: 0

README

selfdoc

Static Site Generator that builds a project's documentation site directly from its source code, so the docs can never drift from the code they describe, with SEO/AEO, first-class blog, search, and cross-project linking built in. It is for maintainers who want a repository's documentation built from the repository itself, so API references, CLI help, schemas and tests are written by the code rather than beside it.

Extractors ship for Go, Python, TypeScript/JavaScript, Svelte, Zig, Dart, Kotlin, Swift and SQL. selfdoc is one Go binary with no runtime to install: stylesheets, scripts, themes and the word list are compiled into it.

Install

go install github.com/stricttools/selfdoc@v0

On a machine with no Go toolchain, download the archive for your platform from the latest GitHub Release -- prebuilt binaries are published for Linux and macOS on amd64 and arm64 -- and put selfdoc on your PATH.

Two optional dependencies, needed only by the features that use them: Pagefind for the search index, and python3 for custom directives (a .py directive script is the only thing selfdoc runs an interpreter for; every built-in extractor, the Python one included, parses in process).

Quick start

# Initialize in an existing project (auto-detects language)
selfdoc init --base-url https://myproject.pages.dev

# Auto-generate API and CLI reference pages
selfdoc gen

# Edit .strictmetadata/docs/ pages -- add directives referencing your code

# Build HTML output
selfdoc build

# Validate directives, coverage, and SEO lint
selfdoc check

# Serve locally with live reload
selfdoc serve

Your selfdoc.json needs versions and locales -- even for a single-version, single-locale project. Each source entry names its own path and language:

{
  "source": [{"path": "internal/", "language": "go"}],
  "base_url": "https://my-project.example.com",
  "versions": [{"version": "1.0.0"}],
  "locales": [{"code": "en", "label": "English", "default": true}]
}

Features

  • Directive syntax -- embed live API references, schemas, tests, and CLI help directly from source code (:-:, :<:, :>:)
  • Auto-generated pages -- API reference and CLI docs from source code structure (selfdoc gen)
  • Generated repository files -- README.md and .claude/CLAUDE.md (where Claude Code reads it) resolved from directive templates listed in root_files, written read-only by selfdoc gen
  • Multi-version docs -- build from git tags, cached builds, version picker UI
  • Localization -- parallel locale directories, hreflang tags, locale picker, per-locale sitemaps
  • Monorepo support -- unified site builder combines multiple projects into one docs site
  • Blog posts -- selfdoc blog post for authoring, listing pages, feeds, and a local editor app
  • Faceted search -- key=value filter syntax, 7 dimensions, chip UI, auto-injected version default
  • Sandboxed data generation -- run scripts in bubblewrap isolation (selfdoc gen-data)
  • Theming -- dark mode, accent colors, custom CSS overrides
  • Search -- Pagefind, indexed at build time, no network at read time
  • SEO -- lint rules, WCAG contrast validation, JSON-LD structured data, sitemaps
  • Coverage tracking -- per-symbol documentation coverage with a configurable threshold
  • Syntax highlighting -- build-time highlighting via chroma, code tabs, sortable tables
  • Performance -- CSS/JS/HTML minification, critical CSS inlining, gzip and Brotli pre-compression
  • Feeds and AI -- Atom feed, robots.txt with AI crawler controls, llms.txt / llms-full.txt
  • Landing page -- hero section, tagline, and feature cards
  • Live reload -- SSE-based dev server
  • Auto-commit -- generated files committed automatically (prefers safegit)

Directive syntax

Directives are inline blocks in your Markdown templates. They get replaced with content extracted from your source code at build time.

:-: directive-name path="arg"

Self-closing directives use :-:. Block directives that wrap a body use :<: to open, :>: to close, with :=: and ::: to delimit sections inside. Directives inside fenced code blocks are ignored.

Built-in directives

Directive Description
callout-danger Styled danger callout block
callout-important Styled important callout block
callout-note Styled note callout block
callout-tip Styled tip callout block
callout-warning Styled warning callout block
code-help Extract CLI help/usage text and flag definitions
code-test Embed test source code (whole file or specific function)
cv Render a curriculum vitae declared in a TOML document, plus the Person it states
list-crawlers List of the crawlers the generated robots.txt allows
list-glossary Definition list from Term: Definition lines
list-modules List source modules with file paths and docstring summaries
list-tree File/directory tree listing
prose-desc Extract module/package docstring as prose text
ref Extract module docstring, exported functions, and classes
table-commands CLI command summary table from strictcli structure
table-config Render a config file (JSON/TOML) as a key-value table
table-config-schema Configuration field reference table from schema
table-dep Dependencies table from pyproject.toml
table-directives Table of all core built-in directives
table-endpoint REST API endpoint table from OpenAPI spec
table-lints Table of every lint selfdoc can emit, by name, with its severity
table-schema Extract dataclass/struct fields as a markdown table
var Interpolate project metadata value

Example -- embed the API docs for a package:

## API Reference

:-: ref path="internal/config"

Example -- show a struct's fields as a table:

:-: table-schema path="internal/config.Field"

Custom directives

Register custom directives in selfdoc.json under the directives key. Each entry maps a directive name to a Python script (relative to project root) that defines a resolve(attrs, config, body) function returning a Markdown string.

{
  "directives": {
    "changelog": "scripts/changelog_directive.py"
  }
}

Script interface:

def resolve(attrs: dict, config: dict, body: list) -> str:
    """Return Markdown string to replace the directive block.

    attrs  -- directive attributes as str->str dict (e.g. {"path": "v1.0.0"})
    config -- the full selfdoc.json config dict
    body   -- body lines from the directive block (empty list for one-liners)
    """
    version = attrs.get("path")
    ...

Use in templates:

:-: changelog path="v1.0.0"

The script runs out of process: selfdoc hands an embedded driver to python3, passes the script's path, and sends attrs, config and body as one JSON object on standard input. What the script prints on standard output replaces the directive. A script that will not load, one with no callable resolve, one that raises, and a machine with no python3 are each a hard error that stops the build -- never a note on the published page.

Dispatch order is content directives, then custom directives, then the language extractors -- so a custom name overrides a code-extraction directive such as ref, but not a content directive such as callout-note.

Configuration

selfdoc.json at the project root:

{
  "source": [{"path": "internal/", "language": "go"}],
  "docs": ".strictmetadata/docs/",
  "output": ".strictmetadata/.docs-cache/build/",
  "base_url": "https://my-project.example.com",
  "versions": [{"version": "1.0.0"}],
  "locales": [{"code": "en", "label": "English", "default": true}],
  "deploy": {
    "provider": "cloudflare-pages",
    "project": "my-docs"
  },
  "directives": {}
}
Field Required Description
source no List of source entries to extract documentation from.
base_url yes Base URL of the generated site, used for canonical links and SEO.
version no Project version. When present, used by deploy instead of reading from the project manifest (VERSION, pyproject.toml or package.json).
docs no Directory containing the handwritten Markdown documentation templates. It lives inside the tool-state directory, and a value outside it is refused as the layout selfdoc used before.
output no Output directory for generated HTML files. It lives inside the tool-state directory, in the uncommitted cache.
changelog no Path to the changelog document published as the site's changelog page, relative to the project root. Absent means the project root's CHANGELOG.md is used if it exists; declare it when that file is not this site's changelog.
theme no Visual theme for the generated site. One of the themes selfdoc ships -- 'minimal', 'clean' or 'tinymoon'. A build's --theme flag overrides this for that build only, without writing anything back here.
repo no GitHub repository URL shown in the site header.
lang no BCP 47 language tag for the site content (e.g. 'en', 'pt-BR').
name no Explicit project name. Used as the single source of truth for the manifest name and the auto-generated API reference index description. When absent, the name is derived heuristically (single-source basename or project directory basename).
description no Short description of the project, used in meta tags and SEO.
branch no Git branch used for source links in the generated site.
search no Search UI mode: icon button, full bar, or hidden.
search_engine yes Search engine that answers this site's search UI. Required and never inferred: every site builds a search UI, so the engine behind it is declared, not defaulted.
code_icons no Style of language icons shown on code blocks.
line_numbers no Show line numbers in code blocks.
run_button no Show a run button on code blocks for supported languages.
page_nav no Show previous/next navigation links between pages.
page_progress no Show a reading progress bar at the top of each page.
glossary no Auto-generate a glossary page from dfn terms.
coverage_threshold no Minimum fraction of public symbols that must be documented for selfdoc check to pass (0.0-1.0). Default 1.0 requires 100% coverage.
feed_max_entries no Maximum number of entries in the Atom feed, sorted by most recent.
root_files no The templates that generate repository-level files, each with the files it generates, e.g. {"template": ".strictmetadata/docs/_README.md", "outputs": ["README.md", "pypi/README.md"]}. No output is declared by more than one entry.
redirects no Page-level redirects expanded across all locale/version combos.
deploy no Deployment configuration for publishing the generated site.
directives no Custom directive mappings from directive name to source file path.
examples no Validator command templates keyed by code-block language, used by 'selfdoc check' to execute fenced blocks marked 'validate'. Each template must contain the '{file}' placeholder. Absent means example validation is off.
author yes The site's author: one Person, named in every page's structured data. Required -- there is no inferred author.
twitter no Twitter/X handle (starts with @) for the twitter:site meta tag.
feedback no Feedback collection configuration (at least one of webhook or ga required).
branding no Landing page branding and call-to-action configuration.
auto_detect no Automatic content detection settings for step guides and API entries.
gen no Configuration for the gen command.
gen_data no Configuration for the gen-data command.
schema_types no Mapping from page type to schema.org @type (e.g. guide -> TechArticle).
versions no List of documentation versions to build.
unversioned no Declares that this project has no public version -- a personal site or portfolio that publishes no artifact. It replaces the 'versions' array (declaring both is an error) and is refused for a project that declares 'source', because code is the thing that gets released and therefore carries a version. An unversioned project's pages show no version badge, offer no version search filter and no version picker.
locales no List of locales for multi-language documentation.
unified no Configuration for unified multi-project documentation.
posts no Blog post configuration.
topology no Deployment topology for multi-project unified sites.
assembly no Assembly configuration for unified site deployment.

selfdoc init auto-detects language and source paths from project files (go.mod, pyproject.toml, tsconfig.json, package.json), and takes the site's own address as --base-url. A project with no detectable language is initialized as a codeless project: no source key, and no code-extraction directive in the starter page.

Commands

Command Description
init Initialize selfdoc in this repository: write selfdoc.json (versioned at the version the project's manifest states, 0.0.0 when it states none), the ownership manifests of .strictmetadata/docs, .strictmetadata/.docs-state, .strictmetadata/.docs-cache, .strictmetadata/vocabulary, and a starter docs page
build Build the documentation site from templates and source code
serve Serve the documentation site locally with live reload
deploy Deploy the built documentation site to the configured provider
check Check documentation coverage, directive resolution, and lint rules, each lint at the value its option in .strictmetadata/options/docs.toml sets (off: not reported; warn: reported, never blocking; error: as registered). A read-only verdict: it writes nothing and commits nothing. The staleness and drift baseline it compares against, .strictmetadata/.docs-state/hashes/hashes.json, is written and committed by the commands that generate content, selfdoc gen among them, and by selfdoc baseline accept, and an archived version it checks is read from a temporary extraction, not from selfdoc's version cache
gen Auto-generate documentation pages from project structure
gen-data Generate data files by running sandboxed scripts via bwrap
spell-corpus Spell-check the docs of every selfdoc project beside this one, using the same engine 'selfdoc check' runs (unknown-word), each project against its own vocabulary: selfdoc's built-in baseline and the project's .strictmetadata/vocabulary/terms.toml. Read-only over every project it visits
quality Show documentation quality tier and metrics for the current project
baseline Manage the content and description hash baselines that drive staleness (stale-page-description) and source-drift (description-drifted-from-source) detection during selfdoc check
baseline accept Accept a reviewed staleness or drift dead-end by advancing a page's stored content and description hash baseline to its current values. Use this only after a human has confirmed the page's content changed but its existing frontmatter description was reviewed and is still accurate. Each named page must currently be reporting a stale-page-description or description-drifted-from-source error; accepting clears that error so selfdoc check passes without rewriting an already-correct description.
layout Inspect, check and migrate the per-repository directories selfdoc owns under .strictmetadata/
layout dump Print selfdoc's layout declaration: every directory it claims, whether the directory is handwritten or generated, whether the repository commits it, the manifest.toml that grants it and what that file must hold, and the paths it replaced
layout validate Check this repository's .strictmetadata/ directory: every directory carries a manifest.toml naming a tool this machine has, every directory selfdoc claims names selfdoc, a directory selfdoc owns starts with a dot exactly when it is generated, every directory selfdoc owns holds only what its side allows, nothing inside selfdoc's committed directories starts with a dot, every uncommitted directory selfdoc owns carries the .gitignore its declaration derives, and .strictmetadata/ holds directories and go.mod only
layout migrate Move this repository off a layout before this one: every directory whose manifest.toml names selfdoc under stricttools/ (the layout before this one, whose generated directories already start with a dot) or under .stricttools/ (the layout before that, which named every directory bare) moves under .strictmetadata/, a generated one behind a dot (stricttools/docs -> .strictmetadata/docs, stricttools/.docs-state -> .strictmetadata/.docs-state, stricttools/.docs-cache -> .strictmetadata/.docs-cache, stricttools/posts -> .strictmetadata/posts, stricttools/vocabulary -> .strictmetadata/vocabulary, .stricttools/docs -> .strictmetadata/docs, .stricttools/docs-state -> .strictmetadata/.docs-state, .stricttools/docs-cache -> .strictmetadata/.docs-cache, .stricttools/posts -> .strictmetadata/posts, .stricttools/vocabulary -> .strictmetadata/vocabulary). It creates .strictmetadata/ (the manifests naming selfdoc are the grant), writes each uncommitted directory's own .gitignore, removes selfdoc's block from the previous root's .gitignore (the file and the previous root go when nothing else is left), rewrites every selfdoc.json value naming a moved path and every generated root file's header, moves a CLAUDE.md an earlier selfdoc generated at the repository root to .claude/CLAUDE.md (where selfdoc generates it now, and Claude Code reads it), writes .strictmetadata/vocabulary/terms.toml empty when the project has none, converts the manifests (.strictmetadata/.docs-state/manifest.json and .strictmetadata/.docs-state/post-manifest.json) from schema_version 1 to 2, which records the project's accepted words and rejected patterns from .strictmetadata/vocabulary/terms.toml, and commits. In every repository it also converts the .strictmetadata/.gitignore an earlier selfdoc derived into one .gitignore per uncommitted directory, and moves strictcli's files out of every .strictcli/ directory into the .strictmetadata/ beside it: schema.json to .strictmetadata/.cli-schema/schema.json, test-coverage.json to .strictmetadata/.cli-test-coverage/manifest.json, and the coverage/ shards to .strictmetadata/.cli-test-coverage/shards/, writing each directory's manifest.toml naming strictcli and the .gitignore that keeps the shards out of the repository, deleting the stub go.mod, and removing the emptied .strictcli/ and an empty coverage/; for the repository root's .strictcli/ it also deletes .rlsbl/bases/.strictcli/go.mod, drops .strictcli/go.mod from .rlsbl/managed-files.json, and removes the root .gitignore's coverage/ line. A repository already on .strictmetadata/ whose manifests are on schema_version 1 gets the manifest conversion alone, and one whose .strictmetadata/vocabulary/ carries no manifest.toml -- a repository moved from the older .selfdoc/ layout -- also gets that directory's grant and .strictmetadata/vocabulary/terms.toml empty when the project has none, and one whose generated CLAUDE.md is still at its root gets that move. Another tool's directories stay where they are, and so does a hand-written CLAUDE.md. Refuses a repository already migrated with its manifests converted, its vocabulary directory granted, its ignore files converted, no .strictcli/ left, and no generated CLAUDE.md at its root; a .strictcli/ holding a file it does not know the place of, or one git does not track; a .strictcli/ file whose new place is taken; an .strictmetadata/.gitignore holding another tool's lines; part-way through a move; holding selfdoc's directories under both previous roots; never on a previous layout; holding .claude/CLAUDE.md beside a generated root CLAUDE.md; or ignoring .claude/CLAUDE.md in git. --dry-run prints the plan and changes nothing
vocabulary Edit this project's vocabulary: the words its pages may use that the English word list does not carry, the terms they may not use (.strictmetadata/vocabulary/terms.toml), and the words proposed for acceptance awaiting review (.strictmetadata/vocabulary/review.toml). The spell check reads selfdoc's built-in baseline and these files, and nothing outside the repository
vocabulary accept Accept a word into .strictmetadata/vocabulary/terms.toml, in sorted position, with its meaning. Matching is case-insensitive, so one entry accepts every casing. Refuses a word already accepted (as a word or an alias, in the project or selfdoc's built-in baseline), a word a rejected term covers, and a word pending review, which 'selfdoc vocabulary approve' resolves instead
vocabulary reject Reject a term in .strictmetadata/vocabulary/terms.toml, in sorted position, with the reason: every page whose prose uses it then fails 'selfdoc check' (rejected-term-in-prose). Refuses a term already rejected, and one that would reject an accepted word: a word of selfdoc's built-in baseline is changed only in selfdoc itself, so a pattern covering one is refused with every covered baseline word listed and must be narrowed to the specific words meant, while a pattern covering a word the project accepts is refused with the command that removes that word
vocabulary remove Remove every entry of .strictmetadata/vocabulary/terms.toml whose word or pattern is the given one, compared case-insensitively: an accepted word, a rejected term, or both. It is also how a word both accepted and rejected is resolved: remove it, then accept or reject it again. An entry of selfdoc's built-in baseline is not the project's to remove, and is refused
vocabulary approve Approve a word pending review: move its entry from .strictmetadata/vocabulary/review.toml into the accepted words of .strictmetadata/vocabulary/terms.toml, with the proposed meaning or, with --meaning, a corrected one. Refuses a word that is not pending
vocabulary drop Drop a word pending review: delete its entry from .strictmetadata/vocabulary/review.toml without accepting it, so pages using it keep failing the spell check until the spelling is fixed. Refuses a word that is not pending
options Print selfdoc's options registry, and write this repository's entries for selfdoc's options in .strictmetadata/options/. Each lint is an option, selfdoc:, filed under docs.toml: an error lint runs at error > warn > off, a warning lint at warn > off, and the check applies each entry's current value (off: not reported; warn: reported, never blocking; error: as registered)
options registry Print selfdoc's options registry: one option per lint, selfdoc:, with the subject it is filed under, the values it ranks strongest first, its default, its scope, and what the lint checks. The TOML printed is the registry document selfdoc ships, in the shape of strictspec's built-in options-registry schema, so a tool that reads every tool's options learns selfdoc's rankings from it; with --json the same declarations are the payload. Needs no selfdoc project
options set Write one selfdoc entry into .strictmetadata/options/docs.toml, or update the entry already there for the same option, keeping every other line of the document. Creates .strictmetadata/options/ and its manifest.toml, naming strictspec as the owner, when absent. strictspec validates the result before anything is written -- every document's shape, and every selfdoc entry with this one in place -- and each refusal is its catalogued diagnostic: a value the option does not declare, a current ranked above the ideal, an entry equal to the default, an empty reason. Refuses an id outside the selfdoc namespace and a directory whose manifest names another owner
assembly Manage the unified multi-project documentation assembly and deployment
assembly init Create and initialize the assembly GitHub repository with workflow and configuration files. Creates a private GitHub repo, pushes initial files via the Contents API, creates a Cloudflare Pages project if credentials are available, and sets GitHub secrets for deployment authentication.
assembly push Dispatch a GitHub Actions workflow to rebuild this project in the documentation assembly. Detects the source repository, resolves the latest git tag as the version reference, and sends a repository dispatch event to the assembly repo with the project slug, version, and commit SHA. Before dispatching it refuses a project whose lifecycle-and-license record lets no releasable publish documentation, and a ref whose tracked files carry a hit of the confidential-term list that the repository's resolutions do not resolve, naming every hit with its file, line, and the text around it.
assembly status Show the status of recent assembly build workflow runs on GitHub. Queries the assembly repository for recent workflow runs using the GitHub CLI and displays their status, conclusion, and timing information for monitoring deployment progress.
assembly rebuild Dispatch rebuild workflows for every project registered in the assembly. Fetches the projects.json manifest from the assembly repository, then sends a separate GitHub Actions repository dispatch event for each registered project to trigger a full documentation rebuild.
assembly retire Retire a project from the unified assembly: remove its [[project]] block from the roster and, in the same commit, delete its whole site subtree, all of its manifests and its membership record, then dispatch a shared-only rebuild so the listing, feed, sitemap and search index stop naming it.
assembly redirects Generate a Cloudflare Pages _redirects file for this project that redirects standalone documentation URLs to the corresponding paths on the unified assembly site. Requires a project slug and assembly base URL as inputs, prints the redirect rules to stdout.
assembly generate-shared Generate the shared cross-project elements for the assembled documentation site. Reads per-project manifest JSON files, merges post overlays, and produces a homepage, blog index, navigation JSON, RSS feed, XML sitemap, robots.txt, a site-wide llms.txt linking to each project's own, a root 404 page and a security headers file in the site output directory. It also deletes the redirect worker a deploy made before the worker was retired left at the site root.
assembly integrate Integrate one dispatched project into the assembly repository checkout and push the result. Builds the cloned source project, replaces its subtree under site/, refreshes its manifest and membership record, regenerates the shared cross-project elements, rebuilds the search index, then commits and pushes with a re-sync retry loop so concurrent deploys converge instead of clobbering each other. A project-scoped run refuses a source checkout whose lifecycle-and-license record lets no releasable publish documentation, and every run scans the whole site tree for confidential-term hits the assembly repository's resolutions do not resolve before it commits. A full-scope deploy first checks the vocabulary the project's manifest records against every other project's manifest on the site and selfdoc's built-in baseline, and refuses before touching the checkout when one project's rejected pattern covers a word another accepts, naming both projects, the word, the pattern and the fix. This is the whole body of the generated deploy workflow.
assembly verify Assert every property a built assembly tree has to have before it is deployed: that the roster, the site subtrees and the manifests name the same projects, that each manifest's pages and posts were actually emitted, that the shared cross-project artifacts exist and parse, that every internal reference, sitemap entry, feed link and cross-project link resolves, that every page has a title and a canonical, and that no unresolved directive or per-project routing file survived. The deploy runs this itself before it pushes; this command is how you run the same assertions by hand against a checkout.
assembly republish-all Publish every project on the assembly's roster again, from local checkouts, in one pass: the one-time step that replaces every project's manifest on the site with one on manifest schema_version 2, which records each project's vocabulary. Before building anything it refuses a checkout not on the .strictmetadata/ layout or whose manifest is missing or on an older schema (naming 'selfdoc layout migrate'), checkouts that declare no single assembly.repo, slugs that are not the roster's exactly or a --home that is not the roster's home project, an assembly deploy workflow pinning a selfdoc older than this one (naming 'selfdoc assembly sync-workflow --pin-selfdoc '), and any two projects' vocabularies, or one and selfdoc's built-in baseline, that disagree about a word, listing every conflict. Then it builds every project locally against the checkouts' own manifests, the home project last, publishes each the way 'blog publish-docs' does (one assembly commit per project, which also converts the project's post overlay on the site, manifests/-posts.json, when an older selfdoc wrote it, keeping its posts and adding the vocabulary of the checkout's manifest), and sends one shared-only deploy request. A checkout whose lifecycle-and-license record lets no releasable publish documentation is refused before anything is built, and every build is scanned for confidential-term hits its repository's resolutions do not resolve before anything is published. --dry-run runs the checks and the local builds, which write only each checkout's build output, and prints what it would publish without publishing anything.
assembly preview Assemble every named local checkout into a preview tree and serve it on loopback. Builds each project with the toolchain running this command, grafts the output exactly as the deploy does -- the home project at the site root, everybody else under their slug -- writes the roster, membership record and manifests the assembly keeps, generates the shared cross-project files and the site chrome, rebuilds the search index, runs the real pre-deploy verification and prints its report, then serves the result with a working 404. Nothing leaves the machine and nothing is published: this is the look-before-you-ship step.
assembly sync-workflow Regenerate the assembly repository's deploy workflow from this project's configuration and push it. The deployed workflow is a generated artifact like any other: without this it stays frozen at whatever the template said when 'assembly init' ran. Pushes only when the content actually differs.
blog Blog posts, the authoring app, and publishing this project's documentation to the unified site
blog publish-docs Publish this project's documentation to the assembly without a release. Builds the docs locally, pushes the built site, its manifest and its membership record into the assembly repo via the Git Data API -- deleting the pages this project published before and no longer produces -- then dispatches a shared-only workflow to regenerate cross-project elements. Refuses before pushing anything when the vocabulary this project's manifest records and another project's on the assembly disagree about a word -- one's rejected pattern covering a word the other, or selfdoc's built-in baseline, accepts -- naming both projects, the word, the pattern and the fix.
blog post Manage blog posts and chronological content for the documentation site
blog post new Scaffold a new blog post markdown file with a date-prefixed filename and frontmatter template containing title, date, slug, tags, draft status, and project metadata. Creates the file in the configured posts directory and exits with an error if the file already exists.
blog post list List all discovered blog posts with date, title, slug, and draft status. Scans the configured posts directory for markdown files with frontmatter, parses their metadata, and prints a formatted summary showing each post's publication date, title, slug identifier, and whether it is marked as a draft.
blog post generate Generate a blog post markdown file from structured release metadata. Takes version, bump type, description, changelog, and registry URLs as inputs, produces a frontmatter-bearing post with title, date, tags, and body content, and updates the project manifest with the new post entry.
blog post publish Publish non-draft blog posts to the documentation assembly. Builds posts locally, pushes built HTML and manifest to the assembly repo via the Git Data API, then dispatches a shared-only workflow to regenerate cross-project elements.
blog editor Run and inspect the local authoring app for blog posts
blog editor list-repos List every repository the editor registry declares, with its kind and where it points. Reads the hand-written registry TOML, validates every entry in full, and prints one line per entry -- a local entry's working tree, or a remote entry's repository, ref and whether it declares that rendering runs against a checkout.
blog editor serve Run the local authoring app: a browser UI over the registry's repositories, with the tinymoon editor component on the left and a live preview on the right. The preview is the publish renderer over the unsaved buffer, so what you approve is byte-for-byte what publishing produces, and rendering a preview writes nothing. Saving writes the buffer into the repository's working tree. Binds 127.0.0.1 only.

Blog and multi-project assembly

Blog posts and the unified multi-project documentation assembly are part of the same binary. selfdoc blog post new|list|generate|publish manages posts, selfdoc blog editor serve runs the local authoring app, and selfdoc assembly ... initializes, pushes, rebuilds and verifies an assembly that mounts every project under its own slug.

Deploy

Cloudflare Pages

Requires the Wrangler CLI installed and authenticated.

{
  "deploy": {
    "provider": "cloudflare-pages",
    "project": "my-docs-project"
  }
}
selfdoc build && selfdoc deploy
GitHub Pages

Pushes the output directory to the gh-pages branch via force-push.

{
  "deploy": {
    "provider": "github-pages"
  }
}

Enable GitHub Pages in your repo settings (source: gh-pages branch).

Integration with rlsbl

When rlsbl detects a selfdoc.json in the project, it can trigger selfdoc build and selfdoc deploy as part of the release lifecycle via the .rlsbl/hooks/post-release.sh hook.

Documentation

Full documentation at selfdoc.smmh.dev.

License

MIT

Documentation

Overview

Command selfdoc: Static Site Generator that builds a project's documentation site directly from its source code, so the docs can never drift from the code they describe, with SEO/AEO, first-class blog, search, and cross-project linking built in.

All of that ships as one binary, which builds the site, checks it, and publishes it.

The entry point is the module root, so the binary installs with "go install github.com/stricttools/selfdoc@v0" and takes its name from the module's last path element. Every engine package lives under internal/, and the command tree -- with the language extractors every code directive is resolved through -- is registered by internal/cli.

Directories

Path Synopsis
internal
address
Package address is the single addressing authority for built pages.
Package address is the single addressing authority for built pages.
blog/assembly
Package assembly carries the assembly's operations: the deploy workflow it generates, the dispatches it sends, the build-and-graft body that deploy runs, and the two publishers that write into it without cloning it.
Package assembly carries the assembly's operations: the deploy workflow it generates, the dispatches it sends, the build-and-graft body that deploy runs, and the two publishers that write into it without cloning it.
blog/assembly/fakegh
Package fakegh is the fake gh the assembly suite puts at the front of PATH.
Package fakegh is the fake gh the assembly suite puts at the front of PATH.
blog/assembly/fakeghcmd command
Command fakeghcmd is the executable the assembly suite installs as "gh" at the front of PATH.
Command fakeghcmd is the executable the assembly suite installs as "gh" at the front of PATH.
blog/chrome
Package chrome is the assembly's one set of page-chrome assets.
Package chrome is the assembly's one set of page-chrome assets.
blog/editor
Package editor is the authoring app's local server: registry, documents, preview, stream.
Package editor is the authoring app's local server: registry, documents, preview, stream.
blog/editor/assets
Package assets decides where the editor's front-end comes from, declared rather than discovered.
Package assets decides where the editor's front-end comes from, declared rather than discovered.
blog/editor/registry
Package registry reads the authoring app's repository registry: a hand-written TOML file.
Package registry reads the authoring app's repository registry: a hand-written TOML file.
blog/listing
Package listing carries the home project's curated project listing: one declared source, two renderings.
Package listing carries the home project's curated project listing: one declared source, two renderings.
blog/posts
Package posts discovers and validates a project's blog posts.
Package posts discovers and validates a project's blog posts.
blog/preview
Package preview builds the whole assembly from local checkouts and serves it on loopback.
Package preview builds the whole assembly from local checkouts and serves it on loopback.
blog/serving
Package serving carries the static-file primitives shared by the two local servers.
Package serving carries the static-file primitives shared by the two local servers.
blog/shared
Package shared generates the elements of an assembled documentation site that belong to the site rather than to any one project.
Package shared generates the elements of an assembled documentation site that belong to the site rather than to any one project.
blog/site
Package site carries the assembly's model: what the unified documentation site declares, what each project published into it, and where a build's output lands once it is grafted in.
Package site carries the assembly's model: what the unified documentation site declares, what each project published into it, and where a build's output lands once it is grafted in.
blog/sitedirectives
Package sitedirectives carries the site-level directives: the generated parts of the home project's authored pages.
Package sitedirectives carries the site-level directives: the generated parts of the home project's authored pages.
blog/unified
Package unified builds one documentation site out of several constituent projects plus a docs-site's own cross-cutting content.
Package unified builds one documentation site out of several constituent projects plus a docs-site's own cross-cutting content.
blog/unifiedcheck
Package unifiedcheck checks every constituent project of a unified documentation site in one pass.
Package unifiedcheck checks every constituent project of a unified documentation site in one pass.
blog/verify
Package verify answers whether a built assembly tree is fit to deploy.
Package verify answers whether a built assembly tree is fit to deploy.
build
Package build is the build pipeline: it walks a project's docs templates, resolves their directives, wraps each page in its chrome, and writes a whole static site under the configured output directory.
Package build is the build pipeline: it walks a project's docs templates, resolves their directives, wraps each page in its chrome, and writes a whole static site under the configured output directory.
catalog
Package catalog is selfdoc's directive catalogue: every built-in directive name and its status.
Package catalog is selfdoc's directive catalogue: every built-in directive name and its status.
check
Package check validates a project's documentation: every directive resolves, every public symbol is covered, and every lint rule holds.
Package check validates a project's documentation: every directive resolves, every public symbol is covered, and every lint rule holds.
cli
Package cli registers selfdoc's whole command tree on one strictcli application.
Package cli registers selfdoc's whole command tree on one strictcli application.
cli/faketool
Package faketool is the fake external tool the cli suite puts at the front of PATH under whatever names a test asks for -- "gh", "npx", and so on.
Package faketool is the fake external tool the cli suite puts at the front of PATH under whatever names a test asks for -- "gh", "npx", and so on.
cli/faketoolcmd command
Command faketoolcmd is the executable the cli suite installs at the front of PATH under each external tool's name.
Command faketoolcmd is the executable the cli suite installs at the front of PATH under each external tool's name.
confidential
Package confidential is selfdoc's half of two rules: the publishing rules of the lifecycle-and-license record (.strictmetadata/lifecycle-and-license/lifecycle-and-license.toml), and the confidential-term list.
Package confidential is selfdoc's half of two rules: the publishing rules of the lifecycle-and-license record (.strictmetadata/lifecycle-and-license/lifecycle-and-license.toml), and the confidential-term list.
config
Package config loads and validates a project's selfdoc.json.
Package config loads and validates a project's selfdoc.json.
content
Package content resolves the content directives: the ones that need no language extractor.
Package content resolves the content directives: the ones that need no language extractor.
cv
Package cv holds the CV as data: one declared document, rendered as a page and as a Person.
Package cv holds the CV as data: one declared document, rendered as a page and as a Person.
deploy
Package deploy publishes a built documentation site.
Package deploy publishes a built documentation site.
directives
Package directives is selfdoc's structured-marker parser.
Package directives is selfdoc's structured-marker parser.
docs
Package docs is the shared resolution pipeline for a project's docs/ templates: it walks the docs directory, parses each page's frontmatter, and resolves every directive the page carries.
Package docs is the shared resolution pipeline for a project's docs/ templates: it walks the docs directory, parses each page's frontmatter, and resolves every directive the page carries.
e2e
Package e2e is the rendered-reality suite: the built site, in a real browser, asserted as painted.
Package e2e is the rendered-reality suite: the built site, in a real browser, asserted as painted.
effects
Package effects is the single authorized surface for effectful calls in selfdoc production code.
Package effects is the single authorized surface for effectful calls in selfdoc production code.
excludes
Package excludes is the single authority for which source paths a project's docs cover.
Package excludes is the single authority for which source paths a project's docs cover.
extractors
Package extractors defines the language-extractor protocol, the shared behavior every extractor embeds, and the registry that resolves a language name to its extractor.
Package extractors defines the language-extractor protocol, the shared behavior every extractor embeds, and the registry that resolves a language name to its extractor.
extractors/dart
Package dart resolves selfdoc's directives against Dart source.
Package dart resolves selfdoc's directives against Dart source.
extractors/golang
Package golang resolves selfdoc's directives against Go source.
Package golang resolves selfdoc's directives against Go source.
extractors/kotlin
Package kotlin resolves selfdoc's directives against Kotlin source.
Package kotlin resolves selfdoc's directives against Kotlin source.
extractors/python
Package python resolves selfdoc's directives against Python source.
Package python resolves selfdoc's directives against Python source.
extractors/sql
Package sql resolves selfdoc's directives against PostgreSQL DDL.
Package sql resolves selfdoc's directives against PostgreSQL DDL.
extractors/svelte
Package svelte reads Svelte component source for selfdoc.
Package svelte reads Svelte component source for selfdoc.
extractors/swift
Package swift resolves selfdoc's directives against Swift source.
Package swift resolves selfdoc's directives against Swift source.
extractors/typescript
Package typescript reads TypeScript and JavaScript source for selfdoc.
Package typescript reads TypeScript and JavaScript source for selfdoc.
extractors/zig
Package zig reads Zig source for selfdoc.
Package zig reads Zig source for selfdoc.
fleet
Package fleet enumerates the selfdoc projects that live beside this one.
Package fleet enumerates the selfdoc projects that live beside this one.
gen
Package gen auto-generates documentation pages from a project's structure.
Package gen auto-generates documentation pages from a project's structure.
gendata
Package gendata generates data files by running sandboxed scripts via bubblewrap (bwrap).
Package gendata generates data files by running sandboxed scripts via bubblewrap (bwrap).
gitcommit
Package gitcommit commits the files a selfdoc command generated.
Package gitcommit commits the files a selfdoc command generated.
html
Package html converts Markdown to the HTML a built page's body carries.
Package html converts Markdown to the HTML a built page's body carries.
icons
Package icons provides the language icons drawn beside a code block's language label.
Package icons provides the language icons drawn beside a code block's language label.
identity
Package identity holds the site's declared author, as the one Person its structured data names.
Package identity holds the site's declared author, as the one Person its structured data names.
js
Package js carries the browser scripts a built page ships and assembles the body bundle each page needs.
Package js carries the browser scripts a built page ships and assembles the body bundle each page needs.
layout
Package layout is selfdoc's declaration of the per-repository directories it owns, and the single authority for every path it reads or writes inside them.
Package layout is selfdoc's declaration of the per-repository directories it owns, and the single authority for every path it reads or writes inside them.
lints
Package lints owns the lint registry and the verdict rules every check entry point shares.
Package lints owns the lint registry and the verdict rules every check entry point shares.
lints/genoptions command
Command genoptions writes selfdoc's options registry, internal/lints/options.toml, from the lint registry: one option per lint.
Command genoptions writes selfdoc's options registry, internal/lints/options.toml, from the lint registry: one option per lint.
manifest
Package manifest generates and reads a project's manifest: the JSON record of what a build published -- the project's identity and version, its pages with their heading anchors, and its posts.
Package manifest generates and reads a project's manifest: the JSON record of what a build published -- the project's identity and version, its pages with their heading anchors, and its posts.
migrate
Package migrate moves a repository off a layout before this one onto the hidden .strictmetadata/ root, where a generated directory's name starts with a dot: selfdoc's directories under the visible stricttools/ root, named as they are named now, or under the hidden .stricttools/ root before it, each under its bare function name.
Package migrate moves a repository off a layout before this one onto the hidden .strictmetadata/ root, where a generated directory's name starts with a dot: selfdoc's directories under the visible stricttools/ root, named as they are named now, or under the hidden .stricttools/ root before it, each under its bare function name.
options
Package options writes selfdoc's entries into a repository's options directory, .strictmetadata/options/: the engine of `selfdoc options set`.
Package options writes selfdoc's entries into a repository's options directory, .strictmetadata/options/: the engine of `selfdoc options set`.
ownership
Package ownership decides whether a generated page's frontmatter description is machine-owned -- a placeholder selfdoc emitted and may freely overwrite -- or handwritten, and must never be overwritten.
Package ownership decides whether a generated page's frontmatter description is machine-owned -- a placeholder selfdoc emitted and may freely overwrite -- or handwritten, and must never be overwritten.
page
Package page builds the chrome a converted Markdown body is wrapped in.
Package page builds the chrome a converted Markdown body is wrapped in.
payloadschemas
Package payloadschemas declares the JSON Schemas of selfdoc's machine-mode payloads.
Package payloadschemas declares the JSON Schemas of selfdoc's machine-mode payloads.
prose
Package prose holds the shared unit-pickers that extract complete linguistic units from text.
Package prose holds the shared unit-pickers that extract complete linguistic units from text.
quality
Package quality scores a project's documentation: a maturity tier (0-5) and a content grade (A-F).
Package quality scores a project's documentation: a maturity tier (0-5) and a content grade (A-F).
render
Package render renders a page from content held in memory, writing nothing.
Package render renders a page from content held in memory, writing nothing.
resolution
Package resolution answers whether every reference a build emitted resolves to a file it wrote.
Package resolution answers whether every reference a build emitted resolves to a file it wrote.
resolver
Package resolver dispatches one directive to whatever can answer it.
Package resolver dispatches one directive to whatever can answer it.
revisions
Package revisions tracks post revisions in a sidecar revisions.json.
Package revisions tracks post revisions in a sidecar revisions.json.
robots
Package robots holds the crawler policy, declared once for every robots.txt this repository writes.
Package robots holds the crawler policy, declared once for every robots.txt this repository writes.
scripts
Package scripts declares selfdoc's hand-run maintenance scripts and renders the commands that fetch and run one.
Package scripts declares selfdoc's hand-run maintenance scripts and renders the commands that fetch and run one.
spellcorpus
Package spellcorpus is the corpus-wide spelling run: the same engine, every sibling project.
Package spellcorpus is the corpus-wide spelling run: the same engine, every sibling project.
spelling
Package spelling is the spelling engine: one word checker serving every surface that needs one.
Package spelling is the spelling engine: one word checker serving every surface that needs one.
staleness
Package staleness detects descriptions that no longer describe what they sit on, by hashing what a description is about and comparing that hash against the one recorded the last time the description was written.
Package staleness detects descriptions that no longer describe what they sit on, by hashing what a description is about and comparing that hash against the one recorded the last time the description was written.
strictclisupport
Package strictclisupport is first-class support for strictcli-based projects.
Package strictclisupport is first-class support for strictcli-based projects.
tables
Package tables renders data as Markdown tables with per-column alignment, optional pretty-printing, and pipe escaping that leaves inline code alone.
Package tables renders data as Markdown tables with per-column alignment, optional pretty-printing, and pipe escaping that leaves inline code alone.
testproject
Package testproject builds the fixture projects the engine's tests run against.
Package testproject builds the fixture projects the engine's tests run against.
themes
Package themes is the theme registry: the stylesheets a built site can be painted with, and the metadata each one carries.
Package themes is the theme registry: the stylesheets a built site can be painted with, and the metadata each one carries.
tokenizer
Package tokenizer is a standalone Markdown block tokenizer.
Package tokenizer is a standalone Markdown block tokenizer.
urls
Package urls builds absolute URLs from relative paths, decoupling URL generation from a hardcoded base_url and supporting locale-prefixed and versioned paths.
Package urls builds absolute URLs from relative paths, decoupling URL generation from a hardcoded base_url and supporting locale-prefixed and versioned paths.
util
Package util holds the small shared helpers the rest of selfdoc builds on: frontmatter parsing, project manifest and version detection, HTML escaping, path joining, date formatting, title casing, and the Python-compatible string, number and JSON spellings the emitted documents are pinned to.
Package util holds the small shared helpers the rest of selfdoc builds on: frontmatter parsing, project manifest and version detection, HTML escaping, path joining, date formatting, title casing, and the Python-compatible string, number and JSON spellings the emitted documents are pinned to.
vocabulary
Package vocabulary is a project's word lists: the words its pages may use that the English word list does not carry, the terms its pages may not use, and the words proposed for acceptance that nobody has reviewed yet.
Package vocabulary is a project's word lists: the words its pages may use that the English word list does not carry, the terms its pages may not use, and the words proposed for acceptance that nobody has reviewed yet.
vocabulary/reviewschema
Code generated by strictspec.
Code generated by strictspec.
vocabulary/termsschema
Code generated by strictspec.
Code generated by strictspec.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL