diff --git a/docs/requirements.txt b/docs/requirements.txt index 77a9dbbc..64c350bc 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -3,3 +3,4 @@ mkdocs==1.6.1 mkdocs-material==9.6.21 mkdocs-llmstxt==0.3.0 +mkdocs-git-revision-date-localized-plugin==1.5.3 diff --git a/docs/site/concepts/gates-and-decisions.md b/docs/site/concepts/gates-and-decisions.md index 1298e0d0..803e4f9c 100644 --- a/docs/site/concepts/gates-and-decisions.md +++ b/docs/site/concepts/gates-and-decisions.md @@ -4,7 +4,7 @@ A gate is the decision point at the end of a stage where nothing advances withou Each call you make sharpens the bar, and the destination is delegation. When you are sufficiently confident in the workflow and the bar you set, hand over the conn and let the first officer drive multiple tasks with auto-approval: -``` +```text you have the conn to drive toward your sprint goal, authorized to approve and merge PR on CI green. use your judgement. ``` @@ -15,7 +15,7 @@ Until then, the gates are yours. A gate review has a fixed spine: the first three lines and the last line carry the decision; everything between is supporting evidence. If you stop reading after line three, you can still vote. -``` +```text Gate review: Fix the flaky login test — review Chosen direction: replace sleep-based waits with event polling Recommend reject: the AC-2 retry scenario has no covering test. diff --git a/docs/site/get-started/first-workflow.md b/docs/site/get-started/first-workflow.md index 8c74024e..55502007 100644 --- a/docs/site/get-started/first-workflow.md +++ b/docs/site/get-started/first-workflow.md @@ -63,7 +63,7 @@ On accept, commission dispatches your seed items in parallel, each moving through the stages until everything is idle or waiting on you. When a work item reaches the `review` gate, you get a gate review: -``` +```text Gate review: Add rate limiting to the API — review Chosen direction: token-bucket limiter at the API middleware layer Recommend approve. diff --git a/docs/site/reference/command-reference.md b/docs/site/reference/command-reference.md index 4945de69..e113254d 100644 --- a/docs/site/reference/command-reference.md +++ b/docs/site/reference/command-reference.md @@ -6,7 +6,7 @@ The `spacedock` binary groups its subcommands into Launch, Setup, and Workflow, `spacedock --version` prints the version and contract level, the sandbox posture, then a per-runtime line reporting the installed spacedock plugin version: -``` +```text spacedock 0.20.1 (contract 1) Sandbox: available, not enabled (no .safehouse profile) claude: spacedock 0.20.1 diff --git a/docs/site/reference/glossary.md b/docs/site/reference/glossary.md new file mode 100644 index 00000000..ad2a8408 --- /dev/null +++ b/docs/site/reference/glossary.md @@ -0,0 +1,39 @@ +# Glossary + +The core Spacedock terms, in one place. Each links to where the concept is covered in depth. + +## Workflow + +A directory plus a `README.md` that declares the stages a work item moves through and what each stage expects. A project can hold more than one. See [Workflows and entities](../concepts/workflows-and-entities.md). + +## Entity + +One work item, stored as a single markdown file inside a workflow. Its frontmatter holds the machine-readable state (id, current stage, outcome); its body holds the problem, design notes, the bar for done, and the reports filed as it advances. See [Workflows and entities](../concepts/workflows-and-entities.md). + +## Stage + +A named step in a workflow (for example `design`, `implementation`, `review`). The README defines what each stage means, what counts as good, and what a worker must produce. See [The stage lifecycle](../concepts/stage-lifecycle.md). + +## Gate + +The decision point at the end of a stage where nothing advances without your vote. When a stage declares a gate, the first officer stops, presents a review, and waits for you to approve, redo with feedback, or reject. See [Gates and decisions](../concepts/gates-and-decisions.md). + +## First officer + +The orchestrator agent. It runs the workflow for you: finds what is ready, dispatches workers, checks results against the bar you set, and brings each gate decision to you with evidence. There is one per session. See [The operating model](../concepts/operating-model.md). + +## Ensign + +The worker agent. It moves one work item through one stage and proves the work, then signals completion. Ensigns come and go with the work. See [The operating model](../concepts/operating-model.md). + +## Mod + +An opt-in extension that adds behavior to a workflow without changing its stages, such as the [pr-merge mod](../advanced/mods-and-standing-teammates.md) that manages the pull-request lifecycle so merging never has to be a stage. See [Mods and standing teammates](../advanced/mods-and-standing-teammates.md). + +## Standing teammate + +A long-lived agent the first officer keeps around across dispatches for a recurring role (for example a reviewer), rather than spawning a fresh worker each time. See [Mods and standing teammates](../advanced/mods-and-standing-teammates.md). + +## Split-root state + +A layout that keeps a workflow's mutable state (entity bodies, stage reports) in a separate state checkout from the code, so concurrent workers commit state without colliding on the code branch. See [Split-root state](../advanced/split-root-state.md). diff --git a/docs/site/running-workflows/commission.md b/docs/site/running-workflows/commission.md index 5340636c..d2b682b4 100644 --- a/docs/site/running-workflows/commission.md +++ b/docs/site/running-workflows/commission.md @@ -4,7 +4,7 @@ A workflow is designed in conversation: you make four decisions and commission d Invoke it from a session started with `spacedock claude`. You can pass the mission inline: -``` +```text /spacedock:commission product idea to simulated customer interview ``` diff --git a/hooks/enrich_md_mirrors.py b/hooks/enrich_md_mirrors.py new file mode 100644 index 00000000..32864d89 --- /dev/null +++ b/hooks/enrich_md_mirrors.py @@ -0,0 +1,137 @@ +# ABOUTME: MkDocs hook that enriches the per-page .md mirrors emitted by +# ABOUTME: mkdocs-llmstxt with YAML frontmatter and a ## Sitemap section. +"""Enrich the llmstxt per-page markdown mirrors for agent-readability. + +mkdocs-llmstxt writes a markdown mirror of every page at +``/index.md`` in its own ``on_post_build``. Those mirrors +carry no frontmatter and no sitemap section, and the plugin offers no config to +inject them. This hook collects each page's metadata during the build, then in +its own ``on_post_build`` (which MkDocs runs after the plugin's, because hooks +register after the ``plugins`` list) prepends a YAML frontmatter block +(``title``, ``description``, ``doc_version``, ``last_updated``) and appends a +short ``## Sitemap`` section of sibling-page links. + +Clears the agent-readability ``markdown.frontmatter`` and +``markdown.sitemap-section`` checks. +""" + +import json +import os +from pathlib import Path + +# Per-page metadata gathered during the build, keyed by the page's output URL +# (e.g. "concepts/workflows-and-entities/", or "" for the home page). Populated +# in on_page_content; consumed in on_post_build. +_PAGES = {} + +# Site-level config captured in on_config for use in on_post_build. +_CONFIG = {} + + +def on_config(config, **kwargs): + _CONFIG["site_description"] = config.get("site_description", "") + _CONFIG["doc_version"] = (config.get("extra", {}) or {}).get("doc_version", "") + _CONFIG["site_dir"] = config["site_dir"] + return config + + +def on_page_content(html, page, config, files, **kwargs): + """Capture each page's frontmatter inputs and its sibling links. + + Runs after the page is rendered, so page.meta (including the git-revision + date) and page.parent are fully populated. + """ + meta = page.meta or {} + + title = (page.title or "").strip() + description = (meta.get("description") or _CONFIG.get("site_description") or "").strip() + + last_updated = ( + meta.get("git_revision_date_localized_raw_iso_datetime") + or meta.get("git_revision_date_localized_raw_datetime") + or "" + ) + + # Sibling pages within the same nav section, linked to their .md mirrors. + siblings = [] + parent = page.parent + if parent is not None and getattr(parent, "children", None): + for child in parent.children: + if getattr(child, "is_page", False) and child.url and child.url != page.url: + child_title = (child.title or "").strip() + # Mirror lives at index.md, relative to this mirror. + href = _relative_mirror_href(page.url, child.url) + siblings.append((child_title, href)) + + _PAGES[page.url] = { + "title": title, + "description": description, + "last_updated": last_updated, + "siblings": siblings, + } + return html + + +def on_post_build(config, **kwargs): + """Rewrite each emitted .md mirror with frontmatter + a Sitemap section.""" + site_dir = Path(_CONFIG["site_dir"]) + doc_version = _CONFIG.get("doc_version", "") + + for url, data in _PAGES.items(): + mirror = site_dir / url / "index.md" + if not mirror.is_file(): + continue + + body = mirror.read_text(encoding="utf-8") + + frontmatter = _build_frontmatter( + title=data["title"], + description=data["description"], + doc_version=doc_version, + last_updated=data["last_updated"], + ) + sitemap = _build_sitemap(data["siblings"]) + + mirror.write_text(frontmatter + body.rstrip("\n") + "\n" + sitemap, encoding="utf-8") + + +def _build_frontmatter(title, description, doc_version, last_updated): + """A YAML frontmatter block with the four agent-readability keys. + + Values are JSON-encoded so colons, quotes, and unicode stay valid YAML + (JSON is a YAML subset for scalar strings). + """ + lines = ["---"] + lines.append("title: " + json.dumps(title, ensure_ascii=False)) + lines.append("description: " + json.dumps(description, ensure_ascii=False)) + lines.append("doc_version: " + json.dumps(str(doc_version), ensure_ascii=False)) + lines.append("last_updated: " + json.dumps(str(last_updated), ensure_ascii=False)) + lines.append("---") + lines.append("") + return "\n".join(lines) + "\n" + + +def _build_sitemap(siblings): + """A ## Sitemap section linking sibling pages' markdown mirrors.""" + lines = ["", "## Sitemap", ""] + if siblings: + for title, href in siblings: + label = title or href + lines.append(f"- [{label}]({href})") + else: + lines.append("- [Documentation home](/docs/)") + lines.append("") + return "\n".join(lines) + + +def _relative_mirror_href(from_url, to_url): + """Relative href from one page's mirror to another page's .md mirror. + + Both mirrors sit at index.md. From "a/b/" to "c/d/" the relative path + from a/b/index.md to c/d/index.md is computed against the page directories. + """ + from_dir = from_url.rstrip("/") + to_dir = to_url.rstrip("/") + target = (to_dir + "/index.md") if to_dir else "index.md" + rel = os.path.relpath(target, start=from_dir if from_dir else ".") + return rel diff --git a/mkdocs.yml b/mkdocs.yml index 8cc62cfa..61b80e62 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -45,6 +45,16 @@ theme: plugins: - search + - git-revision-date-localized: + enable_creation_date: false + type: iso_datetime + # A page with no git history yet (a newly added file) uses the build date + # rather than aborting the strict build with a "no git logs" warning. + fallback_to_build_date: true + # Serial processing: the parallel default needs semaphore creation, + # which is blocked under the sandboxed build environment. Serial cost + # is negligible for this doc set. + enable_parallel_processing: false - llmstxt: full_output: llms-full.txt markdown_description: >- @@ -90,9 +100,20 @@ nav: - Reference: - Command reference: reference/command-reference.md - Frontmatter contract: reference/frontmatter-contract.md + - Glossary: reference/glossary.md - Supported sandboxes: reference/sandbox.md - Contributing: https://github.com/spacedock-dev/spacedock/blob/main/CONTRIBUTING.md +hooks: + # Enriches the per-page .md mirrors emitted by mkdocs-llmstxt with YAML + # frontmatter and a ## Sitemap section (agent-readability markdown.frontmatter + # and markdown.sitemap-section). Runs in on_post_build after llmstxt writes. + - hooks/enrich_md_mirrors.py + +extra: + # Stamped into the per-page .md-mirror frontmatter (doc_version). Bump on release. + doc_version: 0.20.2 + extra_css: - stylesheets/tokens.css - stylesheets/brand.css @@ -101,6 +122,11 @@ markdown_extensions: - admonition - pymdownx.highlight: anchor_linenums: true + # Emit language-* classes on (agent-readability code.language-tags). + # Pygments highlights inline via and drops the language-* class; + # disabling it renders instead of server-side + # syntax coloring. + use_pygments: false - pymdownx.details - pymdownx.superfences: custom_fences: diff --git a/overrides/main.html b/overrides/main.html index e05b415c..c8ca68ac 100644 --- a/overrides/main.html +++ b/overrides/main.html @@ -12,4 +12,43 @@ {{ super() }} + {#- + Agent-readability (a14y scorecard 0.2.0): inject Open Graph, JSON-LD + (TechArticle + BreadcrumbList + dateModified), and a markdown alternate-link + into every content page's , mirroring the landing page's Astro layout. + The {% if page %} guard is load-bearing: Material renders extrahead for the + 404 page with page = None, and an unguarded page.* reference fails + `mkdocs build --strict` with "'None' has no attribute 'meta'". + -#} + {% if page %} + + + + + + + {% endif %} {% endblock %} diff --git a/overrides/partials/footer.html b/overrides/partials/footer.html index e56ae411..2397e78b 100644 --- a/overrides/partials/footer.html +++ b/overrides/partials/footer.html @@ -46,6 +46,7 @@