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
|
||||
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
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`
|
||||
- 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
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"
|
||||
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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user