On this page
#Changelog
#0.42.1
The assembly refreshes every page's sibling-projects block from the current roster on each deploy, so retiring or adding a project no longer leaves stale links that refuse every following deploy.
#Fixes
- A retired or newly added project no longer leaves every other project's page listing the wrong tools. The "More tools from this site" section each assembled page ends with was rendered once, when that project last deployed, and nothing refreshed it afterwards: a project joining the site reached nobody else's pages, and retiring one left its address linked from every other project's -- an address the site no longer serves, which made every following deploy fail verification. Every deploy now regenerates the section on every page in the tree.
#0.42.0
Every selfdoc-owned directory lives under .stricttools/ with a manifest.toml per directory, moved by a dry-run-capable script; titles compose written values only; topology.projects, legacy_blog_host and the Pages worker are gone; the lint table renders from the registry; check resolves generated pages through both docs roots.
#Breaking
- Breaking. Every directory selfdoc owns in a repository moved under one hidden directory,
.stricttools/: the handwritten pages at.stricttools/docs/, the generated pages and the manifests, hash baselines and revisions at.stricttools/docs-state/, the build output and version cache at.stricttools/docs-cache/, the posts at.stricttools/posts/, and the vocabulary at.stricttools/vocabulary/. A repository still carrying.selfdoc/or declaring a docs, output or posts path outside the layout is refused by every command, withscripts/move-to-stricttools-layout.pynamed. Writing into any of those directories needs that directory's ownmanifest.toml-- one line,owner = "selfdoc"-- which selfdoc never writes itself and refuses without, printing the file and the line to create. - Page titles are written text. A page's
<title>is now composed of written values only -- the page's own title, the project's name, and the name of the assembled site -- joined with-, with an empty or repeated name left out so no title rendersX - X. A project's index page names no project, so a standalone build's front page carries its written title alone; it no longer draws on the projectdescription, and the SEO016 lint that asked that description to carry athatclause is gone.og:titleandtwitter:titlecarry the same string the title element does. - **
topology.projectsis refused.** A cross-project link now resolves to<topology.docs_base>/<slug>/for any slug, so the hand-typed slug-to-URL map is gone from the schema and from the URL builder. Aselfdoc.jsonstill carryingtopology.projectsis refused by name, saying what replaced it. - No redirect worker is emitted. The assembly no longer generates a Cloudflare Pages
_worker.js: host redirects belong to the DNS zone and a retired path shape answers with the site's 404.--legacy-blog-hostis removed fromassembly init,generate-shared,integrateandpreview, and aselfdoc.jsoncarryingtopology.legacy_blog_hostis refused by name. A tree still holding a worker from an earlier deploy is refused byassembly verifyand deleted by the next integration.
#Features
- New commands.
selfdoc layout dumpprints selfdoc's layout declaration as JSON -- every directory it claims under.stricttools/, whether the directory is handwritten or generated, whether the repository commits it, themanifest.tomlthat grants it and the exact line that file must hold, and the paths it replaced -- andselfdoc layout validatechecks one repository against it: every directory carries a manifest naming a tool the machine has, every directory selfdoc claims names selfdoc, each holds only what its side allows, nothing inside starts with a dot except the derived.gitignore, and that file carries what the commitment declarations render. - New script.
scripts/move-to-stricttools-layout.pymoves one repository onto the.stricttools/layout: dry-run capable, it moves every tracked file withsafegit mvas one commit, rewrites the paths the moved content names as a second commit, and refuses to finish unless the build that follows publishes the identical URL set. - New page. The documentation now carries a page on the
.stricttools/layout: the directories selfdoc claims with their sides and commitments, the per-directorymanifest.tomlthat grants each one, the derived ignore file, the two docs roots and the addresses they share,selfdoc layout dumpandselfdoc layout validate, and the move script. - Ownership is a manifest inside each directory. A directory under
.stricttools/declares its owner in its ownmanifest.toml-- one line,owner = "<tool>", validated against a schema so an unknown key is refused -- and that file is the permission to write into it. selfdoc never writes one, refuses a directory that carries none with the exact file and line to create, and treats a manifest naming another tool as that tool's.selfdoc layout validateholds every directory under the root to a manifest whose owner is a tool the machine has, andscripts/move-to-stricttools-layout.pywrites the manifests of the directories it moves content into as part of the move commit.
#Fixes
- The project description says what selfdoc is.
selfdoc.json, the README's opening paragraph and the command's doc comment now carry one written line -- a static site generator that builds a project's documentation site directly from its source code, with SEO/AEO, a first-class blog, search and cross-project linking -- instead of three paraphrases of it. - **
selfdoc checkno longer reports generated CLI pages missing.** A strictcli project's CLI reference pages are generated into selfdoc's own generated docs root, but the check looked for them only in the handwritten root, so every command's page was reported missing with CLI001. The check now resolves pages across both roots, the way the build does. - **New directive
list-lints, and the check guide's lint table is now generated.** The page listed every lint code, its severity and what it checks by hand, so a code added or changed in severity left the documentation stating the old thing. The table is now rendered from selfdoc's own lint registry, and any project's page can render it with:-: list-lints.
#0.41.0
Frontmatter is TOML between +++ fences with a declared key registry, converted by a dry-run-capable script; social titles match the document title; the title-length check measures the rendered title; selfdoc check reports broken links again; assembly verification walks links from the arrival pages instead of demanding a complete front page or listing.
#Breaking
- Frontmatter is TOML. Every page and post now carries TOML frontmatter between
+++fences, validated against a declared key registry: an undeclared key is refused rather than ignored, values carry their declared types (dates as TOML local dates, tags as arrays, switches as booleans), and a post's required keys come from the schema. A---block is refused by name, with the converter that rewrites it.ordercollapses intonav_order, andprojectis refused.
#Features
- Frontmatter converter.
scripts/convert-frontmatter-to-toml.pyrewrites a project's pages and posts from the retired---frontmatter block to the TOML block between+++fences. It is dry-run capable, asserts how many files it changes, and refuses -- naming the file and the line -- any value it cannot spell with certainty. - Frontmatter guide. A new documentation page carries the whole frontmatter key registry -- every key, its type, whether a post must carry it, and what it declares -- alongside the fenced block, the reader-supplied keys, and how to convert a retired block.
- The frontmatter converter can convert generated pages too.
--include-generatedconverts pages marked generated (lifting and restoring their write protection), which a project needs once before the firstselfdoc genthat writes TOML, because gen keeps a generated page's handwritten description by reading the page.
#Fixes
- Assembly verification no longer refuses a site whose curated front page or listing omits a project. The reachability check now fails only when neither the site root nor the project listing links a project; the sibling block every assembled page carries makes the root name them all.
#0.40.0
Pages describe their project to search and answer engines: index titles name what the project is, social titles match, the assembled site links every project from every page and its listing pages carry descriptions and structured data, robots.txt allows Claude-SearchBot, and the crawler list, description lints, Go synopsis extraction and stale-build link reporting are corrected.
#Features
- **robots.txt now allows
Claude-SearchBot.** Anthropic's indexing crawler for Claude's search is named explicitly alongside the other AI crawlers, so generated sites are indexable by it. - The assembled site's listing pages are now legible to search engines.
/projects/and/blog/carry a meta description andCollectionPagestructured data with breadcrumbs, and the 404 page asks not to be indexed. - Every page on the assembled site now links the site's other projects. A "More tools from this site" section at the end of each page names each one with its one-line description; a project deployed on its own emits nothing.
- Assembly verification now refuses a site where a project is unreachable. A deploy fails if the site root or the
/projects/listing does not link a declared project's index page.
#Fixes
- A project's index page title now says what the project is. A page titled with the project name rendered
name - name, which no search or answer engine can use. It now renders the project name followed by the thing thedescriptionin selfdoc.json names (the part before itsthatclause), trimmed at a word boundary to at most 70 characters; a description with no such clause leaves the project name alone, andselfdoc checkreports the new SEO016 warning. - Meta description warnings now use a 110-160 character band.
selfdoc checkwarned about any description under 120 characters (SEO009) or over 155 (SEO010); the band is now 110-160, which matches what a search result actually renders. Nothing truncates a description, so what you write is what the page publishes. - **A generated file's
DO NOT EDITbanner no longer becomes a Go package's description.**selfdoc genread any comment above apackageclause, including one separated from it by a blank line, so a package containing a generated file got a page described as "Code generated by ...". Only a comment adjacent to the package clause counts now, which is the rulego docapplies. - **The site-wide
llms.txtmarks a project with no description.** An entry whose manifest states none now reads(no description)instead of stopping after the link, so the gap is visible. - **
selfdoc checkno longer reports links from an out-of-date build.** After a source doc comment changed, check kept reporting the link the previously built page carried (LINK001) untilselfdoc buildwas re-run, an error no source edit could clear. A reference inside a page's content region that the page's current source no longer carries is now skipped. - The sibling-projects section is styled. The "More tools from this site" block every assembled page ends with now renders as a compact grid of project links with muted one-line descriptions, separated from the article by a border, instead of a bare heading over a bulleted list.
- The llms.txt guide lists the crawlers your build actually allows. The "AI Crawler Access" section now renders the user agents straight from the crawler policy
robots.txtis generated from, via a newlist-crawlersdirective, instead of a hand-written copy that could fall out of step with the file. - The project describes itself with a defining first sentence, and its docs base is the unified-site address. The README opened with a fragment and the declared docs base was the retired per-project host.
- Social titles no longer repeat the project name. A page titled with the project name -- the home page -- published
og:titleandtwitter:titleas<name> - <name>; both now carry the same title the page's<title>element shows. - **
selfdoc checkreports a broken link again.** The stale-build filter compared a built page's href (../missing/) against the page's Markdown ([Guide](missing.md)) -- two spellings that never match -- so LINK001 was suppressed on links the source really does write. The filter now compares against the hrefs the current source would emit. - The title-length check now measures the title a page really renders, and the cap is 60 everywhere. SEO004 modelled every title as
<page title> - <project name>, so on the home page -- whose title is the project name -- it reported a length no page carried, and no edit could clear it. It now measures the rendered document title, and the derived home-page title is cut to 60 characters rather than 70, matching the check's own threshold.
#0.39.7
Site assembly repairs absolute links to the site blog across the whole assembled tree, so deploys pass verification again.
#Fixes
- **
selfdoc baseline acceptworks in the unified site's home project.** It registered none of the site-level directives (projects-cards,blog-highlights) thatcheckresolves, so it refused every home page as carrying an unknown directive. - Site assembly repairs absolute links to the site blog on every page. Pages an older toolchain wrote into the assembled tree addressed the blog as
https://<site>/blog/..., and the assembled-tree verification refuses that on every deploy, because a clicked link must stay inside whatever mount the site is served from.assembly integratenow rewrites such links document-relatively across the whole tree, so deploys pass verification again without republishing every project.
#0.39.6
A home project's check stops reporting the assembled site's links
Context
The previous release let a home project's site-level directives resolve, which made its check reach the links those regions render -- links into other projects' subtrees that only the assembled site holds. The resolution check now takes the elements a build does not answer for, so a project exempts its regions and the assembly, where those links must resolve, exempts nothing.
#Fixes
- A home project's own check stops reporting the assembled site's links. The links a site-level directive's region renders address other projects' subtrees and the site-level blog, which the project carrying the region never writes;
selfdoc checkreported every one of them as unresolvable. They are left to the assembly's own pass over the whole tree, which is where they resolve.
#0.39.5
Home projects publish and unversioned projects dispatch
Context
Both defects were inherited from the Python version and only surface on the unified site's home project: the publish and the check never read the assembly's roster, so they never knew the project they were looking at was home, and a project with no public version had no version string to dispatch under.
#Fixes
- The unified site's home project publishes again.
selfdoc blog publish-docsandselfdoc checkbuild a home project with its site-level directives (projects-cards,blog-highlights) resolved against the assembly's manifests, instead of refusing the directive as unknown. - **
selfdoc assembly pushdispatches an unversioned project.** A project declaringunversioned: trueis dispatched at its current branch and recorded as unversioned; before, the push refused it for having noversions.
#0.39.4
Install with go install github.com/smm-h/selfdoc@v0
Context
The binary's package main moved from cmd/selfdoc to the module root, so the install line loses its trailing path segment and the binary still takes its name from the module's last element. Nothing under internal/ can import a root package main, so the version is handed down from the entry point instead: internal/cli takes it on Options.Version, and the selfdoc toolchain pin a generated assembly deploy workflow installs is now a required input rather than a value the assembly package reads for itself. This release also carries the already-committed test-speed work, which builds the suites' fake external tools once instead of re-running the race binary for every invocation.
#Breaking
- **Install with
go install github.com/smm-h/selfdoc@v0.** The entry point moved to the module root, so the former.../cmd/selfdocpath no longer exists.
#0.39.3
The assembly commands name the branch they read and write
Context
Four call sites passed an empty branch into the GitHub Git Data API helpers, which renders "/repos/
#Fixes
- The assembly commands reach the assembly again.
assembly sync-workflow,assembly retireandpost publishnamed no branch when they read or wrote the assembly repository, so each failed withget HEAD ref: gh: Not Found (HTTP 404)-- a missing argument reported as a missing repository. This is what stopped 0.39.2's post-release hook from pinning the deploy workflow to the version it had just released.
#0.39.2
Latest strictcli, strictspec and go-toml-edit; the deploy workflow pins the released version
Context
go-toml-edit was held at 0.3.0 only because the published strictcli/go and strictspec/go did not compile against 0.4.0; both now do, so all three move together. internal/util's TOML decode no longer walks the AST to work around the 0.3.0 decoder dropping an array of tables nested inside another one -- the 0.4.0 read-layer answers that shape correctly, and only document key order, which is a question about how the file was written, still comes off the AST.
The post-release hook's missing pin was found on the 0.39.1 release: it wrote an assembly deploy workflow pinned to 0.39.0 because the selfdoc on PATH at hook time predated the version bump.
#Fixes
- TOML parse errors are reworded. Configuration and data documents (
docs/cv.toml,docs/projects.toml, the assembly roster) are decoded by go-toml-edit 0.4.0, which phrases a syntax error differently:1:18: expected a value, got end of inputin place ofline 1, column 18: expected value, got EOF. The<file> is not valid TOML:sentence around it is unchanged. - The assembly deploy workflow is pinned to the version just released. The post-release hook passed no pin, so the regenerated workflow named the version of the binary on PATH -- built before the release's own version bump -- and could name a version the Go module proxy did not serve.
#0.39.1
The Go module is fetchable again
Context
0.39.0 is tagged and has a GitHub Release, but the Go module proxy refuses to build its module zip because one source file was named aux.go, and aux is reserved as a path component on Windows -- so go install github.com/smm-h/selfdoc/cmd/selfdoc@v0 failed for that version. A retraction cannot make a published version fetchable, so this patch renames the file to auxiliary.go. A guard test walks every path in the module and fails on any component whose name is one of the names Go refuses (CON, PRN, AUX, NUL, COM1-COM9, LPT1-LPT9), so the module can never again be published unfetchable.
#Fixes
- **
go install github.com/smm-h/selfdoc/cmd/selfdoc@v0works again.** 0.39.0 could not be fetched from the Go module proxy because one source file carried a Windows-reserved name; the file is renamed and a test refuses any such path.
#0.39.0
Rewritten in Go as one binary; the selfblog command folds into selfdoc
Context
selfdoc, selfdoc-core and selfblog were three Python packages that had to be installed and upgraded together, and the two command-line tools refused each other's projects even though they built the same pages out of the same engine. This release replaces all three with one Go module, github.com/smm-h/selfdoc, and one binary, selfdoc, that carries the engine, the blog, the assembly and the editor. Every line of Python in the repository is deleted, and the repository itself stops being a single-member workspace and becomes an ordinary standalone rlsbl project. The only interpreter the binary ever starts is python3, and only for a project's own custom directives: Python source is parsed in process now, so documenting a Python project needs no interpreter on the machine.
The port was verified by building every consumer in the fleet twice -- once with the released Python tools, once with the new binary -- and diffing the output trees. The output is byte-identical everywhere except where a library was deliberately swapped: chroma replaces pygments for syntax highlighting and emits its own token class names, and Open Graph cards are the basic generated PNG now that the rich pre-drawn path is gone.
Distribution is the Go binary alone. go install github.com/smm-h/selfdoc/cmd/selfdoc@v0 installs it directly, and every release publishes prebuilt archives for Linux, macOS and Windows on amd64 and arm64 for a machine with no Go toolchain. The npm and PyPI launcher packages this repository used to publish are gone and stop receiving updates.
What consumers have to change: a post-release hook that runs selfblog assembly push becomes selfdoc assembly push, and every other selfblog X invocation becomes selfdoc X. Writing commands sit under one blog group, so selfdoc post X is selfdoc blog post X, selfdoc editor X is selfdoc blog editor X, and selfdoc docs publish is selfdoc blog publish-docs. Custom CSS written against pygments highlight classes must be re-pointed at chroma's. An assembly's deploy workflow is regenerated with selfdoc assembly sync-workflow, which now installs the Go binary and pins one version.
#Breaking
- [root] **selfdoc is one Go binary now, and the
selfblogcommand is gone.** Install it withgo install github.com/smm-h/selfdoc/cmd/selfdoc@v0, or download the archive for your platform from the GitHub Release; everyselfblog Xinvocation is nowselfdoc X, so a post-release hook runningselfblog assembly pushmust be changed toselfdoc assembly push. - [root] **One
buildbuilds every kind of site and onecheckchecks every kind of project.**build --target site|posts|unified|homeselects what to build, andcheckvalidates whichever kind of project it is pointed at, so the refusals each of the two former CLIs raised against the other kind of project are gone. - [root] **Custom directives run out of process through
python3.** Theresolve(attrs, config, body)contract is unchanged, so existing scripts keep working, but a script that fails or that defines noresolveis now a hard error that stops the build instead of an inline note left in the page. - [root] Syntax highlighting comes from chroma, whose token class names differ from pygments. Highlighted code carries chroma class names, so custom CSS written against the old pygments classes must be re-pointed; the stylesheet selfdoc generates follows the new names by itself.
- [root] Open Graph cards are the basic generated PNG only. Every page still gets an OG image, drawn by the built-in generator; the richer pre-drawn card path is gone.
- [root] The assembly deploy workflow installs the Go binary and pins one version. The generated workflow runs
go installinstead of installing two Python packages, andassembly sync-workflowtakes--pin-selfdocand--pin-pagefindto choose the versions it writes; regenerate the workflow withselfdoc assembly sync-workflowafter upgrading. - [root] **Home-project pages wrap site-level directive output in
<selfdoc-region>instead of<selfblog-region>.** The unified assembly refreshes only regions carrying the new tag, so redeploy the home project once after upgrading (selfdoc blog publish-docsfrom the home checkout, or its next release); until then its project cards and newest-post block stay as last published. - [root] **The editor reads its repository list from
~/Projects/ark/selfdoc-registry.tomlby default.** Move an existingselfblog-registry.tomlto the new name, or pass--registrywith the old path. - **The blog commands moved under one
bloggroup.**selfdoc post Xis nowselfdoc blog post X,selfdoc editor Xis nowselfdoc blog editor X, andselfdoc docs publishis nowselfdoc blog publish-docs; change any hook, script or alias that spells an old path. - Go is the only distribution channel. The
selfdocumentingnpm and PyPI packages are gone and stop receiving updates: install withgo install github.com/smm-h/selfdoc/cmd/selfdoc@v0, or, on a machine with no Go toolchain, download the archive for your platform from the GitHub Release and putselfdocon yourPATH.
#Features
- [root] **
python3is needed only for custom directives.** The build, the blog, the assembly and every language extractor -- Python included -- run inside the one binary, so a project that declares no custom directive script needs no interpreter at all. - [root] The Python-only dependencies are gone. Installing selfdoc no longer pulls a Python dependency set along with it: the themes, browser scripts, word list and document validators are compiled into the binary.
- [root] Documenting Python no longer needs a Python interpreter. selfdoc reads Python source itself, so
gen,buildandcheckwork on a machine with nopython3installed -- reference pages, signatures and field tables render exactly as before.
#Fixes
- [root] A Go package reads its documentation from a fixed file. Package documentation no longer depends on which file the source walk reached first, so rebuilding a Go project's reference pages gives the same package text every time.
- [root] The check report keeps a directive's attributes in the order the template wrote them. A directive line in
selfdoc checkoutput reads back the way it was written instead of in a reordered form.
#0.38.1
Fixes the CLI reference pages selfdoc generates failing selfdoc's own spell check: the entity indent on a scoped flag row and the 'Clearable' heading of a sparse update.
Context
selfdoc renders a strictcli app's schema into reference pages and then spell-checks those pages, so every fixed word and every markup construct the renderer emits is judged by SPELL001 in the consumer's project. Two of them were not words the engine knew: the ' ' entity that indents a scoped flag's row under the member it belongs to, and the '- Clearable:' heading a sparse update publishes its unset tokens under. The first project to render a member-spelled selector and an update command with a nullable property got 37 error-severity lints for text it did not write and could not edit.
Both are fixed in the engine (selfdoc-core 0.10.1), each at the layer it belongs to: entity references are markup and are blanked like tags before a line is scanned, and the fixed vocabulary selfdoc's own renderers emit is carried by the engine and accepted everywhere, because the accept list belongs to one machine while a rendered page ships to all of them. A rendered-page test renders a schema exercising every construct the renderer has prose for, so a new fixed word cannot reach a consumer unaccepted.
#Fixes
- [selfdoc] **
selfdoc checkno longer fails on the CLI reference pages selfdoc itself generates.** A command declaring a member-spelled selector rendered its scoped flags behind indents, and a sparse update rendered aClearableheading; the spell checker read both as English, so a project that regenerated its CLI reference got error-severity lints for text it did not write and could not edit.
#0.38.0
CLI reference pages read strictcli schema version 2, and selfdoc's own CLI declares its presence
Context
strictcli 0.41.0 turned a set of previously-inferred facts into mandatory declarations, every one of them a registration-time hard error, and it publishes the result at schema_version 2. Both halves reach selfdoc.
Its own CLI could not start under 0.41 until every flag and argument declared its presence, which is what makes selfdoc runnable at all for a fleet already on the new floor -- selfdoc gen and selfdoc check are steps in every project's release, so nothing downstream could release until this shipped.
Its reader of other projects' schemas was worse than broken: it read a type key v2 deleted and a required key an earlier round deleted, and both reads had a fallback, so every page claimed every flag was a string and every positional argument required, silently and successfully. The reader is now v2-only and refuses any other version rather than guessing, and it renders the constructs v2 adds -- presence, choices records, selectors and their scopes, constraints, update declarations -- instead of flattening them away.
#Breaking
- [selfdoc] **Breaking: machine output is now the strictcli envelope, entered with
--json.**selfdoc check,selfdoc spell-corpusandselfdoc qualityno longer take--format text|json; each writes exactly one document to stdout under--json— the framework envelope, whosepayloadmember is the report. Each command declares its payload's JSON Schema, which the framework validates before writing and--dump-schemapublishes, soschemas/check-output.schema.jsonis gone: the declaration is the artifact to generate against. - [selfdoc] Generated CLI reference pages now require strictcli schema version 2. selfdoc reads
.strictcli/schema.jsonatschema_version: 2only, and refuses any other version with a message naming the regeneration command. v2 is not a superset of v1 -- it deletes keys the old reader read -- so there is no dual path: regenerate the schema with a strictcli 0.41.0 or newer (<app> --dump-schema).
#Features
- [selfdoc] A guide for the new blog-post editor.
docs/editor-guide.mddocuments the authoring app: the registry file's format and every shape it refuses, the two commands and their flags, where the editor's front-end assets come from, and what the preview is -- the publish render of an unsaved buffer, and why that means it writes nothing. - [selfdoc] The editor guide covers the assistance lanes, cross-project links and publishing. New sections describe the spelling and lint marks and where their engines come from, the
](completion and the addresses it inserts, and the publish consent dialog -- including that it publishes the whole repository and that consent is never remembered. - [selfdoc] Glossary guide covers every declaration form. The guide now documents Markdown definition lists alongside
<dfn>tags and thelist-glossarydirective, states that prose is never read as a declaration, and describes theterm-<slug>anchors and the link from a definition site back to its glossary entry. - [selfdoc] The blog guide covers the local assembly preview. A new section documents the command surface, the pipeline table naming which production function runs at each step, that verification reports rather than blocks, what the server does about directory addresses and 404s, and why the output directory is refused inside a checkout.
- [selfdoc]
selfdoc build --theme <name>renders the site under a named theme for that build only, overriding whatselfdoc.jsondeclares and writing nothing back. An unknown name is refused against the theme registry. - [selfdoc] The theming guide describes the tinymoon theme as it now is. It said the theme carries its own base64 faces and defines its own palette; it now covers the framework composition, the variable bridge, the CSS-only three-state resolution and the
css/+fonts/layout. - [selfdoc] Documented which pages print their frontmatter description above the H1. The getting-started guide now states the rule: the home page and posts drop the summary block, every other page keeps it.
- [selfdoc] CLI reference pages render the constructs strictcli schema v2 publishes. A value flag's
choicesappear with their per-entry help. A selector renders as the construct it is: a member-spelled one's own name is never printed as a flag, because it is never typed -- each member becomes its own row, with the flags that exist only while it is elected indented beneath it, to any depth, each naming its scope. Constraints render as the rule each one states. An update command states its resource, its write mode, what an unsupplied property means, that at least one property is required, and the--unset-<property>spellings a nullable property mints. A negatable bool shows--no-<flag>beside its own name, and flag sets and the app's config and env-prefix declarations reach the page too. - [selfdoc] selfdoc requires strictcli 0.41.0 or newer, and its own CLI is declared on the new terms. Every flag and positional argument declares its presence, so
--helpstates which of required, optional or a declared default each one is. What you type is unchanged: an omitted--auto-commitstill commits, an omitted--portstill binds 8000, and each such flag now names its fallback in its own help text.
#Fixes
- [selfdoc] **
selfdoc initno longer invents a version.** A codeless project gets"unversioned": trueinstead of a placeholder0.1.0that was rendered as a topbar badge and offered as a search filter; a project with code whose manifest states no version is refused rather than given a made-up number. - [selfdoc] **
selfdoc checkreports a visible link that names the site's own base.** An<a href>written absolutely against the configured base resolves on the deployed host and nowhere else, so it works on a preview by leaving the preview -- the file-existence half ofLINK001could never see it, because the page it names really is there. It is now reported beside the origin-absolute rule. A project the site mounts under a slug passes that mount to the check, so its subtree is not asked to resolve references into the site around it. - [selfdoc] **Fixed:
selfdoc qualityno longer reports a silent zero when the source-line scan fails.** It read dirstat's output inside a bareexceptthat returned zero LOC on any failure, which graded every project an A on an infinite documentation ratio. A scan that cannot be run, times out, or answers in an unreadable shape is now a hard error naming the cause — submodule subtractions included. - [selfdoc] CLI reference pages stop calling every flag a string and every positional argument required. The reader was looking up a
typekey schema v2 deleted and falling back tostr, and arequiredkey an earlier strictcli deleted and falling back totrue, so a page could label an int flagstrand an optional argument required without anything failing. Both now come from the declaration:value_schemagives the type word (str,bool,int,float,list[T],dict[str, T]) andpresencegives a Presence column stating exactly one of required, optional or a default value.
#0.37.2
Dead links between a project's pages and its posts on the unified site
Context
The live deploy refused the assembled tree: a pgdesign documentation page linked its own post at an address inside the project's subtree, and the post linked back at an address at the site root. Both named files no build writes. The engine floor moves to selfdoc-core 0.9.2, which writes each of those references against the root that serves the target.
#Fixes
- [selfdoc] Documentation built for the unified site no longer carries dead links between a project's pages and its posts. The engine floor moves to selfdoc-core 0.9.2, which addresses a reference crossing the site mount against the root that actually serves it.
#0.37.1
Repairs the unified-site deploy: mounted projects address their posts where the site serves them and stop shipping an unreachable 404, generated reference pages mark extracted code as code and renest doc-comment headings, and the wheel declares its dependency floors.
#Fixes
- [selfdoc] Declared dependency floors. The published wheel now requires
selfdoc-core>=0.9.0andstrictcli>=0.36.0. The 0.37.0 wheel declared a bareselfdoc-core, so a fresh install could resolve a pre-0.9.0 engine that no longer carries the symbols selfdoc imports, and fail at startup. - [selfdoc] A mounted project's posts are addressed where the site serves them. A project declaring
topology.docs_baseandtopology.slugis served under its slug, but the assembly serves every project's posts from one sharedblog/at the site root. The build addressed them under the slug anyway, so a post's canonical, its sitemap and feed entries, and every project-page link to it named an address the site does not serve. A project with no mount keeps a self-contained blog, and a posts-only build now renders a post byte-identically to the full build. - [selfdoc] A mounted build writes no 404 page. A hosting provider answers an unmatched address from the root of what it serves, so a
404.htmlburied under a project's slug is never reached. Every mounted project shipped one anyway, and it was an unreachable page that still had to satisfy every assertion made about a page -- it had no canonical, and that blocked the whole site's deploy. A standalone project, whose output root is the served root, still writes its own. - [selfdoc] An acronym-prefixed identifier is no longer reported as a misspelling. SPELL001 skips a name with an internal case change, but it recognized only a capital following a lowercase letter -- so
TextResponsepassed whileJSONResponseandHTMLResponsewere flagged. A run of capitals meeting a capitalized word is now recognized too. - [selfdoc] A code span written with two or more backticks renders as one span. The RST-style
xform -- also the form CommonMark requires when the code itself contains a backtick -- was split on single backticks and came out as two empty<code>pairs with the text loose between them. The renderer now shares the pattern the directive scanner and the spell mask already used. - [selfdoc] Generated reference pages say which of their text came from code, and a doc comment's headings no longer break the page. Symbol names, module paths, Go flag types and defaults, and SQL object and type labels were emitted as plain prose, so the spell checker read them as English and reported identifiers like
JSONResponseandreturncodeas misspellings an author had no way to fix; they are code spans now, in headings and inline alike. Separately, Go's doc convention writes section titles as# Usageand any language's doc comment can carry markdown headings -- emitted verbatim onto a reference page they became a second H1, which the build refuses, so one such package was enough to stop a project's docs build. Every extractor now renests extracted headings beneath the heading they were emitted under. Docstring prose is unchanged: an identifier written into a sentence is still the author's to backtick.
#0.37.0
check lints posts, spell-checks prose and resolves every link the build emits; init requires the base URL and the author; lint suppression is validated against the registry and reaches warnings only.
Context
One program across all three packages: the site's addressing, the assembly that serves it, and the authoring checks that keep it honest.
Addressing changed first. A page's current version now lives at a stable, unversioned address and superseded versions are archives under v/, declared as such in canonicals, sitemaps and feeds. Posts moved to one site-level blog/ namespace shared by every project. Every link, canonical and sitemap entry the build emits is now resolved against the files it actually wrote (LINK001), and the assembly worker carries a redirect map generated from the manifests, so the retired address schemes still land somewhere real in a single hop.
The assembly stopped being CI shell. The deploy body is a command (assembly integrate), the workflow that calls it is a thin generated artifact whose tool pins are rewritten and checked against PyPI on every sync, and the assembled tree is verified before anything is pushed. Membership is a declared roster with a required home project served at the site root, documentation can be published without a release, and a project can be retired.
The authoring checks grew a spine: every emittable lint code is declared in a registry with its severity, suppression is validated against that registry and can no longer silence an error, posts are linted like pages, and prose is spell-checked -- including the prose a directive renders out of an authored document.
Search switched to Pagefind and the builtin engine was deleted, so search_engine is required and names the one valid value. The author block is required too: one Person is built from it rather than an Organization invented from a directory name.
Breaking changes are minor bumps under the pre-1.0 convention; each is a breaking-type entry in the changelog.
#Breaking
- [selfdoc] **
selfdoc initrequires--base-url.** The site's own address cannot be inferred and every canonical link, sitemap entry and feed URL is built from it, so init now takes it explicitly. - [selfdoc] Lint severities come from the registry.
selfdoc.check.LintResultis the registry's type: it takes noseverityargument and refuses a code that is not declared in selfdoc-core's lint registry. - [selfdoc] SEO007 holds every page type to one word band. Generated
cli-*pages were exempt from the paragraph-length rule entirely; they are now checked like every other page, so a project with generated CLI reference pages will see new SEO007 warnings until their lead-in paragraphs reach 30 words. Only the structural suppressions remain (a heading or short lead-in followed by a directive that supplies the content). The message also names the 30-80 band the rule enforces instead of an unrelated 40-60. - [selfdoc] **
selfdoc check --ignorerefuses a code the lint registry does not carry.** A mistyped code silently suppressed nothing; the flag is now validated against the registry before the check runs, and an unknown code aborts with the list of known codes. - [selfdoc] **
selfdoc check --ignoreandlint_ignorerefuse error-severity lint codes.** Suppression reaches warning-severity codes only: naming an error code is a hard error that names the code and its severity, at config load and at flag parse, and the run stops before any checking happens. - [selfdoc] **
selfdoc initrequires--author-nameand--author-url.** The scaffolded config declares the author alongside the base URL, because nothing about a person is inferable from a directory. - [selfdoc] **Pagefind is the search engine, and
search_enginemust declare it.** The key is required inselfdoc.jsonwith no default and one valid value,"pagefind"; a config that omits it is refused at load, naming the key. The builtin, Fuse.js and MiniSearch engines are gone, along with thesearch-index.jsonfile, thesearch.jsbundle and the CDN script tags -- the build runs Pagefind over the finished HTML instead, and Pagefind writes both the index and the search UI intopagefind/. Install it withuv add 'pagefind[bin]';SEARCH001now reports a missing indexer on every project.
#Features
- [selfdoc] **
selfdoc initworks on projects with no code, and emits a config that builds.** Language detection failing is no longer a refusal: a codeless project gets a config with nosourcekey and a starter page with no extraction directive. Every field load and build require -- base URL, versions, locales -- is written into the file, so the generated configuration needs no hand-editing. - [selfdoc] The lint-rule table documents every rule. The Check Guide's table listed only the SEO, staleness, CLI, example and version rules; it is now generated from the lint registry and covers all 42 codes, including the description-quality, cross-reference, parameter, post and unified-site rules.
- [selfdoc] The blog guide covers publishing without a release, the assembly roster, and retiring a project. New sections explain what a build owns and therefore what it may remove, how
roster.tomldeclares membership, and how a project is unpublished. - [selfdoc] **
selfdoc initno longer writes the retiredindexedkey, andselfdoc checkruns the new LINK001 resolution check over the built site.** An emitted link, canonical, sitemap entry or feed link that names a file the build did not write is a check error. - [selfdoc] The blog guide documents the verification every assembly deploy has to pass. What each assertion checks, what a failure means, how to run the same assertions by hand against a checkout, and how outbound link checking is declared.
- [selfdoc] **
selfdoc checkreports the post directive declaration.** A post with nodirectivesdeclaration is POST006, and a post declaring false that carries a marker is POST007, naming the marker and the line of the post file it sits on. - [selfdoc] **
selfdoc checklints blog posts.** No path linted them before: check never injected posts into the docs tree, and the build lint pass runs after the injected files are removed, so a post could carry any defect and both surfaces reported nothing. Posts are now held to every page rule, reported at the post file own path and its own line numbers. Drafts and the generated listing page are excluded, matching what the build publishes. - [selfdoc] **
selfdoc checkspell-checks documentation pages and posts.** Every page and every published post is now read for misspellings and reports them asSPELL001errors naming the file, line and column. Genuine terms -- project names, tool names, technical vocabulary -- go on a shared accept list at~/Projects/ark/spelling-accept.txt; a missing list simply accepts nothing yet. The newselfdoc spell-corpuscommand runs the same engine, read-only, over every selfdoc project sitting beside this one, so one sweep surfaces the vocabulary a whole machine uses. - [selfdoc] Search ships on every page, with seven filters. The Pagefind dialog opens on Cmd/Ctrl+K from any generated page -- project docs, posts, the blog index, the project listing and the site 404 -- and loads its assets from the index the build wrote, never a CDN. Every page carries filter attributes for version, locale, nav group, page type, deploy target, project and tags, so the dialog offers each as a filter group; tags are emitted one per value, so a page with several appears under all of them.
- [selfdoc] **The directives reference documents the
cvdirective**, including the shape of the CV document and every field it validates. - [selfdoc] Content a directive renders is spell-checked. A page whose body is a directive -- the CV, the curated project listing -- had none of the text a reader sees scanned, because the source file holds a marker rather than the prose. Live CVs shipped with misspellings nothing reported. Findings now name the document the word was written in (
docs/cv.toml,docs/projects.toml) with its line and column there, plus the page it renders into. Names extracted from source code are not reported -- an identifier is not prose.
#Fixes
- [selfdoc] Assembly deploy documentation matches the deploy. The blog guide now describes
selfblog assembly integrate(the deploy body) andselfblog assembly sync-workflow(which regenerates the deployed workflow and its selfblog pin) instead of the retired embedded-shell workflow. - [selfdoc] SEO008 stops counting version strings and years as statistics. The numeric-density rule counted any token with a digit, so a page whose only numbers were
0.36.0and2026looked data-rich and the rule stayed silent. Version-shaped tokens (0.36.0,v2,1.0.0-alpha.1) and bare calendar years no longer count; genuine quantities (42,3.5,87%) still do. - [selfdoc] Generated index pages and the documentation corpus link pages at the address the site serves them from. The auto-generated API and CLI reference indexes still linked siblings as
<page>.html, the form pages took before directory-index URLs, so every link off those two pages resolved inside the index page's own directory and 404'd. - [selfdoc] The blog guide gives the address a post is actually served at. It documented
/<project-slug>/posts/<post-slug>/, which no build has emitted since posts became site-level: a post is at/blog/<post-slug>/, under no project slug, in one slug namespace every project on the assembled site shares. The published-file record example was stale in the same way. - [selfdoc] **
selfdoc buildprints a message instead of a traceback when the config is invalid.** A present-but-unusableselfdoc.json-- noversions, nolocales-- ended the process on an uncaught error; it now reports the problem and exits 1. - [selfdoc] SEO doc: no SearchAction. The structured-data list says the homepage
WebSitenode carries noSearchActionand why. - [selfdoc] The documentation site is canonical on the apex. Every other hostname the site is bound to 301s onto it, and the assembly guide documents the routing and the four machine-readable files at the site root.
- [selfdoc] The configuration reference states the search engine rule. Its table described the three deleted engines and marked
search_engineoptional. - [selfdoc] The contrast check measures the stylesheet the site actually ships.
selfdoc checkread a packaged copy of the theme CSS that had drifted 58 lines behind the one the build emits, so SEO012 scored colors no page used. It now reads the emitted stylesheet, and the stale copy is gone from the wheel. - [selfdoc] **A bad config or an unknown directive stops
selfdoc checkwith a message, not a traceback.**checkreadselfdoc.jsonwith no handler, so a filebuildrefused cleanly crashed it -- as did a suppression list naming an unknown or unsuppressable lint code, and a page carrying a directive nothing answers. All of them now print oneError:line and exit 1.
#0.36.0
Generated CLI reference pages carry effects metadata, and selfdoc check executes validate-marked examples instead of merely parsing them.
Context
Two gaps closed in the same visit.
Generated cli-*.md pages threw away every effects field the strictcli schema carries. A reader of the published docs could not learn that a command was consequential, what it would refuse to do under --dry-run, or which dangerous steps it had authored grant reasons for -- the first notice was a CI job dying on a confirmation prompt. The pages now render the effect classification, the consequential badge with --approve-consequential, the dry-run-unsupported reason and the grants table, and the index gains global flags, the reserved framework-flag quartet, infrastructure and deprecated sections.
Separately, code blocks in documentation were only ever parsed for syntax, so an example that parsed but could not run shipped looking correct. A block marked validate is now assembled and executed through the validator its language declares under the new examples config key; a failing run is EXAMPLE002 with the validator's output, and a validate marker with no configured command is EXAMPLE003 rather than a silent skip. Unmarked blocks are unaffected and are never executed.
Also: schemas/check-output.schema.json now enumerates every lint code selfdoc check emits and declares the two coverage fields it was omitting, so consumers validating or generating types from the JSON output stop losing data.
#Features
- [selfdoc] Effects metadata in CLI reference pages. Generated
cli-*.mdpages now show each command's strictclieffectclassification, a consequential badge naming--approve-consequential, a dry-run-unsupported note with its reason, and a Grants table. The CLI index gains Global flags, Framework flags (the reserved--dry-run/--approve-consequential/--quiet/--verbosequartet), Infrastructure and Deprecated sections. - [selfdoc] Examples are executed, not just parsed (EXAMPLE002/EXAMPLE003).
selfdoc checknow runs every code block markedvalidatethrough the validator configured for its language underexamples, reporting a failing exit as EXAMPLE002 with the validator's output tail. A marker with no configured command is EXAMPLE003 rather than a silent skip. Unmarked blocks keep the syntax-only EXAMPLE001 behavior and are never executed. - [selfdoc] Documented the portfolio canonical. The blog guide gains a section explaining why an assembly's portfolio page names the apex rather than
topology.docs_baseas its canonical, and howassembly.portfolio_canonicalreaches the generated deploy workflow.
#Fixes
- [selfdoc] JSON output schema accepts all current lint codes.
schemas/check-output.schema.jsonenumerated only the SEO and STALE001 codes, so consumers validatingselfdoc check --format jsonrejected output containing EXAMPLE, DQ, PARAM001, RETURN001, DRIFT001 and other current codes. The enum now covers every emitted code. - [selfdoc] JSON output schema declares all coverage fields.
schemas/check-output.schema.jsonomitted thedocumentedanddocumented_symbolsfields thatselfdoc check --format jsonemits, so consumers generating types from the schema silently lost both.
#0.35.0
selfdoc adopts strictcli's effects regime: every command is classified, --dry-run previews instead of executing, deploy asks before it runs, and --yes is replaced by --approve-consequential
Context
selfdoc's dry-run used to be a per-command courtesy: some commands honoured it, some quietly ignored it, and nothing in the code stopped a handler from shelling out anyway. This release moves that guarantee into the framework. Every command now declares whether it is read-only or mutating, and every subprocess launch and filesystem write goes through a single chokepoint, so --dry-run records a numbered would-do log instead of performing the work -- a dry deploy names the Cloudflare project it would push to rather than pushing to it.
The confirmation story changed with it, and it is worth explaining why the flag is new. strictcli originally inferred "prompt the user" from "this command mutates something", which turned out to catch about two thirds of every CLI in the fleet -- including commands nobody wants to confirm. A prompt that fires on two thirds of invocations trains people to dismiss it, which is exactly the reflex a confirmation is supposed to prevent. So consequence is now declared per command rather than inferred, and only the commands that earn it prompt. In selfdoc that is exactly one: deploy, the only command whose effects leave the machine and land on a live public site.
--yes is gone rather than deprecated. yes is now a banned flag name framework-wide, so selfdoc <cmd> --yes is a hard "unknown flag" error instead of a silent no-op. The replacement is --approve-consequential, deliberately unwieldy so it cannot decay into muscle memory the way -y did, and self-documenting wherever it appears in a script. The practical migration is smaller than it sounds: the mutating commands that used to demand --yes from every non-interactive caller now need no flag at all.
#Breaking
- [selfdoc] **Every command is classified, and
--dry-runnow previews instead of executing.** selfdoc rides strictcli's effects regime: each command declares itselfread_onlyormutating, and every subprocess launch and filesystem write routes through a single effects chokepoint.selfdoc <cmd> --dry-runrecords what the command would do and prints a numbered would-do log -- a drydeploynames the Cloudflare Pages deployment or thegh-pagesforce-push (with the reason it is granted) without performing either. Two consequences to plan for: mutating commands now ask for confirmation, so non-interactive callers (CI, release hooks, scripts) must pass--yesor they exit withstdin is not interactive; andselfdoc check's own--dry-runflag is gone, replaced by the framework flag of the same name -- the reserved names--dry-run,--yes,--quietand--verbosenow belong to the framework and are accepted anywhere in argv. - [selfdoc] **
selfdoc deploynow asks before it runs.** It is the one selfdoc command whose effects leave the machine and land on a live, publicly-visible site: a Cloudflare Pages deployment is live the moment it lands, and the GitHub Pages provider force-pushesgh-pages, so the previously published tree is gone from the remote. It is declaredconsequential, so the framework prompts before dispatch and refuses on non-interactive stdin. Pass--approve-consequentialfrom scripts, CI and release pipelines. Every other selfdoc command --gen,build,check,init,serve,gen-data,baseline accept-- is unaffected and still runs bare. - [selfdoc] **
--yesis gone; the confirmation-skip flag is--approve-consequential.** strictcli 0.36.0 replaced the inferred "mutating command means prompt" rule -- which caught two thirds of every CLI in the fleet -- with a per-commandconsequentialdeclaration, and the framework now prompts only for commands that make it.yesis a banned flag name, soselfdoc <cmd> --yesis a hardunknown flagerror rather than a no-op. In practice this means the mutating commands that used to demand--yesfrom every non-interactive caller now need nothing at all.
#0.34.0
Extractor coverage fixes (re-exports, module constants, docstring-less/pydantic classes), new selfdoc quality command, configurable coverage_threshold, three new guides, plus breaking removal of post/assembly subcommands and new explicit deploy-target and assembly config keys
Context
Coordinated minor batch release; the previous batch shipped 2026-07-11. This ships roughly three weeks of accumulated backlog for the selfdoc CLI.
Breaking changes in this batch: the selfdoc post and selfdoc assembly subcommand stubs are gone (that surface lives in selfblog now); deploy_github_pages requires an explicit target instead of inferring it from the current directory's origin; and the assembly config keys changed (topology.assembly is rejected in favour of assembly.repo, and assembly.pages_project is now required).
Features: a new selfdoc quality command scoring documentation on a 0-5 tier scale, a configurable coverage_threshold in selfdoc.json, the gen --version-override release handshake, and new directives/quality/blog guides. Fixes tighten ref coverage accounting (re-exports and module constants are now counted, docstring-less and pydantic classes are no longer dropped), make manifest generation idempotent so --no-allow-dirty releases stop looping, honour package-level excludes, and add remediation hints to DRIFT001/STALE001.
#Breaking
- [selfdoc] **
deploy_github_pagesrequires an explicit target.** The re-exportedselfdoc.deploy.deploy_github_pagesnow takes a requiredtargetkeyword instead of force-pushing to whateveroriginthe current working directory resolves to.selfdoc deployitself is unchanged. - [selfdoc] **
selfdoc postandselfdoc assemblyare gone.** The ten stub subcommands that only printed "moved to selfblog" are deleted; use theselfblogCLI, which has owned these commands since selfblog was split out. Removing them also un-pollutes selfdoc's strictcli schema, so README's command table is generated from the live schema again instead of being hand-maintained. - [selfdoc] Assembly config keys changed.
topology.assemblyis rejected outright (useassembly.repo),assembly.pages_projectis now required for projects that publish into an assembly, andtopology.legacy_blog_hostis new.topology.posts_basenow means the canonical blog URL under the docs site, not a separate blog host.
#Features
- [selfdoc] Version-override handshake.
selfdoc gen --version-override <v>stamps an explicit project version into version-bearing generated root files, andselfdoc check --version-override <v>states the version they are expected to embed. Release orchestrators pass the about-to-be-released version so generated files are no longer committed one release behind. The new VER004 check turns a stale embedded version into a hard failure instead of a silent lag. - [selfdoc] **
coverage_thresholdinselfdoc.json.** New optional float field (0.0-1.0, default 1.0) setting the minimum fraction of public symbols that must be documented forselfdoc checkto pass. Lower it below 1.0 to accept partial coverage. - [selfdoc] **New
selfdoc qualitycommand.** Scores a single project on a 0-5 documentation tier scale with a letter content grade (documentation-to-code ratio, excluding test LOC and templates) and prints the next tier's requirement as concrete guidance. - [selfdoc] Three new guides. Added a directives guide, a quality/tier guide, and a blog-post authoring guide to the documentation.
#Fixes
- [selfdoc] Docs. README now documents
post/assemblyas selfblog commands. - [selfdoc] Coverage respects package-level excludes. A gen.exclude package path (e.g. a vendored package) now excludes its files from coverage counting, matching gen's behavior; previously excluded Go packages leaked into coverage as unreferenced symbols and could fail the coverage threshold.
- [selfdoc] No more STALE001 on every release. The generated CLI index page now renders its version through a
vardirective instead of a baked literal, so a version bump no longer moves the page's content hash. Projects that hit STALE001 on the CLI index at every release -- and had to runselfdoc baseline accepteach time -- no longer do. - [selfdoc] DRIFT001 tells you how to resolve it. Drift errors reported by
selfdoc checknow end with a remediation hint namingselfdoc baseline accept <page>, so an operator whose description is genuinely still accurate has a stated way out instead of having to invent a description edit. - [selfdoc] Release-time version handshake is documented. The rlsbl integration guide now covers
gen --version-override/check --version-overrideand the VER004 check, and the staleness guide no longer claims that a page embeddingproject.versionvia avardirective changes its content hash on every release (it does not -- the content hash has used the raw template body since hash schema v2). - [selfdoc] **
selfdoc.qualityis documented.** Every public function in the quality-scoring module -- tier computation, content grading, the LOC counters, and the report formatters -- now carries a docstring, so its API reference page has real content instead of bare signatures. - [selfdoc] The dependency claims in the docs are true now. The README, homepage, getting-started guide, and comparison page each claimed selfdoc had one runtime dependency (or zero); it has two direct ones --
strictcliandselfdoc-core-- which bringstrictspecandtomlkit. All four are pure Python, and the pages now say exactly that. - [selfdoc] The check guide's lint-code table is complete. It listed only the SEO rules and STALE001; the codes
selfdoc checkcan actually emit -- DRIFT001, STALE002, CLI001, CLI002 and VER001-VER004 -- now have rows too, each with its severity and how to clear it. - [selfdoc] **
refoutput now counts__init__.pyre-exports and module-level constants.** The Python extractor emitted###headings only for classes and functions, so__all__re-exports (from ._impl import X) and module-level constants (__version__) were structurally uncountable in coverage even though they sit in the denominator. They now get headings, andref target=Xresolves a name that is only a re-export instead of erroringnot found. - [selfdoc] **Docstring-less public classes are no longer dropped from
refoutput.** A public class with no docstring, no methods, and no@dataclassdecorator was silently omitted. Any class named in__all__now always renders at least a heading and signature, and pydanticBaseModelsubclasses get the same field-table rendering as dataclasses. - [selfdoc] **
selfdoc genno longer rewrites.selfdoc/manifest.jsonwhen nothing changed.** The manifest was rewritten on every run because itslast_gentimestamp always differs, dirtying the working tree and breaking--no-allow-dirtyrelease flows in a retry loop. It is now written only when its content (ignoring the timestamp) actually changes.
#0.33.0
table-commands discovers the strictcli schema automatically (path attribute removed; schema-dir disambiguates multi-schema repos)
Context
Phase 9.5 coordinated breaking release. table-commands no longer accepts a path attribute; it auto-discovers the unique .strictcli/schema.json by walking the project root, with an optional schema-dir attribute to disambiguate repos that carry more than one schema. Directive attribute handling is now strict across gen and check: an unknown or missing attribute is a hard error (exit 1) reporting the file, line, directive, and allowed attributes.
Because table-commands path removal is a breaking directive change, all in-tree docs and the eight external consumer repositories that use table-commands were migrated to the new bare-directive syntax in lockstep before this release, so no consumer is left on a directive that would now hard-error.
#Breaking
- [selfdoc] Strict directive attributes.
selfdoc checkandselfdoc gennow hard-error (exit 1) when a directive uses an unknown attribute or omits a required one, reporting the file, line, directive, and allowed attributes. - [selfdoc] table-commands auto-discovers the CLI schema.
table-commandsno longer takespath; it finds.strictcli/schema.jsonby walking the project root. Migration: droppath, and if discovery is ambiguous addschema-dir="<dir>".
#0.32.0
Description ownership by content: handwritten text is never overwritten; machine placeholders identified by hash/template/live-recompute
Context
gen now classifies a page's description text itself -- via current/historical module templates, the per-page seed hash, or a live recompute of CLI defaults -- instead of trusting the seeded flag. Hand-rewritten descriptions survive regeneration even with a stale seeded:true marker, and the STALE001/DRIFT001 exemption keys on the ownership predicate so hand-described generated pages get full staleness protection.
#Fixes
- [selfdoc] Hand-written descriptions on generated pages are never overwritten.
selfdoc gennow decides whether to reseed a description by classifying the text itself, so a description you rewrote by hand survives regeneration even if a staleseeded: truemarker was left behind. - [selfdoc] Hand-described generated pages get full stale-description protection. The STALE001/DRIFT001 exemption now applies only to pages whose description is genuinely machine-generated, so a generated page you have described by hand is checked for staleness like any other page.
- [selfdoc] **A legacy auto-generated API reference index description is refreshed on
selfdoc gen.** Projects still carrying the old "Complete auto-generated API reference index …" placeholder now have it reseeded with a project-specific description instead of being frozen as if hand-written.
#0.31.0
New name config key, gen-index naming fix, legacy description reseed, CLI defaults are now complete sentences, and the list-features directive is removed (migrated to list-modules).
Context
Descriptions are handwritten-first with machine placeholders: auto-seeded page and CLI descriptions are recognizable so they can be reseeded, while hand-authored frontmatter is left verbatim. The old 155-character truncation produced mid-sentence cuts, so machine placeholders are now complete first sentences. list-features is superseded by list-modules, which yields richer module summaries.
#Features
- [selfdoc] Auto-seeded page descriptions are complete sentences. Generated module page descriptions now carry the whole first sentence of the docstring, no longer capped at 155 characters or cut with an ellipsis.
#Fixes
- [selfdoc] SEO009 no longer misfires on wrapped first lines. The advisory description-length check now measures the complete first sentence of the opening paragraph (wrapped lines joined) instead of just the first physical line, so pages with a long but soft-wrapped opening sentence no longer get a spurious 'description too short' warning.
- [selfdoc] CLI page descriptions are the complete first sentence of the help text. Auto-generated strictcli command and group reference pages now seed the full first sentence instead of a 155-character mid-sentence cut, and pages still carrying the old truncated default are correctly re-seeded rather than frozen as if hand-edited.
#0.30.0
gen-index descriptions: correct project naming, legacy wrong descriptions self-heal; no more phantom STALE/DRIFT on generated pages.
#Fixes
- [selfdoc] Correct API reference index description.
selfdoc genno longer bakes an arbitrary source folder's name into the API reference index for multi-source projects (it uses the configurednameor a generic phrasing), and it now refreshes index pages that still carry the old hardcoded description instead of preserving it forever. - [selfdoc] No more phantom staleness errors on auto-generated pages.
selfdoc checkno longer reports STALE001/DRIFT001 on skeleton (generated + seeded) pages whose source changed -- those pages advance automatically instead of being stuck in an unfixable error.
#0.29.0
baseline accept command and gen-index staleness fix
Context
Adds selfdoc baseline accept for clearing staleness dead-ends on pages with dynamic content. Fixes gen-index to use content-aware seeded descriptions, preventing STALE001 deadlocks on generated index pages.
#Features
- [selfdoc] **
selfdoc baseline accept <page>...**. New command to clear a STALE001/DRIFT001 dead-end when a page's content changed but its existing frontmatter description was reviewed and is still accurate (e.g. pages embeddingproject.versionthat change every release). It advances the named pages' hash baselines exactly as if the description had been rewritten. Pages are named explicitly (no--all/glob/--force); accepting a page that does not exist, has no baseline, or is not currently stale/drifted is a hard error.
#Fixes
- [selfdoc] Fix. gen-index description is now content-aware with a seeded marker, preventing STALE001 deadlocks on generated index pages.
#0.28.1
Fix CI: install playwright browsers before running tests, fix publish gate regex
Context
v0.28.0 CI failed because playwright browsers were not installed. The publish gate regex also did not match the actual CI job name. Both are fixed.
#Fixes
- [selfdoc] Fix CI. Install playwright browsers before running tests so CI passes and the package publishes to PyPI.
#0.28.0
Monorepo conversion with three independent packages (selfdoc, selfdoc-core, selfblog).
#Breaking
- Blog functionality moved to selfblog. Post discovery, post checks (POST001-POST005), and the unified multi-project check now live in the
selfblogpackage.selfdoc post/selfdoc assemblycommands temporarily delegate to selfblog (install withpip install selfblog);selfdoc checkon unified projects now hard-errors and directs toselfblog check. Newselfblog checkcommand validates posts and unified sites. - [selfdoc] Packaging split.
pip install selfdocno longer bundles theselfdoc_coreandselfblogpackages:selfdoc-coreis now a regular dependency, andselfblogis a separate optional install. Theselfblogconsole script moved to the selfblog package -- install it withpip install selfblog. Theperf/ogextras now route toselfdoc-core[perf]/selfdoc-core[og]. - [selfdoc] selfdoc post/assembly commands removed -- use the selfblog CLI (
pip install selfblog). Unified and posts-only builds throughselfdoc build/servenow hard-error directing to selfblog
#0.27.0
Three-package architecture (selfdoc_core + selfdoc + selfblog), forced-rewrite staleness, per-language list-modules, post revisions.
Context
Splits the codebase into three packages: selfdoc_core (shared build engine and extractors), selfdoc (CLI and documentation generation), and selfblog (blog assembly and CLI). This sets up the future monorepo conversion. Staleness detection gains baseline-hold semantics (baseline does not advance while errors remain), schema-hash gating with seeded markers for CLI pages, and root-file template validation. The list-modules directive now groups modules by language with extractor summaries. Post infrastructure adds a revisions sidecar (content-hash gated revision tracking) and localStorage read indicators. Manifest loading uses a tolerant-reader contract with centralized compatibility checks for forward-compatible schema evolution.
#Features
- Root-file template validation. selfdoc check now validates directives in root-file templates (docs/_CLAUDE.md, docs/_README.md).
- Staleness baseline freeze. Baseline does not advance while errors are outstanding, preventing silent degradation.
- CLI page schema-hash gating. Staleness detection uses seeded markers for CLI page schema hashes, preventing false positives on unchanged content.
- Module listing improvements. Per-language grouping with extractor summaries and hard error for unsupported languages.
- Three-package architecture. Split into selfdoc_core (shared engine), selfdoc (CLI + docs), and selfblog (blog + assembly). Modules moved to their owning packages with re-export shims for backward compatibility.
- selfblog CLI entry point. New
python -m selfblogCLI with post (new, list, generate, publish), assembly (init, push, status, rebuild, generate-shared), and build commands. Per-package pyproject.toml files for future monorepo conversion. - Tolerant-reader manifest contract. Centralized compatibility check for manifest schema versions, allowing older readers to process newer manifests safely.
- Post revisions sidecar. Content-hash gated revisions.json tracks edit history per post with automatic revision capture on content changes.
- Read indicators. Posts track read status in localStorage, enabling visual distinction between read and unread content.
#0.26.1
Assembly workflow cleanup and posts.repo configuration.
#Fixes
- Assembly workflow cleanup. Removed the 42-line inline Python fallback for selfdoc <= 0.24.0, simplifying the assembly CI workflow.
#0.26.0
Posts repo archival, blog redirect via Pages Function, assembly workflow fixes.
#Features
- Posts repo archival. New
posts.repoconfig field archives resolved post content to a git repo during post publish. - Blog redirect via Pages Function. Replace Cloudflare Pages
_redirectswith_worker.jsfor host-specific blog.smmh.dev redirect.
#Fixes
- Assembly workflow fix. Pass
--docs-basetogenerate-sharedin the assembly workflow.
#0.25.1
Fix VER001 for assembly builds, Playwright CI setup, test fixes.
#Fixes
- VER001 version filter.
selfdoc build --version Xno longer validates old versions that can't be extracted from shallow clones, fixing assembly builds for multi-version projects like claudestream. - Playwright CI. CI workflow now installs Playwright browser binaries, fixing 75 test errors that blocked the v0.25.0 publish.
#0.25.0
Local post publish, portfolio homepage, monorepo labels, and blog URL fixes.
#Features
- Local post publish.
selfdoc post publishnow builds posts locally and pushes directly to the assembly repo via Git Data API, eliminating the requirement to push to the project's remote. Posts can be published without software releases. - Portfolio homepage.
selfdoc assembly generate-sharedgains--portfolio-fileflag to use a custom HTML file as the site homepage. Project listing moves to /projects/. - Monorepo label. Projects with version 0.0.0 show 'monorepo' instead of a version badge on the homepage.
#Fixes
- Dependency installation no longer requires machine-local checkouts:
uv.lockpreviously carried local paths forstrictcliandpredraw, breaking installs and CI outside the author's machine. Dependencies now resolve from PyPI (strictcli 0.24.0, predraw 0.2.1). - Python version requirement corrected.
requires-pythonbumped from>=3.11to>=3.12to match actual compatibility (CI tests 3.12/3.13/3.14 only; codebase uses PEP 701 syntax). Deadtomlifallback code removed; ruff config added withtarget-version = "py312". - Expanded CLI help text. All command and flag help strings now meet the 50-character minimum for documentation quality checks.
- Expanded CLI command help text. All command help descriptions now meet the 40-60 word target for AI citation quality, and include numeric data points where applicable.
- Blog index URLs. Fixed broken blog post URLs where project slugs were treated as hostnames.
#0.24.0
Migrated all boolean CLI flags to explicit-default style via strictcli.
Context
All bool flags now use --flag/--no-flag syntax with explicit defaults, following the strictcli migration. --no-commit renamed to --auto-commit, --dry-run and --drafts require explicit values, --from-release is now required.
#Breaking
- Breaking:
--no-commitrenamed to--auto-commit(default=True).--dry-runand--draftsnow require explicit defaults.--from-releaseis now required.
#0.23.0
Posts-only build mode and post publish command for publishing blog posts without software releases.
#Features
- Posts-only build. New
--target postsflag onselfdoc buildfor building only blog posts without the full documentation site. Used by the assembly CI for publishing posts without a software release. - Post publish command. New
selfdoc post publishdispatches blog posts to the documentation assembly without requiring a version bump. Validates posts are committed and pushed before dispatching.
#Fixes
- Assembly workflow robustness. Multi-version projects with failed version detection now error clearly instead of silently attempting a full build. Post-manifest overlays enable updating posts independently from docs.
#0.22.1
Fix null manifest fields crashing assembly shared element generation.
#Fixes
- Fix. Assembly shared element generation no longer crashes when manifest fields are null.
#0.22.0
Assembly Stage 2: multi-project site aggregation with shared elements, search indexing, and persistent combined site.
Context
Rewrites the assembly workflow to implement Solution 10: the assembly repo stores the complete combined site. Each dispatch replaces one project's subtree, regenerates shared elements (homepage, blog index, navigation, feeds, sitemap), runs Pagefind for cross-project search, and deploys the full site to Cloudflare Pages.
#Features
- Assembly Stage 2. New
selfdoc assembly generate-sharedcommand and rewritten assembly workflow for multi-project site aggregation. Each dispatch now surgically updates one project, regenerates shared elements (homepage, blog index, nav, feeds, sitemap, search), commits the combined site, and deploys.
#Fixes
- CF Pages deploy fix. Deploy to the correct Cloudflare Pages project name (
smmhinstead ofdocs-smmh-dev).
#0.21.1
Fix auto-commit with gitignored files, fix inline directive regex, add directive name validation
#Features
- New. Inline directives now validate names against known directives, matching existing behavior for standalone and block directives. Custom directive names in selfdoc.json are validated at load time.
#Fixes
- Fix. Auto-commit no longer silently fails when gitignored files are in the commit list. Gitignored files are filtered out and commit errors are now visible on stderr.
- Fix. Inline directives no longer capture trailing punctuation.
(v:-: name).now resolves correctly instead of greedily capturingname).as the directive name.
#0.21.0
Posts system, page types, unversioned pages, deployment topology, assembly infrastructure, Pagefind search, and shared elements for multi-project documentation sites.
Context
This release introduces a complete blog/posts system integrated with rlsbl releases, a page type system with configurable schema.org mappings and per-type layouts, unversioned pages for content that persists across doc versions, a deployment topology config for multi-project unified sites, an assembly system that manages a throwaway GitHub repo for aggregated documentation deployment to Cloudflare Pages, Pagefind as a new search engine option with chunked indexes and filtering, and shared elements generation (homepage, blog index, nav, feeds, sitemap) for assembled sites.
#Features
- Page type system. Frontmatter-driven page types with configurable schema.org mapping, per-type layout control, and type-aware labeling.
- Unversioned pages. Pages with
versioned: falsefrontmatter build at stable URLs outside the version path, with persistent navigation and search inclusion. - Blog posts. New posts system with
selfdoc post new,selfdoc post list,selfdoc post generate --from-release, date-based navigation, auto-generated listing page, type-aware feed ordering, draft support, and lint rules POST001-POST005. - Deployment topology. Configurable multi-project URL generation with
TopologyURLBuilder, cross-project linking, and topology-aware var directives. - Assembly system.
selfdoc assembly init/push/status/rebuildcommands for managing a unified multi-project documentation site deployed to Cloudflare Pages. - Shared elements generator. Homepage, blog index, global navigation, unified Atom feed, sitemap, and cross-project link validation for assembled sites.
- Pagefind search engine. New
pagefindoption forsearch_engineconfig with chunked lazy-loaded indexes, Cmd/Ctrl+K shortcut, and filtering by project, type, version. - Topology migration script.
scripts/migrate_topology.pyfor applying topology config across multiple projects.
#Fixes
- Bug fixes. Wire schema_types to generate_html, propagate topology URLs to auxiliary files, fix nested index.md URL segments, inject posts for constituent projects in unified builds, fix cross-project link validation for URL-form paths.
#0.20.0
ref+target single-symbol rendering, Go dotted method names, expanded CLI help texts.
Context
ref directives now respect the target attribute across all 9 extractors, rendering a single symbol instead of the entire module. This makes PARAM001/RETURN001 checks reachable for targeted documentation. Go public_symbols returns Type.Method for methods, fixing collision bugs. CLI help texts expanded across selfdoc, rlsbl, safegit, and wesktop to satisfy CLI002.
#Features
- New feature. ref directives now support the target attribute to render a single symbol instead of the entire module.
#Fixes
- Bug fix. Go public_symbols now returns dotted Type.Method names for methods, fixing collision when two types share a method name.
#0.19.1
Remove version_source config field (redundant with rlsbl).
Context
version_source was added in v0.16.0 to read the project version from manifest files. Investigation revealed rlsbl already bumps selfdoc.json during releases via _bump_selfdoc_version, making version_source redundant. Reverted to reduce complexity.
#Breaking
- Breaking. Removed
version_sourceconfig field. rlsbl already handles selfdoc.json version bumping during releases.
#0.19.0
Dotted target syntax, TS re-export fix, CLI002 lint, gen.py protocol integration, coverage matching fix, and TS module JSDoc fix.
Context
symbol_details across all extractors now supports Type.Method dotted targets for class member disambiguation. TypeScript _extract_exports handles export { X } re-exports. Coverage matching uses heading-based regex instead of fragile substring check. gen.py uses module_docstring protocol for all 9 languages (7 gained auto-descriptions). CLI002 validates help text length. TS _extract_module_jsdoc no longer misclassifies function JSDoc as module-level.
#Features
- New lint. CLI002 warns when CLI help texts are shorter than 50 characters (commands, flags, args, and group subcommands).
- All languages get auto-extracted descriptions. gen now uses the
module_docstringprotocol method for all extractors instead of hardcoded Go/Python paths. TypeScript, Zig, Swift, Kotlin, Svelte, Dart, and SQL pages now show real module descriptions.
#Fixes
- Fixed. TypeScript module-level JSDoc detection no longer misidentifies function-attached JSDoc as module documentation.
- Fixed. Coverage computation no longer falsely matches symbols that are substrings of other symbol names.
- Fix. TypeScript ref output now includes re-exported symbols (
export { X }andexport { X } from '...'), fixing coverage reports that showed them as undocumented.
#0.18.0
module_docstring and symbol_details protocol methods for all extractors, handler signature refactor, and inline directive fixes.
Context
Handler functions now receive path and target as separate arguments, eliminating the latent arg-combining bug. module_docstring protocol method replaces language-string dispatch in staleness.py, enabling DRIFT001 across all languages. symbol_details implemented for all 9 extractors, enabling PARAM001/RETURN001 cross-language. Double-backtick inline directive spans now masked correctly. Inline pass 2 fixed to split multi-line resolver output before fence tracking.
#Breaking
- Breaking. Handler functions now receive path and target as separate arguments instead of a combined string.
#Features
- TypeScript extractor. Implement
symbol_detailsmethod for extracting function parameters, types, return types, and JSDoc documentation status. - Swift extractor. Implement
symbol_detailsfor function parameter, return type, and documentation extraction. - Go extractor. Implement
symbol_detailsfor extracting function parameter types, return types, and documentation status from Go source files. - Zig extractor. symbol_details method for function parameter and return type extraction.
#Fixes
- Fix. table-commands directive now correctly joins path attribute with base_dir for monorepo sub-project schema lookup.
- Fix. Inline directives inside double-backtick code spans are no longer resolved.
- Fixed inline directive resolution inside code fences. Directives shown as examples inside code fences in resolved content (e.g., the catalog directive's output) were incorrectly matched and resolved by pass 2, causing errors like 'list-modules returned multi-line output'. Fence tracking now correctly handles multi-line resolver output.
#0.17.0
Inline directives, 10 new doc quality checks, XREF002 Go fix, and ecosystem deployment.
Context
Five new lint codes (EXAMPLE001, CLI001, DRIFT001, PARAM001, RETURN001) complete the doc quality pipeline. Inline directive support enables mid-line dynamic values via a two-pass resolver. Protocol extended with symbol_details() and structural docstring parser. XREF002 fixed for Go package directories. Ecosystem: version_source migrated to 17 projects, extractors deployed to incantino/F/gamehome.
#Features
- New feature. CLI001 lint checks CLI reference completeness for strictcli-based projects.
- New feature. EXAMPLE001 lint validates Python and JSON syntax in code blocks.
- New feature. DRIFT001 lint detects when source docstrings change without corresponding documentation updates.
- New feature. PARAM001 lint warns when function parameters are not documented.
- New feature. RETURN001 lint warns when return types are not documented.
- New feature. Inline directive support: self-closing directives can now appear mid-line for dynamic values in prose.
#Fixes
- Fix. XREF002 no longer fires false positives on Go package paths (directories).
#0.16.0
Five new language extractors (Swift, Kotlin, Svelte, Dart, SQL), version_source config, and doc quality checks.
Context
This release adds five new language extractors: Swift (doc comments, struct schemas, public symbol detection), Kotlin (KDoc parsing), Svelte (component props, instance/module exports, JSDoc), Dart (class/mixin modifiers, doc comments, part file following, export following with show/hide combinators), and SQL (PostgreSQL DDL -- CREATE TABLE/VIEW/TYPE/FUNCTION and COMMENT ON). It also introduces the version_source config field for reading the project version from the primary manifest, and four new doc quality lint checks: XREF001 (internal page link validation), XREF002 (directive path validation), DQ001 (description restates symbol name), DQ002 (description too short), and DQ003 (function-referencing pages need substantive descriptions). Several base extractor utilities were refactored into shared helpers to reduce duplication across extractors.
#Features
- New feature.
version_sourceconfig field for reading version from primary manifest. - New feature. Swift language extractor with doc comment parsing, struct schema extraction, and public symbol detection.
- New feature. Kotlin language extractor with KDoc parsing.
- New extractor. Svelte language extractor for component documentation -- extracts props, instance/module exports, and JSDoc from .svelte files.
- New feature. Dart language extractor with class/mixin modifier detection, doc comment parsing, part file following, and export following with show/hide combinators and cycle detection.
- New feature. SQL schema extractor for PostgreSQL DDL -- parses CREATE TABLE/VIEW/TYPE/FUNCTION statements and COMMENT ON documentation from .sql files.
- New feature. XREF002 lint check -- verifies that resolved directive source files actually exist on disk.
- New feature. Doc quality checks: XREF001 (broken page links), DQ001 (restated description), DQ002 (short description), DQ003 (missing function info).
#Fixes
- Fix. Svelte detection now runs before TypeScript in auto-detection order, preventing Svelte projects from being misidentified as TypeScript.
#0.15.1
Fix pipe escaping in unclosed backtick spans.
#Fixes
- Bug fix. Pipes inside unclosed backtick spans in table cells are now correctly escaped.
#0.15.0
Generic markdown table renderer as public API, with full internal refactoring.
Context
Added render_markdown_table in selfdoc.tables -- a public utility for custom directive authors to render markdown tables with alignment, pretty-print, and pipe escaping. Refactored all 25 internal table-building callsites to use it, eliminating duplicated table construction across extractors, content directives, and strictcli support. Also updated html.py to handle escaped pipes and alignment markers in table parsing.
#Features
- New utility.
render_markdown_tableinselfdoc.tables-- public API for rendering markdown tables with alignment support, pretty-print mode, and backtick-aware pipe escaping. - Init command. Detects all languages in multi-language projects for config generation.
#0.14.1
Exclude Python test files from coverage
#Fixes
- Fix. Python test files (
test_*.py,conftest.py) and test directories (tests/,test/,__tests__/) are now excluded from documentation coverage, matching whatselfdoc genalready excluded.
#0.14.0
Stub extractors for unsupported languages, skip .venv in source walks, remove javascript alias
#Breaking
- Breaking. Removed
javascriptalias from the extractor registry. Usetypescriptfor both TypeScript and JavaScript files.
#Features
- New feature. Unsupported languages in source entries get a stub extractor instead of a config error. Supported languages work normally alongside unsupported ones.
LANG001lint error reports which source paths lack an extractor.
#Fixes
- Fix. Source walks now skip
.venv,node_modules,__pycache__, and other non-source directories. Previously, a.venv/inside a source path would generate hundreds of doc pages for third-party packages.
#0.13.1
Fix Go root package ambiguity in multi-language projects
#Fixes
- Fix. Go root packages in multi-source-path projects no longer crash with ambiguity errors. Each source path's root package gets a unique qualified path instead of the ambiguous
path=".".
#0.13.0
Multi-language project support and Zig extractor
#Breaking
- Breaking. Top-level
languageconfig field removed. Each source entry is now an object withpathandlanguagefields:{"path": "src/", "language": "python"}.
#Features
- New feature. Zig language extractor with support for
pub fn,pub const,pub var, struct/enum/union declarations,///doc comments,//!module docs, and test block extraction. - New feature. Resolver dispatches directives to the correct language extractor based on source path. Cross-language path ambiguity is detected and errors.
- New feature.
selfdoc genandselfdoc checkwork across multiple languages in a single project. Coverage enforcement applies to all languages -- undocumented symbols in any language cause failure. - New feature.
list-modulesdirective auto-detects language from source path.project.languagevariable returns comma-separated list for multi-language projects.
#0.12.0
100% coverage enforced. selfdoc check now requires all public symbols to be documented on non-skeleton pages. The min_coverage config field is removed.
#Breaking
- 100% coverage enforced.
selfdoc checknow requires all public symbols to be documented on non-skeleton pages. Themin_coverageconfig field is removed.
#Features
- Extractor accuracy. Python extractor respects
__all__for public symbol detection. Go extractor handles exported symbols insideconst()andvar()blocks. - Stale file tracking.
selfdoc gennow tracks and commits deleted stale doc files alongside newly generated ones. - Schema provenance.
selfdoc genandselfdoc checkvalidate that.strictcli/schema.jsonbelongs to the current project via aproject_idfield.
#Fixes
- Hash consistency.
selfdoc genupdates content hashes after generating, eliminating the 'run check twice' ritual. Gen, check, and build now use consistent locale-prefixed hash keys. - Skeleton-only display.
selfdoc checknow shows which symbols are only referenced on skeleton pages when documented coverage is below 100%. - Wider lint suppression.
lint_ignoreconfig now accepts all lint code formats (e.g., STALE001, VER002), not just SEO codes. - Safer stale cleanup. Stale generated files are now deleted after new files are written, preventing data loss if generation fails midway.
- Git command safety. All git subprocess calls use
--separator before filenames, preventing filenames from being mistaken for git options.
#0.11.0
New feature. Config-driven redirects for renamed/deleted doc pages. Add a redirects list in selfdoc.json with {from, to} page slugs — the build expands across all locale/version combos, generating both Cloudflare _redirects rules and HTML meta-refresh pages. Existing pages in cached old versions are preserved.
#Features
- New feature. Config-driven redirects for renamed/deleted doc pages. Add a
redirectslist inselfdoc.jsonwith{from, to}page slugs — the build expands across all locale/version combos, generating both Cloudflare_redirectsrules and HTML meta-refresh pages. Existing pages in cached old versions are preserved. - Docs. Added prose documentation for the
excludeattribute ontable-schemaandtable-configdirectives.
#0.10.0
New feature. exclude attribute for table-schema and table-config directives, allowing users to exclude specific top-level keys when rendering JSON/TOML/JSONC config files as tables.
#Features
- New feature.
excludeattribute fortable-schemaandtable-configdirectives, allowing users to exclude specific top-level keys when rendering JSON/TOML/JSONC config files as tables.
#Fixes
- Bug fix. Go extractor
table-schemanow correctly renders JSON/TOML config files instead of failing with 'no struct types found' when the file exists in the project.
#0.9.1
Fix. Go gen root package now correctly uses ref path="." instead of the module name.
#Fixes
- Fix. Go gen root package now correctly uses
ref path="."instead of the module name.
#0.9.0
New feature. Consolidated version detection with new VER002/VER003 lint codes that error when selfdoc.json version drifts from the project manifest.
#Features
- New feature. Consolidated version detection with new VER002/VER003 lint codes that error when
selfdoc.jsonversion drifts from the project manifest. - New feature.
table-endpointcontent directive renders REST API documentation from OpenAPI 3.x JSON specs. - New feature. Two-tier coverage reporting distinguishes truly documented symbols (on human-written pages) from merely referenced symbols (on auto-generated skeleton pages).
- New feature.
selfdoc.jsonnow supports an optionalversionfield for explicit project version declaration.
#Fixes
- Fix. Go gen now produces per-package (per-directory) documentation pages instead of broken per-file pages.
#0.8.1
Check validates all versions. selfdoc check now extracts and validates old tagged versions, not just the working tree. Monorepo version pinning lets docs-site releases freeze constituent project versions.
#Features
- Check validates all versions.
selfdoc checknow extracts and validates old tagged versions, not just the working tree. Monorepo version pinning lets docs-site releases freeze constituent project versions. - gen_data dogfooding. selfdoc's own docs now use a gen_data script that extracts the directive catalog from source code.
#Fixes
- Fix frontmatter list parsing. Use bracket syntax
[a, b, c]for list values instead of comma detection, which broke description fields containing commas.
#0.8.0
Breaking: always-prefixed URLs. All output now uses /<locale>/<version>/page/ URL structure. versions and locales arrays are now required in selfdoc.json. Version and locale picker dropdowns in the header.
#Breaking
- Breaking: always-prefixed URLs. All output now uses
/<locale>/<version>/page/URL structure.versionsandlocalesarrays are now required in selfdoc.json. Version and locale picker dropdowns in the header.
#Features
- Internal refactoring. Introduced BuildContext/PageContext/SearchEntry dataclasses, extracted JS to real files, decomposed \_wrap\_page into sub-renderers, extracted build\_single from build().
- Multi-version docs. Build documentation for multiple tagged versions from git history. Version picker, old-version banner, per-version SEO (noindex, canonical to latest), git tag extraction with caching.
--versionCLI flag for dev builds. - Localization support. Parallel locale directories (
docs/en/,docs/fa/), multi-locale build loop, hreflang tags, per-locale sitemaps with sitemap-index, backward compat for single-locale projects.--localeCLI flag. - Search filters. Filter search by version, locale, group, type, target, project, and tags. Syntax:
key=value,key=a|b(OR),-key=value(NOT). Auto-injectedversion=latestdefault. Chip UI for active filters. - Monorepo unified site. New
selfdoc/unified.pymodule builds one site from multiple projects. Hybrid sidebar with project groups, auto-generated landing page, rlsbl workspace.toml validation, unified search/sitemap/glossary,check_unifiedfor all constituent projects. - Documentation overhaul. Rewrote homepage, restructured nav into 4 groups, enriched 20 API reference stubs, populated 23-term glossary, added callouts, created 14 new guide pages (search, SEO, code blocks, custom directives, check, comparisons, rlsbl integration, feeds, llms.txt, glossary guide, multi-language, root files, gen-data, staleness).
#Fixes
- Fix Python logo SVG. The two snake halves in code block language icons no longer appear separated.
#0.7.0
RECOVERY OBLIGATION: no description was recoverable for this version (neither the GitHub Release notes nor the CHANGELOG.md section carried one). Author a real description from this version's changelog entries and regenerate.
#Breaking
- strictcli dependency bumped to >=0.7.0 for
--dump-schemasupport.
#Features
- Shared resolution pipeline. Directive resolution centralized in a single pass, eliminating redundant walks and double resolution in
selfdoc check. - **
--dry-runflag forselfdoc check.** Reports staleness without writing hashes or committing. Hash computation centralized instaleness.py. - Glossary opt-out. Set
"glossary": falsein selfdoc.json to suppress auto-generated glossary page. Feed entry limit. Set"feed_max_entries": Nto limit the Atom feed to the N most recent pages. - Schema.json-based CLI extraction. Replaced AST-based CLI structure extraction with reading
.strictcli/schema.json. Richer data (flag choices, hidden flags, deprecated commands). Go projects get CLI documentation for the first time.
#0.6.0
RECOVERY OBLIGATION: no description was recoverable for this version (neither the GitHub Release notes nor the CHANGELOG.md section carried one). Author a real description from this version's changelog entries and regenerate.
#Features
- Declarative config schema. Config validation is now driven by a schema (FieldType enum + FieldSpec dataclass) instead of procedural code. Unknown top-level keys in selfdoc.json are now rejected.
- Five new directives.
list-modules,table-commands,table-directives,table-config-schema, andvarfor generating module lists, CLI command tables, directive catalogs, config reference tables, and project metadata interpolation. - Root file generation.
selfdoc gencan now produce root-level Markdown files (CLAUDE.md, README.md, etc.) from underscore-prefixed templates in docs/. Configure viaroot_filesin selfdoc.json. Generated files are read-only (0o444) with an auto-generated header.
#Fixes
- Underscore-prefixed templates excluded from lint.
selfdoc checknow skips_*.mdfiles in docs/, matching the existing build exclusion.
#0.5.2
RECOVERY OBLIGATION: no description was recoverable for this version (neither the GitHub Release notes nor the CHANGELOG.md section carried one). Author a real description from this version's changelog entries and regenerate.
#Fixes
- CLI page preservation fix end-to-end. Handwritten CLI page descriptions now actually survive
selfdoc gen(the 0.5.1 preservation logic was defeated by stale-file cleanup deleting CLI pages before they could be read for preservation).
#0.5.1
RECOVERY OBLIGATION: no description was recoverable for this version (neither the GitHub Release notes nor the CHANGELOG.md section carried one). Author a real description from this version's changelog entries and regenerate.
#Features
- Module page descriptions from docstrings.
selfdoc gennow seeds new module pages with the first line of the module's docstring instead of the generic 'API reference for X' template; quote-wrapping length is also fixed.
#Fixes
- CLI page description preservation. Handwritten
descriptionfrontmatter on CLI pages (per-command, per-group, and the CLI index) now survivesselfdoc genruns, matching the behavior added for module pages in 0.5.0. - Sticky column visuals. Sticky first column in tables now preserves row stripe and hover backgrounds instead of showing a flat fill.
#0.5.0
RECOVERY OBLIGATION: no description was recoverable for this version (neither the GitHub Release notes nor the CHANGELOG.md section carried one). Author a real description from this version's changelog entries and regenerate.
#Breaking
- API entry cards default to off. Re-enable via
auto_detect.api_entriesin config.
#Features
- Module page descriptions preserved across regenerations.
selfdoc gennow reads each existing page's frontmatter; if thedescription:has been customized (i.e., differs from the default 'API reference for X' template), it's preserved instead of overwritten. Bootstrap workflow: runselfdoc genonce, then handwrite each page's description in its frontmatter — subsequentselfdoc genruns keep your descriptions. - Table redesign. Tables now use content-based widths and support sortable columns.
- Language icons for code blocks. New display modes: colorful, monochrome, or none.
- Optional line numbers for code blocks.
- Subtle code block buttons with a coherent visual design.
- Configurable run button, page navigation, and page progress indicators.
- Theme-consistent styling for
mark,kbd,abbr,del,ins,small,figure,var,samp,cite. - New directives:
prose-desc,list-tree,table-dep,list-features. - CLI page descriptions use command help text instead of generic boilerplate.
#Fixes
- Demo knob panel cleanup: removed unused knobs, added font options, fixed bugs.
- Visual fixes: heading anchor hover, collapsible padding, code background, sidebar spacing.
- Favicon and OG card now use the theme accent color.
- Improved coverage matching for generated doc pages.
- Recursive coverage matching now uses
startswithfor prefix-based directory matches. - code-help directive: path resolution and strictcli flag extraction for Go projects.
#0.4.5
RECOVERY OBLIGATION: no description was recoverable for this version (neither the GitHub Release notes nor the CHANGELOG.md section carried one). Author a real description from this version's changelog entries and regenerate.
#Breaking
- Removed deprecated --warn-only flag (warnings are non-fatal by default since 0.4.0)
#Features
- JSON schema for
selfdoc check --format jsonoutput (schemas/check-output.schema.json)
#0.4.4
RECOVERY OBLIGATION: no description was recoverable for this version (neither the GitHub Release notes nor the CHANGELOG.md section carried one). Author a real description from this version's changelog entries and regenerate.
#Fixes
- Build now cleans the output directory before writing, preventing stale files from previous builds
#0.4.3
RECOVERY OBLIGATION: no description was recoverable for this version (neither the GitHub Release notes nor the CHANGELOG.md section carried one). Author a real description from this version's changelog entries and regenerate.
#Features
- Directive catalog now includes descriptions, attribute specs, and usage examples for all core directives
- Generated API and CLI reference pages now appear in organized sidebar groups
#0.4.2
RECOVERY OBLIGATION: no description was recoverable for this version (neither the GitHub Release notes nor the CHANGELOG.md section carried one). Author a real description from this version's changelog entries and regenerate.
#Features
- Auto-commit prefers rlsbl commit when available, marking commits with Autogenerated trailer for changelog coverage exemption
#0.4.1
RECOVERY OBLIGATION: no description was recoverable for this version (neither the GitHub Release notes nor the CHANGELOG.md section carried one). Author a real description from this version's changelog entries and regenerate.
- No user-facing changes.
#0.4.0
RECOVERY OBLIGATION: no description was recoverable for this version (neither the GitHub Release notes nor the CHANGELOG.md section carried one). Author a real description from this version's changelog entries and regenerate.
#Features
- Directive syntax redesigned: new attribute-based format (
:-:,:<:,:>:) with a formal directive catalog replacing the old:::name argsyntax. All existing directive blocks must be migrated. Theglossarydirective is nowlist-glossary. - Custom directive scripts must update from
resolve(arg, config)toresolve(attrs, config, body). Body content is now forwarded. - Extractors refactored to a
LanguageExtractorprotocol with a registry. Custom extractor integrations may need updating. - URL scheme changed from flat (
guide.html) to directory-index (guide/index.html, served asguide/). All canonical URLs, sitemap entries, and internal links updated. External links pointing to old.htmlURLs will 404. - H1 headings are now auto-generated from the page title. The first
# headingin Markdown is consumed as the title source (not rendered as-is). Multiple#headings in a single page now cause a build error. selfdoc buildno longer exits 1 for lint warnings. Exit 1 is reserved for errors only. The--warn-onlyflag is deprecated (warnings are non-fatal by default).selfdoc gencommand: auto-generates documentation pages from source code structure, with exclusion patterns,generated: truefrontmatter, and stale file cleanupselfdoc gen-datacommand: runs sandboxed scripts (via bubblewrap) to generate CSV/JSON data files for documentation- First-class strictcli support: auto-detects strictcli usage and generates CLI documentation pages
- Description staleness detection:
selfdoc checkwarns (STALE001) when a page description no longer matches page content, tracked via content hashing in.selfdoc/hashes/ - Pluggable search engine: choose
"builtin","fuse", or"minisearch"via thesearch_engineconfig field - Landing page template: hero section with tagline, CTA button, and feature cards, configured via
brandingconfig field - Cross-page term linking:
<dfn>definitions automatically linked across pages with dotted-underline.term-linkstyling - Documentation coverage for Go (exported symbols) and TypeScript/JavaScript (
exportdeclarations), previously Python-only - Per-symbol coverage tracking with configurable
min_coveragethreshold - Callout directives (note, tip, warning, danger, important) as first-class directive types
- Feed filtering via
feed: falsefrontmatter; changelog pages auto-detected and excluded by default - Reading progress bar fixed below the topbar
- Scroll affordance gradients on overflowing code blocks and tables
- Sticky first column on horizontally-scrolling tables
auto_detectconfig field to disable step guide and API entry heuristics globally or per-page viaauto_steps/auto_apifrontmatter- Markdown block tokenizer (
selfdoc/tokenizer.py): standalone module with zero dependencies, 10 token types. Used internally for both HTML rendering and lint analysis. - Auto-commit system:
selfdoc build,check,init,gen, andgen-datanow auto-commit changed project files (hashes, generated docs, generated data). Disable with--no-commit. Usessafegitwhen available, falls back to git. - Directive token support in tokenizer:
:::name arg/:::blocks are recognized as structured tokens - SearchAction in WebSite JSON-LD on the homepage, enabling Google sitelinks search
- GitHub Pages security headers:
<meta http-equiv>tags for X-Content-Type-Options, X-Frame-Options, and Content-Security-Policy injected when deploy target isgithub-pages - WCAG contrast validation for user
custom.css: SEO012 checks CSS variable overrides indocs/custom.cssagainst theme backgrounds - High-contrast mode overrides: both themes override
--link,--text-secondary,--sidebar-text, and--sidebar-activeinprefers-contrast: moremedia query - Heading anchor IDs now deduplicate (appends
-1,-2for repeated headings) and preserve Unicode characters - Scrollspy correctly tracks headings when scrolling in both directions
- Step guide detection tightened: keyword must appear at start of heading text, 200-char lookback (no more false positives on "Next Steps" or "Troubleshooting Steps")
- API entry wrapping tightened: requires identifier-like heading and single-line code block (no more false positives on tutorial sections)
- Prev/next links show directional labels ("Previous" / "Next") above page titles
- Admonition icons use CSS mask-image technique, adapting correctly to dark mode
- Diff highlighting uses
+/-prefix symbols in addition to color - Collapsible section indicators replaced with 16x16px SVG chevrons (previously 8x12px CSS triangles)
- Clean theme link color (
#635bff) barely passed WCAG AA in light mode (4.70:1) and failed in dark mode (4.23:1). New color#5046e4passes comfortably (6.29:1 light, 5.93:1 dark). - SEO lint false positives from fenced code blocks: heading counts (SEO001), heading level gaps (SEO002), empty alt text (SEO003), paragraph length (SEO007), empty sections (SEO011), and all other checks now use the tokenizer, making them immune to code block content
- SEO007 false positives on directive-heavy pages: when a heading is followed by a
:::directiveblock, the short-paragraph warning is suppressed - Statistics density check (SEO008) now evaluates prose content only, excluding code blocks
md_to_html()refactored from a 180-line line-walking loop into a clean tokenize-then-render pipeline_redirectsfile is no longer generated (directory-index URLs don't need trailing-slash redirects)_headersfile is now only generated forcloudflare-pagesdeploy target- Auto-generated glossary page: collects all
<dfn>terms site-wide into an alphabetical glossary with source links - Changelog auto-detection:
CHANGELOG.mdin project root is automatically included as a documentation page selfdoc build --warn-onlyflag to treat lint warnings as non-fatal- Page progress indicator ("Page X of Y") between prev/next links
- Current page title shown in the topbar on non-index pages
- URL-triggered search: navigate to
?q=termto open search pre-filled with results - Meta description auto-generation: pages without frontmatter
descriptionnow get<meta name="description">auto-extracted from the first paragraph - Horizontal rule support:
---,***, and___in Markdown now render as<hr> - Code tab sync no longer infinite-loops with 3+ tab groups sharing a language
- Heading copy-to-clipboard shows a toast notification
- Edit link opens in a new tab
- OG description falls back to first paragraph when no frontmatter description
- Each admonition type has a distinct background color
- Focus indicators use
:focus-visiblethroughout (keyboard-only, no mouse outlines) - Sidebar active link has a visible background highlight
- Mobile sidebar traps focus within the overlay
- Mobile sidebar closes on Escape key
- Table rows highlight on hover
- Cmd+K label adapts to platform (shows Ctrl+K on Windows/Linux)
- "Last updated" date shown at the top of the page alongside breadcrumbs
- Search "no results" message includes guidance ("Try different terms or browse the sidebar")
- Feedback "No" response prompts for written feedback instead of just "Thanks"
- Negative feedback provides a text input for follow-up
<summary>elements have:focus-visibleoutlines<pre>elements havearia-labeldescribing the code languagellms-full.txtincludes page boundaries with title headings and path comments
#0.3.1
RECOVERY OBLIGATION: no description was recoverable for this version (neither the GitHub Release notes nor the CHANGELOG.md section carried one). Author a real description from this version's changelog entries and regenerate.
#Features
- npm package renamed from
selfdoctoselfdocumenting(npm blocksselfdocdue to similarity with abandonedself-docpackage). Install vianpm install -g selfdocumentingornpx selfdocumenting. The CLI command remainsselfdoc.
#0.3.0
RECOVERY OBLIGATION: no description was recoverable for this version (neither the GitHub Release notes nor the CHANGELOG.md section carried one). Author a real description from this version's changelog entries and regenerate.
#Features
base_urlis now a required field inselfdoc.json(previously optional)- Frontmatter
descriptionis now required on every page (auto-extraction removed); missing description is a build error selfdoc buildnow fails on SEO lint warnings- Subdirectory-based nested nav groups with collapsible sidebar sections, localStorage persistence, and frontmatter overrides (
nav_group,nav_order) - Configurable search trigger via
searchconfig field:"icon"(magnifying glass button),"bar"(text input with Cmd+K hint), or"hidden" - Functional feedback widget via
feedbackconfig field with webhook POST and Google Analytics event support - Atom feed generation (
feed.xml) with auto-discovery<link>tag in<head> - Definition list syntax (
term\n: definition) with glossary styling and DefinedTerm JSON-LD - Inline stat markup (
==value==) producing semantic<data>elements - Code tabs for switching between language variants with localStorage persistence
- Git branch auto-detection for edit links; configurable via
branchconfig field - Rich OG card PNG generation with text overlay via predraw (optional dependency)
- SEO lint framework with 15 rules covering headings, descriptions, images, contrast, and structured data
- JSON-LD structured data: TechArticle, BreadcrumbList, WebSite, SoftwareSourceCode, Organization/Person, ItemList, DefinedTermSet
- Open Graph and Twitter Card meta tags with
og:locale,og:image:alt, and auto-generated social card images robots.txtwith explicit AI crawler permissions (GPTBot, ClaudeBot, PerplexityBot, etc.)- Visible "Last updated" dates with
<time>elements,dateModifiedin JSON-LD, and sitemaplastmod selfdoc check --ignore SEO007,SEO008to suppress specific lint rulesselfdoc check --format jsonfor machine-readable output- Color-coded
selfdoc checkoutput (green/yellow/red by severity) - New config fields:
lang(BCP 47),author,twitter,branch,search,feedback - Sticky table headers no longer hide behind the fixed topbar
- Copy button now always visible on code blocks (was hidden until hover, invisible on touch)
- Fixed dark mode contrast for all accent colors
- Fixed breadcrumb intermediate links pointing to non-existent directory index pages
- Build-time Pygments syntax highlighting (replaced client-side highlight.js)
- Build-time CSS, JS, and HTML minification with critical CSS inlining
- Gzip and Brotli pre-compression of build output
- Search JS externalized to
search.jswith lazy index loading - Conditional JS inclusion based on page content
- ARIA labels on sidebar nav, TOC nav, and search dialog
- Dynamic theme toggle ARIA label indicating current state
- Roving tabindex on code tabs per WAI-ARIA pattern
- Heading anchors visible on touch devices
- Card-style prev/next navigation links
- Print stylesheet: 2cm margins, forced light colors, hidden breadcrumbs, code wrapping
- Security headers and trailing slash redirects for Cloudflare Pages
- Edit link shown at both top and bottom of content area
llms.txtandllms-full.txtfor AI documentation ingestionselfdoc checkreports undocumented public symbols when coverage is below 100%lint_ignoreconfig field for project-level lint rule suppression- Edit link and "Last updated" date no longer run together (flex layout with gap)
- Search shows "No results" message instead of blank space
- Search dialog closes when clicking a result link
- Fixed code-block hover shadow invisible in dark mode
- Table
<caption>derived from preceding heading for screen readers - 44px minimum touch targets on all interactive elements
- Admonition icons (distinct SVG per type: info, lightbulb, warning triangle, octagon, exclamation)
- Styled generic
<details>/<summary>in content - Styled standalone
<dfn>tags outside glossary context - RSS feed link in site footer
- Fragment highlight animation when navigating to
#sectionURLs - Topbar truncates long project names with ellipsis
#0.2.0
RECOVERY OBLIGATION: no description was recoverable for this version (neither the GitHub Release notes nor the CHANGELOG.md section carried one). Author a real description from this version's changelog entries and regenerate.
#Features
- Theme system with per-project theming via
"theme"in selfdoc.json and optionaldocs/custom.cssoverrides - Syntax highlighting via highlight.js (light + dark themes)
- Google-style docstring formatting (Args, Returns, Raises rendered as structured lists)
- Heading hierarchy: directive expansions use h2/h3/h4 instead of injecting h1
- Module name mangling (
selfdoc.extractorsthonbug) - Nested
_build/_buildrecursion when rebuilding - Deploy supports
CF_ACCOUNT_IDandCF_PAGES_API_TOKENenv var names (remapped to wrangler's expected names) - Minimal theme: clean typography, dark mode, high-contrast, and reduced-motion variants (all auto-detected from OS preferences)
- Top bar with project name and version badge
- Heading anchor links for deep linking
- CSS extracted to cacheable
style.cssinstead of inlined per page
#0.1.0
RECOVERY OBLIGATION: no description was recoverable for this version (neither the GitHub Release notes nor the CHANGELOG.md section carried one). Author a real description from this version's changelog entries and regenerate.
#Features
- Python, Go, TypeScript/JS extractors
- Custom directive plugins
selfdoc checkcoverage analysis- HTML generation with responsive CSS
- Deploy to Cloudflare Pages + GitHub Pages
- SSE live reload in
selfdoc serve - Code-aware static site generator
:::directivesyntax for embedding code-extracted content- 5 built-in directives (module, schema, test, cli, config)