ctx migrate
ctx migrate scans a project for knowledge you already wrote — an llm-wiki or Obsidian tree, docs/, ADRs, README.md, CLAUDE.md/AGENTS.md, Cursor rules — lists it, and stages the items you pick so your coding agent can rewrite them as Contexo pages.
When you’d reach for it
Section titled “When you’d reach for it”Run it once, right after ctx init, on any project that isn’t starting from zero. It’s also how you pull in a knowledge base that lives somewhere else: --from takes a local path or a git URL.
The thing people get wrong: ctx migrate writes no pages. It writes one staging file, .contexo/migrate.json, and stops. The distillation — reading each source, choosing a slug and type, redacting secrets, writing frontmatter — is done by your agent through the ctx_migrate MCP tool. Until you ask the agent to finish, your knowledge base is untouched. The second surprise is that staging records absolute paths: move or delete the sources between staging and finishing and the resume step has nothing to read.
Synopsis
Section titled “Synopsis”ctx migrate # scan, pick interactively, stagectx migrate --list # show what would be staged, stage nothingctx migrate --list --type concept # narrow the listingctx migrate --all --yes # stage everything, no promptsctx migrate --from ../team-wiki # scan another directoryctx migrate --from https://github.com/acme/wiki.git| Flag | Type | Default | Description |
|---|---|---|---|
--all | bool | false | select every item without prompting |
--detector | string | only items from this detector (kbtree|docs|agentfiles) | |
--from | string | import from an external path or git URL instead of this project | |
--list | bool | false | only list what would be migrated; stage nothing |
--type | string | only items of this suggested type (concept|entity|analysis|source) | |
--yes, -y | bool | false | skip the confirmation prompt |
--list is a dry run: the catalog is built and rendered, then the command returns before the selection prompt.
--type and --detector filter the catalog and renumber it — the IDs you type at the prompt always refer to the list in front of you, so [2] in a --detector docs listing is not necessarily [2] in the unfiltered one.
--all answers the selection prompt, not the confirmation. On its own it still asks Stage N item(s) for the agent to import? [y/N]. A scripted run needs both --all and --yes.
At the interactive prompt the accepted forms are a comma list, ranges, or everything: 1,2,5-7, all, *.
--from takes either a directory or a git remote (anything starting https://, http://, git@, or ending .git). A remote is cloned with git clone --depth 1 into .contexo/.migrate-cache/<12-hex-digest>, and that path is recorded in the manifest as external_cache, which ctx_migrate(done=true) deletes along with the manifest. A run that stages nothing — --list, or a declined confirmation — still clones but records nothing, so that copy stays until you delete it or until the next --from of the same URL overwrites it.
| Flag | Type | Default | Description |
|---|---|---|---|
--root | string | project root directory (default: current directory) |
Examples
Section titled “Examples”Survey a project before committing to anything:
ctx migrate --list
docs:
[1] entity README.md ~21t
[2] concept docs\architecture.md ~22t
agentfiles:
[3] concept CLAUDE.md ~26tThe ~21t column is a token estimate (source bytes ÷ 4), there to help you judge what an import will cost in context. The group headers are detector names.
Narrow to one detector when a repo has a big wiki and you only want the prose docs:
ctx migrate --list --detector docs
docs:
[1] entity README.md ~21t
[2] concept docs\architecture.md ~22tStage everything without prompts — the form for a setup script:
ctx migrate --all --yesThat writes .contexo/migrate.json and prints Staged N source(s). Ask your agent: "finish the contexo migration". Say exactly that to your agent (Claude Code, Cursor, Codex — anything with the Contexo MCP server wired up) and it takes over.
How it works
Section titled “How it works”-
The command requires an existing
.contexo/under the project root (--root, else the current directory). It does not search parent directories. -
Three detectors run in a fixed order. That order decides dedup precedence (first detector to claim a path wins) and the grouping you see:
- kbtree — fires only if the scan root has a
wiki/or an.obsidian/directory. An llm-wiki layout mapswiki/concepts→concept,wiki/entities→entity,wiki/analyses→analysis,raw/→source. In an Obsidian vault every.mdbecomes aconcept. - docs — root-level
*.mdplus thedocs/anddocumentation/trees.README*→entity,CHANGELOG*→source, anything under/adr/or/decisions/→analysis, everything else →concept. - agentfiles —
CLAUDE.md,AGENTS.md,GEMINI.mdat the root, and.cursor/rules/*.md/*.mdc. Allconcept. The docs detector deliberately skips those three root files so they’re never classified twice.
Walks skip
.git,.contexo,node_modules,vendor,.obsidian,dist,build,.nextand.migrate-cache. A detector that errors prints to stderr and is skipped; the rest of the catalog still renders. - kbtree — fires only if the scan root has a
-
Each candidate gets a title (first Markdown heading after any frontmatter, else the file name), a slug derived from that title, and an
(already imported — will overwrite)flag if that slug already exists at.contexo/wiki/concepts|entities|analyses/<slug>.mdor.contexo/raw/sessions/<slug>.md. At most 100 rows render before the rest is summarised. -
After you pick and confirm, the only thing written is
.contexo/migrate.json:version,created(UTC RFC 3339),source_root,external_cache, alabelderived from the scan directory’s name, and one entry per pick withabs_path,rel_path,detector,suggested_type,suggested_slug. No pages are created, no git commit is made, no request goes to the Contexo server. The only network call this command can make is thegit clonebehind--from. -
When you ask the agent to finish,
ctx_migratereads that manifest and returns aMIGRATE_RESUMEdirective: for each item, read the file and callctx_write_pagewith a real slug, type, tags,reasoning_summaryandsources: ["<YYYY-MM-DD>-migrate-<label>"]; write that provenancesourcepage;ctx_pushwithno_distill=true; thenctx_migrate(done=true), which deletes the manifest and any external clone.
Common errors
Section titled “Common errors”migrate: no .contexo/ here — run 'ctx init' first— you’re outside a Contexo project, or one directory too deep.ctxdoesn’t walk upward looking for.contexo/; run from the project root or pass--root.migrate: --from path "…": no such file or directory/migrate: --from "…" is not a directory—--fromresolved to something that is neither a git URL nor a readable directory.git clone failed: exit status 128: …— the--fromremote couldn’t be cloned (private repo, bad URL, no network).ctxshells out to yourgitwith your credentials, so try the same clone by hand to see the real reason.migrate: index 7 out of range 1..3/migrate: bad index "x"/migrate: empty selection— the selection line didn’t parse. Indices are 1-based against the list as displayed, ranges arelo-hiwithlono greater thanhi, and an empty line is rejected rather than treated as “none”.No existing knowledge found to migrate.— not an error; no detector matched. Normal in a code-only repo without aREADME.md, and exactly whatctx generateexists for.
See also
Section titled “See also”- Migration guide — the end-to-end narrative, including what the agent does with each file
ctx generate— seed a knowledge base from source code when there are no docs to migratectx_migrate— the MCP tool that consumes the manifestctx init— creates the.contexo/this command requires
