Migrate Docs
Precondition: a clean working tree. The migration ends in a commit of everything it changed; unrelated uncommitted changes would be swept into that commit. If git status --porcelain is not empty, stop and ask the user to commit or stash first.
Step 0: Adopt doctier (pre-doctier harness repos)
Before discovering anything, check whether this repo already uses harness but predates doctier — .harness/ exists but isn't tracked in git:
[ -d .harness ] && { [ ! -f .doctier.yml ] || [ -z "$(git ls-files .harness)" ]; } && echo pre-doctier
If it prints pre-doctier, the docs are already in their harness locations — the migration needed is the storage change, not a doc move. Follow the adoption recipe in REFERENCE.md ("Adopting doctier on a repo with an existing (gitignored) .harness/"): it runs the doctier bootstrap (Step 4 below), removes the legacy gitignore line, and commits the docs encrypted.
After the adoption, continue with Step 1 only if the repo also has scattered docs outside .harness/ to migrate. If not, report the adoption — files now tracked (git ls-files .harness), ciphertext verified (git show :.harness/<file> | head -1), commit hash — and stop; the migration is complete.
Step 1: Discover
Scan the entire repo for documentation. Do not limit yourself to obvious locations — check everywhere.
Root-level files: README.md, CHANGELOG.md, HISTORY.md, CONTRIBUTING.md, CONTRIBUTION.md, DESIGN.md, SPEC.md, PRD.md, ROADMAP.md, ARCHITECTURE.md, SECURITY.md, CODE_OF_CONDUCT.md, and any other .md files at the root.
Common doc directories — scan recursively: docs/, documentation/, wiki/, spec/, specs/, notes/, planning/, design/, .github/
Package manifests — check for usable product context: package.json (description, keywords), pyproject.toml, go.mod, Cargo.toml
Existing harness structure — read .harness/ if present to avoid overwriting already-migrated content.
Build a complete inventory: path, one-line description, approximate size. Include everything — classify in the next step.
Step 2: Classify
For each file, assign one classification:
keep-public — stays at repo root, no content changes: README.md, CHANGELOG.md/HISTORY.md, LICENSE, CONTRIBUTING.md/CONTRIBUTION.md, DESIGN.md when it is a public design-token specification, SECURITY.md, CODE_OF_CONDUCT.md
clean-public — stays at repo root but contains internal content to extract: a README mixing architecture decisions, competitive analysis, or internal strategy with public content.
migrate — move entirely to .harness/:
| Content type | Destination |
|---|---|
| Product vision, purpose, audience | .harness/product/product.md |
| Roadmap, backlog, feature lists | .harness/product/roadmap.md |
| Competitive analysis, market research | .harness/product/competitors.md |
| UX flows, interaction design | .harness/product/ux.md |
| Domain glossary, entity definitions | .harness/product/CONTEXT.md |
| Idea notes, viability research | .harness/product/idea.md |
| System architecture, tech stack | .harness/engineering/architecture.md |
| Implementation plans, task lists | .harness/engineering/implementation-plan.md |
| Per-feature specs | .harness/engineering/features/[slug].md |
| Architecture Decision Records | .harness/adr/NNNN-[slug].md |
| QA reports, test results | .harness/qa/report.md |
split — file covers more than one harness destination. List each destination and which portion maps there.
ignore — generated files, lock files, READMEs inside dependency folders.
Path-referenced docs
Before classifying a file as migrate, check whether anything reads it by path: skills, scripts, validators, CI, or links from a public doc (grep -r "<filename>" --include='*.md' --include='*.yml' -l, plus the obvious code extensions). A doc that tooling consumes or a public README links to is a functional input — classify it keep-public no matter how internal its content looks, and note why in the plan. Moving it breaks the tool or leaves a dangling link, and Step 7 cannot fix a reference that lives inside a skill or script.
DESIGN.md special case
Do not assume every DESIGN.md should stay public. Classify by content:
- Public token/spec file — keep as
DESIGN.md. It should contain concrete design tokens and contributor-facing implementation rules: colors, typography, spacing, radius, and component tokens. - UX rationale, product design direction, design principles, interaction model, creative north star, or internal rationale — migrate to
.harness/product/ux.md. - Mixed token spec and UX rationale — split it: keep only the public token/spec material in
DESIGN.md, and move the UX/design-rationale material to.harness/product/ux.md.
Never link public DESIGN.md to .harness/, and never keep internal design strategy public just because the filename is DESIGN.md.
Step 3: Show the plan and confirm
Present a table:
| Current path | Action | Destination |
|-------------------------|---------------|------------------------------------------|
| docs/vision.md | migrate | .harness/product/product.md |
| README.md | clean-public | README.md (extract strategy to .harness) |
| CHANGELOG.md | keep-public | CHANGELOG.md |
Also list: harness files with no source content — these will not be created (note which skill produces each properly, e.g. /product-plan for product docs, /dev-plan for engineering docs) — and files being ignored and why. Never invent content: a harness file only exists if real source material migrated into it.
Ask: "Does this migration plan look right? Anything to change before I proceed?"
Wait for explicit confirmation. Do not write any files until the user approves.
Step 4: Migrate — spawn parallel subagents
Doctier bootstrap (once per repo)
.harness/ is tracked in git as age-encrypted blobs via doctier. If .doctier.yml exists at the repo root, skip this — the repo is already set up (a pre-doctier .harness/ was already handled in Step 0). Otherwise, run the bootstrap in ../_shared/doctier-bootstrap.md.
Spawn the migration subagents
Runtime note: without parallel subagents (e.g. Codex), see ../_shared/runtime-parallelism.md and migrate one destination file at a time.
Spawn one subagent per destination harness file that has source content — destinations with nothing to migrate are not created (see Step 3). Each writes a different destination file — two subagents must never edit the same .harness/ file (one writer per doc — concurrent edits merge byte-wise but conflict semantically).
Each receives: full source content, destination path, the canonical template for that destination (mapped in REFERENCE.md — the subagent reads the owning skill's template file), and these instructions:
Transform the source content into the harness template format. Preserve ALL information — do not discard or summarise away anything from the original. Restructure into template sections; if content doesn't fit, add it under the closest match. Do not invent content for empty sections. Write the result to the destination path.
Step 5: Handle clean-public files
For clean-public files: extract internal content to the appropriate .harness/ file (via the relevant subagent), then edit the public file to remove the extracted sections. Ensure what remains reads cleanly. Never leave a dangling reference.
Step 6: Confirm deletion
List every original that was fully migrated. Ask: "All content has been written to its harness location. Delete the originals?"
Wait for confirmation. If confirmed: delete each original and remove empty directories.
Never delete an original if there is any doubt that its content was fully captured.
Step 7: Fix broken references
Scan every public doc for references to paths that no longer exist. For each: if content moved to .harness/, remove the reference entirely (never replace with a .harness/ link); if merged into a public doc, update the reference. Do not leave this step until grep -r "docs/" *.md returns no matches in public docs.
Step 8: Commit and report
Refresh the doc index and commit the migration in one commit — the migrated .harness/ docs together with the cleaned public files (worktrees and future sessions only see committed .harness/ content):
doctier agents --write
git add -A && git commit -m "docs: migrate documentation to harness layout"
## Migration complete
### Written to .harness/
- [path] — migrated from [source]
### Public docs (kept or cleaned)
- [path] — kept unchanged / internal content extracted
### Originals deleted
- [path]
### Not created (no source content)
- [path] — produce it with /[skill-name] when needed
### Suggested next step
Suggest based on state: product docs only → /dev-plan; thin or missing product docs → /product-plan; feature specs present → /implement or /qa; all docs present → /update-docs.
Fleet mode
When asked to migrate many repos (or "all my repos"), run the sweep as a structured batch instead of improvising per repo:
- Enumerate —
gh repo list --limit 200 --json name,isPrivate,isArchived,url. Drop archived repos, apply the visibility filter the user asked for, and add any repos they named explicitly. - Ask the scope questions once, up front, for the whole fleet: adoption-only vs full doc migration, commit-only vs push, and which edge-case repos to include or skip. Do not re-ask per repo.
- Locate or clone each repo locally.
git pull --ff-onlyany that are behind; a repo with a dirty working tree pauses that repo (see the precondition), not the fleet. - Classify each repo by state:
.harness/exists but untracked → Step 0 adoption. No per-repo plan confirmation needed — adoption moves storage, not content.- No
.harness/, docs scattered or absent → full Steps 1–8; the Step 3 plan confirmation still applies per repo (batch the plans into one message where practical). - Already on doctier → skip, report as such.
- Prove the flow on one repo first (adopt, commit, push, verify remote ciphertext), then batch the rest with the same recipe. Verify every repo independently: encryption gate on staged blobs,
doctier check, and remote ciphertext after push — never assume a repeat run worked because the first one did. - Report one summary table: repo, action taken, encrypted-file count, sync state, and anything skipped with the reason.