feat: WP-0006 — packaging & distribution infrastructure

CI matrix (Python 3.11/3.12) with pip cache and coverage job. PyPI publish
workflow (OIDC trusted publishing, triggered on v*.*.* tags). Docker image
for REST service with non-root user + ghcr.io push workflow. markidocx
--version flag. Diagram optional extras (diagram-mermaid/graphviz/plantuml)
and readme/urls in pyproject.toml. CHANGELOG.md (Keep a Changelog format)
with retrospective v0.1.0 entry. docs/release-process.md with executable
checklist. All 272 tests pass; ruff and mypy clean; twine check PASSED.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-17 14:30:08 +00:00
parent 5564747060
commit 6cf973b017
11 changed files with 290 additions and 6 deletions

18
.dockerignore Normal file
View File

@@ -0,0 +1,18 @@
.git
.github
tests
docs
workplans
specs
contrib
dist
*.egg-info
__pycache__
*.pyc
*.pyo
.pytest_cache
.mypy_cache
.ruff_cache
.markidocx
requirements.txt
CHANGELOG.md

View File

@@ -20,6 +20,7 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: "pip"
- name: Install dependencies
run: pip install -e ".[dev]"
@@ -31,4 +32,30 @@ jobs:
run: mypy src/
- name: Run tests
run: pytest
run: pytest --tb=short -q
coverage:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
needs: test
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: "pip"
- name: Install dependencies
run: pip install -e ".[dev]"
- name: Run tests with coverage
run: pytest --cov=markidocx --cov-report=xml --tb=short -q
- name: Upload coverage report
uses: actions/upload-artifact@v4
with:
name: coverage-xml
path: coverage.xml

36
.github/workflows/docker.yml vendored Normal file
View File

@@ -0,0 +1,36 @@
name: Docker
on:
push:
tags:
- "v*.*.*"
jobs:
docker:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract version from tag
id: version
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository }}:latest
ghcr.io/${{ github.repository }}:${{ github.ref_name }}

33
.github/workflows/publish.yml vendored Normal file
View File

@@ -0,0 +1,33 @@
name: Publish to PyPI
on:
push:
tags:
- "v*.*.*"
jobs:
publish:
runs-on: ubuntu-latest
permissions:
id-token: write # required for OIDC trusted publishing
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: "pip"
- name: Install build tools
run: pip install build twine
- name: Build distribution
run: python -m build
- name: Check distribution
run: twine check dist/*
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1

61
CHANGELOG.md Normal file
View File

@@ -0,0 +1,61 @@
# Changelog
All notable changes to markidocx are documented in this file.
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
---
## [Unreleased]
---
## [0.1.0] — 2026-03-17
### Added
**Core pipeline (WP-0001)**
- `manifest.py` — project manifest parsing and validation (FR-100)
- `builder.py` — Markdown → DOCX conversion, LEVEL1 feature set: headings, lists, tables,
footnotes, images, hyperlinks (FR-200)
- `importer.py` — DOCX → Markdown round-trip import (FR-300, FR-400)
- `differ.py` — structural drift detection between original Markdown and re-imported result (FR-700)
- `templates.py` — document family management: `article`, `book`, `website` (FR-600)
- `evidence.py` — evidence and report assembly (FR-1400)
**Service interfaces (WP-0002)**
- `cli.py` — Typer CLI: `build`, `import`, `compare`, `validate`, `serve`, `workflow`,
`mcp`, `template` commands
- `rest.py` — FastAPI REST service with structured `WarningRecord` / `FailureRecord` output
(FR-900, FR-1208)
- `mcp_server.py` — FastMCP server exposing same functional surface as CLI/REST (FR-1000)
- `errors.py``WarningRecord`, `FailureRecord`, `OutputState` error framework (FR-12011210)
**LEVEL3 advanced features (WP-0003)**
- `level3.py` — LEVEL3 support detection and capability disclosure (FR-537539)
- `xref.py` — cross-reference round-trip helpers (FR-531, FR-540)
- `figures.py` — numbered figure round-trip helpers (FR-532, FR-541)
- `diagrams.py` — auto-diagram source-only preservation (FR-533, FR-534)
- `bibliography.py` — citation and references section round-trip (FR-535, FR-536, FR-542)
- `workflows.py` — composite workflow orchestration: `single-file-roundtrip`,
`multi-file-roundtrip`, `release-regression`, `family-switch-build` (FR-1300)
**Diagram renderer integration (WP-0005)**
- Pluggable `DiagramRenderer` protocol and `RendererResult` type
- `MermaidRenderer` — shells out to `mmdc` when available (FR-533)
- `GraphvizRenderer` — shells out to `dot` when available (FR-533)
- `PlantUMLRenderer` — shells out to `plantuml` when available (FR-533)
- Graceful source-only fallback with `WarningRecord(reason="renderer-unavailable")` when
tool is absent (FR-538)
- Alt-text source markers enable diagram round-trip after rendering (FR-534)
- Optional extras: `diagram-mermaid`, `diagram-graphviz`, `diagram-plantuml`
**Packaging & distribution (WP-0006)**
- GitHub Actions CI: matrix test on Python 3.11 + 3.12, ruff, mypy, coverage
- PyPI publish workflow triggered on `v*.*.*` tags via OIDC trusted publishing
- Docker image for REST service (`ghcr.io`)
- `markidocx --version` command
[Unreleased]: https://github.com/tegwick/marki-docx/compare/v0.1.0...HEAD
[0.1.0]: https://github.com/tegwick/marki-docx/releases/tag/v0.1.0

View File

@@ -320,3 +320,5 @@ Then either:
- MCP tool reference: `~/the-custodian/state-hub/mcp_server/TOOLS.md`
- ADR-001 (workplan convention): `~/the-custodian/canon/architecture/adr-001-workplans-as-repo-artefacts.md`
- Contribution convention: `~/the-custodian/canon/standards/contribution-convention_v0.1.md`
- Release process: `docs/release-process.md`
- Changelog: `CHANGELOG.md`

19
Dockerfile Normal file
View File

@@ -0,0 +1,19 @@
FROM python:3.12-slim
# Create non-root user
RUN useradd --create-home --shell /bin/bash appuser
WORKDIR /app
# Install markidocx
COPY pyproject.toml ./
COPY src/ ./src/
RUN pip install --no-cache-dir .
# Switch to non-root user
USER appuser
EXPOSE 8080
ENTRYPOINT ["markidocx", "serve"]
CMD ["--host", "0.0.0.0", "--port", "8080"]

58
docs/release-process.md Normal file
View File

@@ -0,0 +1,58 @@
# Release Process
## Pre-release checklist
Before tagging a release, verify every item:
- [ ] All tests pass: `pytest --tb=short -q`
- [ ] Lint clean: `ruff check .`
- [ ] Type-check clean: `mypy src/`
- [ ] Corpus regression passes: `markidocx test`
- [ ] SBOM is current (re-run after any dependency change — see CLAUDE.md)
- [ ] `CHANGELOG.md` updated: move items from `[Unreleased]` to a new versioned section
- [ ] Version bumped consistently:
- `src/markidocx/__init__.py``__version__ = "X.Y.Z"`
- `pyproject.toml``version = "X.Y.Z"`
- [ ] Build artefacts clean: `python -m build && twine check dist/*`
## Tagging and releasing
```bash
# Verify version strings match
python -c "import markidocx; print(markidocx.__version__)"
# Create a signed tag
git tag -s vX.Y.Z -m "Release vX.Y.Z"
# Push tag — triggers publish.yml (PyPI) and docker.yml (ghcr.io) automatically
git push origin vX.Y.Z
```
Pushing the tag triggers two GitHub Actions workflows:
- **`publish.yml`** — builds and publishes to PyPI via OIDC trusted publishing
- **`docker.yml`** — builds and pushes Docker image to `ghcr.io`
## Post-release
- Create a GitHub release from the tag and paste the relevant CHANGELOG section
- Verify installation: `pip install markidocx==X.Y.Z && markidocx --version`
- Verify Docker image: `docker run --rm ghcr.io/tegwick/marki-docx:vX.Y.Z --version`
## Versioning policy
markidocx follows [Semantic Versioning](https://semver.org/):
| Change type | Version bump |
|------------|-------------|
| Breaking change to CLI/API/round-trip contract | MAJOR |
| New feature, backwards-compatible | MINOR |
| Bug fix, docs, tooling | PATCH |
## Setting up PyPI trusted publishing (one-time)
In the PyPI project settings, add a trusted publisher:
- Publisher: GitHub Actions
- Repository owner: `tegwick`
- Repository name: `marki-docx`
- Workflow filename: `publish.yml`
- Environment name: (leave blank)

View File

@@ -6,6 +6,7 @@ build-backend = "setuptools.build_meta"
name = "markidocx"
version = "0.1.0"
description = "Markdown ↔ DOCX round-trip editing system"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"python-docx>=1.1.0",
@@ -27,6 +28,14 @@ dev = [
"types-PyYAML>=6.0",
"httpx>=0.27",
]
diagram-mermaid = ["nodeenv>=1.8"]
diagram-graphviz = ["graphviz>=0.20"]
diagram-plantuml = []
[project.urls]
Homepage = "https://github.com/tegwick/marki-docx"
Source = "https://github.com/tegwick/marki-docx"
Tracker = "https://github.com/tegwick/marki-docx/issues"
[project.scripts]
markidocx = "markidocx.cli:app"
@@ -37,6 +46,9 @@ where = ["src"]
[tool.setuptools.package-data]
markidocx = ["templates/*.docx"]
[tool.setuptools]
license-files = ["LICENSE"]
[tool.ruff]
line-length = 100
target-version = "py311"

View File

@@ -10,6 +10,8 @@ import typer
from rich.console import Console
from rich.table import Table
from markidocx import __version__
app = typer.Typer(
name="markidocx",
help="Markdown ↔ DOCX round-trip editing system.",
@@ -18,6 +20,22 @@ app = typer.Typer(
template_app = typer.Typer(help="Template family management.")
app.add_typer(template_app, name="template")
def _version_callback(value: bool) -> None:
if value:
print(f"markidocx {__version__}")
raise typer.Exit()
@app.callback()
def _main(
version: Annotated[
bool,
typer.Option("--version", callback=_version_callback, is_eager=True, help="Show version and exit."),
] = False,
) -> None:
pass
console = Console()
err_console = Console(stderr=True)

View File

@@ -3,7 +3,7 @@ id: MRKD-WP-0006
type: workplan
domain: markitect
repo: marki-docx
status: active
status: done
state_hub_workstream_id: 7e255145-8d18-4f22-b1ca-31f02944b890
created: 2026-03-16
updated: 2026-03-16
@@ -25,7 +25,7 @@ release artefact production.
```task
id: MRKD-WP-0006-T01
status: todo
status: done
priority: high
state_hub_task_id: cfca0094-b5ae-45ec-80c9-e7705f37bd12
```
@@ -54,7 +54,7 @@ Deliverable: `.github/workflows/ci.yml` present; CI passes on a clean push to `m
```task
id: MRKD-WP-0006-T02
status: todo
status: done
priority: high
state_hub_task_id: 44f60455-b144-48e3-93a0-74869018c2ea
```
@@ -84,7 +84,7 @@ returns the correct version string.
```task
id: MRKD-WP-0006-T03
status: todo
status: done
priority: medium
state_hub_task_id: 05ff77b0-6347-4c96-9fcd-c2b2baaf0fce
```
@@ -121,7 +121,7 @@ Deliverable: `docker build . && docker run -p 8080:8080 <image>` starts the REST
```task
id: MRKD-WP-0006-T04
status: todo
status: done
priority: medium
state_hub_task_id: 177e4861-d153-4b1d-85d4-a272da14bfe5
```