generated from coulomb/repo-seed
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:
18
.dockerignore
Normal file
18
.dockerignore
Normal 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
|
||||||
29
.github/workflows/ci.yml
vendored
29
.github/workflows/ci.yml
vendored
@@ -20,6 +20,7 @@ jobs:
|
|||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
|
cache: "pip"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pip install -e ".[dev]"
|
run: pip install -e ".[dev]"
|
||||||
@@ -31,4 +32,30 @@ jobs:
|
|||||||
run: mypy src/
|
run: mypy src/
|
||||||
|
|
||||||
- name: Run tests
|
- 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
36
.github/workflows/docker.yml
vendored
Normal 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
33
.github/workflows/publish.yml
vendored
Normal 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
61
CHANGELOG.md
Normal 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-1201–1210)
|
||||||
|
|
||||||
|
**LEVEL3 advanced features (WP-0003)**
|
||||||
|
- `level3.py` — LEVEL3 support detection and capability disclosure (FR-537–539)
|
||||||
|
- `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
|
||||||
@@ -320,3 +320,5 @@ Then either:
|
|||||||
- MCP tool reference: `~/the-custodian/state-hub/mcp_server/TOOLS.md`
|
- 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`
|
- 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`
|
- Contribution convention: `~/the-custodian/canon/standards/contribution-convention_v0.1.md`
|
||||||
|
- Release process: `docs/release-process.md`
|
||||||
|
- Changelog: `CHANGELOG.md`
|
||||||
|
|||||||
19
Dockerfile
Normal file
19
Dockerfile
Normal 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
58
docs/release-process.md
Normal 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)
|
||||||
@@ -6,6 +6,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
name = "markidocx"
|
name = "markidocx"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Markdown ↔ DOCX round-trip editing system"
|
description = "Markdown ↔ DOCX round-trip editing system"
|
||||||
|
readme = "README.md"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"python-docx>=1.1.0",
|
"python-docx>=1.1.0",
|
||||||
@@ -27,6 +28,14 @@ dev = [
|
|||||||
"types-PyYAML>=6.0",
|
"types-PyYAML>=6.0",
|
||||||
"httpx>=0.27",
|
"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]
|
[project.scripts]
|
||||||
markidocx = "markidocx.cli:app"
|
markidocx = "markidocx.cli:app"
|
||||||
@@ -37,6 +46,9 @@ where = ["src"]
|
|||||||
[tool.setuptools.package-data]
|
[tool.setuptools.package-data]
|
||||||
markidocx = ["templates/*.docx"]
|
markidocx = ["templates/*.docx"]
|
||||||
|
|
||||||
|
[tool.setuptools]
|
||||||
|
license-files = ["LICENSE"]
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
line-length = 100
|
line-length = 100
|
||||||
target-version = "py311"
|
target-version = "py311"
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import typer
|
|||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
from rich.table import Table
|
from rich.table import Table
|
||||||
|
|
||||||
|
from markidocx import __version__
|
||||||
|
|
||||||
app = typer.Typer(
|
app = typer.Typer(
|
||||||
name="markidocx",
|
name="markidocx",
|
||||||
help="Markdown ↔ DOCX round-trip editing system.",
|
help="Markdown ↔ DOCX round-trip editing system.",
|
||||||
@@ -18,6 +20,22 @@ app = typer.Typer(
|
|||||||
template_app = typer.Typer(help="Template family management.")
|
template_app = typer.Typer(help="Template family management.")
|
||||||
app.add_typer(template_app, name="template")
|
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()
|
console = Console()
|
||||||
err_console = Console(stderr=True)
|
err_console = Console(stderr=True)
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ id: MRKD-WP-0006
|
|||||||
type: workplan
|
type: workplan
|
||||||
domain: markitect
|
domain: markitect
|
||||||
repo: marki-docx
|
repo: marki-docx
|
||||||
status: active
|
status: done
|
||||||
state_hub_workstream_id: 7e255145-8d18-4f22-b1ca-31f02944b890
|
state_hub_workstream_id: 7e255145-8d18-4f22-b1ca-31f02944b890
|
||||||
created: 2026-03-16
|
created: 2026-03-16
|
||||||
updated: 2026-03-16
|
updated: 2026-03-16
|
||||||
@@ -25,7 +25,7 @@ release artefact production.
|
|||||||
|
|
||||||
```task
|
```task
|
||||||
id: MRKD-WP-0006-T01
|
id: MRKD-WP-0006-T01
|
||||||
status: todo
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: cfca0094-b5ae-45ec-80c9-e7705f37bd12
|
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
|
```task
|
||||||
id: MRKD-WP-0006-T02
|
id: MRKD-WP-0006-T02
|
||||||
status: todo
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: 44f60455-b144-48e3-93a0-74869018c2ea
|
state_hub_task_id: 44f60455-b144-48e3-93a0-74869018c2ea
|
||||||
```
|
```
|
||||||
@@ -84,7 +84,7 @@ returns the correct version string.
|
|||||||
|
|
||||||
```task
|
```task
|
||||||
id: MRKD-WP-0006-T03
|
id: MRKD-WP-0006-T03
|
||||||
status: todo
|
status: done
|
||||||
priority: medium
|
priority: medium
|
||||||
state_hub_task_id: 05ff77b0-6347-4c96-9fcd-c2b2baaf0fce
|
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
|
```task
|
||||||
id: MRKD-WP-0006-T04
|
id: MRKD-WP-0006-T04
|
||||||
status: todo
|
status: done
|
||||||
priority: medium
|
priority: medium
|
||||||
state_hub_task_id: 177e4861-d153-4b1d-85d4-a272da14bfe5
|
state_hub_task_id: 177e4861-d153-4b1d-85d4-a272da14bfe5
|
||||||
```
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user