Skip to content

ID-68 {68.12} — Docs/Ledger Repository-Separation Investigation

ID-68 {68.12} — Docs/Ledger Repository-Separation Investigation

Section titled “ID-68 {68.12} — Docs/Ledger Repository-Separation Investigation”

Type: Investigation report (NO implementation). Feeds the {68.12} implementation design. Authored: 02/06/2026 (S299-prep, research agent). Read-only — no ledger or code edits made. Supersedes the framing of: DOCS-RELOCATION-SUBTASK.md R-Split (which kept ledgers/specs in the main repo). Liam’s S294 override moves everything under /docs

  • /.planning out, which changes the calculus entirely.

The prior continuation prompt and the original DOCS-RELOCATION-SUBTASK.md spec framed this as “how do the docs-site automation + 5 docs skills + run-skill.ts mechanise the split.” That framing is wrong (Liam’s correction). Those tools exist to keep the codebase and documentation in sync — a separate docs site is standard industry practice. The real question this report answers:

  1. What realistic repo topologies exist for a private internal docs/specs/ledger repo living beside a (private-now, public-later) code repo (submodule ruled out)?
  2. After the split, how do code and docs stay in sync — what do the docs skills + run-skill.ts + docubot need to do to operate across two repos?
  3. The hard one: the override relocates the SDLC write-path (ledgers + specs + workflow-evaluation/). What breaks, and how do we keep the write-path working?

The KH case is not the textbook “public docs site from private code” (federated / specialised topology). It is the inverse: a private docs/ledger corpus that the soon-to-be-public code repo must shed, where the docs corpus contains live workflow state the orchestration loop writes every session.


1. Codebase reality (verified this session)

Section titled “1. Codebase reality (verified this session)”
TreeSizegitignored?Nature
docs/28 MBNo (tracked; only docs/database/ ignored)Mixed: ledgers (workflow state) + specs + runbooks + themes + workflow-evaluation/ (20 sessions) + continuation-prompts + design
.planning/.archive/bulk of 36 MB / 1,269 filesNo (tracked)Historical IP + client-name concentration
.planning/codebase/7 files (ARCHITECTURE/CONCERNS/CONVENTIONS/INTEGRATIONS/STACK/STRUCTURE/TESTING.md)NoRETIRED per Liam (GitNexus + ast-dataflow supersede)
docs-site/Astro+Starlight; src 2.9 MBNo (tsconfig excludes it from app TS)Build consumer of docs/ via source_root: "../docs"

git check-ignore docs .planning docs-site → all tracked (confirmed). So all become world-readable on the {68.9} flip unless relocated, and remain in history unless {68.10} strips them.

1.2 The SDLC write-path coupling — what actually reads/writes the moving files

Section titled “1.2 The SDLC write-path coupling — what actually reads/writes the moving files”

This is the crux. Inventory of code-repo tooling that resolves a real docs/ path (not just mentions it in a comment):

A. Ledger read/write (docs/reference/*.json) — written on essentially every session:

  • scripts/ledger-cli.ts — the canonical ledger writer. Good news: path resolution is ledgerPath(dir, name) = resolve(dir, FILE) (line 129) where dir defaults to the relative 'docs/reference' (line 363) but is overridable via --ledger-dir <path> (line 288). Verified empirically: --ledger-dir ../knowledge-hub-docs-site/docs/reference resolves to the sibling checkout with zero change to the CLI’s path logic. The CLI is already relocation-ready if every caller passes --ledger-dir.
  • scripts/regen-mirrors.shBREAKS. Lines 77-79 + 85 hardcode docs/reference/task-list.json etc. and MIRROR_DIRS=(docs/reference/...). It does NOT honour --ledger-dir. ledger-cli.ts spawns it by default after every write (spawnSync('bash', ['scripts/regen-mirrors.sh']), line 1610). So even with a correct --ledger-dir, the mirror regen writes to the wrong place. Must be parameterised (env var or arg for the ledger base dir).
  • lib/validation/ledger-budgets.ts:89DISCIPLINE_DOC = 'docs/reference/task-list-discipline.md' (a string constant for messaging; low-risk, repoint).
  • One-shot maintenance scripts hardcoding the paths: ledger-normalise-oqls2.ts:36-38, ledger-sweep-s269.ts:38, backfill-capability-theme.ts:39-40, detect-roadmap-shipped-framings.ts:54, roadmap-from-json.ts. Low-frequency; repoint or accept they run from the docs repo.

B. Roundtrip guard tests (run on every bun run test):

  • __tests__/docs/{backlog-schema,retro-ledger,roadmap,umbrellas-task-list}-roundtrip.test.ts — these readFileSync('docs/reference/*.json') relative to the code repo root. If the ledgers move out, these tests can no longer find them in the code repo. They must either move to the docs repo’s test suite, or read from the sibling checkout, or be retired from the code repo.

  • __tests__/validation/doc-freshness.test.ts — reads docs/reference/data-entry-points.md, classification-architecture.md, docs/runbooks/taxonomy-change-runbook.md via PROJECT_ROOT = join(__dirname, '../..') (code repo root). Same break.

C. Skill invocations of the ledger CLI: 39 bun scripts/ledger-cli.ts … invocations across .claude/skills/*/SKILL.md use the default (no --ledger-dir). Every one resolves docs/reference against cwd. If the loop runs from the code repo root after the move, all 39 break. (These are instruction text, not executable code — repoint = edit the skill bodies to add --ledger-dir $KH_DOCS_DIR or run the loop with cwd in the docs repo.)

D. workflow-evaluation/ writers: .claude/skills/session-driver-cmux/scripts/stop-worker.sh:346 writes archived sessions to ${PROJECT_ROOT}/docs/workflow-evaluation/sessions. PROJECT_ROOT is a knob, but defaults to the code-repo checkout. The evaluate-workflow / evaluate-findings skills + ledger-sweep-s269.ts read from there. These all move with docs/.

E. Orchestration / spec skills (instruction-level, not runtime): workflow-orchestration reads docs/reference/task-list.json (SKILL.md:57); write-product-spec/write-tech-spec/ spec-driven-implementation/implement-subtask resolve N from task-list.json and write docs/specs/ID-N-…/. These are prose pointers an agent follows — repoint the prose, or run the agent with the docs repo on path.

  • docs-site/sync-manifest.json"source_root": "../docs" (relative).
  • docs-site/scripts/sync-content.ts:78 resolves source_root relative to the manifest dir, and already accepts a --source-root override (sourceRootOverride, parsed at line 205). So the docs-site is already parameterised for an external source root.
  • The manifest already deny-lists the four ledger JSON + their MD mirrors + reference/{tasks,roadmap,backlog}/ — confirming the ledgers were never docs-site content; they are workflow state that merely lives under docs/reference/.
  • docs-site/package.json build = sync && check-broken-links && check-token-parity && astro check && astro build.
  • Co-relocation keeps ../docs intact: if docs/ and docs-site/ move together into one repo, the relative coupling survives unchanged — no manifest edit needed.

1.4 Docs-automation across the repo boundary

Section titled “1.4 Docs-automation across the repo boundary”
  • scripts/skills/run-skill.ts:43 and scripts/docubot/run-agent.ts:47 both root on process.env.GITHUB_WORKSPACE ?? process.cwd()a single base-path knob. Point GITHUB_WORKSPACE (or cwd) at the right checkout and they work.
  • The five docs skills target docs-site/src/content/docs/** (the build output tree), not docs/. sync-source-docs is the only one that bridges code→docs: it reads supabase/types/, lib/mcp/, app/api/**/route.ts (CODE repo) and writes docs-site/src/content/docs/reference/{schema-quick-reference,mcp-inventory,api-routes}.md (DOCS repo, post-move). This is the one skill that must straddle both repos.
  • docubot writes docs-site/src/content/docs/<space>/<file>.md and reads docs/reference/documentation-inventory.md — both move with the docs repo.

1.5 CI workflow trigger direction (the real cross-repo problem)

Section titled “1.5 CI workflow trigger direction (the real cross-repo problem)”

Every docs workflow currently lives in .github/workflows/ of the code repo and uses actions/checkout on the code repo:

WorkflowTriggerWhat it needs post-move
docs-site-deploy.ymlpush on main/staging touching docs-site/**, docs/**Moves to docs repo (it builds the docs site) — trigger on docs-repo pushes
docubot.ymlmerged PRNeeds the CODE (to read source) + DOCS (to write pages) — must check out both
sync-source-docs.ymlweekly cron + dispatchReads code, writes docs — must check out both, or live in code repo and push to docs repo
review-docs-pr.ymlPR review on docubot/ branchesMoves to docs repo (reviews docs PRs there)
missing-docs.yml / docs-seo-audit.ymlcronMove to docs repo
task-view-vendor-drift.ymlpush touching lib/validation/{task-list,roadmap,backlog}-schema.tsStays in CODE repo (watches code schema), but the mirror-regen it guards now targets the docs repo

The asymmetry: anything that consumes the docs corpus moves to the docs repo; anything that bridges code → docs (sync-source-docs, docubot) must touch both repos and therefore needs cross-repo checkout + a cross-repo PR/push mechanism (a PAT or GitHub App installed on both, plus repository_dispatch for trigger fan-out).


Submodule is OUT (Liam). The realistic candidates:

Section titled “Option A — Two plain repos, docs repo checked out as a sibling directory, base-path via env var ★ RECOMMENDED”

knowledge-hub (code, →public) and knowledge-hub-docs-site (docs+ledgers+docs-site, private). Locally and in CI they sit side-by-side:

~/Documents/development/
knowledge-hub/ # code (public-ready)
knowledge-hub-docs-site/ # docs/ + .planning/.archive/ + docs-site/ (private)
docs/reference/*.json # the ledgers live here now

The SDLC loop sets one env var, e.g. KH_DOCS_DIR=../knowledge-hub-docs-site, and the ledger CLI / mirror regen / skills resolve ledger + spec paths against it.

  • Pros: Lowest friction. ledger-cli --ledger-dir already works (verified). sync-content.ts already takes --source-root. Both run-skill/docubot already root on one env knob. No pointer-bump commits (the submodule killer). Independent visibility (one public, one private) and independent release cadence (docs-as-code “orthogonal” autonomy). History of each repo is clean.
  • Cons: Ledger and code edits land in two PRs/commits (split-brain risk if a code change and its spec update aren’t both pushed). Cross-repo CI needs a PAT/App. The industry “sidecar” benefit — docs+code in one PR, zero drift — is lost (this is the fundamental cost of the override; see §4 verdict).
  • Why it wins anyway: it is the only option that satisfies “nothing under /docs public” without the per-session double-commit friction a submodule imposes, and the existing tooling is already 80% parameterised for it.

Code repo keeps a subtree of the docs repo; git subtree push/pull syncs.

  • Pros: Single working tree locally; no env var.
  • Cons: git subtree on a directory the loop mutates every session is error-prone and slow; history still entangled; and crucially it does NOT remove docs from the public repo’s HEAD (the whole point of the override). Disqualified — fails the public-scope requirement.

Option C — Federated / multi-root docs build (Antora-style, or Starlight content-loader)

Section titled “Option C — Federated / multi-root docs build (Antora-style, or Starlight content-loader)”

Docs repo’s site generator pulls reference content from the public code repo at build time (prior art: Antora multi-repo aggregation; Docusaurus docusaurus-plugin-remote-content; Starlight custom GitHub file-loader — all confirmed viable but immature for Starlight).

  • Pros: Single-source; the site can still surface code-derived reference docs.
  • Cons: This solves a problem the override removed: under “nothing in /docs public” there is no public staying-reference to aggregate. Starlight multi-repo support is explicitly not yet first-class (withastro/starlight#2844, #956). Over-engineered for this case. Keep as a future option only if some reference docs are later re-published.

Option D — Two-repo “monorepo-lite” via a parent workspace

Section titled “Option D — Two-repo “monorepo-lite” via a parent workspace”

A wrapper dir or bun/pnpm workspace listing both repos as siblings.

  • Pros: Tooling discovers both automatically.
  • Cons: Adds a third artefact to manage; no real advantage over Option A’s sibling-dir + env var for this use case.

Prior-art grounding: Passo.uno’s docs-as-code topologies maps this exactly. KH is moving from the sidecar pattern (docs+code together, zero drift, the current state) to the orthogonal pattern (separate docs repo, docs gain autonomy but the code↔docs link becomes manual). The literature’s headline warning — orthogonal “lacks explicit sync mechanisms, creating fragmentation” — is precisely the drift risk KH inherits, and is why the code↔docs sync mechanics (§3) and the SDLC-write-path fix (§4) are the load-bearing parts of {68.12}, not the file move itself.


3. Code ↔ docs sync after the split (Q2)

Section titled “3. Code ↔ docs sync after the split (Q2)”

The good news: KH’s tooling is mostly parameterised already. The mechanics, concretely:

  1. sync-source-docs (code→docs) — the one true cross-repo bridge. It reads the CODE repo (supabase/types, lib/mcp, app/api) and writes the DOCS repo (docs-site/src/content/docs/reference/*). Cleanest mechanic: run it in the code repo’s CI (where the source lives, so drift is detected at source-change time), checkout the docs repo as a second path, write the generated MDs there, and open a PR against the docs repo via a PAT/GitHub-App token. CI trigger direction = code-repo push → docs-repo PR. (Alternative: docs-repo cron clones the code repo read-only and pulls — but that detects drift late and re-implements what the code repo already knows.)

  2. docubot (code-context→docs pages). Already roots on GITHUB_WORKSPACE. Move the workflow to the docs repo OR keep it in the code repo and have it check out both. It reads source context (code repo) and documentation-inventory.md (docs repo) and writes docs-site pages (docs repo). Needs both checkouts + cross-repo push token regardless of which repo hosts the workflow.

  3. docs-site build + the 5 consumer skills. check-for-broken-links, missing-docs, docs-seo-audit, review-docs-pr all operate on docs-site/src/content/docs/**. Once docs-site/ co-relocates with docs/, they run entirely within the docs repo with ../docs intact — no cross-repo wiring. Their run-skill.ts driver just needs GITHUB_WORKSPACE = docs-repo checkout. The five workflows move to the docs repo.

  4. task-view-vendor-drift.yml stays in the code repo (it watches the code-repo schema files lib/validation/*-schema.ts). But what it protects — the per-record mirror regen — now writes to the docs repo. So regen-mirrors.sh must be repointed (§4) and the ledger-mirror-parity CI gate must run in (or against) the docs repo.

Trigger-direction summary: code→docs automation (sync-source-docs, docubot) lives where the source is (code repo) and pushes to docs; docs-internal automation (site build, link/SEO/missing-docs, docs-PR review) lives in the docs repo. The one piece of glue both directions need is a cross-repo token (fine-grained PAT or a GitHub App installed on both repos) plus repository_dispatch for trigger fan-out.


4. The SDLC write-path verdict (Q3 — the hard one)

Section titled “4. The SDLC write-path verdict (Q3 — the hard one)”

What breaks if the ledgers + specs + workflow-evaluation/ live in another repo:

ToolingBreaks?WhyFix
ledger-cli.ts coreNo--ledger-dir already overrides; resolve() is cwd/arg-relative (verified)Pass --ledger-dir $KH_DOCS_DIR/docs/reference
regen-mirrors.shYESHardcodes docs/reference/* (L77-79, 85); spawned by default after every writeParameterise base dir (env KH_DOCS_DIR or arg); thread it from ledger-cli
39 skill invocations of ledger-cliYESOmit --ledger-dir → resolve against cwd (code repo)Add --ledger-dir, OR run the loop with cwd in the docs repo
4 roundtrip guard testsYESreadFileSync('docs/reference/*.json') from code-repo rootMove to docs-repo test suite (cleanest) or read sibling checkout
doc-freshness.test.tsYESreads docs/reference/*.md + docs/runbooks/* from PROJECT_ROOTMove to docs-repo suite or repoint
ledger-budgets.ts DISCIPLINE_DOCCosmeticString constant for messagesRepoint string
stop-worker.sh workflow-eval archiveYESWrites ${PROJECT_ROOT}/docs/workflow-evaluation/sessionsRepoint PROJECT_ROOT/base to docs repo
spec-driven skillsYES (prose)Resolve docs/specs/ID-N-…/ from code rootRepoint prose to $KH_DOCS_DIR
documentation-inventory guard, CLAUDE.md (23 docs/ + 2 .planning/ pointers)YES (prose)Pointers resolve to code rootRepoint the moved pointers; keep the few staying

Verdict: The write-path is recoverable with modest, well-scoped work — it is NOT a blocker, because the two highest-frequency writers are already parameterised (ledger-cli --ledger-dir, sync-content --source-root) and the agent drivers root on a single env knob (GITHUB_WORKSPACE). The break surface is concentrated in four hardcoded spots (regen-mirrors.sh, the roundtrip + doc-freshness tests, stop-worker.sh) plus prose repointing (39 skill invocations + CLAUDE.md + spec skills).

Cleanest write-path design — the “sibling checkout + one env var” pattern (Option A):

  1. Introduce a single source of truth for the docs-repo location: env var KH_DOCS_DIR (default ../knowledge-hub-docs-site), resolved in ledger-cli, regen-mirrors.sh, stop-worker.sh, and the agent drivers.
  2. The SDLC loop runs with both repos checked out side-by-side. Ledger/spec writes go to $KH_DOCS_DIR; code writes go to the code repo. Each session may produce a docs-repo commit AND a code-repo commit — accept the double-commit (it is the cost of the override; far cheaper than submodule pointer-bumps because there is no pointer to bump, just two independent git pushes).
  3. Relocate the ledger guard tests + workflow-evaluation tooling to the docs repo so bun run test in the docs repo gates ledger integrity, and the code repo’s bun run test no longer depends on files it doesn’t own.
  4. The ledger-mirror-parity CI gate moves to (or also runs in) the docs repo.

The unavoidable structural cost (be explicit with Liam): moving from sidecar→orthogonal trades zero-drift-by-construction for autonomy. Today a spec and the code it describes land in one PR; after the split they land in two repos and can drift. The docs skills (sync-source-docs, docubot, the freshness guards) become the compensating control for that drift — which is exactly the role the prior framing misattributed to them as “mechanising the split.” They don’t mechanise the move; they counteract the drift the move introduces. That is the honest, grounded reframe.


Topology (one sentence): Two plain private/public repos with the docs repo (knowledge-hub-docs-site, holding docs/ + .planning/.archive/ + the co-relocated docs-site/ Astro project) checked out as a sibling directory of the code repo, with a single KH_DOCS_DIR env var threaded through the four hardcoded write-path spots — NOT a submodule, NOT subtree, NOT a multi-root docs build.

Phased migration sketch (for the implementation session)

Section titled “Phased migration sketch (for the implementation session)”
  • Phase 0 — parameterise in place (in the CODE repo, before any move): add KH_DOCS_DIR support to regen-mirrors.sh, stop-worker.sh, and thread --ledger-dir through the 39 skill invocations + CLAUDE.md. Land + verify bun run test green with KH_DOCS_DIR pointing at the current in-repo docs/ (proves the knob works with zero behaviour change).
  • Phase 1 — create the private repo + extract history: git filter-repo --path docs/ --path .planning/.archive/ --path docs-site/ from a pre-purge mirror clone into knowledge-hub-docs-site (preserves authorship/history). RETIRE .planning/codebase/ (do not carry it over).
  • Phase 2 — move the consumers: relocate the 6 docs-internal workflows + the 5 docs skills + docubot + run-skill.ts + the ledger guard tests + workflow-evaluation tooling into the docs repo; wire the one cross-repo bridge (sync-source-docs/docubot → docs-repo PR via PAT/App).
  • Phase 3 — repoint + verify: repoint CLAUDE.md (23/2 pointers), .gcloudignore, onprem-deploy.yml comment, .prettierignore, knip.config.ts; bun run test green in both repos; docs-site build dry-run green in the docs repo; git grep finds no load-bearing code-repo refs to relocated paths.
  • Phase 4 — {68.9} flip (only after Phases 0-3 verified).
  • Phase 5 — {68.10} history purge: extend the filter-repo --invert-paths set to strip docs/**, .planning/**, docs-site/** from ALL code-repo history + the “zero blobs across all history for the relocated path set” acceptance line.

Specific files to repoint “outside the repository”

Section titled “Specific files to repoint “outside the repository””
  • Hardcoded write-path (code edits): scripts/regen-mirrors.sh, scripts/stop-worker.sh (under .claude/skills/session-driver-cmux/scripts/), lib/validation/ledger-budgets.ts, __tests__/docs/*-roundtrip.test.ts (×4), __tests__/validation/doc-freshness.test.ts, the one-shot scripts/ledger-*.ts + backfill-capability-theme.ts + detect-roadmap-shipped-framings.ts + roadmap-from-json.ts.
  • Agent/skill/prose repointing: 39 ledger-cli invocations across .claude/skills/*/SKILL.md; workflow-orchestration, write-product-spec, write-tech-spec, spec-driven-implementation, implement-subtask, update-roadmap-backlog, evaluate-workflow, evaluate-findings, start-session skill bodies; root CLAUDE.md (23 docs/ + 2 .planning/ pointers) + .ast-dataflow/CLAUDE.md.
  • CI workflows to relocate to the docs repo: docs-site-deploy.yml, missing-docs.yml, docs-seo-audit.yml, review-docs-pr.yml, and the docs-internal half of sync-source-docs.yml
    • docubot.yml (these two straddle both repos).
  • Config (trim/repoint, low-risk): .gcloudignore, .github/workflows/onprem-deploy.yml comment (L112), .prettierignore, knip.config.ts, tsconfig.json (only the docs-site exclude, if docs-site moves).
  • Driver env knobs (already exist — just set them): GITHUB_WORKSPACE / process.cwd() in run-skill.ts:43 + docubot/run-agent.ts:47; --source-root in sync-content.ts; --ledger-dir in ledger-cli.ts.

  1. OQ-R1 — write-path home: confirm the sibling-checkout + KH_DOCS_DIR model (Option A) over running the entire SDLC loop from inside the docs repo. Sibling-checkout keeps code edits and ledger edits in their natural repos; loop-from-docs-repo would invert which repo is “primary.” Recommend sibling-checkout.
  2. OQ-R2 — cross-repo auth: fine-grained PAT vs a GitHub App installed on both repos for the sync-source-docs/docubot code→docs PR bridge? (App is cleaner long-term; PAT is faster to stand up.)
  3. OQ-R3 — bun run test ownership: confirm the 4 ledger roundtrip guards + doc-freshness.test.ts move to the docs repo’s test suite (so the code repo’s tests don’t depend on files it no longer owns), vs reading them from the sibling checkout.
  4. OQ-R4 — workflow-evaluation/ cadence: the evaluator sweeps (evaluate-workflow/ evaluate-findings) read 20+ archived sessions. Confirm they run from the docs repo post-move (they write retro findings to product-retros.json, which also moves) — i.e. the whole evaluation lane relocates as a unit.
  5. OQ-R5 — double-commit acceptance: explicit sign-off that a session producing both a code commit and a docs/ledger commit (two pushes) is acceptable — this is the structural cost of the override and the sidecar→orthogonal trade. No tooling avoids it short of a submodule (rejected).
  6. OQ-R6 — docs/runbooks/ operational pointers: several runbooks are cited by code-repo CI workflows (onprem-deploy.yml, staging-reference-refresh.yml) as docs/runbooks/… comments. Pure comments (harmless if they dangle), but confirm whether any operationally live runbook should be mirrored back into the code repo for on-call discoverability.