Skip to content

Docs infrastructure reorganization plan

Goal: migrate the flat docs/ directory to a structured MkDocs + Material site with ADR conventions, topic-grouped hierarchy, link-checked build, and a private-visibility GitHub Pages deploy, without losing any git history and without breaking the Stage S2 Phase C research queue.

This file is the authoritative execution plan. It is designed to be fully self-contained so that after a context compaction the executor can open this file, the related Q1-Q5 tracker, and the current Stage 2 plan, and proceed without re-deriving anything.

Status and safety net

Before the reorganization started the following already existed:

  • plan-stage2-phase-c-queue.md — the standalone Q1-Q5 research tracker (committed as 7d1ae8c). This file must not be lost, merged, or rewritten during the reorganization. It can be moved to a new path (see Appendix A: git-mv map) with history preserved, but its contents are owned by the research track, not by this plan.
  • research/s2c-self-experiment/ — the self-experiment artifacts and FINDINGS. These are also moved by git mv only.
  • patches/pymobiledevice3/ — the pymobiledevice3 upstream-plus-patches overlay. Left untouched in place (the directory already sits at the correct place in the target hierarchy).

Every phase below ends with a commit and a push. Any phase can be interrupted cleanly at that point without leaving the repo in an inconsistent state.

Constraints (in priority order)

  1. Git is the source of truth. Everything lives in the repo; the rendered MkDocs site is a derived artifact.
  2. Markdown is the format. No format translations.
  3. git mv, not cp + rm. History must be followable across the reorganization via git log --follow.
  4. No broken links. mkdocs --strict build must pass on every commit from Phase 1 onward. Old paths get 301-redirected via mkdocs-redirects plugin.
  5. Python venv for docs in /home/op/venvs/iosmux-docs/, per the user rule that all Python venvs live under /home/op/venvs/.
  6. No personal data in committed docs. UDIDs, serials, hostnames, MAC-derived IPv6 addresses are swept before every commit that adds new research output.
  7. No guessing. Structural choices are either explicit in this plan or deferred to the user before execution.

Target directory hierarchy

docs/
├── index.md                          # Landing (was docs/README.md)
├── stylesheets/
│   └── extra.css                     # Custom admonitions, status badges
├── architecture/                     # How the system works NOW (current state)
│   ├── .pages                        # awesome-pages ordering
│   ├── index.md                      # Overview + top-level diagram
│   ├── connection.md                 # was architecture-connection.md
│   └── protocol/
│       ├── .pages
│       ├── rsd-remotexpc.md          # was research/remotexpc-protocol.md
│       ├── coredevice-xpc.md         # was research/coredevice-xpc-protocol.md
│       ├── l2-bridge.md              # was research/l2-bridge.md
│       └── os-remote-device.md       # was research/os-remote-device-api.md
├── adr/                              # Architecture Decision Records (immutable)
│   ├── .pages
│   ├── index.md                      # ADR conventions + list
│   ├── template.md                   # copy-to-create shape
│   ├── 0001-use-pymobiledevice3-not-goios.md
│   ├── 0002-tunneld-on-vm-not-host.md
│   ├── 0003-pymob-patches-overlay-not-fork.md
│   ├── 0004-stage2-option-delta-shim.md
│   ├── 0005-phase-d-go-only-no-new-python.md
│   ├── 0006-empirical-only-no-guessing.md
│   └── 0007-mkdocs-material-docs-site.md
├── plans/                            # Forward planning
│   ├── .pages
│   ├── index.md                      # Active plans + archive link
│   ├── stage1.md                     # was plan-stage1-rebuild.md
│   ├── stage2.md                     # was plan-stage2-pair-flow.md
│   ├── stage2-phase-c-queue.md       # was plan-stage2-phase-c-queue.md
│   ├── docs-reorganization.md        # this file (was plan-docs-reorganization.md)
│   └── archive/
│       ├── .pages
│       ├── 2026-03-forward-roadmap.md         # was plan-forward-roadmap.md
│       ├── 2026-03-full-xcode-integration.md  # was plan-full-xcode-integration.md
│       ├── 2026-03-cds-rsd-inject.md          # was plan-cds-rsd-inject.md
│       └── 2026-03-next-steps.md              # was plan-next-steps.md
├── research/                         # Empirical work, grouped by TOPIC
│   ├── .pages
│   ├── index.md                      # Research index with status per entry
│   ├── protocol/                     # Wire captures and RemoteXPC analysis
│   │   ├── .pages
│   │   ├── s2b-pair-attempt-log.md
│   │   ├── s2c-self-experiment/
│   │   │   ├── index.md              # was README.md
│   │   │   ├── FINDINGS.md
│   │   │   ├── iosmux-spike-listener.py
│   │   │   └── results/              # raw pcaps / logs (personal-data-swept)
│   │   └── rsd-info-ios2641-reference.json
│   ├── coredevice-internals/         # CoreDevice.framework disasm
│   │   ├── .pages
│   │   ├── coredevice-injection.md
│   │   ├── coredevice-representation.md
│   │   ├── rsd-wrapper-init-analysis.md
│   │   ├── s1a-properties-audit.md
│   │   ├── s1c-static-browser-disasm.md
│   │   ├── s1c-managed-service-devices-registration.md
│   │   ├── action-interception-full-picture.md
│   │   └── pair-button-and-cfnetwork.md
│   └── environment/                  # Bridge, USB, audits, wrapper test results
│       ├── .pages
│       ├── code-audit-findings.md
│       ├── phase2-behavior-analysis.md
│       └── phase2-wrapper-fix-test-results.md
├── patches/                          # UNCHANGED
│   └── pymobiledevice3/...
└── runbooks/                         # How-to guides
    ├── .pages
    ├── index.md                      # Runbook index
    └── restore-environment.md        # refers to scripts/iosmux-restore.sh

Notes on the hierarchy:

  • Topic-grouped research (user preference), no session-based split. The filename prefixes (s1a-, s1c-, s2b-, s2c-) still carry the provenance, so git blame plus those prefixes preserve temporal ordering without a dedicated session-N/ directory.
  • session8-five-questions.md moves to plans/archive/ because it is historically a planning / five-questions doc, not an empirical research output. Filename becomes 2026-03-session8-five-questions.md for date-prefix consistency with other archived plans.
  • plan-stage2-phase-c-queue.md and this file both move into plans/ as active plans alongside stage1.md and stage2.md.
  • patches/pymobiledevice3/ is already under docs/, so no move needed.

Plugin list (final, pinned in docs/requirements.txt)

mkdocs >= 1.6, < 2
mkdocs-material >= 9.5
mkdocs-material-extensions
pymdown-extensions
mkdocs-git-revision-date-localized-plugin
mkdocs-git-authors-plugin
mkdocs-awesome-pages-plugin
mkdocs-macros-plugin
mkdocs-glightbox
mkdocs-redirects

Lint tooling (added in Phase 5):

pymarkdownlnt
linkchecker

Admonition set (final, 4 types)

Custom admonition styles configured in docs/stylesheets/extra.css with icons + colors:

Type Purpose Color hint
verified Empirically confirmed fact with cited evidence. Strongest claim. green
hypothesis Plausible but unconfirmed. Must link to the research task that would confirm or refute it. yellow
gap Known empirical gap — we know we don't know this. Halts any downstream decision that would require the missing data. red
superseded This section / claim has been replaced; points at the replacement. grey

These supplement (do not replace) the standard MkDocs Material admonitions (note, info, tip, warning, danger, success, question, failure, bug, example, quote).

Status frontmatter (applied per file)

Every markdown file under docs/ gains a YAML frontmatter block at the top:

---
status: verified | reviewed | draft | hypothesis | superseded | archived
last-verified: YYYY-MM-DD
evidence:
  - path: research/protocol/s2c-self-experiment/results/iosmux_wire.log
    what: sandbox probe errno=0
---
  • status: verified — empirical or primary-source confirmed
  • status: reviewed — derived logically from verified facts, ran through review
  • status: draft — in progress
  • status: hypothesis — unconfirmed, needs an empirical task
  • status: superseded — replaced; includes superseded-by field
  • status: archived — historical, no longer active

Not every file gets every field. Evidence list is optional but mandatory when status: verified on a research finding.

ADR list (7 retrospective records)

Each ADR is short — the pattern is Michael Nygard's "Documenting Architecture Decisions": Title, Status, Context, Decision, Consequences, Evidence, References. Immutable after acceptance.

  • 0001 — Use pymobiledevice3, not go-ios. Go-ios was rejected for iOS 17+ instability. Context from CLAUDE.md ("Key Decisions"). Status: accepted.
  • 0002 — tunneld runs inside the VM, not on the host. Context from architecture-connection.md "Historical note: why host-side tunneld was wrong". Status: accepted.
  • 0003 — pymobiledevice3 patches live as an overlay in iosmux, not as a GitHub fork. Context: temporary Python dependency, avoid long-lived fork inertia before the Go port. Status: accepted.
  • 0004 — Stage 2 uses Option δ shim, not α/β/γ. Context from plan-stage2-pair-flow.md and research/s2b-pair-attempt-log.md. Status: accepted.
  • 0005 — Phase D backend is Go only, no new production Python. Context: project is Go-based; Python is contained to the existing pymobiledevice3 remote tunneld subprocess; new long-lived Python introduces removal debt. Status: accepted.
  • 0006 — Empirical-only discipline: no guessing in research or planning. Every claim either has evidence or is explicitly marked UNKNOWN/hypothesis/gap. Status: accepted.
  • 0007 — Documentation engine is MkDocs + Material for MkDocs. Context: this file and the preceding research on docs engines. Status: accepted.

Execution phases

Each phase ends with one commit + push. Phases can be executed back-to-back in one session or split across sessions.

Phase 1 — Bootstrap MkDocs + Material (flat layout, no reorg yet)

Goal: working mkdocs serve and mkdocs build --strict on the current flat docs/ layout. Reorganization comes in Phase 2.

Steps:

  1. 1.1 Create venv: python3 -m venv /home/op/venvs/iosmux-docs
  2. 1.2 Create docs/requirements.txt with the plugin list above (without lint tooling, that comes in Phase 5).
  3. 1.3 Install: /home/op/venvs/iosmux-docs/bin/pip install --upgrade pip && /home/op/venvs/iosmux-docs/bin/pip install -r docs/requirements.txt
  4. 1.4 Create mkdocs.yml at the repo root (see Appendix B for the template).
  5. 1.5 Create docs/stylesheets/extra.css with custom admonition definitions (see Appendix C).
  6. 1.6 Create scripts/docs-venv-setup.sh — idempotent venv bootstrap (see Appendix D).
  7. 1.7 Create scripts/docs-serve.shmkdocs serve wrapper (see Appendix D).
  8. 1.8 Create scripts/docs-build.shmkdocs build --strict wrapper (see Appendix D).
  9. 1.9 Add site/ to .gitignore.
  10. 1.10 Run ./scripts/docs-build.sh. Expect broken-link errors from existing docs — fix them inline by updating relative paths. Iterate until the build is clean.
  11. 1.11 Run ./scripts/docs-serve.sh, open http://127.0.0.1:8000, visually check: landing, Mermaid diagrams on architecture-connection.md, hex code blocks on research/s2c-self-experiment/FINDINGS.md, search works.
  12. 1.12 Commit + push:

    Bootstrap MkDocs + Material docs site (flat layout)
    
    Adds mkdocs.yml, docs/requirements.txt, docs/stylesheets/extra.css
    (custom verified/hypothesis/gap/superseded admonitions), and
    scripts/docs-{venv-setup,serve,build}.sh. Docs render from the
    existing flat docs/ layout; reorganization to the target
    hierarchy is Phase 2.
    
    Build passes with mkdocs --strict after fixing N pre-existing
    broken relative links in <list of files>.
    

Done criteria:

  • ./scripts/docs-build.sh exits 0 with --strict
  • ./scripts/docs-serve.sh serves on :8000
  • All existing docs render correctly (Mermaid, code blocks, tables, internal links)
  • Commit pushed

Phase 2 — Reorganize docs/ to target hierarchy

Goal: move every file to its target location per the hierarchy above. Redirects preserve old URLs.

Steps:

  1. 2.1 Create the target directory skeleton:
cd docs
mkdir -p architecture/protocol
mkdir -p adr
mkdir -p plans/archive
mkdir -p research/protocol/s2c-self-experiment
mkdir -p research/coredevice-internals
mkdir -p research/environment
mkdir -p runbooks
  1. 2.2 Execute the git mv operations per Appendix A. Do them in dependency order: destination parent dir exists, then move.
  2. 2.3 Rename landing page: git mv docs/README.md docs/index.md.
  3. 2.4 For each subdirectory with an index, rename to index.md where needed (e.g. research/protocol/s2c-self-experiment/README.md → .../s2c-self-experiment/index.md).
  4. 2.5 Create .pages files in each new directory with logical ordering (see Appendix E for templates). Key directories needing .pages:
  5. docs/.pages
  6. docs/architecture/.pages
  7. docs/architecture/protocol/.pages
  8. docs/adr/.pages
  9. docs/plans/.pages
  10. docs/plans/archive/.pages
  11. docs/research/.pages
  12. docs/research/protocol/.pages
  13. docs/research/coredevice-internals/.pages
  14. docs/research/environment/.pages
  15. docs/runbooks/.pages
  16. 2.6 Create stub index.md files where missing so that awesome-pages has a landing for each directory:
  17. docs/architecture/index.md
  18. docs/architecture/protocol/index.md
  19. docs/adr/index.md (the ADR conventions doc)
  20. docs/plans/index.md
  21. docs/research/index.md
  22. docs/research/protocol/index.md
  23. docs/research/coredevice-internals/index.md
  24. docs/research/environment/index.md
  25. docs/runbooks/index.md
  26. 2.7 Update all internal links to point at the new paths. Approach:
  27. Grep for each old path referenced in any markdown file
  28. Replace with the new path
  29. Run mkdocs build --strict — any leftover broken link fails the build

Specifically the following files have many internal links that need rewriting: - docs/index.md (landing, lots of refs) - docs/plans/stage2.md (was plan-stage2-pair-flow.md) - docs/plans/stage2-phase-c-queue.md - docs/architecture/connection.md - docs/research/protocol/s2c-self-experiment/FINDINGS.md - docs/research/protocol/s2c-self-experiment/index.md - CLAUDE.md — check for any docs/... refs - Root README.md — check for any docs/... refs 8. 2.8 Configure mkdocs-redirects in mkdocs.yml with the full old→new path map from Appendix A. Every old path returns a 301 to the new path when the site is served. 9. 2.9 Run ./scripts/docs-build.sh — must pass with --strict. Fix any missed refs. 10. 2.10 Run ./scripts/docs-serve.sh, click through every section: architecture, adr (still sparse), plans, research subtrees, patches, runbooks. Confirm navigation tree looks sensible. Visit a few old URLs to confirm redirects fire. 11. 2.11 Commit + push:

```
Reorganize docs/ into architecture / plans / research / adr / runbooks

Topic-grouped layout, per the user-approved target hierarchy in
plan-docs-reorganization.md. Every file moved via git mv so
--follow history is preserved. mkdocs-redirects plugin
301-redirects every old path to its new location; mkdocs build
--strict is clean.

Also moves README.md → index.md (MkDocs landing convention) and
adds stub index.md + .pages files for all new directories.
```

Done criteria:

  • git log --follow docs/architecture/connection.md shows the full pre-rename history
  • docs-build.sh --strict passes
  • mkdocs-redirects config includes every old path
  • Navigation tree matches the target hierarchy
  • Commit pushed

Phase 3 — Status frontmatter + custom admonitions

Goal: make every key doc carry explicit status metadata; introduce the 4 custom admonitions rendered by extra.css.

Steps:

  1. 3.1 The extra.css for custom admonitions was added in Phase 1. Phase 3 adds actual usage.
  2. 3.2 Write docs/index.md section "How to read these docs" explaining:
  3. The status frontmatter values
  4. The 4 custom admonitions (verified, hypothesis, gap, superseded) and when to use each
  5. Hard rule: empirical evidence or explicit UNKNOWN, never guessing (pointer to ADR 0006)
  6. 3.3 Apply status frontmatter to these key files (not all at once, prioritize the ones readers will hit first):
  7. docs/index.md — status: verified
  8. docs/architecture/connection.md — status: verified
  9. docs/plans/stage1.md — status: archived (stage is done)
  10. docs/plans/stage2.md — status: reviewed
  11. docs/plans/stage2-phase-c-queue.md — status: draft (already has this frontmatter)
  12. docs/plans/docs-reorganization.md — status: draft (this file)
  13. docs/research/protocol/s2b-pair-attempt-log.md — status: verified
  14. docs/research/protocol/s2c-self-experiment/FINDINGS.md — status: verified
  15. docs/research/protocol/s2c-self-experiment/index.md — status: verified
  16. docs/research/coredevice-internals/*.md — status: archived or reviewed case by case (they are historical)
  17. docs/research/environment/*.md — same
  18. docs/plans/archive/*.md — status: archived
  19. 3.4 Use the new admonitions in 2-3 places as examples (don't spray everywhere — prove the mechanism works, let future content adopt them organically):
  20. In FINDINGS.md: !!! verified "Blocker Z closed" wrapping the sandbox probe results
  21. In plans/stage2.md: !!! hypothesis "Expected pymobiledevice3 pair flow behavior" wrapping any unverified assumption
  22. In plans/stage2-phase-c-queue.md: !!! gap "Post-TLS server responses" wrapping the friend-capture fallback paragraph
  23. 3.5 Verify with ./scripts/docs-serve.sh that frontmatter renders as a badge and custom admonitions have the expected color / icon.
  24. 3.6 Commit + push:

    Add status frontmatter + custom admonitions on key docs
    
    Every key doc now carries a YAML frontmatter block with status
    (verified/reviewed/draft/hypothesis/superseded/archived),
    last-verified date, and optional evidence list. Four custom
    admonitions (verified, hypothesis, gap, superseded) are defined
    in docs/stylesheets/extra.css and demonstrated in three places.
    
    Landing page gains a "How to read these docs" section wiring
    the metadata convention together with ADR 0006's empirical-only
    rule.
    

Done criteria:

  • docs/index.md documents the metadata convention
  • At least 10 files have status: frontmatter
  • Visual check: badges render, admonitions look right
  • Commit pushed

Phase 4 — ADR framework + 7 retrospective records

Goal: the docs/adr/ skeleton and 7 backfilled ADRs that capture decisions the project already made implicitly.

Steps:

  1. 4.1 Write docs/adr/index.md — ADR conventions, index of current ADRs, link to template, filing rules (immutable, superseded-by only, numeric prefix).
  2. 4.2 Write docs/adr/template.md — the Nygard pattern.
  3. 4.3 Write each ADR 0001..0007 per the list above. Each ADR is 1-2 pages max, heavily evidence-linked. Context for each ADR:
  4. 0001 — reference CLAUDE.md "Key Decisions: go-ios REJECTED (unstable iOS 18+) → pymobiledevice3 tunneld as subprocess"
  5. 0002 — reference docs/architecture/connection.md "Historical note: why an earlier design with host-side tunneld was wrong"
  6. 0003 — reference docs/patches/pymobiledevice3/README.md and the user's decision rationale in this session
  7. 0004 — reference docs/research/protocol/s2b-pair-attempt-log.md (Phase B findings that killed α/γ and promoted δ)
  8. 0005 — reference docs/research/protocol/s2c-self-experiment/FINDINGS.md (Shape B confirmed viable) and the Go pivot section in docs/plans/stage2.md
  9. 0006 — empirical-only rule, no direct file quote — this is a meta-decision. Reference plan-stage2-phase-c-queue.md as a concrete instance of the rule being applied.
  10. 0007 — this very reorganization plan + the options research that preceded it
  11. 4.4 Each ADR gets status: accepted in frontmatter + a date: field.
  12. 4.5 Update mkdocs.yml / .pages so ADRs appear in the top-level navigation.
  13. 4.6 ./scripts/docs-build.sh — check no broken links in the new ADR cross-references.
  14. 4.7 Commit + push:

    Add ADR framework + 7 retrospective decision records
    
    docs/adr/ now holds:
      - index.md with conventions and list
      - template.md (Nygard pattern)
      - 0001..0007 backfilled from already-made decisions
    
    Each ADR is short, heavily cross-referenced to the evidence
    (previous commits, research docs, plan files). Future decisions
    follow the same pattern; once an ADR is accepted it is
    immutable — changes come as new ADRs with superseded-by.
    

Done criteria:

  • 9 files under docs/adr/ (index + template + 7 ADRs)
  • Every ADR cross-links to its evidence
  • ADR section visible in site navigation
  • Commit pushed

Goal: tooling to keep docs from rotting. Fails builds on broken links and style issues.

Steps:

  1. 5.1 Add pymarkdownlnt and linkchecker to docs/requirements.txt.
  2. 5.2 scripts/docs-venv-setup.sh upgrades so the new deps install.
  3. 5.3 Create .pymarkdown.yml at the repo root with reasonable defaults (MD013 line-length off, MD033 HTML off, MD041 first-line-h1 off for frontmatter files).
  4. 5.4 Create scripts/docs-lint.sh:

    #!/usr/bin/env bash
    set -euo pipefail
    source /home/op/venvs/iosmux-docs/bin/activate
    cd "$(git rev-parse --show-toplevel)"
    pymarkdown scan docs/
    

Add linkcheck as a separate step (heavy, optional):

# Run only if linkchecker is installed
# (docs-lint.sh does not gate on it)
  1. 5.5 Create Makefile (or extend if present) at repo root with targets:
.PHONY: docs-setup docs-serve docs-build docs-lint docs-clean

docs-setup:
 ./scripts/docs-venv-setup.sh

docs-serve:
 ./scripts/docs-serve.sh

docs-build:
 ./scripts/docs-build.sh

docs-lint:
 ./scripts/docs-lint.sh

docs-clean:
 rm -rf site/
  1. 5.6 Run make docs-lint. Fix cosmetic warnings.
  2. 5.7 Run make docs-build. Must pass.
  3. 5.8 Commit + push:

    Add docs lint (pymarkdown + linkchecker) + Makefile targets
    
    New docs-lint.sh runs pymarkdown style checks over docs/;
    Makefile targets (docs-setup, docs-serve, docs-build, docs-lint,
    docs-clean) wrap the scripts for one-line invocation. lint
    passes clean over the current corpus.
    

Done criteria:

  • make docs-lint and make docs-build both exit 0
  • Commit pushed

Phase 6 — GitHub Pages deploy via Actions

Goal: on every push to main that touches docs, rebuild the site and deploy to GitHub Pages (private visibility, GH Pro account).

Steps:

  1. 6.1 Create .github/workflows/docs.yml per Appendix F.
  2. 6.2 In mkdocs.yml, set site_url to https://staticwire.github.io/iosmux/ (confirm the exact path once the first deploy succeeds — for an organization repo it is typically https://<owner>.github.io/<repo>/).
  3. 6.3 User action: in the GitHub repo web UI:
  4. Settings → Pages → Build and deployment → Source: GitHub Actions
  5. Settings → Pages → Privacy: keep the default (organization-private for GH Pro)
  6. Confirm the repo is private (already true)
  7. 6.4 Push the commit; watch the Actions tab for the workflow run. First run often fails on environment differences between local and runner — the most common:
  8. fetch-depth: 0 missing → mkdocs-git-revision-date plugin errors
  9. Python version mismatch → pin in workflow
  10. strict build catching a link that only awesome-pages introduced
  11. 6.5 Iterate on the workflow until the build and deploy jobs both succeed.
  12. 6.6 Open the returned Pages URL. Log in as a repo collaborator. Confirm the site is visible and looks identical to local.
  13. 6.7 Update docs/index.md landing with a "View this site at " link, replacing the placeholder.
  14. 6.8 Commit + push:

    Add GitHub Actions workflow to deploy docs to GitHub Pages
    
    Triggered on pushes to main that touch docs/, mkdocs.yml,
    docs/requirements.txt, or the workflow itself. Uses the
    official actions/deploy-pages@v4 path. Private visibility
    via GH Pro; only repo collaborators can view the rendered
    site.
    
    site_url in mkdocs.yml now points at the deployed location;
    docs/index.md landing links to the live site.
    

Done criteria:

  • Workflow run green
  • Rendered site visible at the Pages URL for a logged-in collaborator
  • docs/index.md links to it
  • Commit pushed

Phase 7 — Final polish + cross-refs in root README and CLAUDE.md

Goal: tie the docs site into the project's main entry points.

Steps:

  1. 7.1 Walk the deployed site. For each top-level section (architecture, adr, plans, research, patches, runbooks), read the index.md. Fix any typos, stale claims, broken refs.
  2. 7.2 Root README.md: add a ## Documentation section with:
  3. Link to the GH Pages URL
  4. Pointer to docs/ as the source
  5. Link to the current Stage 2 plan
  6. Link to the Q1-Q5 queue
  7. 7.3 CLAUDE.md (project instructions): add a ## Documentation discipline section enforcing:
  8. New empirical findings go under docs/research/ with status: verified and evidence
  9. New decisions go as ADRs under docs/adr/, never inline in plans
  10. Plans may be rewritten freely but old versions get date-prefixed and moved to docs/plans/archive/
  11. No guessing — any claim not backed by evidence is either status: hypothesis or named as a gap
  12. 7.4 Commit + push:

    Cross-ref docs site from project README.md and CLAUDE.md
    
    Root README gains a Documentation section pointing at the
    deployed docs site and the key entry points. CLAUDE.md gains
    a Documentation discipline section formalizing the
    research/ADR/plans conventions + the no-guessing rule.
    

Done criteria:

  • Root README has a Documentation section
  • CLAUDE.md has a Documentation discipline section
  • Site is fully walked and polished
  • Commit pushed

Appendix A — git mv map

Execute in this order. Destination parent directories must exist (created in step 2.1).

# landing
docs/README.md                                              → docs/index.md

# architecture
docs/architecture-connection.md                             → docs/architecture/connection.md
docs/research/remotexpc-protocol.md                         → docs/architecture/protocol/rsd-remotexpc.md
docs/research/coredevice-xpc-protocol.md                    → docs/architecture/protocol/coredevice-xpc.md
docs/research/l2-bridge.md                                  → docs/architecture/protocol/l2-bridge.md
docs/research/os-remote-device-api.md                       → docs/architecture/protocol/os-remote-device.md

# plans (active)
docs/plan-stage1-rebuild.md                                 → docs/plans/stage1.md
docs/plan-stage2-pair-flow.md                               → docs/plans/stage2.md
docs/plan-stage2-phase-c-queue.md                           → docs/plans/stage2-phase-c-queue.md
docs/plan-docs-reorganization.md                            → docs/plans/docs-reorganization.md

# plans (archive)
docs/plan-forward-roadmap.md                                → docs/plans/archive/2026-03-forward-roadmap.md
docs/plan-full-xcode-integration.md                         → docs/plans/archive/2026-03-full-xcode-integration.md
docs/plan-cds-rsd-inject.md                                 → docs/plans/archive/2026-03-cds-rsd-inject.md
docs/plan-next-steps.md                                     → docs/plans/archive/2026-03-next-steps.md
docs/research/session8-five-questions.md                    → docs/plans/archive/2026-03-session8-five-questions.md

# research/protocol
docs/research/s2b-pair-attempt-log.md                       → docs/research/protocol/s2b-pair-attempt-log.md
docs/research/s2c-self-experiment/                          → docs/research/protocol/s2c-self-experiment/
docs/research/s2c-self-experiment/README.md                 → docs/research/protocol/s2c-self-experiment/index.md
docs/research/rsd-info-ios2641-reference.json               → docs/research/protocol/rsd-info-ios2641-reference.json

# research/coredevice-internals
docs/research/coredevice-injection.md                       → docs/research/coredevice-internals/coredevice-injection.md
docs/research/coredevice-representation.md                  → docs/research/coredevice-internals/coredevice-representation.md
docs/research/rsd-wrapper-init-analysis.md                  → docs/research/coredevice-internals/rsd-wrapper-init-analysis.md
docs/research/s1a-properties-audit.md                       → docs/research/coredevice-internals/s1a-properties-audit.md
docs/research/s1c-static-browser-disasm.md                  → docs/research/coredevice-internals/s1c-static-browser-disasm.md
docs/research/s1c-managed-service-devices-registration.md   → docs/research/coredevice-internals/s1c-managed-service-devices-registration.md
docs/research/action-interception-full-picture.md           → docs/research/coredevice-internals/action-interception-full-picture.md
docs/research/pair-button-and-cfnetwork.md                  → docs/research/coredevice-internals/pair-button-and-cfnetwork.md

# research/environment
docs/research/code-audit-findings.md                        → docs/research/environment/code-audit-findings.md
docs/research/phase2-behavior-analysis.md                   → docs/research/environment/phase2-behavior-analysis.md
docs/research/phase2-wrapper-fix-test-results.md            → docs/research/environment/phase2-wrapper-fix-test-results.md

# patches — NO MOVE (already correct)
# docs/patches/pymobiledevice3/* — unchanged

Nested s2c-self-experiment/ children (FINDINGS.md, iosmux-spike-listener.py, results/) move with the parent git mv.

The mkdocs-redirects config (mkdocs.yml) gets one entry per row in this map, in the form 'old/path.md': new/path.md.

Appendix B — mkdocs.yml template

site_name: iosmux
site_description: iOS 17+ remote device proxy — research and implementation notes
site_author: staticwire
site_url: https://staticwire.github.io/iosmux/   # confirmed in Phase 6
repo_url: https://github.com/staticwire/iosmux
repo_name: staticwire/iosmux
edit_uri: edit/main/docs/

docs_dir: docs
site_dir: site
strict: true

theme:
  name: material
  language: en
  palette:
    - media: "(prefers-color-scheme: light)"
      scheme: default
      primary: indigo
      accent: indigo
      toggle:
        icon: material/weather-sunny
        name: Switch to dark mode
    - media: "(prefers-color-scheme: dark)"
      scheme: slate
      primary: indigo
      accent: indigo
      toggle:
        icon: material/weather-night
        name: Switch to light mode
  features:
    - navigation.tabs
    - navigation.sections
    - navigation.top
    - navigation.indexes
    - navigation.tracking
    - toc.follow
    - toc.integrate
    - search.suggest
    - search.highlight
    - search.share
    - content.code.copy
    - content.code.annotate
    - content.action.edit
    - content.action.view
    - content.tabs.link
  icon:
    repo: fontawesome/brands/github

extra_css:
  - stylesheets/extra.css

plugins:
  - search
  - awesome-pages
  - git-revision-date-localized:
      enable_creation_date: true
      type: iso_date
      fallback_to_build_date: true
  - git-authors
  - glightbox
  - macros
  - redirects:
      redirect_maps:
        # filled in Phase 2 with the full Appendix A map
        # 'README.md': index.md
        # 'architecture-connection.md': architecture/connection.md
        # ... etc

markdown_extensions:
  - abbr
  - admonition
  - attr_list
  - def_list
  - footnotes
  - md_in_html
  - tables
  - toc:
      permalink: true
      toc_depth: 4
  - pymdownx.arithmatex:
      generic: true
  - pymdownx.betterem
  - pymdownx.caret
  - pymdownx.critic
  - pymdownx.details
  - pymdownx.emoji
  - pymdownx.highlight:
      anchor_linenums: true
      line_spans: __span
      pygments_lang_class: true
  - pymdownx.inlinehilite
  - pymdownx.keys
  - pymdownx.mark
  - pymdownx.smartsymbols
  - pymdownx.snippets
  - pymdownx.superfences:
      custom_fences:
        - name: mermaid
          class: mermaid
          format: !!python/name:pymdownx.superfences.fence_code_format
  - pymdownx.tabbed:
      alternate_style: true
  - pymdownx.tasklist:
      custom_checkbox: true
  - pymdownx.tilde

Appendix C — docs/stylesheets/extra.css template

Custom admonition definitions. Material convention: each custom type needs a CSS class and an SVG icon reference via .md-typeset .admonition.<type> + --md-admonition-icon--<type>.

/* ---- Custom admonitions ---- */

/* verified — strongest factual claim */
:root {
  --md-admonition-icon--verified: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z"/></svg>');
}
.md-typeset .admonition.verified,
.md-typeset details.verified {
  border-color: #2e7d32;
}
.md-typeset .verified > .admonition-title,
.md-typeset .verified > summary {
  background-color: rgba(46, 125, 50, 0.1);
  border-color: #2e7d32;
}
.md-typeset .verified > .admonition-title::before,
.md-typeset .verified > summary::before {
  background-color: #2e7d32;
  -webkit-mask-image: var(--md-admonition-icon--verified);
          mask-image: var(--md-admonition-icon--verified);
}

/* hypothesis — unconfirmed, needs research */
:root {
  --md-admonition-icon--hypothesis: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2zm1 17h-2v-2h2zm2.07-7.75-.9.92A3.4 3.4 0 0 0 13 15h-2v-.5a4.2 4.2 0 0 1 1.17-2.83l1.24-1.26a2 2 0 0 0 .59-1.41 2 2 0 0 0-4 0H8a4 4 0 0 1 8 0 3.23 3.23 0 0 1-.93 2.25z"/></svg>');
}
.md-typeset .admonition.hypothesis,
.md-typeset details.hypothesis {
  border-color: #f9a825;
}
.md-typeset .hypothesis > .admonition-title,
.md-typeset .hypothesis > summary {
  background-color: rgba(249, 168, 37, 0.1);
  border-color: #f9a825;
}
.md-typeset .hypothesis > .admonition-title::before,
.md-typeset .hypothesis > summary::before {
  background-color: #f9a825;
  -webkit-mask-image: var(--md-admonition-icon--hypothesis);
          mask-image: var(--md-admonition-icon--hypothesis);
}

/* gap — known empirical unknown, halts downstream */
:root {
  --md-admonition-icon--gap: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/></svg>');
}
.md-typeset .admonition.gap,
.md-typeset details.gap {
  border-color: #c62828;
}
.md-typeset .gap > .admonition-title,
.md-typeset .gap > summary {
  background-color: rgba(198, 40, 40, 0.1);
  border-color: #c62828;
}
.md-typeset .gap > .admonition-title::before,
.md-typeset .gap > summary::before {
  background-color: #c62828;
  -webkit-mask-image: var(--md-admonition-icon--gap);
          mask-image: var(--md-admonition-icon--gap);
}

/* superseded — content replaced, links forward to new version */
:root {
  --md-admonition-icon--superseded: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M17.65 6.35A7.958 7.958 0 0 0 12 4a8 8 0 1 0 7.73 10h-2.08A6 6 0 1 1 12 6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg>');
}
.md-typeset .admonition.superseded,
.md-typeset details.superseded {
  border-color: #546e7a;
}
.md-typeset .superseded > .admonition-title,
.md-typeset .superseded > summary {
  background-color: rgba(84, 110, 122, 0.1);
  border-color: #546e7a;
}
.md-typeset .superseded > .admonition-title::before,
.md-typeset .superseded > summary::before {
  background-color: #546e7a;
  -webkit-mask-image: var(--md-admonition-icon--superseded);
          mask-image: var(--md-admonition-icon--superseded);
}

Appendix D — scripts/ templates

All three scripts have the SPDX header and set -euo pipefail.

scripts/docs-venv-setup.sh:

#!/usr/bin/env bash
# SPDX-License-Identifier: AGPL-3.0-or-later
set -euo pipefail

VENV="/home/op/venvs/iosmux-docs"
REQS="$(git rev-parse --show-toplevel)/docs/requirements.txt"

if [[ ! -d "$VENV" ]]; then
    python3 -m venv "$VENV"
fi

"$VENV/bin/pip" install --quiet --upgrade pip
"$VENV/bin/pip" install --quiet -r "$REQS"

echo "docs venv ready at $VENV"

scripts/docs-serve.sh:

#!/usr/bin/env bash
# SPDX-License-Identifier: AGPL-3.0-or-later
set -euo pipefail

VENV="/home/op/venvs/iosmux-docs"
[[ -d "$VENV" ]] || { echo "run scripts/docs-venv-setup.sh first" >&2; exit 1; }

source "$VENV/bin/activate"
cd "$(git rev-parse --show-toplevel)"
exec mkdocs serve --dev-addr 127.0.0.1:8000 "$@"

scripts/docs-build.sh:

#!/usr/bin/env bash
# SPDX-License-Identifier: AGPL-3.0-or-later
set -euo pipefail

VENV="/home/op/venvs/iosmux-docs"
[[ -d "$VENV" ]] || { echo "run scripts/docs-venv-setup.sh first" >&2; exit 1; }

source "$VENV/bin/activate"
cd "$(git rev-parse --show-toplevel)"
exec mkdocs build --strict --clean "$@"

scripts/docs-lint.sh (Phase 5):

#!/usr/bin/env bash
# SPDX-License-Identifier: AGPL-3.0-or-later
set -euo pipefail

VENV="/home/op/venvs/iosmux-docs"
[[ -d "$VENV" ]] || { echo "run scripts/docs-venv-setup.sh first" >&2; exit 1; }

source "$VENV/bin/activate"
cd "$(git rev-parse --show-toplevel)"
exec pymarkdown --config .pymarkdown.yml scan docs/

Appendix E — .pages templates

mkdocs-awesome-pages-plugin reads a .pages file per directory to control nav order and titles.

docs/.pages:

nav:
  - index.md
  - architecture
  - plans
  - research
  - adr
  - patches
  - runbooks

docs/architecture/.pages:

nav:
  - index.md
  - connection.md
  - protocol

docs/architecture/protocol/.pages:

nav:
  - rsd-remotexpc.md
  - coredevice-xpc.md
  - l2-bridge.md
  - os-remote-device.md

docs/plans/.pages:

nav:
  - index.md
  - stage1.md
  - stage2.md
  - stage2-phase-c-queue.md
  - docs-reorganization.md
  - archive

docs/plans/archive/.pages:

title: Archive
nav:
  - ...

(Ellipsis = "everything else alphabetical".)

docs/research/.pages:

nav:
  - index.md
  - protocol
  - coredevice-internals
  - environment

docs/research/protocol/.pages:

nav:
  - s2b-pair-attempt-log.md
  - s2c-self-experiment
  - rsd-info-ios2641-reference.json

docs/research/coredevice-internals/.pages:

nav:
  - ...

docs/research/environment/.pages:

nav:
  - ...

docs/adr/.pages:

nav:
  - index.md
  - template.md
  - 0001-use-pymobiledevice3-not-goios.md
  - 0002-tunneld-on-vm-not-host.md
  - 0003-pymob-patches-overlay-not-fork.md
  - 0004-stage2-option-delta-shim.md
  - 0005-phase-d-go-only-no-new-python.md
  - 0006-empirical-only-no-guessing.md
  - 0007-mkdocs-material-docs-site.md

docs/runbooks/.pages:

nav:
  - index.md
  - ...

Appendix F — .github/workflows/docs.yml template

name: docs

on:
  push:
    branches: [main]
    paths:
      - 'docs/**'
      - 'mkdocs.yml'
      - 'docs/requirements.txt'
      - '.github/workflows/docs.yml'
  workflow_dispatch:

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: docs
  cancel-in-progress: true

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0   # required by mkdocs-git-revision-date plugin
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r docs/requirements.txt
      - name: Build site
        run: mkdocs build --strict --clean
      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: site

  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4

Running the plan

Open this file in docs/plans/docs-reorganization.md (after Phase 2) or docs/plan-docs-reorganization.md (before). Work each Phase in order, top to bottom. Each Phase ends with a commit + push; the repo is safe to interrupt at any phase boundary.

Reference files the executor should keep open alongside this plan:

Resume point after the current context compaction: start of Phase 1 (Bootstrap MkDocs). All of Phase 0 (standalone Q1-Q5 file extraction) is already committed as 7d1ae8c.