Skip to content

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.

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.

Terminal window
ctx migrate # scan, pick interactively, stage
ctx migrate --list # show what would be staged, stage nothing
ctx migrate --list --type concept # narrow the listing
ctx migrate --all --yes # stage everything, no prompts
ctx migrate --from ../team-wiki # scan another directory
ctx migrate --from https://github.com/acme/wiki.git
FlagTypeDefaultDescription
--allboolfalseselect every item without prompting
--detectorstringonly items from this detector (kbtree|docs|agentfiles)
--fromstringimport from an external path or git URL instead of this project
--listboolfalseonly list what would be migrated; stage nothing
--typestringonly items of this suggested type (concept|entity|analysis|source)
--yes, -yboolfalseskip 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.

FlagTypeDefaultDescription
--rootstringproject root directory (default: current directory)

Survey a project before committing to anything:

Terminal window
ctx migrate --list
ctx migrate --list

docs:
  [1] entity   README.md  ~21t
  [2] concept  docs\architecture.md  ~22t

agentfiles:
  [3] concept  CLAUDE.md  ~26t

The ~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:

Terminal window
ctx migrate --list --detector docs
ctx migrate --list --detector docs

docs:
  [1] entity   README.md  ~21t
  [2] concept  docs\architecture.md  ~22t

Stage everything without prompts — the form for a setup script:

Terminal window
ctx migrate --all --yes

That 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.

  1. The command requires an existing .contexo/ under the project root (--root, else the current directory). It does not search parent directories.

  2. 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 maps wiki/conceptsconcept, wiki/entitiesentity, wiki/analysesanalysis, raw/source. In an Obsidian vault every .md becomes a concept.
    • docs — root-level *.md plus the docs/ and documentation/ trees. README*entity, CHANGELOG*source, anything under /adr/ or /decisions/analysis, everything else → concept.
    • agentfilesCLAUDE.md, AGENTS.md, GEMINI.md at the root, and .cursor/rules/*.md / *.mdc. All concept. 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, .next and .migrate-cache. A detector that errors prints to stderr and is skipped; the rest of the catalog still renders.

  3. 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>.md or .contexo/raw/sessions/<slug>.md. At most 100 rows render before the rest is summarised.

  4. After you pick and confirm, the only thing written is .contexo/migrate.json: version, created (UTC RFC 3339), source_root, external_cache, a label derived from the scan directory’s name, and one entry per pick with abs_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 the git clone behind --from.

  5. When you ask the agent to finish, ctx_migrate reads that manifest and returns a MIGRATE_RESUME directive: for each item, read the file and call ctx_write_page with a real slug, type, tags, reasoning_summary and sources: ["<YYYY-MM-DD>-migrate-<label>"]; write that provenance source page; ctx_push with no_distill=true; then ctx_migrate(done=true), which deletes the manifest and any external clone.

  • migrate: no .contexo/ here — run 'ctx init' first — you’re outside a Contexo project, or one directory too deep. ctx doesn’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--from resolved to something that is neither a git URL nor a readable directory.
  • git clone failed: exit status 128: … — the --from remote couldn’t be cloned (private repo, bad URL, no network). ctx shells out to your git with 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 are lo-hi with lo no greater than hi, 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 a README.md, and exactly what ctx generate exists for.
  • 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 migrate
  • ctx_migrate — the MCP tool that consumes the manifest
  • ctx init — creates the .contexo/ this command requires