diff --git a/AGENTS.md b/AGENTS.md index 9171961..5e56db0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,5 +10,5 @@ Agents should treat these areas independently so changes can be reasoned about a ## Working Guidelines - Keep shared instructions in this file minimal; place deeper guidance in `input/AGENTS.md` or `output/AGENTS.md` as appropriate. - When making automated edits, avoid touching both `input/` and `output/` in the same change set unless the work explicitly spans both pipelines. -- Resume conversion templates live under `input/templates`. Output services mount them read-only; update templates from the input side and verify with a fresh conversion run. +- Resume conversion templates live under `output/templates`. Copy the `.example` files to matching names (without the suffix) when you need local overrides, and verify with a fresh conversion run. - Use conventional commits (`(scope): `) to signal which side of the system a change targets, e.g., `feat(output): add failed-processing bucket`. diff --git a/output/AGENTS.md b/output/AGENTS.md index 7e05134..87875e8 100644 --- a/output/AGENTS.md +++ b/output/AGENTS.md @@ -20,7 +20,7 @@ Operate the output pipeline that turns one approved Markdown resume at a time in ## Guardrails (Do Not Cross) - Treat `ForRelease/outbox` and `ForRelease/processed` as immutable history managed exclusively by the human. Only append new artifacts when explicitly directed; never delete, move, rename, reorganize, or even contemplate cleanup, retention, or consolidation actions. - Refrain from generating additional files outside the described workflow. All job-specific Markdown arrives from the human, and all cleanup decisions belong to the human. -- Leave shared templates mounted from `input/templates` untouched; template maintenance happens in the input system. +- Leave shared templates under `templates/` untouched unless the human provides explicit replacements (copy `.example` files to matching names without the suffix to override). - Do not queue multiple Markdown files or attempt parallel conversions. This pipeline is intentionally single-job at any moment. - Escalate irregularities (missing templates, repeated failures, permission issues) to the human instead of improvising fixes. diff --git a/output/Docker/docker-compose.yml b/output/Docker/docker-compose.yml index 2426f63..fa4119e 100644 --- a/output/Docker/docker-compose.yml +++ b/output/Docker/docker-compose.yml @@ -16,4 +16,4 @@ services: - ../ForRelease/outbox:/data/outbox - ../ForRelease/processed:/data/processed - ../ForRelease/failed:/data/failed - - ../../input/templates:/templates:ro + - ../templates:/templates:ro diff --git a/output/Docker/watch_and_convert.py b/output/Docker/watch_and_convert.py index 6fcd414..72064c1 100755 --- a/output/Docker/watch_and_convert.py +++ b/output/Docker/watch_and_convert.py @@ -20,17 +20,39 @@ OUTBOX = Path("/data/outbox") PROCESSED = Path("/data/processed") FAILED = Path("/data/failed") TEMPLATES = Path("/templates") +TEMPLATE_CACHE = Path("/tmp/templates") DOCX_TEMPLATE = TEMPLATES / "resume-reference.docx" +DOCX_TEMPLATE_EXAMPLE = TEMPLATES / "resume-reference.docx.example" TEX_TEMPLATE = TEMPLATES / "resume-template.tex" +TEX_TEMPLATE_EXAMPLE = TEMPLATES / "resume-template.tex.example" + +RESOLVED_DOCX_TEMPLATE: Path | None = None +RESOLVED_TEX_TEMPLATE: Path | None = None POLL_INTERVAL_SECONDS = 5 +def resolve_template(primary: Path, example: Path, cache_dir: Path) -> Path: + """Return the template path, copying .example into a writable cache if needed.""" + if primary.exists(): + return primary + + if example.exists(): + cache_dir.mkdir(parents=True, exist_ok=True) + cached = cache_dir / primary.name + shutil.copy(example, cached) + return cached + + raise FileNotFoundError(f"Template missing: {primary} (no example found)") + + def ensure_environment() -> None: """Verify required files and directories exist before processing starts.""" + global RESOLVED_DOCX_TEMPLATE, RESOLVED_TEX_TEMPLATE + missing = [] - for path in (INBOX, OUTBOX, PROCESSED, FAILED, DOCX_TEMPLATE, TEX_TEMPLATE): + for path in (INBOX, OUTBOX, PROCESSED, FAILED, TEMPLATES): if not path.exists(): missing.append(str(path)) @@ -39,6 +61,9 @@ def ensure_environment() -> None: "Required paths are missing inside the container: " + ", ".join(missing) ) + RESOLVED_DOCX_TEMPLATE = resolve_template(DOCX_TEMPLATE, DOCX_TEMPLATE_EXAMPLE, TEMPLATE_CACHE) + RESOLVED_TEX_TEMPLATE = resolve_template(TEX_TEMPLATE, TEX_TEMPLATE_EXAMPLE, TEMPLATE_CACHE) + def run_pandoc(input_md: Path, output_docx: Path, output_pdf: Path) -> None: """Invoke pandoc twice to create DOCX and PDF artifacts.""" @@ -51,7 +76,7 @@ def run_pandoc(input_md: Path, output_docx: Path, output_pdf: Path) -> None: "--to", "docx", "--reference-doc", - str(DOCX_TEMPLATE), + str(RESOLVED_DOCX_TEMPLATE), "--output", str(output_docx), ], @@ -67,7 +92,7 @@ def run_pandoc(input_md: Path, output_docx: Path, output_pdf: Path) -> None: "--pdf-engine", "xelatex", "--template", - str(TEX_TEMPLATE), + str(RESOLVED_TEX_TEMPLATE), "--output", str(output_pdf), ], diff --git a/output/README.md b/output/README.md index b41f523..8ae6634 100644 --- a/output/README.md +++ b/output/README.md @@ -8,6 +8,7 @@ This directory houses the post-processing side of ResumeCustomizer. It accepts o - `ForRelease/processed/YYYY/MM/DD/HHMM` – timestamped archives of Markdown files that converted successfully. - `ForRelease/failed` – holding area for Markdown files that Pandoc could not render. - `Docker/` – Dockerfile, compose stack, watcher script, and wrapper used to run the conversion container. +- `templates/` – default Pandoc assets (`*.example`) the watcher copies into `/tmp` at runtime; copy any `.example` file without the suffix to override it locally (ignored by git). All `ForRelease` subdirectories include `.gitkeep` and `.gitignore` so artifacts stay local and never reach version control. @@ -23,7 +24,7 @@ The wrapper auto-detects the Docker Compose plugin or legacy `docker-compose`, f ## Conversion Flow 1. The watcher polls `ForRelease/inbox` every few seconds for exactly one Markdown resume. -2. Pandoc renders DOCX and PDF using the shared templates. +2. Pandoc renders DOCX and PDF using the shared templates (and auto-falls back to the bundled `.example` files if no overrides exist). 3. Artifacts land in a timestamped folder under `ForRelease/outbox`. 4. The source Markdown moves into the matching timestamp folder under `ForRelease/processed`. 5. On Pandoc failure, the Markdown shifts into `ForRelease/failed` for human review before retrying. @@ -32,7 +33,7 @@ Outbox and processed directories are append-only historical records managed sole ## Prerequisites - Docker Engine with the Compose plugin (`docker compose`) or the standalone `docker-compose` binary. -- Template assets mounted read-only from `input/templates`. +- Template assets mounted read-only from `templates/` (copy `.example` files to matching names without the suffix to customize them). Stop or inspect the stack with: diff --git a/output/templates/.gitignore b/output/templates/.gitignore new file mode 100644 index 0000000..8ce1143 --- /dev/null +++ b/output/templates/.gitignore @@ -0,0 +1,4 @@ +*.docx +*.tex +!*.docx.example +!*.tex.example diff --git a/input/templates/resume-reference.docx b/output/templates/resume-reference.docx.example similarity index 100% rename from input/templates/resume-reference.docx rename to output/templates/resume-reference.docx.example diff --git a/input/templates/resume-template.tex b/output/templates/resume-template.tex.example similarity index 100% rename from input/templates/resume-template.tex rename to output/templates/resume-template.tex.example