<?xml version="1.0" encoding="UTF-8"?>
<spec xmlns="https://vibevm.org/spec/1">
  <title id="root">PROP-051: vibe refactor — the source-refactoring umbrella</title>
  <status stage="impl" state="done" comment="commissioned and ruled by the owner 2026-08-23/24 (mandate quoted in §1 verbatim); the verb family landed 2026-08-24 (K1 bf6a9bd9 + K1b 2609cfc3, panel all green both) — the application K2-K7 rides vibevm/vibespecs/terraforms/CONVERT-SOURCE-APPLICATION-v0.1.xml"/>
  <section id="mandate" title="1. Mandate">
    <p p="1"><fact id="REFACTOR-MANDATE-VERBATIM" status="spec/done">The owner's mandate, verbatim (2026-08-23,
chat, three clauses in one sitting): «я хочу все наши спецификации в
проекте (всё внутри spec и packages, итп) сконвертировать в XML как язык
исходников. … Это гораздо более продвинутый формат для нас, потому что
нейросеть понимает сложные спецификации в нем проще.» — «у тебя уже
где-то должна быть система бесшовного переписывания форматов
спецификаций. Я предлагаю сделать команду "vibe refactor" которая будет
зонтиком для разных команд рефакторинга. Дальше сделать команду vibe
refactor convert-source для конвертирования форматов исходников между
Markdown и XML. Если в ходе конвертирования будут происходить какие-то
деструктивные операции - выдается ошибка с описанием что потеряется и
потребуется подтверждение (либо интерактивное, либо флаг --force снимает
проверку). Проверку стоит делать честно, обратным переконвертированием.»
— «и дальше уже ты просто применишь этот инструмент к своим же файлам.»
The seamless-rewrite system the owner names already exists: it is
PROP-045's pivot (`vibe-specdoc`, parse → IR → emit, all four edges);
this PROP adds the user-facing verb over AUTHORED sources and the honesty
contract that verb must carry.</fact></p>
    <p p="2"><fact id="VIBEDEPS-NEVER-CONVERTED" status="spec/done">The owner's fourth clause, verbatim
(2026-08-23): «файлы внутри vibedeps нигде не нужно переконвертировать
вручную, нужно вместо этого сконвертировать исходники и выполнить
материализацию». The law: `convert-source` operates on AUTHORED sources
only; materialised state (`vibedeps/`, the machine cache, generated boot
artifacts) is never converted in place — sources convert, then
`vibe install` re-materialises the slots under the standing PROP-045
machinery (the derived-manifest law re-transforms them honestly).</fact></p>
  </section>
  <section id="umbrella" title="2. The umbrella">
    <p p="3"><fact id="UMBRELLA-VERB" status="impl/done">`vibe refactor &lt;verb&gt;` is the command family for
meaning-preserving rewrites of authored source trees. Charter: a
refactoring changes the FORM of sources, never their meaning, and every
verb under the umbrella must carry its own machine-checked honesty proof
(for `convert-source`: the reverse-reconversion check of §3). First
citizen: `convert-source`; future verbs (anchor renames with tombstones,
renumbering) join under the same charter, each with its own PROP-level
fact before it ships.</fact></p>
  </section>
  <section id="convert-source" title="3. convert-source">
    <p p="4"><fact id="CONVERT-SOURCE-SURFACE" status="impl/done" action="continue" actionstage="doc" audience="author">`vibe refactor convert-source --to
xml|markdown [PATH…]` converts spec sources between the two PROP-045
serialisations (`md` accepted as an alias of `markdown`). Each PATH is a
file or a directory; a directory walks recursively and selects spec
sources (`is_spec_source`) currently in the OPPOSITE form; a file in the
target form already is reported `already` and skipped. The walk always
skips: `vibedeps/` (##VIBEDEPS-NEVER-CONVERTED), dot-directories,
`target/`, and any file whose leading lines carry the `generated by
vibe` marker (generated artifacts regenerate; they are not authored
sources) — an EXPLICIT file argument overrides only the marker skip,
never the `vibedeps/` skip.</fact></p>
    <p p="5"><fact id="ONE-DOCUMENT-ONE-FORM-ON-CONVERT" status="impl/done" action="continue" actionstage="doc" audience="author">A conversion writes the sibling
serialisation and deletes the original in the same act, so the
one-document-one-form law (PROP-045 ##TARGET-MIXED, the pair-collision
check) holds at every instant — the tree never holds `X.md` and `X.xml`
together, not even transiently between files. The verb assumes version
control underneath it and keeps no backups of its own.</fact></p>
    <p p="6"><fact id="HONESTY-BY-REVERSE" status="impl/done" action="continue" actionstage="doc" audience="author">The destructiveness check is the owner's law,
implemented by reverse reconversion through the pivot: for a source
`S`, parse to IR, emit the target form, read the target form back, and
project it into the SOURCE form again; compare. Three classes:
**(1) byte-stable** — the back-projection equals `S` byte-for-byte:
converts silently. **(2) IR-stable loss** — the back-projection
re-parses to the SAME IR but differs in bytes (dropped MD/XML comments,
normalised layout): the verb REFUSES with a per-file description of
what is lost, and proceeds only on interactive confirmation or
`--force`. **(3) IR-divergent** — the back-projection re-parses to a
DIFFERENT IR: always refused, `--force` does not apply; that class is a
`vibe-specdoc` defect to file (the pivot broke its own round-trip law),
never a corpus to damage.</fact></p>
    <p p="7"><fact id="LOSS-REPORT" status="impl/done">The class-2 description is honest and concrete: it
names the file and shows the lost or changed lines (a unified diff of
the source against its back-projection), with dropped comments called
out as CONTENT loss (PROP-045 ##REVIEW-COMMENTS-LAW: the pivot drops
comments by design — `REVIEW:` markers and prose comments do not
survive conversion) rather than folded into formatting noise.</fact></p>
    <p p="8"><fact id="FORCE-AND-PROMPT" status="impl/done" action="continue" actionstage="doc" audience="author">On a TTY, class-2 files prompt per file
(yes / no / all); off a TTY, class-2 without `--force` is an error
listing every lossy file and what each loses. `--force` waives class 2
only. `--dry-run` classifies and reports every file, writes nothing,
and always exits 0 (an inventory, not an attempt); a real run exits 0
iff every requested conversion landed, non-zero when any file was
refused or declined.</fact></p>
    <p p="9"><fact id="CONVERT-CORE-IN-SPECDOC" status="impl/done">The classification core — (source text,
direction) → class + emitted target + loss diff — lives in
`vibe-specdoc`, the crate that owns the IR and all four edges (PROP-045
##PIVOT-MODEL), so the honesty check is a library property with its own
unit tests; the CLI verb is a thin walker / prompter / writer over it,
in the house thin-command genre.</fact></p>
    <p p="10"><fact id="FOREIGN-AND-HARNESS-SKIPS" status="spec/done">Two skip classes the K2 inventory proved
(2026-08-24), amending ##CONVERT-SOURCE-SURFACE: the walk also skips
**`node_modules/` directories** — foreign vendored trees are the same
genre as `vibedeps/`, never authored here (the inventory found 129
third-party `.md` under package tool dirs, including files only the
foreign author may reshape) — and **harness-contract basenames**
(`CLAUDE.md`, `AGENTS.md`, `GEMINI.md`, `MEMORY.md`, `SKILL.md`,
`README.md`, `LICENSE.md` — the last two by the owner's 2026-08-24
ruling, verbatim: «это же зарезервированные имена, это не
спецификации… чтобы на сайтах типа Гитхаба они отображались
нормально»; the K3/K4 flip converted 100 READMEs and 72 LICENSEs
before the ruling and they were converted back the same sitting):
their FILENAME and format are an external harness's contract (a
`SKILL.md` carries YAML frontmatter the pivot does not model — the
inventory showed conversion would strip it and break the skill), so
they stay Markdown by law, reported as their own skip class rather
than silently converted. An explicit file argument does NOT override
either skip — unlike the generated-marker skip, these are not
heuristics.</fact></p>
    <p p="11"><fact id="FROM-AND-TO" status="impl/done">Both directions are first-class on every converting
verb (owner, 2026-08-24, verbatim: «В настройках у обеих должно быть из
какого формата в какой (я так понял что ты запланировал только MD-&gt;XML,
а хочется чтобы была и XML-&gt;MD. Параметры --from и --to)»). The surface
is `--from &lt;markdown|xml&gt;` + `--to &lt;markdown|xml&gt;` (`md` an alias of
`markdown` on both): `--to` is required, `--from` defaults to the
counterpart of `--to`, and `--from` equal to `--to` is a loud error —
never a silent no-op. `--from` is a FILTER: only sources currently in
that form are selected; everything else reports `already`/skips
unchanged.</fact></p>
    <p p="12"><fact id="CONVERT-PACKAGE-SRC" status="impl/done">`vibe refactor convert-package-src [--from …]
--to … &lt;package-root&gt;…` converts WHOLE PACKAGES, not individual files
(owner, 2026-08-24, verbatim: «Она будет конвертировать целиком пакеты,
в отличие от предыдущей команды, которая конвертирует конкретные
классы»). Each argument is a package root — a directory carrying
`vibe.toml` (anything else is a loud error; that validation is what
distinguishes this verb from pointing `convert-source` at a bare
directory) — and the conversion walks the package's SPEC HOMES with
the same core, the same skips and the same honesty contract as
`convert-source` (##HONESTY-BY-REVERSE applies file by file).
**The perimeter ruling (owner, 2026-08-24, verbatim: «нужно переводить
спецификации из директории спецификаций (spec, packages, всякие
зарезервированные имена в корне если они есть), а не все подряд
файлы» — «тут дело не столько в том, что README.md это
зарезервированное имя, сколько что ты конвертировал всё подряд»):**
the spec homes are `spec/` and a nested `packages/` — and NOTHING
else: root-level working docs (one house's WAL/checkpoint family) are
NOT presumed, because the verb serves every project and such names may
mean anything elsewhere (owner correction, 2026-08-24, near-verbatim:
«не у всех проектов есть эти файлы… это конкретно особенность vibevm.
А утилита рефакторинга работает на все проекты»); a house converts
its own root extras with explicit `convert-source` paths. Everything
else at a package root — code, tooling, reserved-name docs — is
outside the verb's perimeter, with the reserved-name skips of
##FOREIGN-AND-HARNESS-SKIPS as the second, independent guard. A root
carrying neither spec home is a loud refusal.</fact></p>
    <p p="13"><fact id="CONVERT-SPEC-SRC" status="impl/done">`vibe refactor convert-spec-src [--from …]
--to … [&lt;package-root&gt;]` converts the `spec/` directory of the chosen
package; with no argument it targets the CURRENT ROOT PROJECT's
`spec/` tree (owner, 2026-08-24, verbatim: «она конвертирует
директорию spec для выбранного пакета (если не сказано что -
конвертирует текущий корневой проект)»), resolved the same way the
other project-rooted verbs resolve it.</fact></p>
    <p p="14"><fact id="WRAPPERS-SHARE-THE-CORE" status="impl/done">The two package-shaped verbs are thin
wrappers: they resolve WHICH directories to convert and then run the
very same walker and classification core `convert-source` runs (owner,
2026-08-24, verbatim: «Внутри они используют тот же код что
convert-src, вызываемый для разных директорий по смыслу») — no second
conversion path, no second honesty check to drift. Canonical names
follow the owner's own spellings (`convert-package-src`,
`convert-spec-src`), and `convert-src` rides as an alias of
`convert-source`, so both spelling families the owner used in chat
resolve.</fact></p>
  </section>
  <section id="application" title="4. Applying it to our own tree">
    <p p="15"><fact id="APPLICATION-PLAN" status="spec/done">The mandate's third clause is a build of its own:
the host `spec/` tree and every `packages/` member convert
Markdown → XML with this verb, `vibedeps/` re-materialises (never
hand-converted), and the by-name references to `.md` spec paths in
prose and contracts (inline links, CLAUDE.md's boot pointers, hooks,
tooling docs) are swept to the new names — no-legacy, by the standing
F5 precedent (PROP-050 ##MIGRATION-NO-LEGACY genre: one form, no dual
reading of retired shapes). Slices, perimeter and the dry-run inventory
live in the application plan: `vibevm/vibespecs/terraforms/
CONVERT-SOURCE-APPLICATION-v0.1.xml`. `CLAUDE.md` / `AGENTS.md` /
`GEMINI.md` themselves stay Markdown — their names are harness
contracts, not spec sources.</fact></p>
  </section>
  <section id="history" title="5. History">
    <p p="16"><fact id="HISTORY-COMMISSIONED" status="spec/done">2026-08-23: mandated, designed and ratified in
one owner sitting, immediately after the PROP-050 visibility landing;
authored against the already-BUILT PROP-045 pivot (recipe `specdoc/4`,
host lane already `STATIC.xml`, host `spec_format = "xml"`), which is
why the whole feature is one verb and one application wave rather than
a format migration.</fact></p>
  </section>
</spec>
