Skip to content

Astro + Starlight docs site + Warp docubot port + /update-docs decommission — TECH

Astro + Starlight docs site + Warp docubot port + /update-docs decommission — TECH

Section titled “Astro + Starlight docs site + Warp docubot port + /update-docs decommission — TECH”

Status: RATIFIED — authored kh-prod-readiness-S60 as {9.3} of Task ID-9 by a FRESH Planner instance per Q-PLANNER-2. Full REWRITE of the stale S47 TECH (which covered only third 1 of 4 and predates PRODUCT.md ratification). Companion: ./PRODUCT.md (52 numbered Behaviour invariants across four scope-thirds + supporting deliverables). Predecessors: ./PRODUCT.md (ratified S59) + docs/research/docs-site-rebuild-research.md (Subtask {9.1} RESEARCH).


This is the technical companion to ./PRODUCT.md. PRODUCT lists 52 numbered Behaviour invariants (Inv-1..Inv-52) across four scope-thirds plus supporting deliverables. This TECH is structured so that every invariant resolves to a concrete piece of plumbing or a verification step:

  • §1 Context — current state + decommission targets.
  • §2 Docs site infrastructure — implements Inv-1..Inv-24 (third 1 of 4).
  • §3 Docubot port — implements Inv-25..Inv-35 (third 2 of 4).
  • §4 Five-skill port — implements Inv-36..Inv-43 (third 3 of 4).
  • §5 /update-docs decommission — implements Inv-44..Inv-50 (third 4 of 4).
  • §6 Cross-third integration — supporting deliverables Inv-51..Inv-52 (AGENTS.md + keep-docs-in-sync).
  • §7 Cross-project packaging — token-sharing, generator wiring, secrets contract.
  • §Testing-and-validation — the 1-to-1 invariant → test mapping table.
  • End-to-end flow — Mermaid sequence diagram for the docubot happy path.
  • Risks-and-mitigations / Parallelization / Follow-ups / New OQs.

Open-question carve-outs introduced by this TECH

Section titled “Open-question carve-outs introduced by this TECH”

Three new OQs are surfaced at TECH (OQ-T1 / OQ-T2 / OQ-T3 — see §Open-questions at the end). Each carries a default the Executor may proceed against unless Liam overrides. Together with PRODUCT’s three new OQs (OQ-6 / OQ-7 / OQ-8) this Task carries six open-question carve-outs into implementation; none block {9.4} PLAN dispatch.

Critical locks (load-bearing for the Executor)

Section titled “Critical locks (load-bearing for the Executor)”

Four locks land in this spec and the Executor MUST NOT silently deviate:

  1. Inv-30 docubot direct-write path: docubot writes directly to docs-site/src/content/docs/<space>/<file>.md. The docs/ tree is NOT updated by docubot on this path. Divergence handling proposed in §3.4 (manifest-tracked “docubot-owned” paths + reverse-sync rebase on next build-time sync).
  2. KH-persona prompt template (RESEARCH §2.3 adaptation): the concrete envsubst’d template lands at .github/actions/docubot/prompt.txt per §3.3 below — not a reference, the actual structure.
  3. Canonical 5-skill workflow shape (Inv-43): all five ported skills share one .github/workflows/<skill-name>.yml template documented at §4.1 — not five bespoke shapes.
  4. Vercel-default-subdomain framing throughout: every reference to a published-site URL uses “Vercel default subdomain (TBC at deploy time)” or <vercel-default-subdomain> as a placeholder. The stale S47 TECH’s docs.kh.phew.org.uk references are PURGED across this REWRITE — no occurrence survives.

Knowledge Hub today has no published documentation surface. The docs/ tree (478 live .md files at HEAD 2370bf96) holds high-quality markdown across:

  • docs/product-functionality/ (per-feature behavioural docs; subdirs administration/, ai-integration/, bid-management/, content-management/, knowledge-organisation/, quality-governance/, search/)
  • docs/ontology/ (33 numbered taxonomy / cardinality specs 01-.. through 33-..)
  • docs/reference/ (32 canonical reference docs — schema, AI policy, skill routing, state-of-the-product, taxonomies)
  • docs/runbooks/ (12 operational runbooks — CI, local dev, staging refresh, deployments)
  • docs/design/ (Warm Meridian implementation spec + philosophy + identity PDF)
  • docs/generated/ (auto-generated codebase stats + MCP inventory + type-drift baseline)
  • docs/specs/<id>/ (per-Task PRODUCT.md + TECH.md pairs — current Task ID-9 lives here)
  • docs/research/ (per-Task research outputs — current {9.1} RESEARCH lives here)
  • docs/continuation-prompts/ (session handover docs — OUT-OF-SCOPE for publication)

Reading these requires cloning the repo or browsing GitHub. The S45 decision archived a GitBook approach in favour of Astro + Starlight, with github.com/warpdotdev/docs as the reference implementation. The S58 RESEARCH ({9.1}) inventoried Warp’s 6 docs skills + docubot mechanism + current KH IA + delta vs stale S47 specs.

Four artefacts retire across this Task:

ArtefactSuccessorDecommission gate
.claude/skills/update-docs/SKILL.md (27KB, 4 functions)Functions (a)+(b)+(d) → docubot; function (c) bun run stats + bun run generate:mcp-inventoryci.yml regenerate-stats jobSession B (Inv-48)
Stale docs/specs/id-9-astro-starlight-docs-foundation/TECH.md (S47)This REWRITE (commit replaces file atomically)This Subtask close
docs.kh.phew.org.uk framing (stale)Vercel default subdomain (<vercel-default-subdomain>)This Subtask close
CLAUDE.md /update-docs referencesDocubot + keep-docs-in-sync referencesSession B (Inv-49 atomic)

The Executor will need these for implementation:

  • docs/design/warm-meridian-implementation-spec.md — Warm Meridian token vocabulary (consumed in §2.4 theming).
  • app/globals.css — canonical CSS custom properties (mirrored into docs-site/src/styles/warm-meridian.css).
  • docs/reference/ai-visibility-policy.md — AI-invisibility rules consumed by the AGENTS.md style guide + CI grep guard (Inv-23).
  • docs/reference/documentation-inventory.md — consulted by docubot before drafting (Inv-31).
  • .github/workflows/ci.yml — extant 7-job topology; new regenerate-stats job appends (Inv-45).
  • proxy.ts:13 publicRoutes — irrelevant to the docs site directly (separate origin) but cross-links from docs to app routes should respect the allowlist.
  • package.json:43 "stats" + :45 "generate:mcp-inventory" — the two commands the new regenerate-stats job runs.
  • .claude/skills/update-docs/SKILL.md — the skill being decommissioned (read before removal to verify successor coverage).
  • .gitnexus/CLAUDE.md + .ast-dataflow/CLAUDE.md — imported into CLAUDE.md (atomic update in Inv-49 must keep these intact).

The stale S47 TECH covers ONLY third 1 of 4 (docs site infrastructure), predates PRODUCT.md ratification, and uses three frames now invalidated:

  • The opt-in manifest model (PRODUCT Inv-3 flips to include-by-default + deny-list — corpus is too large for hand-curation).
  • The wp6-ontology-harness first-sync target (does not exist at HEAD; PRODUCT Inv-3 obviates “first-sync target” framing entirely).
  • The docs.kh.phew.org.uk domain (PRODUCT Inv-2 confirms Vercel default subdomain; the stale framing is PURGED here).

Extending the stale TECH would carry forward three frames that no longer match PRODUCT — REWRITE is cleaner and the diff is preserved in git history for audit. The stale 21-invariant → verification mapping in the S47 TECH §Testing-and-validation is fully superseded by the 52-invariant table at §Testing-and-validation below.


§2 Docs site infrastructure (third 1 of 4 — implements Inv-1..Inv-24)

Section titled “§2 Docs site infrastructure (third 1 of 4 — implements Inv-1..Inv-24)”

Sibling docs-site/ directory inside the KH repo (resolved at stale S47 TECH Q2; carries forward unchanged):

knowledge-hub/
app/ (Next.js app, unchanged)
docs/ (markdown source-of-truth, unchanged)
product-functionality/
ontology/
reference/
runbooks/
design/
generated/
specs/<id>/ (PRODUCT.md + TECH.md pairs)
docs-site/ (NEW — Astro + Starlight project root)
astro.config.mjs
package.json (independent dependency tree, bun)
sync-manifest.json (deny-list + IA overrides — NOT allow-list)
src/
content.config.ts (Zod front-matter schema)
content/
docs/ (Starlight content collection)
product-functionality/
ontology/
reference/
runbooks/
decisions/ (sourced from ratified docs/specs/<id>/)
styles/
warm-meridian.css
assets/
public/
scripts/
sync-content.ts (build-time sync; --source-root flag)
vercel.json
package.json (root — Next.js, unchanged)
.github/
actions/
docubot/ (NEW — composite action; third 2 of 4)
action.yml
prompt.txt
workflows/
ci.yml (EDIT — append regenerate-stats job)
docubot.yml (NEW — third 2 of 4)
review-docs-pr.yml (NEW — third 3 of 4, skill 1)
sync-source-docs.yml (NEW — third 3 of 4, skill 2)
missing-docs.yml (NEW — third 3 of 4, skill 3)
check-for-broken-links.yml (NEW — third 3 of 4, skill 4)
docs-seo-audit.yml (NEW — third 3 of 4, skill 5)
.claude/
skills/
keep-docs-in-sync/SKILL.md (NEW — {9.5+} per Inv-52)
review-docs-pr/SKILL.md (NEW — third 3 of 4)
sync-source-docs/SKILL.md (NEW — third 3 of 4; renamed from Warp `sync-error-docs`)
missing-docs/SKILL.md (NEW — third 3 of 4)
check-for-broken-links/SKILL.md (NEW — third 3 of 4)
docs-seo-audit/SKILL.md (NEW — third 3 of 4)
update-docs/ (REMOVE in Session B per Inv-48)
AGENTS.md (NEW — repo-root style guide; {9.5+} per Inv-51)

Rationale: isolation of Astro dependency tree from Next.js, clean deploy boundary, two package.json trees co-located but separately resolved. Mirrors Warp’s separation (their docs are a sibling repo; KH co-locates because the corpus is repo-internal).

Sibling Vercel project knowledge-hub-docs (separate Vercel project from the existing KH app project). Resolved at stale S47 TECH Q1.

  • Build settings: root directory docs-site/, build command bun install && bun run build, output directory docs-site/dist.
  • Domain: Vercel default subdomain (<vercel-default-subdomain> — exact string TBC by Liam from Vercel project history pre-deploy; per Inv-2 NOT under kh.phew.org.uk).
  • Branch deploy boundary: production deploys from main. Preview deploys from any branch with a docs-site change. Long-lived production-readiness worktree contributes via merge to main like any other branch (Inv-21).
  • Independent rollback cadence: broken docs build does NOT block app deploys (separate Vercel project).

docs-site/astro.config.mjs:

import { defineConfig } from 'astro/config';
import starlight from '@astrojs/starlight';
import vercel from '@astrojs/vercel/static';
export default defineConfig({
// Vercel default subdomain — TBC at deploy time per PRODUCT Inv-2
// (not under kh.phew.org.uk per stale-S47 Q4 reframing).
site: 'https://<vercel-default-subdomain>',
adapter: vercel(),
integrations: [
starlight({
title: 'Knowledge Hub',
description:
'Specs, runbooks, ontology, and reference for the Knowledge Hub platform.',
logo: { src: './src/assets/kh-wordmark.svg', replacesTitle: true },
customCss: [
'./src/styles/warm-meridian.css',
],
editLink: {
baseUrl:
'https://github.com/ai-solution-hub/knowledge-hub/edit/main/',
},
lastUpdated: true,
pagination: true,
social: [
{
icon: 'github',
label: 'GitHub',
href: 'https://github.com/ai-solution-hub/knowledge-hub',
},
],
// Five-space IA per PRODUCT Inv-4 (ordered).
// OQ-4 OVERRIDE: split platform/ into product-functionality/ + ontology/.
sidebar: [
{
label: 'Product functionality',
autogenerate: { directory: 'product-functionality' },
},
{
label: 'Ontology',
autogenerate: { directory: 'ontology' },
},
{
label: 'Reference',
autogenerate: { directory: 'reference' },
},
{
label: 'Runbooks',
autogenerate: { directory: 'runbooks' },
},
{
label: 'Decisions',
autogenerate: { directory: 'decisions' },
},
],
// 404 page — branded with search + home link per Inv-17.
// Default Starlight 404 is sufficient; verify post-deploy.
}),
],
});

Notes (resolving each invariant):

  • Static adapter (@astrojs/vercel/static) — no per-request rendering per PRODUCT non-goals (Inv-7).
  • editLink.baseUrl → canonical source on main (Inv-13).
  • lastUpdated: true → git-history-derived DD/MM/YYYY (Inv-14). Starlight default formats date locale-aware; explicit date-fns localisation may be needed — verify in §Testing-and-validation row Inv-14.
  • sidebar.autogenerate → adding a file to a canonical front-door directory publishes automatically (Inv-3 include-by-default).
  • Pagefind built-in to Starlight (Inv-8).
  • Single-version at foundation; no versions config (Inv-7).

2.4 Content collection schema (Inv-16 Zod-validated front matter)

Section titled “2.4 Content collection schema (Inv-16 Zod-validated front matter)”

docs-site/src/content.config.ts:

import { defineCollection } from 'astro:content';
import { docsLoader } from '@astrojs/starlight/loaders';
import { docsSchema } from '@astrojs/starlight/schema';
import { z } from 'astro:content';
export const collections = {
docs: defineCollection({
loader: docsLoader(),
schema: docsSchema({
extend: z.object({
// Sync-script-managed; do NOT hand-edit. Required for published docs.
kh_source: z
.string()
.optional()
.describe(
'Repo-relative path of source markdown (set by sync-content.ts).',
),
kh_last_verified: z
.string()
.regex(/^\d{2}\/\d{2}\/\d{4}$/)
.optional()
.describe('DD/MM/YYYY of last KH session that verified this doc.'),
// Docubot-owned paths flag (per §3.4 divergence handling).
// Set true by docubot when it writes to docs-site/src/content/docs/
// directly without docs/ source. Build-time sync skips on next run.
kh_docubot_owned: z
.boolean()
.optional()
.describe('Path is docubot-owned (no docs/ source); skip build-time sync.'),
}),
}),
}),
};

Front-matter contract (per Inv-16):

  • Required: title.
  • Optional (author-facing): description, sidebar (order/label per Starlight schema), lastUpdated override.
  • Sync-managed (do NOT hand-edit): kh_source, kh_last_verified, kh_docubot_owned.

Build fails (Zod) on unknown or malformed fields — loud failure per Inv-16 + Inv-17.

2.5 Build-time sync (Inv-19 SUPPLEMENTARY path)

Section titled “2.5 Build-time sync (Inv-19 SUPPLEMENTARY path)”

docs-site/scripts/sync-content.ts is the build-time sync script. Per Inv-3, it operates on include-by-default + deny-list semantics over the five canonical front-door directories. Per Inv-19 it is SUPPLEMENTARY (dev-loop + first-build + deterministic re-mirror) — not the primary path.

docs-site/sync-manifest.json shape:

{
"source_root": "../docs",
"target_root": "./src/content/docs",
"spaces": [
{ "source": "product-functionality", "target": "product-functionality" },
{ "source": "ontology", "target": "ontology" },
{ "source": "reference", "target": "reference" },
{ "source": "runbooks", "target": "runbooks" },
{ "source": "specs", "target": "decisions",
"filter": "ratified-only" }
],
"additional_source_dirs": {
"reference": ["design", "generated"]
},
"deny_list": [
"reference/example-tm-tasks.json",
"reference/state-of-the-product-change-log.md",
"reference/state-of-the-product-change-log-section-5.md",
"reference/state-of-the-product-change-log-section-8.md",
"reference/task-list.json",
"reference/product-roadmap.json",
"reference/product-backlog.json",
"reference/taskmaster-schema-reference.md"
],
"docubot_owned_paths": []
}

Script behaviour (per Inv-3 + Inv-19 + Inv-22):

  1. Read manifest.
  2. For each spaces[] entry: walk source_root/source/ recursively, collect all .md + .mdx.
  3. For each additional_source_dirs[<target>][] entry: walk + append to the target space’s pool.
  4. For decisions space (specs source, filter: ratified-only): include docs/specs/<id>/PRODUCT.md + TECH.md only when both have Status: RATIFIED in their first 10 lines (cheap header grep).
  5. Exclude any path matching deny_list[].
  6. Skip any target whose front matter has kh_docubot_owned: true (the docubot owns this path; build-time sync would clobber the bot’s edit).
  7. For each surviving source: read, parse front matter (gray-matter), inject kh_source (repo-relative path), copy to target_root/<space>/<path>.
  8. Validate every target file matches the content-collection Zod schema (astro check).
  9. Build the diff of targets vs previous build (added / modified / removed paths) and print. CI logs the diff (Inv-22 — explicit removal tracking).
  10. Fail loudly on: missing source after manifest declaration, schema validation error, broken internal link in synced content (Starlight’s link validator), removed referenced page (per Inv-12 + Inv-22 — no silent 404).

The sync runs as a pre-build step:

docs-site/package.json
{
"scripts": {
"sync": "bun scripts/sync-content.ts",
"dev": "bun run sync && astro dev",
"build": "bun run sync && astro check && astro build"
}
}

--source-root flag (per PRODUCT OQ-7 DEFAULT — cross-worktree preview):

Terminal window
bun run sync -- --source-root ../../knowledge-hub/docs # sibling main worktree
bun run sync -- --source-root ../../knowledge-hub-production-readiness/docs

Default --source-root is ../docs (the current worktree’s docs). Sub-agent worktrees that want a localhost preview against a sibling worktree’s docs/ set this flag at bun run dev. CI always uses the default (this worktree’s docs/ is the source).

Section titled “2.6 Cross-space link rewriting (Inv-6 absolute-paths-only)”

Per Inv-6, cross-space links MUST be absolute path-style URLs (Starlight’s content-collection routing does NOT traverse spaces via relative paths). The sync script handles this in two passes:

  1. Detect: Walk the synced corpus, find every markdown link of shape […](../<space-or-non-space>/…). If the link target resolves to a different space than the source file’s space, flag it.
  2. Rewrite: Replace flagged links with absolute path-style URLs (/<target-space>/<slug>/). Same-space relative links unchanged.

Build fails if a flagged cross-space link does not resolve to a published target (per Inv-12).

2.7 Warm Meridian theming (Inv-10 + Inv-11)

Section titled “2.7 Warm Meridian theming (Inv-10 + Inv-11)”

docs-site/src/styles/warm-meridian.css mirrors app/globals.css semantic tokens onto Starlight’s --sl-color-* + --sl-font vocabulary. Light + dark mode both supported via Starlight’s data-theme attribute (parity with KH app’s .dark class-based toggle is NOT required — independent toggle, identical palette values per Inv-10).

Token approach (per the stale S47 §6 — carries forward, with the canonical-token block sourced from docs/design/warm-meridian-implementation-spec.md):

  • Mirror oklch primitives + KH semantic tokens (--background, --foreground, --primary, --border, --muted, --muted-foreground) into :root + [data-theme='dark'].
  • Override Starlight surface variables (--sl-color-accent, --sl-color-bg, --sl-color-bg-nav, --sl-color-bg-sidebar, --sl-color-text, --sl-color-hairline) to consume the KH tokens via var(--…).
  • Load Instrument Sans via Starlight’s head config (Google Fonts <link> for parity with next/font/google in app/layout.tsx).
  • Set --sl-content-width: 50rem for Warm Meridian’s compositional restraint.

Token-drift guard (per stale S47 §Risks + carried forward):

  • A small CI script asserts the warm-meridian.css token block matches a generated snapshot of app/globals.css core tokens. Cheap, deterministic. Lives at docs-site/scripts/check-token-parity.ts; runs in the docs-site build job (§7.2).

2.8 Code blocks, tables, callouts (Inv-9 + Inv-11)

Section titled “2.8 Code blocks, tables, callouts (Inv-9 + Inv-11)”

Starlight’s built-in Code component covers syntax highlighting + language label + copy button. Line numbers via expressive-code plugin (Starlight default).

Callouts / admonitions: Starlight’s built-in Aside component carries icon + text label by default (Inv-9 — never colour-only). Verify in axe-core scan (§Testing-and-validation row Inv-9).

Tables: Starlight default styling preserved. Header emphasis + aligned columns — manual visual check on first deploy.

  • 404 page: Starlight default branded 404 (consumes Warm Meridian theme via customCss). Offers search + home link by default. Verify post-deploy.
  • Empty sidebar group: Starlight omits empty groups by default. No bespoke handling needed.
  • No-match search: Pagefind built-in “no results” state. Verify with smoke test (§Testing-and-validation row Inv-8).
  • Build with malformed front matter / broken internal link / missing referenced page: astro check + Starlight link validator + sync script (§2.5 step 10) fail loudly. Verify with deliberate-broken-link fixture test (§Testing-and-validation row Inv-12 + Inv-17).

A grep test in CI fails the build if synced content matches the regex AI-powered|Sparkles|powered by (Claude|GPT|Anthropic|OpenAI). Implementation: docs-site/scripts/check-ai-invisibility.ts runs over docs-site/src/content/docs/**/*.{md,mdx} post-sync, exits non-zero on any match. Wired into the build job.

The regex is permissive at foundation — extend with a deny-list file if false positives surface (e.g. a doc legitimately discussing AI-visibility policy). Default deny-list entries: docs-site/src/content/docs/reference/ai-visibility-policy.md (the policy doc itself may reference these terms in a quoted/policy-explanatory context).

Body copy, navigation labels, callout titles, footer text — UK English (colour, organisation, DD/MM/YYYY). Enforcement at build time:

  • Optional retext-british (or similar wordlist-based lint) over synced content. Foundation-level: ship without the lint, add in a Follow-up if drift surfaces.
  • The AGENTS.md style guide (Inv-51) encodes UK English explicitly so docubot + the 5 skills emit UK English by default.
  • The sync script does not modify content (preserves source UK English verbatim).

§3 Docubot port (third 2 of 4 — implements Inv-25..Inv-35)

Section titled “§3 Docubot port (third 2 of 4 — implements Inv-25..Inv-35)”

.github/actions/docubot/action.yml:

name: 'Docubot — KH docs auto-PR'
description: 'Drives a Claude agent (via @anthropic-ai/claude-agent-sdk) to open a follow-up docs PR for a source-code PR merge.'
inputs:
pr_number:
description: 'PR number to react to (provided by workflow trigger).'
required: true
prompt_override:
description: 'Optional prompt override for workflow_dispatch invocation.'
required: false
claude_model:
description: 'Claude model to use (KH-renamed from Warp warp_channel).'
required: false
default: 'claude-opus-4-7'
runs:
using: 'composite'
steps:
- name: Checkout repo (full history for diff)
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Install deps (workspace-wide for SDK + scripts)
shell: bash
run: bun install --frozen-lockfile
- name: Gather PR context
id: pr_context
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PR_NUMBER='${{ inputs.pr_number }}'
echo "pr_title=$(gh pr view "$PR_NUMBER" --json title -q .title)" >> "$GITHUB_OUTPUT"
echo "pr_body=$(gh pr view "$PR_NUMBER" --json body -q .body)" >> "$GITHUB_OUTPUT"
echo "pr_url=$(gh pr view "$PR_NUMBER" --json url -q .url)" >> "$GITHUB_OUTPUT"
echo "pr_files=$(gh pr diff "$PR_NUMBER" --name-only | head -200)" >> "$GITHUB_OUTPUT"
- name: Render prompt template (envsubst)
shell: bash
env:
PR_OR_ISSUE_TITLE: ${{ steps.pr_context.outputs.pr_title }}
PR_OR_ISSUE_BODY: ${{ steps.pr_context.outputs.pr_body }}
PR_OR_ISSUE_URL: ${{ steps.pr_context.outputs.pr_url }}
PR_CHANGED_FILES: ${{ steps.pr_context.outputs.pr_files }}
PROMPT_OVERRIDE: ${{ inputs.prompt_override }}
run: |
if [ -n "$PROMPT_OVERRIDE" ]; then
echo "$PROMPT_OVERRIDE" > prompt.output.txt
else
envsubst < .github/actions/docubot/prompt.txt > prompt.output.txt
fi
- name: Run Claude agent
shell: bash
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CLAUDE_MODEL: ${{ inputs.claude_model }}
run: bun scripts/docubot/run-agent.ts --prompt-file prompt.output.txt
- name: Upload run artefacts (success OR fail)
if: always()
uses: actions/upload-artifact@v4
with:
name: docubot-run-${{ inputs.pr_number }}
path: |
prompt.output.txt
.docubot/run.log
retention-days: 30

Notes (resolving each invariant):

  • runs-on lives in the WORKFLOW (.github/workflows/docubot.yml) — composite action is runner-agnostic.
  • actions/checkout@v4 with fetch-depth: 0 — Inv-32 requires git history for source-PR diff.
  • oven-sh/setup-bun@v2 — KH uses bun (CLAUDE.md “Package manager: bun”). NOT setup-node@v4.
  • gh pr view / gh pr diff for context gather (Inv-32 / Inv-29 / Inv-30 source diff input).
  • envsubst template render → prompt.output.txt (which upload-artifact@v4 ships per Inv-34).
  • bun scripts/docubot/run-agent.ts — the TypeScript driver that loads the rendered prompt + invokes @anthropic-ai/claude-agent-sdk (Inv-32). Driver lives at scripts/docubot/run-agent.ts (NEW).
  • if: always() on the artefact upload — Inv-34 requires upload regardless of run outcome.

.github/workflows/docubot.yml:

name: docubot — KH docs auto-PR
on:
workflow_dispatch:
inputs:
pr_number:
description: 'PR number to react to'
required: true
prompt_override:
description: 'Optional bespoke prompt'
required: false
claude_model:
description: 'Claude model'
required: false
default: 'claude-opus-4-7'
pull_request:
types: [closed]
branches: [main]
# NOTE: issue_comment + pull_request_review_comment triggers DELIBERATELY
# OMITTED per Inv-25 (Warp's lesson: caused org-wide noise).
# NOTE: pull_request.types: [closed] requires the merged==true filter in the
# job-level if: — closed-without-merge must not trigger.
jobs:
docubot:
if: github.event_name == 'workflow_dispatch' || github.event.pull_request.merged == true
runs-on: ubuntu-latest # NOT Warp's namespace-profile-* per Inv-33
timeout-minutes: 30 # Inv-28 — alert if run exceeds 30 min
permissions:
contents: write # branch creation + push
pull-requests: write # gh pr create + gh pr comment
issues: write # gh pr comment on source PR
steps:
- name: Resolve PR number
id: pr
run: |
if [ -n "${{ inputs.pr_number }}" ]; then
echo "number=${{ inputs.pr_number }}" >> "$GITHUB_OUTPUT"
else
echo "number=${{ github.event.pull_request.number }}" >> "$GITHUB_OUTPUT"
fi
- uses: ./.github/actions/docubot
with:
pr_number: ${{ steps.pr.outputs.number }}
prompt_override: ${{ inputs.prompt_override }}
claude_model: ${{ inputs.claude_model }}

The if: on the job filters PRs that were closed-without-merge — only merged == true triggers per Inv-25.

3.3 KH-persona prompt template (Inv-31 + RESEARCH §2.3 adaptation)

Section titled “3.3 KH-persona prompt template (Inv-31 + RESEARCH §2.3 adaptation)”

.github/actions/docubot/prompt.txt (verbatim shape per RESEARCH §2.3, adapted for KH):

You are Claude, helping with the Knowledge Hub repository's docs.
Here is the context for your current task:
<pr_or_issue_title>$PR_OR_ISSUE_TITLE</pr_or_issue_title>
<pr_or_issue_body>$PR_OR_ISSUE_BODY</pr_or_issue_body>
<pr_or_issue_url>$PR_OR_ISSUE_URL</pr_or_issue_url>
<changed_files>$PR_CHANGED_FILES</changed_files>
Your persona: You are **docubot**, an automated docs-PR opener for the
Knowledge Hub repository (`ai-solution-hub/knowledge-hub`). When a source-code
PR merges to `main`, you read the diff and open a follow-up docs PR that
writes directly to `docs-site/src/content/docs/<space>/<file>.md` for any
documentation that needs updating.
You always run in the same repository as the source PR (NOT a sibling docs
repo). The docs-site lives at `docs-site/` within this repository.
Key rules:
- Truth first: only document behaviour present in the merged PR or linked
issues. Add `<!-- TODO: clarify -->` if something is ambiguous. Never
invent behaviour.
- Scope: update only the docs-site pages whose content is affected by the
merged PR plus any directly-impacted cross-references. Do NOT touch the
upstream `docs/` source tree on this path — your write target is
`docs-site/src/content/docs/<space>/<file>.md` exclusively (the
human-authored `docs/` source remains the source-of-truth for non-docubot
paths; build-time sync at `docs-site/scripts/sync-content.ts` mirrors
`docs/` → `docs-site/` for those).
- Mark docubot-owned writes: every file you create or modify under
`docs-site/src/content/docs/` MUST carry `kh_docubot_owned: true` in its
front matter. This is the signal that build-time sync will skip the path
on subsequent runs (so your edit is not clobbered).
- Style: load `AGENTS.md` (repo root) for KH voice, terminology, frontmatter
contract, content-type style guides, and AI-invisibility rules. Load
`.claude/skills/keep-docs-in-sync/SKILL.md` for IA conventions (five-space
layout: product-functionality / ontology / reference / runbooks /
decisions), commit + PR conventions, and the single-comment guardrail.
- Inventory check: before drafting a NEW doc, consult
`docs/reference/documentation-inventory.md`. Do NOT recreate a doc that
already exists; update or cross-reference instead.
- UK English: `colour`, `organisation`, `behaviour`, DD/MM/YYYY dates.
- AI-invisibility: per `docs/reference/ai-visibility-policy.md` — no
"AI-powered" badges, no Sparkles icons, no model names in user-facing
copy.
- Assets: place images in `docs-site/public/`, use kebab-case names, add
alt text.
- Privacy: no secrets, tokens, or internal credentials in commits or PR
bodies.
- Cross-linking: cross-space links MUST be absolute path-style URLs (e.g.
`/reference/schema-quick-reference/`). Same-space relative links are
fine.
Commit & PR conventions:
- Branch name: `docubot/<short-slug-from-source-pr-title>`
- Commit message style: `docs(area): concise summary` (Conventional Commits;
area = the affected space, e.g. `docs(reference): …`)
- PR title: `Docs: <concise summary> (from #<source-PR-number>)`
- PR body MUST include three sections:
- **Summary:** what changed and why (one paragraph).
- **Pages updated:** bulleted list of `docs-site/src/content/docs/<space>/<file>.md` paths.
- **Why:** which behaviour from the source PR drove each page update.
IMPORTANT OUTPUT INSTRUCTIONS:
- You are only allowed to leave ONE comment on the source PR per run. Post
it AT THE END of your run, NEVER earlier.
- Push your local branch with `-u origin <branch>` before creating the PR.
- The single comment MUST either:
(1) Provide the docs-PR link + a one-paragraph summary + tag the source-PR
author as a reviewer (e.g. `@<author>`).
(2) Explain why no doc changes were needed (with a one-paragraph
reasoning).
ADDITIONAL WORKFLOW REQUIREMENTS:
- Time-to-docs-PR target is 15 minutes from source-PR merge under normal
load. The action's timeout-minutes is 30; if you cannot complete within
that, the run is allowed to fail and a workflow-failure notification fires.
- All run artefacts (this prompt + your run log at .docubot/run.log) upload
via actions/upload-artifact@v4 with if: always() — your output is visible
for debugging regardless of run outcome.

Substitutions: envsubst resolves $PR_OR_ISSUE_TITLE, $PR_OR_ISSUE_BODY, $PR_OR_ISSUE_URL, $PR_CHANGED_FILES from the composite action’s environment (populated by the “Gather PR context” step).

3.4 Inv-30 docubot direct-write path + divergence handling

Section titled “3.4 Inv-30 docubot direct-write path + divergence handling”

Lock: docubot writes to docs-site/src/content/docs/<space>/<file>.md directly. The docs/ tree is NOT updated by docubot on this path. Per Inv-18 this is the PRIMARY production sync path; per Inv-19 the build-time sync at docs-site/scripts/sync-content.ts mirrors docs/docs-site/ for the SUPPLEMENTARY dev-loop + first-build paths.

Divergence problem: without coordination, two competing writers can collide on the same target path:

  • Path P is docubot-touched at time T1 (docubot writes docs-site/src/content/docs/reference/X.md directly).
  • Path P has a docs/reference/X.md source. At time T2, a developer runs bun run sync and the build-time sync rewrites docs-site/src/content/docs/reference/X.md with docs/reference/X.md’s contents — clobbering docubot’s edit.

Proposed mechanism (RECOMMENDED — Option A in OQ-T1): docubot-owned-path flagging via front-matter + sync-script honour.

  1. When docubot writes a file under docs-site/src/content/docs/, it sets kh_docubot_owned: true in the front matter (prompt rule in §3.3 enforces this).
  2. The build-time sync script (§2.5 step 6) reads the target file’s front matter before writing. If kh_docubot_owned: true is present, the sync script skips that target on this run.
  3. The path remains docubot-owned until a human authors a docs-PR that explicitly removes kh_docubot_owned: true from the front matter (which signals “this is back under build-time-sync ownership; please reverse-sync to the docs/ source”). A separate reverse-sync workflow (not in foundation scope; deferred to Follow-ups) handles the docs-site → docs/ reverse-mirror when this flag flips back.

Alternatives considered (rejected at foundation):

  • Option B — docubot writes to both docs/ AND docs-site/: rejected. PRODUCT Inv-30 says docubot does NOT update the docs/ tree. Writing to both would put docubot in conflict with the human-authoring contract for docs/.
  • Option C — lockfile / atomic write-to-temp+rename / retry-on-conflict: rejected. These solve different problems (concurrent writes, fs race conditions) and don’t address the semantic divergence between docubot ownership and build-time-sync ownership of a path.

Edge case — first docubot write to a previously build-time-synced path: docubot’s write IS the source-of-truth from that point forward. The previous build-time content was a mirror of docs/<space>/<file>.md; docubot’s edit replaces it AND sets the kh_docubot_owned flag. The docs/ source is now stale but UNTOUCHED — a follow-up reverse-sync (manual at foundation, or via a future reverse-sync workflow) can flow docubot’s content back upstream when the divergence is resolved.

Manifest tracking (optional Follow-up): docs-site/sync-manifest.json has a docubot_owned_paths: [] array that the sync script can use as a secondary index (faster than reading every target’s front matter). Foundation: front-matter check is authoritative; manifest array is a build-time optimisation deferred to Follow-ups.

scripts/docubot/run-agent.ts (NEW; ~80-120 LOC):

#!/usr/bin/env bun
import { Anthropic } from '@anthropic-ai/sdk';
import { Agent } from '@anthropic-ai/claude-agent-sdk';
import { readFile } from 'node:fs/promises';
import { parseArgs } from 'node:util';
const { values } = parseArgs({
options: {
'prompt-file': { type: 'string' },
},
});
const promptPath = values['prompt-file'];
if (!promptPath) {
console.error('--prompt-file is required');
process.exit(1);
}
const prompt = await readFile(promptPath, 'utf8');
const apiKey = process.env.ANTHROPIC_API_KEY;
if (!apiKey) {
console.error('ANTHROPIC_API_KEY not set');
process.exit(1);
}
const model = process.env.CLAUDE_MODEL ?? 'claude-opus-4-7';
const agent = new Agent({
client: new Anthropic({ apiKey }),
model,
// Tool surface: shell (gh CLI + git ops), Read/Write/Edit/Bash for
// file edits under docs-site/src/content/docs/, MCP server registrations
// available (KH MCP server runs in CI for the duration of the agent's
// session — see lib/mcp/ for tool/resource/prompt inventory).
tools: ['shell', 'fs.read', 'fs.write', 'fs.edit', 'mcp'],
// Cwd: $GITHUB_WORKSPACE (the source-PR repo root).
cwd: process.env.GITHUB_WORKSPACE,
});
const result = await agent.run({ prompt });
console.log(JSON.stringify(result, null, 2));
// run.log is written by the SDK; uploaded by the composite action's
// upload-artifact step regardless of exit code.

Notes:

  • Direct import from @anthropic-ai/claude-agent-sdk (NO barrel re-export — CLAUDE.md rule).
  • Agent shape per the SDK at the time of authoring; verify against installed version at {9.5+} implementation. Versions of the SDK are still evolving; pin to a known-good version in package.json.
  • cwd is $GITHUB_WORKSPACE (the source-PR repo root) — agent reads docs/, lib/, app/ for context AND writes to docs-site/src/content/docs/<space>/<file>.md directly per Inv-30.
  • MCP server access: KH MCP server (lib/mcp/) registrations are available to the agent for richer context (e.g. schema queries, taxonomy fetches). Verify the MCP server runs in CI before agent invocation; if not, drop the mcp tool from the agent’s tool list at foundation.
  • ${{ secrets.ANTHROPIC_API_KEY }} — for the SDK. NEW secret; Liam to add via GitHub repo settings before first run.
  • ${{ secrets.GITHUB_TOKEN }} — built-in; same-repo, so no PAT needed for git ops + gh pr comment + gh pr create.
  • runs-on: ubuntu-latest (Inv-33; explicitly NOT namespace-profile-ubuntu-20-04 per RESEARCH §2.2).

3.7 Time-to-docs-PR + observability (Inv-28)

Section titled “3.7 Time-to-docs-PR + observability (Inv-28)”
  • timeout-minutes: 30 at the workflow job level (above) — workflow fails (and notifies via standard GH Actions email/Slack integration) if exceeded.
  • Soft target 15 minutes per Inv-28. Foundation does NOT include bespoke metrics dashboarding; observability = the workflow’s per-run duration shown in the GH Actions UI.
  • Sample-run verification (per acceptance criteria third 2): “Time-to-docs-PR ≤ 15 minutes on at least 80% of sample runs” measured via GitHub Actions API timestamps (bun scripts/docubot/audit-time-to-pr.ts is a candidate Follow-up — not in foundation scope).

§4 Five-skill port (third 3 of 4 — implements Inv-36..Inv-43)

Section titled “§4 Five-skill port (third 3 of 4 — implements Inv-36..Inv-43)”

4.1 Canonical workflow shape (Inv-43 lock)

Section titled “4.1 Canonical workflow shape (Inv-43 lock)”

All 5 ported skills share ONE workflow template (Inv-43 lock — not five bespoke shapes). The template at .github/workflows/<skill-name>.yml:

name: <skill-name> — KH docs maintenance
on:
workflow_dispatch:
inputs:
target_pr_number:
description: 'Optional PR number to scope the skill to'
required: false
prompt_override:
description: 'Optional bespoke prompt override'
required: false
schedule:
# Per-skill cron (see §4.3-§4.7); commented-out for skills that don't schedule.
- cron: '<per-skill cron>'
jobs:
<skill-name>:
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: write
pull-requests: write
issues: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- run: bun install --frozen-lockfile
- name: Run skill via Claude Agent SDK
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TARGET_PR_NUMBER: ${{ inputs.target_pr_number }}
PROMPT_OVERRIDE: ${{ inputs.prompt_override }}
run: |
bun scripts/skills/run-skill.ts \
--skill <skill-name> \
--skill-md .claude/skills/<skill-name>/SKILL.md
- name: Upload run artefacts
if: always()
uses: actions/upload-artifact@v4
with:
name: <skill-name>-run-${{ github.run_id }}
path: |
.skills/<skill-name>/run.log
.skills/<skill-name>/output/**
retention-days: 30

Single driver script: scripts/skills/run-skill.ts (NEW; ~100 LOC). Takes --skill + --skill-md flags, loads the SKILL.md body, AGENTS.md, .claude/skills/keep-docs-in-sync/SKILL.md, plus skill-specific references/*.md. Invokes @anthropic-ai/claude-agent-sdk (same SDK driver shape as §3.5 but parameterised by skill). One driver, five workflows that call it with different --skill arguments — Inv-43 satisfied.

Per-skill references/*.md and scripts/*.py (when applicable) live at .claude/skills/<skill-name>/{references,scripts}/.

5 of Warp’s 6 docs skills port. update-changelog is OUT-OF-SCOPE per OQ-3 OVERRIDE. The 5 that port:

KH skill nameWarp originTrigger patternOutput
review-docs-prreview-docs-prworkflow_dispatch + PR-review eventreview.json + PR comments
sync-source-docssync-error-docs (KH-renamed)workflow_dispatch + schedule: cron weeklyDrift docs-PR
missing-docsmissing_docsworkflow_dispatch (+ Phase 2 schedule TBD)Gap list (Phase 1) + drafts (Phase 2)
check-for-broken-linkscheck_for_broken_linksworkflow_dispatch + schedule: cron dailyFindings report + PR comments
docs-seo-auditdocs-seo-auditworkflow_dispatch + schedule: cron monthlySEO findings + ASK-before-fix

Each skill lives at .claude/skills/<skill-name>/SKILL.md + (where applicable) scripts/*.py + references/*.md. Each loads AGENTS.md (Inv-37 / Inv-51) at runtime via the shared run-skill.ts driver.

Workflow shape per §4.1. Per-skill specifics:

  • Trigger override: in addition to workflow_dispatch, the workflow also triggers on pull_request_review events (when a human or bot reviews a docs PR). The job’s if: filters to PRs whose head branch is docubot/* OR base branch is main and the PR title starts with Docs: — so the review runs against docubot-opened docs PRs as a second auto-review pass (per Inv-35 Phase-2 composability) and against human-authored docs PRs.
  • Output contract: the agent emits .skills/review-docs-pr/output/review.json with { summary: string, comments: Array<{ path, line, severity, body }> }. Severity values: CRITICAL, IMPORTANT, SUGGESTION, NIT (Inv-38 — Warp’s labels ported without emoji per KH no-emoji rule).
  • PR comment posting: the driver script reads review.json and runs gh pr comment $PR_NUMBER --body "..." for each comments[] entry, with the severity as a prefix (e.g. [CRITICAL] …).
  • SKILL.md body: structural template from Warp’s review-docs-pr SKILL.md, with KH adaptations (UK English, no-emoji, Warm Meridian context for visual-callout reviews, link to docs/reference/ai-visibility-policy.md).

4.4 sync-source-docs (Inv-39 — renamed from Warp sync-error-docs)

Section titled “4.4 sync-source-docs (Inv-39 — renamed from Warp sync-error-docs)”

Per-skill specifics:

  • Trigger: workflow_dispatch + schedule: cron '0 6 * * 1' (weekly Monday 06:00 UTC).
  • Three source pairs:
    1. supabase/types/database.types.tsdocs-site/src/content/docs/reference/schema-quick-reference.md (and per-table pages where they exist under docs-site/src/content/docs/reference/schema/<table>.md — NEW; created on first run if missing).
    2. MCP registrations in lib/mcp/docs-site/src/content/docs/reference/mcp-inventory.md (currently generated by bun run generate:mcp-inventorysync-source-docs consumes its output OR independently audits the source).
    3. Route definitions in app/api/**/route.tsdocs-site/src/content/docs/reference/api-routes.md (NEW; sync-source-docs is responsible for first creation + ongoing sync).
  • Drift detection: the agent reads each source + the corresponding doc target + compares. On detected drift, opens a docs-PR titled docs(reference): sync <surface> drift with the rewritten target file(s). Marks the file kh_docubot_owned: true so build-time sync doesn’t clobber it (per §3.4).
  • SKILL.md body: structural template from Warp’s sync-error-docs SKILL.md, with KH adaptations (three KH source pairs above, no Warp-error-doc references).

Per-skill specifics:

  • Trigger: workflow_dispatch only at foundation. Phase 2 may add schedule: cron monthly.
  • Phase 1 audit: .claude/skills/missing-docs/scripts/audit_docs.py (Python). Four sub-audits:
    1. Feature flags / env vars: parse .env.example + grep process.env\. in lib/, app/, scripts/; cross-check against docs-site/src/content/docs/runbooks/local-development.md + docs-site/src/content/docs/runbooks/github-environments.md.
    2. CLI commands: parse package.json scripts + cross-check against docs-site/src/content/docs/runbooks/*.md.
    3. MCP / route surfaces: parse lib/mcp/tools/*.ts + app/api/**/route.ts + cross-check against docs-site/src/content/docs/reference/{mcp-inventory,api-routes}.md.
    4. Terminology staleness: read .claude/skills/missing-docs/references/stale_terms.md (KH-specific; lists terms like digest (should be change reports), Sparkles, AI-powered etc.) + grep across docs-site/src/content/docs/**/*.md.
  • Phase 2 draft: the agent reads .claude/skills/missing-docs/references/kh_surface_map.md (KH-specific; feature → doc-path mapping) + 2-3 strong examples from the existing corpus, then drafts the gap fills + opens a docs-PR per gap cluster.
  • Phase independence: each phase is independently triggerable via workflow_dispatch inputs (phase: audit | draft).

Per-skill specifics:

  • Trigger: workflow_dispatch + schedule: cron '0 5 * * *' (daily 05:00 UTC) + invoked from inside review-docs-pr (per Warp’s pattern — the agent driver calls bun scripts/skills/run-skill.ts --skill check-for-broken-links mid-review).
  • Script: .claude/skills/check-for-broken-links/scripts/check_links.py (Python). Generic link-walker over docs-site/src/content/docs/**/*.md. Five error types (per RESEARCH §1.5 + Inv-41):
    1. file-not-found
    2. case-mismatch (Astro is case-sensitive)
    3. missing .mdx extension on directory link
    4. cross-space relative path (per Inv-6 — should be absolute path-style)
    5. HTTP 4xx / timeout for external links (configurable timeout; default 10s)
  • Output: findings printed to stdout + uploaded as run artefact + (optional) posted as PR comment via --gh-pr-comment flag (replaces Warp’s --slack-notify mode per Inv-41).

Per-skill specifics:

  • Trigger: workflow_dispatch + schedule: cron '0 7 1 * *' (monthly 1st of month 07:00 UTC).
  • Deferral note: runs only after the docs-site is live + deployed (sitemap exists). At foundation: deploy first, then enable this skill’s schedule. Until deploy completes, the workflow file is committed but the cron is commented out.
  • Script + references: .claude/skills/docs-seo-audit/scripts/audit_seo.py + .claude/skills/docs-seo-audit/references/seo_issues.md (11+ issue types across error / warning / info severity tiers per RESEARCH §1.6).
  • ASK-before-fixing guardrail: the SKILL.md body preserves Warp’s “ASK before fixing” rule verbatim — the skill EMITS findings but does NOT auto-rewrite. Human approval gates any mass-rewrite via a follow-up workflow_dispatch with --fix mode. (Inv-42 explicit guard.)

§5 /update-docs decommission (third 4 of 4 — implements Inv-44..Inv-50)

Section titled “§5 /update-docs decommission (third 4 of 4 — implements Inv-44..Inv-50)”

5.1 Successor mapping summary (Inv-44 + Inv-45 + Inv-46)

Section titled “5.1 Successor mapping summary (Inv-44 + Inv-45 + Inv-46)”
Current /update-docs functionSuccessorSuccessor implementation
(a) refresh docs/reference/product-roadmap.jsonDocubot (third 2 of 4)§3 above; docubot reads PR diff and updates
(b) refresh docs/reference/state-of-the-product.mdDocubot§3 above
(c) bun run stats + bun run generate:mcp-inventoryci.yml regenerate-stats job (Inv-45)§5.2 below
(d) refresh docs/reference/product-backlog.jsonDocubot§3 above
auto-chain to /handoff/handoff standalone (Inv-46 — preserved as standalone skill)NO change; callers invoke /handoff directly

New job appended to .github/workflows/ci.yml after the existing ci-summary job:

regenerate-stats:
name: Regenerate generated docs (stats + MCP inventory)
# Only on push to main (after merge); NOT on PRs (PRs don't have write access
# to main, and we don't want stats regen on every PR run).
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: [quality-precheck, quality-test, mcp-build, mcp-eval-seed, mcp-eval, integration, type-drift-parity, ci-summary]
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: write # for the direct-commit path (OQ-6 Option A)
pull-requests: write # for the side-PR fallback path (OQ-6 Option B)
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
ref: main
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- run: bun install --frozen-lockfile
- name: Regenerate stats + MCP inventory
run: |
bun run stats
bun run generate:mcp-inventory
- name: Check for changes
id: diff
run: |
if git diff --quiet docs/generated/; then
echo "changed=false" >> "$GITHUB_OUTPUT"
else
echo "changed=true" >> "$GITHUB_OUTPUT"
fi
- name: Commit + push (Option A — direct commit; OQ-6 default)
if: steps.diff.outputs.changed == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git config user.email "actions@github.com"
git config user.name "github-actions[bot]"
git add docs/generated/
git commit -m "chore(docs): regenerate stats + MCP inventory [skip ci]"
if ! git push origin main; then
echo "Direct push failed (likely branch protection). Falling back to side-PR (OQ-6 Option B)."
BRANCH="chore/regenerate-stats-${{ github.run_id }}"
git checkout -b "$BRANCH"
git push -u origin "$BRANCH"
gh pr create --base main --head "$BRANCH" \
--title "chore: regenerate stats" \
--body "Automated stats regen from $GITHUB_SHA. Auto-merge once CI passes."
fi

OQ-6 ratification (PRODUCT OQ-6 — direct-commit vs side-PR): Option A default with Option B fallback (per the if ! git push branch above). Executor verifies branch protection allows the github-actions[bot] identity to push directly to main; if blocked, the script auto-falls-back. No human intervention needed.

The [skip ci] suffix on the commit message prevents an infinite CI loop (the regen commit would otherwise re-trigger ci.yml).

5.3 Two-session decommission sequence (Inv-47 + Inv-48)

Section titled “5.3 Two-session decommission sequence (Inv-47 + Inv-48)”

Session A (decommission gate 1) — ship docubot + verify:

Acceptance per PRODUCT Inv-47:

  1. Docubot opens at least 3 sample docs-PRs on real source-PR merges.
  2. All 3 narrative-doc updates ((a) + (b) + (d) workloads) execute correctly (Liam manual review).
  3. Docubot’s single-comment + commit-conventions hold across all 3 samples.

Session A is dispatched as one or more {9.5+} implementation Subtasks (decomposed in {9.4} PLAN). Status moves Session A → done when all 3 acceptance items verified.

Session B (decommission gate 2) — wire ci.yml stats + remove /update-docs:

Acceptance per PRODUCT Inv-48:

  1. ci.yml regenerate-stats job runs successfully on at least one merge-to-main commit.
  2. .claude/skills/update-docs/ directory removed.
  3. Any callers of /update-docs in .claude/ configs / hooks updated to invoke /handoff directly (where applicable) or removed (where redundant with docubot).

Session B is dispatched as one or more {9.5+} implementation Subtasks. Status moves Session B → done when all 3 acceptance items verified.

In the SAME commit as Session B’s removal of .claude/skills/update-docs/, CLAUDE.md is edited to:

  • Remove /update-docs references (Implementation Workflow section; any reference-doc tables that cite it).
  • Add docubot reference (new line in Implementation Workflow: “Docs auto-sync via .github/workflows/docubot.yml; keep-docs-in-sync skill for KH conventions”).
  • Add keep-docs-in-sync reference (new line in skill-routing or Implementation Workflow as appropriate).
  • Memory (MemPalace) section: review for any /update-docs mention; remove if present (foundation: grep CLAUDE.md once, evidence here is minimal — section likely untouched).
  • Reference-doc tables: review for /update-docs mention; remove if present.

No transition window where CLAUDE.md and the skill set are out of sync. The same commit removes /update-docs/ AND edits CLAUDE.md.

Atomicity verification: the commit’s diff shows BOTH .claude/skills/update-docs/ deletion AND CLAUDE.md edits in one atomic git operation. The Checker for the Session B Subtask verifies this.

Session B is hard-deadlined: /update-docs MUST be removed by end of Session B regardless of corner-case discoveries. Corner cases get filed as new Subtask {9.M+} follow-ups against docubot OR against the 5 skills, NEVER as “keep /update-docs around for a bit longer.”

Two-session bound is non-negotiable per Inv-50 — prevents decision-fatigue + dual-path drift.


§6 Cross-third integration (supporting deliverables — implements Inv-51..Inv-52)

Section titled “§6 Cross-third integration (supporting deliverables — implements Inv-51..Inv-52)”

NEW file at AGENTS.md (repo root, sibling to CLAUDE.md). Authored as one or more {9.5+} Subtasks (decomposed in {9.4} PLAN). Contents per PRODUCT Inv-51 + RESEARCH §4.3.5:

  1. Voice + tone rules: UK English, professional-direct, no marketing copy, no emoji.
  2. Terminology table: Knowledge Hub vs “the platform” vs “the app”; “Change Reports” vs “Digest” (per the S248 rename); canonical taxonomy terms; “docubot” vs Claude vs the SDK.
  3. Frontmatter contract: matching the Zod schema in §2.4 above.
  4. Content-type style guides: concept docs, procedural docs, reference docs, troubleshooting docs.
  5. AI-invisibility rules: per Inv-23 + docs/reference/ai-visibility-policy.md.

OQ-8 ratification (PRODUCT OQ-8 — AGENTS.md vs CLAUDE.md separation of concerns): Option A default (complementary; AGENTS.md cross-references CLAUDE.md for project-wide rules + ADDS docs-specific rules). The 5 ported skills look for AGENTS.md by name (per Warp convention); renaming/merging would break port fidelity.

AGENTS.md cross-references CLAUDE.md for project-wide rules (UK English, no-emoji, package-manager-bun, etc.) via section opener: “For project-wide conventions, see CLAUDE.md. This file adds docs-corpus-specific conventions on top.”

6.2 keep-docs-in-sync skill at .claude/skills/keep-docs-in-sync/SKILL.md (Inv-52)

Section titled “6.2 keep-docs-in-sync skill at .claude/skills/keep-docs-in-sync/SKILL.md (Inv-52)”

NEW skill, authored as one or more {9.5+} Subtasks. Contents per PRODUCT Inv-52 + RESEARCH §4.3.4:

  1. KH docs/ IA conventions (five-space layout per Inv-4 + cross-space link rule per Inv-6).
  2. Warm Meridian palette + typography references (per docs/design/warm-meridian-implementation-spec.md).
  3. AI-invisibility policy reference (per docs/reference/ai-visibility-policy.md).
  4. UK English requirements.
  5. docs/reference/documentation-inventory.md index (so docubot does not recreate existing docs).
  6. Commit + PR conventions (matching commit-commands:commit-push-pr).
  7. Single-comment guardrail (matching Warp’s pattern + Inv-27).

The docubot loads this skill in its prompt by default (§3.3). The 5 ported skills load it via scripts/skills/run-skill.ts (§4.1). Acts as the canonical “how does docubot author KH docs?” reference.

Both AGENTS.md and keep-docs-in-sync/SKILL.md are loaded by docubot + every ported skill. Loading happens in the agent driver scripts:

  • scripts/docubot/run-agent.ts reads AGENTS.md + .claude/skills/keep-docs-in-sync/SKILL.md into the prompt context before invoking the SDK.
  • scripts/skills/run-skill.ts reads AGENTS.md + .claude/skills/keep-docs-in-sync/SKILL.md + the per-skill SKILL.md (per --skill flag) into prompt context.

Verification (per acceptance criteria supporting deliverables): grep the driver scripts for AGENTS.md reference, OR run a test workflow with a deliberately-broken AGENTS.md (e.g. missing terminology table) and verify the agent surfaces the missing context.


7.1 Token-sharing between app/globals.css and docs-site/src/styles/warm-meridian.css

Section titled “7.1 Token-sharing between app/globals.css and docs-site/src/styles/warm-meridian.css”

At foundation, tokens are duplicated (mirrored) between the two files. Per the stale S47 §6 + §Risks: long-term, extract Warm Meridian into a shared package both consume, but defer until a second consumer materialises (docs-site IS that second consumer, so this is a candidate Follow-up).

Token-drift guard (§2.7) provides the safety net at foundation: CI fails the docs-site build if the mirrored token block drifts from app/globals.css.

The docs-site build does NOT live in ci.yml (per OQ-6 / Vercel sibling-project model). The docs-site has its own Vercel deploy pipeline:

  • PR preview: Vercel auto-deploys preview URLs for any branch touching docs-site/.
  • Production: Vercel deploys on push to main (after ci.yml regenerate-stats job lands the regenerated docs/generated/ files).

The ci.yml topology gains ONE new job (regenerate-stats per §5.2). No other ci.yml jobs are affected.

Separately, the docubot + 5 skills workflows (docubot.yml + 5 sibling files) are independent .github/workflows/ files, each with its own trigger + permissions. They do NOT chain into ci.yml.

SecretUsed bySource
ANTHROPIC_API_KEYdocubot.yml + all 5 skill workflowsNEW; Liam adds via GitHub Settings
GITHUB_TOKENAll workflows (built-in)GitHub auto-provided
Vercel deploy keysVercel sibling project (configured Vercel-side)Vercel dashboard

No new Supabase secrets, no new env vars on the KH app side. Docs-site is fully independent.

  • Root package.json unchanged (Next.js app deps).
  • New docs-site/package.json: astro, @astrojs/starlight, @astrojs/vercel, gray-matter, zod (peer of astro:content).
  • @anthropic-ai/claude-agent-sdk + @anthropic-ai/sdk at root package.json devDependencies (since the agent drivers are CI-only, not bundled with the app).
  • Bun lockfiles for both: bun.lockb at root, docs-site/bun.lockb separately.

§Testing-and-validation — 52-invariant 1-to-1 mapping

Section titled “§Testing-and-validation — 52-invariant 1-to-1 mapping”

Each row maps a PRODUCT invariant to its TECH implementation section + concrete test file location. Test files marked (NEW) are authored at {9.5+} implementation Subtasks per {9.4} PLAN decomposition.

Per CLAUDE.md test philosophy: tests verify real behaviour, not implementation. Vitest tests via bun run test (NOT bun test). Playwright tests via bun run test:e2e.

InvWhat it assertsTECH §Test file / verification
1Authoring stays in repo docs/ markdown; no authoring inside docs-site/§2.1docs-site/__tests__/sync-content.test.ts (NEW) — assert sync script reads from ../docs; assert no .md author files committed under docs-site/src/content/docs/ outside sync output.
2Static site at Vercel default subdomain; not under kh.phew.org.uk§2.2Manual: post-deploy URL check. docs-site/astro.config.mjs grep test (NEW) asserts site: does NOT contain kh.phew.org.uk.
3Include-by-default canonical front-door directories; deny-list opt-out§2.5docs-site/__tests__/sync-manifest.test.ts (NEW) — fixture: 5 canonical dirs each with sample files + deny-list entries; assert all non-denied files published.
4Five-space IA in sidebar order: product-functionality / ontology / reference / runbooks / decisions§2.3docs-site/__tests__/astro-config-sidebar.test.ts (NEW) — parse astro.config.mjs sidebar array; assert 5 entries in exact order with correct autogenerate.directory values.
5Each space’s landing index page declares purpose statement§2.3 + §2.5docs-site/__tests__/space-landing-pages.test.ts (NEW) — assert each space’s index page exists; assert first paragraph contains purpose-matching keywords (one per space).
6Cross-space links use absolute path-style URLs; build rejects relative cross-space§2.6docs-site/__tests__/cross-space-links.test.ts (NEW) — fixture: deliberate cross-space relative link; assert sync script flags + build fails.
7Single-version; no version switcher§2.3Manual: rendered chrome lacks version switcher. docs-site/__tests__/astro-config-versioning.test.ts (NEW) — assert versions field absent in astro.config.mjs.
8Pagefind search across all 5 spaces§2.3E2E docs-site/e2e/search.spec.ts (NEW) — Playwright: open search, query “ontology”, assert results from at least 2 spaces.
9Keyboard nav + screen reader + mobile + WCAG 2.1 AA; callouts have icon + text label§2.3 + §2.8E2E docs-site/e2e/accessibility.spec.ts (NEW) — Playwright + @axe-core/playwright axe scan on sample page; manual keyboard-only nav check; assert callout <Aside> carries icon.
10Warm Meridian palette + typography; light + dark via Starlight data-theme§2.7Manual visual check on deployed preview URL (screenshot stored alongside this spec). docs-site/__tests__/warm-meridian-tokens.test.ts (NEW) — assert mirrored tokens match app/globals.css snapshot.
11Code blocks: syntax highlight + line numbers + copy + language label; tables: header emphasis§2.8Manual visual check on rendered page with code + table. Playwright e2e (NEW) on code-block presence: copy button + language label visible.
12Internal links resolve to canonical published paths; removed-page references → build fail§2.3 + §2.5 + §2.9docs-site/__tests__/link-validator.test.ts (NEW) — fixture: deliberate broken link; assert Starlight build fails.
13”Edit this page on GitHub” link points at canonical source on main§2.3E2E docs-site/e2e/edit-link.spec.ts (NEW) — Playwright: open a page, click “Edit on GitHub”, assert URL contains /edit/main/.
14Last-updated DD/MM/YYYY from git history; not build time§2.3docs-site/__tests__/last-updated.test.ts (NEW) — pick a sample page, derive expected date from git log, assert rendered date matches.
15Pure-.md documents render without MDX§2.5docs-site/__tests__/pure-md-render.test.ts (NEW) — fixture: pure .md page; assert build + render succeeds.
16Minimal Zod-validated front matter; build fails on unknown / malformed fields§2.4docs-site/__tests__/frontmatter-schema.test.ts (NEW) — fixture: doc with unknown field; assert build fails with useful error.
17Empty / loading / error states; 404 branded; build-fail on malformed front matter / broken link§2.9E2E docs-site/e2e/404-page.spec.ts (NEW) — open /non-existent-path; assert branded 404 with search + home link. Combined with Inv-12 + Inv-16 broken-link fixture tests.
18PR-merge docubot is PRIMARY production sync path§3 + §3.4docs-site/__tests__/sync-hierarchy.test.ts (NEW) — assert sync script honours kh_docubot_owned: true (docubot ownership wins). Plus: §Testing-and-validation row Inv-30.
19Build-time sync is SUPPLEMENTARY dev-loop primitive§2.5docs-site/__tests__/build-time-sync.test.ts (NEW) — fixture: run bun run sync against fixture ../docs; assert content collection populated.
20Schema-driven scheduled syncs SUPPLEMENTARY for code-generated content§4.4__tests__/skills/sync-source-docs.test.ts (NEW) — fixture: drift in supabase/types/database.types.ts mock; assert sync-source-docs driver detects + opens drift PR.
21Search + navigation stable across worktrees (built from main; long-lived worktrees merge in)§2.2Manual: confirm production-readiness branch changes do NOT appear in production docs until merged to main. Documented in runbook.
22Content removal explicit + tracked (no silent drop on manifest typo)§2.5docs-site/__tests__/removal-tracking.test.ts (NEW) — fixture: remove a source file; assert sync diff lists the removal; assert build fails if a published doc references the removed file.
23AI-invisibility throughout docs surface; CI grep guard blocks publication of forbidden phrases§2.10docs-site/__tests__/ai-invisibility-guard.test.ts (NEW) — fixture: doc with “AI-powered” string; assert check-ai-invisibility.ts script exits non-zero.
24UK English throughout§2.11docs-site/__tests__/uk-english.test.ts (NEW) — fixture: doc with color (US); assert lint warns (optional retext-british integration). At foundation: AGENTS.md enforces; lint deferred.
InvWhat it assertsTECH §Test file / verification
25Trigger surface: workflow_dispatch + pull_request.types: [closed] filtered to merged == true§3.2.github/workflows/docubot.yml shape-test (NEW at __tests__/workflows/docubot-workflow-shape.test.ts) — parse YAML; assert triggers list = exactly [workflow_dispatch, pull_request]; assert if: contains merged == true.
26Single docs-PR per source-PR merge§3.3Integration test: run docubot against a sample source PR with multiple doc surfaces affected; assert exactly 1 docs-PR opened. Manual at foundation; automated via __tests__/integration/docubot-single-pr.integration.test.ts (NEW) once SDK harness exists.
27Single comment on source PR per run§3.3Integration test (paired with Inv-26): assert exactly 1 gh pr comment invocation. Prompt-rule enforcement; verify via run log inspection.
28Time-to-docs-PR target: 15 min; 30 min triggers workflow-failure notification§3.2 + §3.7.github/workflows/docubot.yml shape-test: assert timeout-minutes: 30. Live verification: 80% of sample runs < 15 min (per PRODUCT acceptance criteria third 2).
29KH commit conventions: branch docubot/<slug>; commit docs(area): summary; PR title prefix Docs:§3.3Integration test: sample run; assert branch name starts with docubot/; commit message matches ^docs\(.*\): ; PR title starts with Docs: .
30Docubot writes directly to docs-site/src/content/docs/<space>/<file>.md (NOT docs/)§3.3 + §3.4Integration test: sample run; assert docubot’s commit touches ONLY paths under docs-site/src/content/docs/; assert no docs/ paths touched. Plus: docs-site/__tests__/sync-hierarchy.test.ts per Inv-18.
31Docubot loads AGENTS.md + keep-docs-in-sync/SKILL.md; consults documentation-inventory.md§3.3 + §6.3__tests__/scripts/docubot-prompt-loading.test.ts (NEW) — grep scripts/docubot/run-agent.ts for AGENTS.md + keep-docs-in-sync references. Run log inspection at sample run: assert documentation-inventory referenced in agent reasoning.
32Claude integration via @anthropic-ai/claude-agent-sdk§3.5package.json test (NEW at __tests__/dependencies/claude-agent-sdk.test.ts) — assert @anthropic-ai/claude-agent-sdk in devDependencies. Grep scripts/docubot/run-agent.ts for the import.
33Secrets contract: ANTHROPIC_API_KEY + GITHUB_TOKEN; runs-on ubuntu-latest§3.6.github/workflows/docubot.yml shape-test: assert runs-on: ubuntu-latest; assert env: block uses ANTHROPIC_API_KEY + GITHUB_TOKEN (not Warp’s namespace-profile-*).
34Run artefacts upload on every run§3.1.github/actions/docubot/action.yml shape-test (NEW at __tests__/workflows/docubot-action-shape.test.ts) — assert upload-artifact step has if: always().
35Docubot composable with 5 skills (review-docs-pr runs against docubot PRs)§4.3.github/workflows/review-docs-pr.yml shape-test: assert trigger filter includes PRs with docubot/* head branch.

Third 3 — Five-skill port (Inv-36..Inv-43)

Section titled “Third 3 — Five-skill port (Inv-36..Inv-43)”
InvWhat it assertsTECH §Test file / verification
365 skills port; update-changelog NOT ported§4.2__tests__/skills/skill-inventory.test.ts (NEW) — assert .claude/skills/ contains the 5 named skills + does NOT contain update-changelog/.
37Each ported skill loads AGENTS.md§6.3__tests__/scripts/run-skill-loading.test.ts (NEW) — grep scripts/skills/run-skill.ts for AGENTS.md reference. Per-skill SKILL.md grep: each references AGENTS.md.
38review-docs-pr ports verbatim shape; emits review.json; severity prefixes without emoji§4.3__tests__/skills/review-docs-pr.test.ts (NEW) — fixture: sample PR diff; assert agent emits review.json with summary + comments[]; assert no emoji in comment bodies.
39sync-source-docs ports KH-adapted; three KH source pairs§4.4__tests__/skills/sync-source-docs.test.ts (NEW) — fixture: drift in each of the 3 source pairs; assert detection + docs-PR opened per pair.
40missing-docs two-phase audit + draft; four sub-audits§4.5__tests__/skills/missing-docs-audit.test.ts (NEW) — fixture: env var present in code but absent from docs; assert sub-audit 1 detects. Per sub-audit fixture test.
41check-for-broken-links five error types; --gh-pr-comment mode§4.6__tests__/skills/check-for-broken-links.test.ts (NEW) — fixture per error type; assert detection. Plus: assert --gh-pr-comment mode posts via gh pr comment (mock).
42docs-seo-audit 11+ issue types; 3 severity tiers; ASK-before-fixing§4.7__tests__/skills/docs-seo-audit.test.ts (NEW) — fixture sitemap; assert per-issue detection. SKILL.md grep: assert “ASK before fixing” rule present.
43Each skill workflow_dispatch-triggerable; canonical workflow shape§4.1__tests__/workflows/skill-workflow-shape.test.ts (NEW) — for each of 5 workflow files, parse YAML; assert workflow_dispatch trigger present + runs-on: ubuntu-latest. All 5 share canonical shape from template at §4.1.

Third 4 — /update-docs decommission (Inv-44..Inv-50)

Section titled “Third 4 — /update-docs decommission (Inv-44..Inv-50)”
InvWhat it assertsTECH §Test file / verification
44(a)+(b)+(d) move to docubot§5.1Session A acceptance verification (PRODUCT third 4 acceptance): 3 sample docubot PRs handle (a)+(b)+(d) workloads. Manual review at session close.
45(c) moves to ci.yml regenerate-stats job§5.2.github/workflows/ci.yml shape-test (NEW at __tests__/workflows/ci-regenerate-stats.test.ts) — assert regenerate-stats job exists; assert it runs bun run stats + bun run generate:mcp-inventory; assert direct-commit + side-PR-fallback logic present.
46/handoff preserved as standalone skill§5.1__tests__/skills/handoff-preserved.test.ts (NEW) — assert .claude/skills/handoff/SKILL.md exists post-Session B. Cross-check: callers of /update-docs updated to /handoff directly.
47Session A acceptance: 3 sample docs-PRs; (a)+(b)+(d) execute correctly; single-comment holds§5.3Manual at session close (Liam reviews 3 sample docubot PRs). Documented in Session A close-out doc.
48Session B acceptance: ci.yml regenerate-stats runs; /update-docs/ removed; callers updated§5.3__tests__/decommission/update-docs-removed.test.ts (NEW) — assert .claude/skills/update-docs/ directory does NOT exist post-Session B. Grep .claude/ for /update-docs references; assert none survive (except in deletion-comment lines).
49CLAUDE.md atomic update (same commit as removal)§5.4Commit inspection: the Session B removal commit MUST show CLAUDE.md edits AND .claude/skills/update-docs/ deletion in the same diff. Manual verification at Session B close.
50Hard deadline: /update-docs removed by end of Session B§5.5Calendar gate: Session B Subtask MUST close with .claude/skills/update-docs/ removed. No deferral allowed.

Supporting deliverables — Inv-51..Inv-52

Section titled “Supporting deliverables — Inv-51..Inv-52”
InvWhat it assertsTECH §Test file / verification
51AGENTS.md at repo root with all 5 content sections§6.1__tests__/agents-md/agents-md-shape.test.ts (NEW) — assert AGENTS.md exists at repo root; assert it contains the 5 required sections (voice, terminology, frontmatter, content-type, AI-invisibility).
52keep-docs-in-sync skill with all 7 content sections§6.2__tests__/skills/keep-docs-in-sync-shape.test.ts (NEW) — assert .claude/skills/keep-docs-in-sync/SKILL.md exists; assert 7 required sections present (IA, palette, AI-invisibility, UK English, doc inventory, commit conventions, single-comment guardrail).

Per docs/reference/test-philosophy.md (read before authoring tests):

  • Tests verify real behaviour, not mocks. Where SDK / GH-Actions integration tests are infeasible at foundation (e.g. live Anthropic API call gate), use shape tests on the workflow / config YAML + manual verification at session close. Mark these explicitly with comments referencing the deferred integration coverage.
  • Shared createMockSupabaseClient() helpers in __tests__/helpers/mock-supabase.ts apply ONLY when a test touches Supabase; the docs-site tests above are filesystem + YAML + grep based and do NOT need this helper.
  • Date-sensitive tests (Inv-14) pin time via vi.spyOn(Date, 'now').

sequenceDiagram
autonumber
actor Author as PR author
participant GH as GitHub (KH repo)
participant CIWF as ci.yml workflow
participant DBWF as docubot.yml workflow
participant Comp as docubot composite action
participant SDK as Claude Agent SDK
participant DocSite as docs-site Vercel project
Author->>GH: Open source-code PR
Author->>GH: Merge PR to main (merged == true)
GH->>CIWF: Trigger ci.yml on push:main
GH->>DBWF: Trigger docubot.yml on pull_request closed (merged==true)
Note over DBWF,Comp: docubot job runs (ubuntu-latest, timeout 30min)
DBWF->>Comp: Invoke composite action (pr_number)
Comp->>GH: gh pr view + gh pr diff (gather context)
Comp->>Comp: envsubst prompt.txt → prompt.output.txt
Comp->>SDK: bun scripts/docubot/run-agent.ts --prompt-file prompt.output.txt
Note over SDK: Agent reads AGENTS.md +<br/>keep-docs-in-sync SKILL.md +<br/>documentation-inventory.md
SDK->>GH: git checkout -b docubot/<slug>
SDK->>SDK: Read source PR diff, decide doc updates
SDK->>GH: Write docs-site/src/content/docs/<space>/<file>.md<br/>(with kh_docubot_owned: true frontmatter)
SDK->>GH: git commit -m "docs(area): concise summary"
SDK->>GH: git push -u origin docubot/<slug>
SDK->>GH: gh pr create --title "Docs: <summary> (from #<PR>)"
SDK->>GH: gh pr comment <source-PR-number> --body "<link + summary + @author>"
Note over SDK,GH: SINGLE comment posted at end of run (Inv-27)
Comp->>Comp: upload-artifact (prompt.output.txt + .docubot/run.log)<br/>if: always()
GH->>CIWF: Run regenerate-stats job (push:main)
CIWF->>GH: bun run stats + bun run generate:mcp-inventory
CIWF->>GH: Direct commit to main (or side-PR fallback)
GH->>DocSite: Vercel auto-deploys docs PR preview
DocSite-->>Author: Preview URL in PR
Note over Author,DocSite: Liam reviews docs-PR, merges if good.<br/>Production deploy follows on merge to main.

The 5 skills run independently of this flow (their workflows have their own triggers). The review-docs-pr skill DOES participate by triggering on the docubot-opened docs-PR as a second auto-review pass (per Inv-35).


RiskMitigation
Docubot writes to docs-site but the next bun run sync clobbers its edit (divergence)kh_docubot_owned: true front-matter flag + sync script honour (§3.4). Manifest docubot_owned_paths[] as a secondary index (deferred to Follow-ups). Front-matter is authoritative.
Token duplication between app/globals.css and docs-site/src/styles/warm-meridian.css driftsdocs-site/scripts/check-token-parity.ts CI guard (§2.7). Long-term: extract Warm Meridian into a shared package both consume (Follow-up; deferred until shared-pkg overhead is justified).
Sync script becomes a one-off snowflake nobody understandsKeep sync-content.ts under 250 LOC; manifest declarative; deny-list overrides via JSON not code. Resist accreting per-doc special cases.
Docubot opens an empty / nonsense docs-PR (false positive)Single-comment guardrail (Inv-27) means the cost is one PR + one comment. The review-docs-pr skill (Inv-35) is the auto-review pass that catches low-quality output. Human merge gate.
Claude Agent SDK API instability (SDK still evolving)Pin to a known-good SDK version in package.json; document the verified version in the SKILL.md body. Migration path: when SDK version bumps, run a sample docubot dispatch against a sandbox source-PR before adoption.
Branch protection blocks regenerate-stats direct commitAutomatic fallback to side-PR (§5.2 + OQ-6 ratification). No human intervention.
[skip ci] on regenerate-stats commit prevents re-CI; some other CI guard might still re-triggerVerify post-first-run that no other workflow triggers on the regen commit (e.g. taxonomy-sync.yml). Add [skip ci] honour to those if needed.
5 skill workflows each invoke @anthropic-ai/claude-agent-sdk — quota / cost surgePer-workflow timeout-minutes: 30 caps blast radius. Scheduled crons stagger (daily / weekly / monthly per skill). Cost monitoring deferred to Follow-up.
AGENTS.md content drift from CLAUDE.md (overlapping voice + UK English rules)AGENTS.md cross-references CLAUDE.md for project-wide rules (per OQ-8 default). Periodic check via missing-docs Phase 1 terminology audit. Drift surfaces as a finding rather than silent skew.
Inv-30 docubot-direct-write conflicts with human-authored docs/ updates on same pathkh_docubot_owned: true flag isolates docubot’s writes. Reverse-sync from docs-site/docs/ is a Follow-up — not in foundation scope. Foundation: docubot-owned paths stay docubot-owned until explicit human un-flag.
Vercel default subdomain string TBC delays first deployLiam confirms subdomain pre-deploy. astro.config.mjs uses <vercel-default-subdomain> placeholder until then; deploy gate is the subdomain confirmation.
Stale TECH.md preserved on disk causes confusionThis REWRITE replaces the stale file atomically (single commit). No dual-file state. Git history preserves the stale version for audit.
Decommission Session B slips past hard deadlineInv-50 hard-deadline forcing function. Corner cases get filed as {9.M+} follow-ups against docubot, not as “keep /update-docs around.”
Knip / dead-code scans confused by docs-site/ + .claude/skills/<skill-name>/ boundariesAdd docs-site/** + .claude/skills/<skill-name>/** to Knip ignore lists once foundation lands. Do not pollute root Knip config until boundaries are real.
Hook propagation discipline (CLAUDE.md ID-19.4) — sub-agent worktree dispatches at {9.5+} SubtasksPre-dispatch check per CLAUDE.md: `git show origin/main:.claude/settings.json

The four scope-thirds + 2 supporting deliverables decompose into worktree-isolatable slices for {9.4} PLAN decomposition. Suggested slicing (the Planner authoring {9.4} decides the final cuts):

SliceWorktree-isolatable?Depends onSuggested Subtask grouping
docs-site/ scaffold (§2.1 + §2.3 + §2.4)YES(none — first slice)One {9.5} Subtask
Sync script + manifest (§2.5 + §2.6)YESdocs-site scaffoldOne {9.6} Subtask
Warm Meridian theming + token-drift guard (§2.7)YESdocs-site scaffoldOne {9.7} Subtask
AI-invisibility + UK English CI guards (§2.10 + §2.11)YESsync scriptOne {9.8} Subtask
AGENTS.md (§6.1)YES(none — independent text doc; loaded by docubot + skills)One {9.9} Subtask
keep-docs-in-sync SKILL.md (§6.2)YESAGENTS.md (cross-reference)One {9.10} Subtask
Docubot composite action (§3.1 + §3.3 + §3.6)YESAGENTS.md + keep-docs-in-syncOne {9.11} Subtask
Docubot workflow + driver (§3.2 + §3.5)YEScomposite actionOne {9.12} Subtask
5 skill scaffolds + shared driver (§4.1 + §4.2)YESdocubot driver (shape inheritance)One {9.13} Subtask
Per-skill SKILL.md + workflow (§4.3-§4.7)YESshared driver scaffoldFive Subtasks {9.14..9.18} (one per skill) or one batch Subtask
ci.yml regenerate-stats job (§5.2)YES(none — independent ci.yml edit)One {9.19} Subtask
Session A decommission verification (§5.3 gate 1)NO (verification step)docubot + 5 skills shippedOne {9.20} Subtask (manual + sample-run gate)
Session B decommission + CLAUDE.md (§5.4 + §5.5)NO (atomic commit)Session A acceptanceOne {9.21} Subtask

Total: ~17 implementation Subtasks (well under the 25-Subtask soft ceiling per §3.4 / A7). {9.4} PLAN may collapse some (e.g. all 5 per-skill workflows into one batch) or split others (e.g. each sync-source-docs source pair into its own Subtask).

Sibling-only Subtask dependency rule: all Subtasks above depend ONLY on other Subtasks within Task ID-9 (no cross-Task dependencies). If {9.4} decomposition surfaces a cross-Task dependency (e.g. “Subtask 9.X depends on Subtask 22.Y”), the Task boundary is wrong — escalate to Orchestrator per workflow-orchestration §3.3.

Sub-agent worktree dispatch: each {9.5+} Subtask dispatched via isolation: "worktree" per CLAUDE.md “Parallel agent isolation.” Pre-dispatch check: hook propagation discipline (CLAUDE.md ID-19.4).


Explicit non-goals at foundation; recorded so the next iteration does not re-derive:

  • Reverse-sync workflow — when a docubot-owned path’s kh_docubot_owned flag flips back to false, reverse-sync flows the docs-site content back to docs/. Not in foundation; deferred to a future Task.
  • Visual regression suite (Percy / Chromatic) — defer until a second Warm Meridian consumer materialises (per PRODUCT non-goals).
  • Shared Warm Meridian package — extract app/globals.css + docs-site/src/styles/warm-meridian.css token block into a shared package both consume. Defer until token-drift surfaces as a real maintenance burden.
  • MCP resource exposing the docs collection — the manifest shape is the same a future MCP resource list would want; expose docs-site/src/content/docs/ as an MCP resource for AI consumers. Deferred.
  • Per-locale content / i18n — single-locale at foundation.
  • Algolia / hosted search — Pagefind covers foundation.
  • Version switcher / archived snapshots — single-version at foundation.
  • Vercel auth or path-prefix split for admin-restricted docs — deferred; no current internal-only-content need.
  • The other 18 Warp skills beyond the 6 highlighted — Phase-3 expansion Task.
  • update-changelog KH equivalent — explicit non-goal per OQ-3 OVERRIDE; revisit only when KH has tagged releases.
  • docubot_owned_paths[] manifest secondary index — performance optimisation deferred; foundation uses front-matter check (authoritative).
  • Time-to-docs-PR observability dashboard — bespoke metrics; foundation uses GH Actions UI’s per-run duration. bun scripts/docubot/audit-time-to-pr.ts is a candidate Follow-up script.
  • docs-seo-audit first run scheduling — defer cron enable until docs-site live + sitemap exists.
  • CLAUDE.md MemPalace section update — Inv-49 covers atomicity, but if the MemPalace section grows references to docubot post-foundation, schedule a follow-up sweep.

This TECH introduces three new OQs beyond the 3 from PRODUCT (OQ-6 / OQ-7 / OQ-8 — handled per PRODUCT). The Orchestrator routes each below to Liam before {9.4} PLAN dispatch (or, if non-blocking, before implementation).

New OQ-T1 — Divergence handling mechanism for docubot-owned paths

Section titled “New OQ-T1 — Divergence handling mechanism for docubot-owned paths”

Question: Per §3.4, the proposed mechanism is kh_docubot_owned: true front-matter flag + sync-script honour. Two alternatives considered + rejected: Option B (docubot writes both docs/ AND docs-site/) violates Inv-30; Option C (lockfile / atomic write-to-temp) solves a different problem. Confirm Option A (front-matter flag) is preferred, OR surface a fourth alternative.

Default proposed: Option A (front-matter flag). Cheap to implement, makes ownership explicit at the file level (greppable), survives manual edits (the flag is human-readable). Manifest secondary index is a Follow-up. Reverse-sync workflow is a separate Follow-up.

Why this is an OQ rather than a lock: PRODUCT Inv-30 explicitly defers divergence handling to TECH (“Divergence handling for docubot-touched paths … is a TECH-phase concern”). TECH proposes Option A; Liam may override before {9.4} if a different mechanism is preferred.

New OQ-T2 — Per-skill workflow trigger overrides (sched cron vs workflow_dispatch-only at foundation)

Section titled “New OQ-T2 — Per-skill workflow trigger overrides (sched cron vs workflow_dispatch-only at foundation)”

Question: Per §4.3-§4.7, four of the five skills have schedule: cron triggers:

  • sync-source-docs — weekly Monday 06:00 UTC
  • check-for-broken-links — daily 05:00 UTC
  • docs-seo-audit — monthly 1st 07:00 UTC (gated on live-deploy)
  • missing-docsworkflow_dispatch only at foundation; Phase 2 may add monthly

The cron values are proposals. At foundation: enable all 5 schedule cron triggers, OR start with workflow_dispatch-only across all 5 and add schedules in a Follow-up?

Default proposed: Enable all 4 active schedule crons at foundation. Rationale: the skills are cheap to run (each is one Claude Agent SDK invocation per scheduled run); failure mode is a docs-PR opening with low-signal content (caught by the review-docs-pr second-pass + human merge gate); no risk of accidental production damage (skills write to PRs, not directly to main).

Why this is an OQ: Operating costs (Anthropic API quota on daily / weekly / monthly cadence) are a Liam-decision. If cost-sensitivity is high at foundation, default to workflow_dispatch-only and add schedules post-cost-baseline.

New OQ-T3 — docs-seo-audit deferred-enable mechanism

Section titled “New OQ-T3 — docs-seo-audit deferred-enable mechanism”

Question: Per §4.7, docs-seo-audit runs only after the docs-site is live + deployed (sitemap exists). Two ways to handle the deferral:

  • Option A (recommended): commit the workflow file at foundation with the schedule: block commented out. Post-deploy, a Follow-up commit uncomments. Visible in git history.
  • Option B: omit the workflow file entirely at foundation; add it post-deploy. Cleaner but creates a Follow-up that’s easy to forget.

Default proposed: Option A. Workflow file landing AT foundation makes the intent visible; the commented-out cron + a Follow-up commit at first-deploy is a small, well-bounded action. Avoids the “forgotten Follow-up” failure mode.

Why this is an OQ: Liam may prefer Option B if cluttering .github/workflows/ with not-yet-active workflows is undesirable. Either is defensible.


End of TECH spec {9.3} for Task ID-9.