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).
§0 Name resolution + reading order
Section titled “§0 Name resolution + reading order”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-docsdecommission — 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:
- Inv-30 docubot direct-write path: docubot writes directly to
docs-site/src/content/docs/<space>/<file>.md. Thedocs/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). - KH-persona prompt template (RESEARCH §2.3 adaptation): the concrete envsubst’d template lands at
.github/actions/docubot/prompt.txtper §3.3 below — not a reference, the actual structure. - Canonical 5-skill workflow shape (Inv-43): all five ported skills share one
.github/workflows/<skill-name>.ymltemplate documented at §4.1 — not five bespoke shapes. - 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’sdocs.kh.phew.org.ukreferences are PURGED across this REWRITE — no occurrence survives.
§1 Context
Section titled “§1 Context”1.1 Current state of the KH docs surface
Section titled “1.1 Current state of the KH docs surface”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; subdirsadministration/,ai-integration/,bid-management/,content-management/,knowledge-organisation/,quality-governance/,search/)docs/ontology/(33 numbered taxonomy / cardinality specs01-..through33-..)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.
1.2 Decommission targets
Section titled “1.2 Decommission targets”Four artefacts retire across this Task:
| Artefact | Successor | Decommission gate |
|---|---|---|
.claude/skills/update-docs/SKILL.md (27KB, 4 functions) | Functions (a)+(b)+(d) → docubot; function (c) bun run stats + bun run generate:mcp-inventory → ci.yml regenerate-stats job | Session 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 references | Docubot + keep-docs-in-sync references | Session B (Inv-49 atomic) |
1.3 Relevant in-repo anchors
Section titled “1.3 Relevant in-repo anchors”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 intodocs-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; newregenerate-statsjob appends (Inv-45).proxy.ts:13publicRoutes— 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 newregenerate-statsjob 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).
1.4 Why this REWRITE and not an extension
Section titled “1.4 Why this REWRITE and not an extension”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-harnessfirst-sync target (does not exist at HEAD; PRODUCT Inv-3 obviates “first-sync target” framing entirely). - The
docs.kh.phew.org.ukdomain (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)”2.1 Repository layout
Section titled “2.1 Repository layout”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).
2.2 Vercel project shape
Section titled “2.2 Vercel project shape”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 commandbun install && bun run build, output directorydocs-site/dist. - Domain: Vercel default subdomain (
<vercel-default-subdomain>— exact string TBC by Liam from Vercel project history pre-deploy; per Inv-2 NOT underkh.phew.org.uk). - Branch deploy boundary: production deploys from
main. Preview deploys from any branch with a docs-site change. Long-livedproduction-readinessworktree contributes via merge tomainlike any other branch (Inv-21). - Independent rollback cadence: broken docs build does NOT block app deploys (separate Vercel project).
2.3 Astro + Starlight configuration
Section titled “2.3 Astro + Starlight configuration”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 onmain(Inv-13).lastUpdated: true→ git-history-derived DD/MM/YYYY (Inv-14). Starlight default formats date locale-aware; explicitdate-fnslocalisation 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
versionsconfig (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),lastUpdatedoverride. - 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):
- Read manifest.
- For each
spaces[]entry: walksource_root/source/recursively, collect all.md+.mdx. - For each
additional_source_dirs[<target>][]entry: walk + append to the target space’s pool. - For
decisionsspace (specssource,filter: ratified-only): includedocs/specs/<id>/PRODUCT.md+TECH.mdonly when both haveStatus: RATIFIEDin their first 10 lines (cheap header grep). - Exclude any path matching
deny_list[]. - 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). - For each surviving source: read, parse front matter (gray-matter), inject
kh_source(repo-relative path), copy totarget_root/<space>/<path>. - Validate every target file matches the content-collection Zod schema (
astro check). - Build the diff of targets vs previous build (added / modified / removed paths) and print. CI logs the diff (Inv-22 — explicit removal tracking).
- 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:
{ "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):
bun run sync -- --source-root ../../knowledge-hub/docs # sibling main worktreebun run sync -- --source-root ../../knowledge-hub-production-readiness/docsDefault --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).
2.6 Cross-space link rewriting (Inv-6 absolute-paths-only)
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:
- 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. - 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 viavar(--…). - Load Instrument Sans via Starlight’s
headconfig (Google Fonts<link>for parity withnext/font/googleinapp/layout.tsx). - Set
--sl-content-width: 50remfor Warm Meridian’s compositional restraint.
Token-drift guard (per stale S47 §Risks + carried forward):
- A small CI script asserts the
warm-meridian.csstoken block matches a generated snapshot ofapp/globals.csscore tokens. Cheap, deterministic. Lives atdocs-site/scripts/check-token-parity.ts; runs in thedocs-sitebuild 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.
2.9 404 + empty states (Inv-17)
Section titled “2.9 404 + empty states (Inv-17)”- 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).
2.10 AI-invisibility CI guard (Inv-23)
Section titled “2.10 AI-invisibility CI guard (Inv-23)”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).
2.11 UK English (Inv-24)
Section titled “2.11 UK English (Inv-24)”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)”3.1 Composite action structure
Section titled “3.1 Composite action structure”.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: 30Notes (resolving each invariant):
runs-onlives in the WORKFLOW (.github/workflows/docubot.yml) — composite action is runner-agnostic.actions/checkout@v4withfetch-depth: 0— Inv-32 requires git history for source-PR diff.oven-sh/setup-bun@v2— KH uses bun (CLAUDE.md “Package manager: bun”). NOTsetup-node@v4.gh pr view/gh pr difffor context gather (Inv-32 / Inv-29 / Inv-30 source diff input).envsubsttemplate render →prompt.output.txt(whichupload-artifact@v4ships 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 atscripts/docubot/run-agent.ts(NEW).if: always()on the artefact upload — Inv-34 requires upload regardless of run outcome.
3.2 Workflow trigger
Section titled “3.2 Workflow trigger”.github/workflows/docubot.yml:
name: docubot — KH docs auto-PRon: 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 theKnowledge Hub repository (`ai-solution-hub/knowledge-hub`). When a source-codePR merges to `main`, you read the diff and open a follow-up docs PR thatwrites directly to `docs-site/src/content/docs/<space>/<file>.md` for anydocumentation that needs updating.
You always run in the same repository as the source PR (NOT a sibling docsrepo). 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.mddirectly). - Path P has a
docs/reference/X.mdsource. At time T2, a developer runsbun run syncand the build-time sync rewritesdocs-site/src/content/docs/reference/X.mdwithdocs/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.
- When docubot writes a file under
docs-site/src/content/docs/, it setskh_docubot_owned: truein the front matter (prompt rule in §3.3 enforces this). - The build-time sync script (§2.5 step 6) reads the target file’s front matter before writing. If
kh_docubot_owned: trueis present, the sync script skips that target on this run. - The path remains docubot-owned until a human authors a docs-PR that explicitly removes
kh_docubot_owned: truefrom the front matter (which signals “this is back under build-time-sync ownership; please reverse-sync to the docs/ source”). A separatereverse-syncworkflow (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/ANDdocs-site/: rejected. PRODUCT Inv-30 says docubot does NOT update thedocs/tree. Writing to both would put docubot in conflict with the human-authoring contract fordocs/. - 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.
3.5 Claude Agent SDK driver
Section titled “3.5 Claude Agent SDK driver”scripts/docubot/run-agent.ts (NEW; ~80-120 LOC):
#!/usr/bin/env bunimport { 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
importfrom@anthropic-ai/claude-agent-sdk(NO barrel re-export — CLAUDE.md rule). Agentshape 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 inpackage.json.cwdis$GITHUB_WORKSPACE(the source-PR repo root) — agent readsdocs/,lib/,app/for context AND writes todocs-site/src/content/docs/<space>/<file>.mddirectly 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 themcptool from the agent’s tool list at foundation.
3.6 Secrets contract (Inv-33)
Section titled “3.6 Secrets contract (Inv-33)”${{ 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 NOTnamespace-profile-ubuntu-20-04per 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: 30at 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.tsis 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 maintenanceon: 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: 30Single 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}/.
4.2 Skill set scope (Inv-36)
Section titled “4.2 Skill set scope (Inv-36)”5 of Warp’s 6 docs skills port. update-changelog is OUT-OF-SCOPE per OQ-3 OVERRIDE. The 5 that port:
| KH skill name | Warp origin | Trigger pattern | Output |
|---|---|---|---|
review-docs-pr | review-docs-pr | workflow_dispatch + PR-review event | review.json + PR comments |
sync-source-docs | sync-error-docs (KH-renamed) | workflow_dispatch + schedule: cron weekly | Drift docs-PR |
missing-docs | missing_docs | workflow_dispatch (+ Phase 2 schedule TBD) | Gap list (Phase 1) + drafts (Phase 2) |
check-for-broken-links | check_for_broken_links | workflow_dispatch + schedule: cron daily | Findings report + PR comments |
docs-seo-audit | docs-seo-audit | workflow_dispatch + schedule: cron monthly | SEO 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.
4.3 review-docs-pr (Inv-38)
Section titled “4.3 review-docs-pr (Inv-38)”Workflow shape per §4.1. Per-skill specifics:
- Trigger override: in addition to
workflow_dispatch, the workflow also triggers onpull_request_reviewevents (when a human or bot reviews a docs PR). The job’sif:filters to PRs whose head branch isdocubot/*OR base branch ismainand the PR title starts withDocs:— 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.jsonwith{ 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.jsonand runsgh pr comment $PR_NUMBER --body "..."for eachcomments[]entry, with the severity as a prefix (e.g.[CRITICAL] …). - SKILL.md body: structural template from Warp’s
review-docs-prSKILL.md, with KH adaptations (UK English, no-emoji, Warm Meridian context for visual-callout reviews, link todocs/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:
supabase/types/database.types.ts→docs-site/src/content/docs/reference/schema-quick-reference.md(and per-table pages where they exist underdocs-site/src/content/docs/reference/schema/<table>.md— NEW; created on first run if missing).- MCP registrations in
lib/mcp/→docs-site/src/content/docs/reference/mcp-inventory.md(currently generated bybun run generate:mcp-inventory—sync-source-docsconsumes its output OR independently audits the source). - Route definitions in
app/api/**/route.ts→docs-site/src/content/docs/reference/api-routes.md(NEW;sync-source-docsis 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> driftwith the rewritten target file(s). Marks the filekh_docubot_owned: trueso build-time sync doesn’t clobber it (per §3.4). - SKILL.md body: structural template from Warp’s
sync-error-docsSKILL.md, with KH adaptations (three KH source pairs above, no Warp-error-doc references).
4.5 missing-docs (Inv-40)
Section titled “4.5 missing-docs (Inv-40)”Per-skill specifics:
- Trigger:
workflow_dispatchonly at foundation. Phase 2 may addschedule: cron monthly. - Phase 1 audit:
.claude/skills/missing-docs/scripts/audit_docs.py(Python). Four sub-audits:- Feature flags / env vars: parse
.env.example+ grepprocess.env\.inlib/,app/,scripts/; cross-check againstdocs-site/src/content/docs/runbooks/local-development.md+docs-site/src/content/docs/runbooks/github-environments.md. - CLI commands: parse
package.jsonscripts+ cross-check againstdocs-site/src/content/docs/runbooks/*.md. - MCP / route surfaces: parse
lib/mcp/tools/*.ts+app/api/**/route.ts+ cross-check againstdocs-site/src/content/docs/reference/{mcp-inventory,api-routes}.md. - Terminology staleness: read
.claude/skills/missing-docs/references/stale_terms.md(KH-specific; lists terms likedigest(should bechange reports),Sparkles,AI-poweredetc.) + grep acrossdocs-site/src/content/docs/**/*.md.
- Feature flags / env vars: parse
- 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_dispatchinputs (phase: audit | draft).
4.6 check-for-broken-links (Inv-41)
Section titled “4.6 check-for-broken-links (Inv-41)”Per-skill specifics:
- Trigger:
workflow_dispatch+schedule: cron '0 5 * * *'(daily 05:00 UTC) + invoked from insidereview-docs-pr(per Warp’s pattern — the agent driver callsbun scripts/skills/run-skill.ts --skill check-for-broken-linksmid-review). - Script:
.claude/skills/check-for-broken-links/scripts/check_links.py(Python). Generic link-walker overdocs-site/src/content/docs/**/*.md. Five error types (per RESEARCH §1.5 + Inv-41):- file-not-found
- case-mismatch (Astro is case-sensitive)
- missing
.mdxextension on directory link - cross-space relative path (per Inv-6 — should be absolute path-style)
- 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-commentflag (replaces Warp’s--slack-notifymode per Inv-41).
4.7 docs-seo-audit (Inv-42)
Section titled “4.7 docs-seo-audit (Inv-42)”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_dispatchwith--fixmode. (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 function | Successor | Successor implementation |
|---|---|---|
(a) refresh docs/reference/product-roadmap.json | Docubot (third 2 of 4) | §3 above; docubot reads PR diff and updates |
(b) refresh docs/reference/state-of-the-product.md | Docubot | §3 above |
(c) bun run stats + bun run generate:mcp-inventory | ci.yml regenerate-stats job (Inv-45) | §5.2 below |
(d) refresh docs/reference/product-backlog.json | Docubot | §3 above |
auto-chain to /handoff | /handoff standalone (Inv-46 — preserved as standalone skill) | NO change; callers invoke /handoff directly |
5.2 ci.yml regenerate-stats job (Inv-45)
Section titled “5.2 ci.yml regenerate-stats job (Inv-45)”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." fiOQ-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:
- Docubot opens at least 3 sample docs-PRs on real source-PR merges.
- All 3 narrative-doc updates ((a) + (b) + (d) workloads) execute correctly (Liam manual review).
- 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:
ci.ymlregenerate-statsjob runs successfully on at least one merge-to-main commit..claude/skills/update-docs/directory removed.- Any callers of
/update-docsin.claude/configs / hooks updated to invoke/handoffdirectly (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.
5.4 CLAUDE.md atomic update (Inv-49)
Section titled “5.4 CLAUDE.md atomic update (Inv-49)”In the SAME commit as Session B’s removal of .claude/skills/update-docs/, CLAUDE.md is edited to:
- Remove
/update-docsreferences (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-syncskill for KH conventions”). - Add
keep-docs-in-syncreference (new line in skill-routing or Implementation Workflow as appropriate). - Memory (MemPalace) section: review for any
/update-docsmention; remove if present (foundation: grep CLAUDE.md once, evidence here is minimal — section likely untouched). - Reference-doc tables: review for
/update-docsmention; 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.
5.5 Hard deadline (Inv-50)
Section titled “5.5 Hard deadline (Inv-50)”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)”6.1 AGENTS.md at repo root (Inv-51)
Section titled “6.1 AGENTS.md at repo root (Inv-51)”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:
- Voice + tone rules: UK English, professional-direct, no marketing copy, no emoji.
- 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.
- Frontmatter contract: matching the Zod schema in §2.4 above.
- Content-type style guides: concept docs, procedural docs, reference docs, troubleshooting docs.
- 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:
- KH
docs/IA conventions (five-space layout per Inv-4 + cross-space link rule per Inv-6). - Warm Meridian palette + typography references (per
docs/design/warm-meridian-implementation-spec.md). - AI-invisibility policy reference (per
docs/reference/ai-visibility-policy.md). - UK English requirements.
docs/reference/documentation-inventory.mdindex (so docubot does not recreate existing docs).- Commit + PR conventions (matching
commit-commands:commit-push-pr). - 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.
6.3 Loading contract (Inv-37)
Section titled “6.3 Loading contract (Inv-37)”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.tsreadsAGENTS.md+.claude/skills/keep-docs-in-sync/SKILL.mdinto the prompt context before invoking the SDK.scripts/skills/run-skill.tsreadsAGENTS.md+.claude/skills/keep-docs-in-sync/SKILL.md+ the per-skillSKILL.md(per--skillflag) 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 Cross-project packaging
Section titled “§7 Cross-project packaging”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.
7.2 CI integration topology
Section titled “7.2 CI integration topology”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(afterci.ymlregenerate-statsjob lands the regenerateddocs/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.
7.3 Secrets contract (consolidated)
Section titled “7.3 Secrets contract (consolidated)”| Secret | Used by | Source |
|---|---|---|
ANTHROPIC_API_KEY | docubot.yml + all 5 skill workflows | NEW; Liam adds via GitHub Settings |
GITHUB_TOKEN | All workflows (built-in) | GitHub auto-provided |
| Vercel deploy keys | Vercel 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.
7.4 Package management
Section titled “7.4 Package management”- Root
package.jsonunchanged (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/sdkat rootpackage.jsondevDependencies (since the agent drivers are CI-only, not bundled with the app).- Bun lockfiles for both:
bun.lockbat root,docs-site/bun.lockbseparately.
§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.
Third 1 — Docs site (Inv-1..Inv-24)
Section titled “Third 1 — Docs site (Inv-1..Inv-24)”| Inv | What it asserts | TECH § | Test file / verification |
|---|---|---|---|
| 1 | Authoring stays in repo docs/ markdown; no authoring inside docs-site/ | §2.1 | docs-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. |
| 2 | Static site at Vercel default subdomain; not under kh.phew.org.uk | §2.2 | Manual: post-deploy URL check. docs-site/astro.config.mjs grep test (NEW) asserts site: does NOT contain kh.phew.org.uk. |
| 3 | Include-by-default canonical front-door directories; deny-list opt-out | §2.5 | docs-site/__tests__/sync-manifest.test.ts (NEW) — fixture: 5 canonical dirs each with sample files + deny-list entries; assert all non-denied files published. |
| 4 | Five-space IA in sidebar order: product-functionality / ontology / reference / runbooks / decisions | §2.3 | docs-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. |
| 5 | Each space’s landing index page declares purpose statement | §2.3 + §2.5 | docs-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). |
| 6 | Cross-space links use absolute path-style URLs; build rejects relative cross-space | §2.6 | docs-site/__tests__/cross-space-links.test.ts (NEW) — fixture: deliberate cross-space relative link; assert sync script flags + build fails. |
| 7 | Single-version; no version switcher | §2.3 | Manual: rendered chrome lacks version switcher. docs-site/__tests__/astro-config-versioning.test.ts (NEW) — assert versions field absent in astro.config.mjs. |
| 8 | Pagefind search across all 5 spaces | §2.3 | E2E docs-site/e2e/search.spec.ts (NEW) — Playwright: open search, query “ontology”, assert results from at least 2 spaces. |
| 9 | Keyboard nav + screen reader + mobile + WCAG 2.1 AA; callouts have icon + text label | §2.3 + §2.8 | E2E 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. |
| 10 | Warm Meridian palette + typography; light + dark via Starlight data-theme | §2.7 | Manual 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. |
| 11 | Code blocks: syntax highlight + line numbers + copy + language label; tables: header emphasis | §2.8 | Manual visual check on rendered page with code + table. Playwright e2e (NEW) on code-block presence: copy button + language label visible. |
| 12 | Internal links resolve to canonical published paths; removed-page references → build fail | §2.3 + §2.5 + §2.9 | docs-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.3 | E2E docs-site/e2e/edit-link.spec.ts (NEW) — Playwright: open a page, click “Edit on GitHub”, assert URL contains /edit/main/. |
| 14 | Last-updated DD/MM/YYYY from git history; not build time | §2.3 | docs-site/__tests__/last-updated.test.ts (NEW) — pick a sample page, derive expected date from git log, assert rendered date matches. |
| 15 | Pure-.md documents render without MDX | §2.5 | docs-site/__tests__/pure-md-render.test.ts (NEW) — fixture: pure .md page; assert build + render succeeds. |
| 16 | Minimal Zod-validated front matter; build fails on unknown / malformed fields | §2.4 | docs-site/__tests__/frontmatter-schema.test.ts (NEW) — fixture: doc with unknown field; assert build fails with useful error. |
| 17 | Empty / loading / error states; 404 branded; build-fail on malformed front matter / broken link | §2.9 | E2E 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. |
| 18 | PR-merge docubot is PRIMARY production sync path | §3 + §3.4 | docs-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. |
| 19 | Build-time sync is SUPPLEMENTARY dev-loop primitive | §2.5 | docs-site/__tests__/build-time-sync.test.ts (NEW) — fixture: run bun run sync against fixture ../docs; assert content collection populated. |
| 20 | Schema-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. |
| 21 | Search + navigation stable across worktrees (built from main; long-lived worktrees merge in) | §2.2 | Manual: confirm production-readiness branch changes do NOT appear in production docs until merged to main. Documented in runbook. |
| 22 | Content removal explicit + tracked (no silent drop on manifest typo) | §2.5 | docs-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. |
| 23 | AI-invisibility throughout docs surface; CI grep guard blocks publication of forbidden phrases | §2.10 | docs-site/__tests__/ai-invisibility-guard.test.ts (NEW) — fixture: doc with “AI-powered” string; assert check-ai-invisibility.ts script exits non-zero. |
| 24 | UK English throughout | §2.11 | docs-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. |
Third 2 — Docubot port (Inv-25..Inv-35)
Section titled “Third 2 — Docubot port (Inv-25..Inv-35)”| Inv | What it asserts | TECH § | Test file / verification |
|---|---|---|---|
| 25 | Trigger 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. |
| 26 | Single docs-PR per source-PR merge | §3.3 | Integration 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. |
| 27 | Single comment on source PR per run | §3.3 | Integration test (paired with Inv-26): assert exactly 1 gh pr comment invocation. Prompt-rule enforcement; verify via run log inspection. |
| 28 | Time-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). |
| 29 | KH commit conventions: branch docubot/<slug>; commit docs(area): summary; PR title prefix Docs: | §3.3 | Integration test: sample run; assert branch name starts with docubot/; commit message matches ^docs\(.*\): ; PR title starts with Docs: . |
| 30 | Docubot writes directly to docs-site/src/content/docs/<space>/<file>.md (NOT docs/) | §3.3 + §3.4 | Integration 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. |
| 31 | Docubot 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. |
| 32 | Claude integration via @anthropic-ai/claude-agent-sdk | §3.5 | package.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. |
| 33 | Secrets 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-*). |
| 34 | Run 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(). |
| 35 | Docubot 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)”| Inv | What it asserts | TECH § | Test file / verification |
|---|---|---|---|
| 36 | 5 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/. |
| 37 | Each 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. |
| 38 | review-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. |
| 39 | sync-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. |
| 40 | missing-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. |
| 41 | check-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). |
| 42 | docs-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. |
| 43 | Each 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)”| Inv | What it asserts | TECH § | Test file / verification |
|---|---|---|---|
| 44 | (a)+(b)+(d) move to docubot | §5.1 | Session 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. |
| 47 | Session A acceptance: 3 sample docs-PRs; (a)+(b)+(d) execute correctly; single-comment holds | §5.3 | Manual at session close (Liam reviews 3 sample docubot PRs). Documented in Session A close-out doc. |
| 48 | Session 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). |
| 49 | CLAUDE.md atomic update (same commit as removal) | §5.4 | Commit 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. |
| 50 | Hard deadline: /update-docs removed by end of Session B | §5.5 | Calendar 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”| Inv | What it asserts | TECH § | Test file / verification |
|---|---|---|---|
| 51 | AGENTS.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). |
| 52 | keep-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). |
Test-philosophy alignment
Section titled “Test-philosophy alignment”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.tsapply 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').
End-to-end flow (docubot happy path)
Section titled “End-to-end flow (docubot happy path)”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).
Risks and mitigations
Section titled “Risks and mitigations”| Risk | Mitigation |
|---|---|
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 drifts | docs-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 understands | Keep 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 commit | Automatic 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-trigger | Verify 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 surge | Per-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 path | kh_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 deploy | Liam 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 confusion | This 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 deadline | Inv-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>/ boundaries | Add 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+} Subtasks | Pre-dispatch check per CLAUDE.md: `git show origin/main:.claude/settings.json |
Parallelization
Section titled “Parallelization”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):
| Slice | Worktree-isolatable? | Depends on | Suggested 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) | YES | docs-site scaffold | One {9.6} Subtask |
| Warm Meridian theming + token-drift guard (§2.7) | YES | docs-site scaffold | One {9.7} Subtask |
| AI-invisibility + UK English CI guards (§2.10 + §2.11) | YES | sync script | One {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) | YES | AGENTS.md (cross-reference) | One {9.10} Subtask |
| Docubot composite action (§3.1 + §3.3 + §3.6) | YES | AGENTS.md + keep-docs-in-sync | One {9.11} Subtask |
| Docubot workflow + driver (§3.2 + §3.5) | YES | composite action | One {9.12} Subtask |
| 5 skill scaffolds + shared driver (§4.1 + §4.2) | YES | docubot driver (shape inheritance) | One {9.13} Subtask |
| Per-skill SKILL.md + workflow (§4.3-§4.7) | YES | shared driver scaffold | Five 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 shipped | One {9.20} Subtask (manual + sample-run gate) |
| Session B decommission + CLAUDE.md (§5.4 + §5.5) | NO (atomic commit) | Session A acceptance | One {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).
Follow-ups
Section titled “Follow-ups”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_ownedflag flips back to false, reverse-sync flows the docs-site content back todocs/. 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.csstoken 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-changelogKH 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.tsis a candidate Follow-up script. docs-seo-auditfirst 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.
Open questions
Section titled “Open questions”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 UTCcheck-for-broken-links— daily 05:00 UTCdocs-seo-audit— monthly 1st 07:00 UTC (gated on live-deploy)missing-docs—workflow_dispatchonly 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.