Metadata-Version: 2.4
Name: cedit
Version: 0.3.3
Summary: Continuous editing of vendored Markdown via 3-way structural merge
Author-email: Valentin Kantor <kantorvv@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/sdlctools/cedit
Project-URL: Repository, https://github.com/sdlctools/cedit
Project-URL: Documentation, https://github.com/sdlctools/cedit/blob/main/USERGUIDE.md
Project-URL: Issues, https://github.com/sdlctools/cedit/issues
Keywords: markdown,merge,vendoring,three-way-merge,mdformat
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Version Control
Classifier: Topic :: Text Processing :: Markup :: Markdown
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: markdown-it-py==4.2.0
Requires-Dist: mdit-py-plugins==0.6.1
Requires-Dist: mdformat==1.0.0
Requires-Dist: mdformat-gfm==1.0.0
Requires-Dist: mdformat-frontmatter==2.1.2
Requires-Dist: mdformat-footnote==0.1.3
Requires-Dist: linkify-it-py==2.1.0
Dynamic: license-file

# cedit — continuous editing of vendored Markdown

[![PyPI](https://img.shields.io/pypi/v/cedit)](https://pypi.org/project/cedit/)
[![Python versions](https://img.shields.io/pypi/pyversions/cedit)](https://pypi.org/project/cedit/)
[![Tests](https://github.com/sdlctools/cedit/actions/workflows/tests.yml/badge.svg)](https://github.com/sdlctools/cedit/actions/workflows/tests.yml)
[![License: MIT](https://img.shields.io/pypi/l/cedit)](https://github.com/sdlctools/cedit/blob/main/LICENSE)

Keep **local adaptations** of vendored Markdown alive across **upstream
updates**: a persistent block-level overlay, re-applied by 3-way structural
merge on the document's AST. Vendored a skill whose commands assume `bash`
but your environment runs `zsh`? Rewrite the fences once — every later
`sync` re-applies your rewrite over whatever upstream changed, and tells
you precisely (per block, with all three versions) when upstream touched
the same thing you did.

## Documentation

| Document | What's in it |
| --- | --- |
| [USERGUIDE.md](https://github.com/sdlctools/cedit/blob/main/USERGUIDE.md) | **How to drive it** — a five-minute tour, a per-flag reference for all five subcommands and for the `md` parser views, the conflict lifecycle worked end to end, the `.cedit/` layout, a cookbook and a troubleshooting table |
| [SPEC.md](https://github.com/sdlctools/cedit/blob/main/SPEC.md) | **The design** — the merge matrix, the normative sync algorithm, the state format, the reuse rules, and what is phase 1 vs. phase 2 vs. never |
| [AGENTS.md](https://github.com/sdlctools/cedit/blob/main/AGENTS.md) | **Changing cedit itself** — build and test commands, the architecture in one table, and the five invariants a change must not violate. `CLAUDE.md` exists only to pull this in, so every AI assistant reads the same file |
| [ARCHITECTURE.md](https://github.com/sdlctools/cedit/blob/main/ARCHITECTURE.md) | **The code, and how to change it** — every function, dataclass field and constant, the end-to-end call graph from `cli.main` down to the splice, where each invariant is actually enforced, and a *Changing cedit* section: which changes move consumers' stored hashes, and what to touch to add a subcommand, a block kind or a state field |
| [.claude/rules/release-pipeline.md](https://github.com/sdlctools/cedit/blob/main/.claude/rules/release-pipeline.md) | **How this repo ships** — the two tag shapes, the dev-build / cut / release flow end to end, who owns the version at each step, the five workflow invariants, and a failure-mode table |
| [cedit-canonicalization-reference.md](https://github.com/sdlctools/cedit/blob/main/cedit-canonicalization-reference.md) | **Canonicalization reference** — every Markdown element and how `cedit md canonicalize` transforms it, known caveats, and quick test commands |

Using cedit? You want USERGUIDE.md. The last three are for working *on* it.

The pinned parser and the Merkle-hash diff engine live — frozen — in
[`cedit/mdcore/`](https://github.com/sdlctools/cedit/tree/main/cedit/mdcore/).
Every hash cedit records is a function of them, so they change only through
the drift check described in
[.claude/rules/hash-stability.md](https://github.com/sdlctools/cedit/blob/main/.claude/rules/hash-stability.md).

## Install

```bash
pipx install cedit   # or: pip install cedit
cedit --help
```

**Python 3.10 or newer** — every version cedit claims is a version CI runs
the suite on, which is the whole point of the claim. 3.10 and 3.11 are the
Ubuntu 22.04 and Debian 12 system interpreters, and cedit is a developer tool
that lands on whatever Python a machine already has.

That installs the `cedit` command **and** the importable package with the
pinned parsing stack as real dependencies. The docs write `cedit
<subcommand>` throughout; `python3 -m cedit <subcommand>` is the same entry
point with the same arguments, and is what you want when cedit lives in a
virtualenv you'd rather not activate.

**Install cedit into an environment of its own** — that's what `pipx` above
buys you; a dedicated virtualenv does the same. `mdcore/utils.make_parser`
appends *every installed* mdformat parser extension, so the set of mdformat
plugins present in the environment is part of the parser identity. Dropping
cedit into a shared environment that already carries other mdformat plugins
can move the hashes in your `.cedit/` state even though cedit's own pins are
honoured — and moved hashes read as conflicts against blocks nobody touched.

### Working on cedit itself

Developing *cedit* rather than using it? Work from a source checkout:

```bash
python3 -m venv venv
venv/bin/pip install -r requirements.txt   # the parsing stack is pinned EXACTLY — see the file
venv/bin/pip install -e .                  # optional: only to run cedit from another repo
venv/bin/python3 -m pytest                 # 105 tests, no network
```

## Quickstart

Run from the root of the repository holding your vendored copies — a
*different* repo than this one. State lives in `.cedit/` (commit it — the
base snapshots *are* the merge's memory).

```bash
# 1. start tracking (vendors the file if it doesn't exist yet)
cedit snapshot skills/SKILL.md --from vendor/skills/SKILL.md

# 2. adapt the file in place — e.g. rewrite bash fences for zsh — then:
cedit diff
# [edit opaque fence] #c564262de9cbba0f:0  sim=0.98
#     ctx  : 1. Discovery and healthcheck
#     base : bash "${CLAUDE_PLUGIN_ROOT}/.../ensure_local_env.sh" || exit 1
#     local: zsh "${CLAUDE_PLUGIN_ROOT}/.../ensure_local_env.sh" || exit 1

# 3. upstream evolved — merge it in (your edits re-apply, even across moves
#    and reflows; upstream changes to blocks you didn't touch flow in)
cedit sync --from vendor
# skills/SKILL.md: 1 edit(s) reapplied, 1 block(s) updated from upstream, 1 conflict(s)
# [CONFLICT opaque fence] #c564262de9cbba0f:0
#     base    : bash ".../ensure_local_env.sh" || exit 1
#     upstream: bash ".../ensure_local_env.sh" --quiet || exit 1
#     local   : zsh ".../ensure_local_env.sh" || exit 1  (kept in the working file)

# 4. a conflict means upstream changed the very block you adapted — decide:
cedit resolve skills/SKILL.md c564262de9cbba0f --show           # all three versions
cedit resolve skills/SKILL.md c564262de9cbba0f --take local    # keep the adaptation
cedit resolve skills/SKILL.md c564262de9cbba0f --take upstream # take upstream's text

cedit status
# skills/SKILL.md: 2 local edit(s), 0 unresolved conflict(s); base 92b023942934d656 ...
```

Exit codes: `0` clean, `1` unresolved conflicts, `2` errors. A document
with open conflicts refuses to sync again until they're resolved, and the
working file always keeps *your* text on a conflict — resolution is
explicit, never a silent clobber.

Everything above in depth — every flag, every output line, the conflict
lifecycle end to end, the `.cedit/` layout and a troubleshooting table — is
in [USERGUIDE.md](https://github.com/sdlctools/cedit/blob/main/USERGUIDE.md).

## Looking at the parser directly

Those five subcommands are stateful. `cedit md` is a group of stateless
verbs — a file (or stdin) in, stdout out, no `.cedit/` touched — for seeing
what the parser actually does to a document:

```bash
cedit md canonicalize SKILL.md          # the mdformat round-trip .cedit/base/ stores
cedit md canonicalize --check SKILL.md  # exit 1 if it isn't already canonical
cedit md blocks SKILL.md                # the blocks the merge keys on, with hashes
cedit md ast --hashes SKILL.md          # the parse tree, every node's Merkle hash
cedit md json SKILL.md | cedit md from-json   # md -> tokens -> md, losslessly
```

`md blocks` is the one to reach for when a conflict key is a mystery: it
prints the same `<hash>:<occurrence>` keys that `cedit status` and `cedit
resolve` speak.

## What it will not do (yet)

- Local **structural** changes — inserting, deleting or moving whole
  blocks — are detected and rejected with a per-block report (phase 2 in
  the spec). Phase 1 merges *replacements*: prose rewrites, fence
  rewrites, table-cell tweaks, front-matter edits.
- Fetching upstream. `--from` takes a directory (mirroring your doc
  paths) or a file; git submodules, subtrees or curl are your transport.
- **Link reference definitions are inlined when used, and unused ones are
  dropped with a warning on stderr.** See
  [USERGUIDE.md §13](https://github.com/sdlctools/cedit/blob/main/USERGUIDE.md#13-limits-stated-plainly).

## Layout

| Path | |
| --- | --- |
| `cedit/__main__.py` | the `python3 -m cedit` entry point |
| `cedit/cli.py` | the five subcommands: snapshot / diff / sync / status / resolve |
| `cedit/mdcli.py` | the `md` group: stateless parser views — canonicalize / ast / json / from-json / blocks |
| `cedit/merge3.py` | the 3-way merge matrix + overlay derivation |
| `cedit/align.py` | block-sequence alignment (LCS over Merkle hashes, moves, fuzzy) |
| `cedit/blocks.py` | block extraction, splicing, render-and-verify |
| `cedit/mathguard.py` | the `$...$` math guard: carry the math through canonicalisation byte-exact, warn on stderr about what it cannot |
| `cedit/rowguard.py` | the table-row guard: carry what a body row holds past the header's last column through canonicalisation byte-exact, warn on stderr about what it cannot |
| `cedit/state.py` | `.cedit/` — base snapshots, manifest (+ conflicts), overlay |
| `cedit/store.py` | atomic writes: temp file + `rename(2)`, so a crash never leaves half-written state |
| `cedit/mdcore/` | **frozen**: the pinned parser + tree_diff — every recorded hash is a function of these |
| `tests/` | merge matrix + end-to-end CLI lifecycle + packaging metadata + the parser drift check |
| `pyproject.toml` | packaging metadata: the exact runtime pins, the `cedit` console script, explicit package discovery |
| `.github/workflows/tests.yml` | the suite on 3.10 – 3.14, installed from `requirements.txt` |

## Status

**Alpha** (`Development Status :: 3 - Alpha`). The merge is phase 1: it
re-applies *replacements* — prose, fences, table cells, front matter — and
**rejects local structural changes** (inserting, deleting or moving whole
blocks) with a per-block report rather than guessing. Structural local edits
are phase 2 in
[SPEC.md](https://github.com/sdlctools/cedit/blob/main/SPEC.md). The CLI
surface, the exit codes and the `.cedit/` state format are what phase 2 will
build on, but nothing here is promised stable before 1.0 — pin the version if
that matters to you.

## License

MIT — see
[LICENSE](https://github.com/sdlctools/cedit/blob/main/LICENSE).
