# resume-formatter

Renders a plain-text resume into a `.docx` matching a fixed reference layout:
Bookman Old Style 10 pt, A4, 0.5″ margins, justified bullets on real list
numbering, bold + underlined section headers.

Works as a Claude Code skill, as a tool your Codex or Cursor agent calls, or as a
plain command-line program with no agent at all.

Write the content in plain text as often as you like. The formatting never
drifts.

## Install

There is no install step beyond putting this folder where your agent looks.

**Claude Code / Claude Desktop** — copy it into the skills directory; it is
picked up at next startup:

```bash
cp -R resume-formatter ~/.claude/skills/          # everywhere
mkdir -p .claude/skills && cp -R resume-formatter .claude/skills/   # one project
```

**Codex, Cursor, or any other agent** — they have no skills directory. Put this
folder in your project and add a pointer to `AGENTS.md` (or that tool's own
instructions file):

```markdown
## Resume formatting

`resume-formatter/` turns a plain-text resume into a formatted .docx.

- Read `resume-formatter/SKILL.md` before using it.
- Build:    `python3 resume-formatter/scripts/build_resume.py <input.txt>`
- Validate: `python3 resume-formatter/scripts/validate.py <file.docx> -i <input.txt>`
- It is a formatter, not a writer. Reproduce the user's wording exactly.
```

**No agent** — see [Use it directly](#use-it-directly) below.

Standard library Python 3.8 or newer, no third-party packages. On Windows use
`py` rather than `python3`.

Verify:

```bash
python3 ~/.claude/skills/resume-formatter/tests/run_tests.py
```

## Use it through an agent

Paste your resume text and say *"format this resume"*, *"make this a docx in my
format"*, or *"here's my updated resume text"*. Claude Code triggers on
resume-shaped input automatically; Codex and Cursor will use it once `AGENTS.md`
points at the folder.

## Use it directly

```bash
python3 scripts/build_resume.py resume.txt
```

Every run creates its own timestamped folder, so old versions are never
clobbered and you can always see which text produced which document:

```
Jordan Rivera's resume 17 August 2026 19-52/
├── Jordan Rivera - Platform Engineer.docx
└── source.txt
```

The name comes from line 1 of your text and the title from line 2. Then check it:

```bash
python3 scripts/validate.py "Jordan Rivera's resume 17 August 2026 19-52/Jordan Rivera - Platform Engineer.docx" -i resume.txt
```

Reads stdin when no input file is given.

| Flag | Default | Effect |
|---|---|---|
| `-d, --outdir DIR` | `.` | where the dated folder goes |
| `-o, --output PATH` | — | exact filename, no folder |
| `--no-folder` | off | bare `.docx`, no folder |
| `--no-source-copy` | off | skip the `source.txt` snapshot |
| `--page-size {a4,letter}` | `a4` | page size |
| `--no-justify` | off | left-align body text |
| `--font "<name>"` | `Bookman Old Style` | body font |
| `--size <pt>` | `10` | body size |

Defaults reproduce the reference exactly. The clock in the folder name is
`HH-MM` rather than `HH:MM` because Windows forbids `:` in filenames and macOS
Finder displays it as `/`. Two runs in the same minute get ` (2)`, ` (3)`.

## Input format

```
Full Name
Target Job Title
email@example.com | +1 555-0100

PROFESSIONAL SUMMARY:
•  A bullet.

TECHNICAL SKILLS:
Category: item, item, item

PROFESSIONAL EXPERIENCE:
Company, City ST | Month Year – Month Year
Job Title
Responsibilities:
•  A bullet.

EDUCATION:
•  Degree - Institution.
```

Bullet markers `• - * ·` and tabs are all accepted and stripped. Headers work
with or without a trailing colon and in any case. Date separators `-`, `--`,
`—`, `to` normalize to an en dash. `Responsibilities:` is emitted whether or not
you type it. Extra ALL-CAPS sections (`CERTIFICATIONS`, `PROJECTS`) are kept.

## It formats, it does not write

The skill reproduces your text verbatim. It never invents, rewords or
"improves" a bullet, and never adds an employer, date, metric or credential you
did not supply. A missing section stays missing. `validate.py` diffs the
generated document against your input and fails on any character that was
dropped, added or altered.

## Layout

```
resume-formatter/
├── SKILL.md                     operating procedure for the agent
├── README.md
├── starter.txt                  a fictional, correctly shaped sample resume
├── scripts/
│   ├── build_resume.py          generator
│   └── validate.py              round-trip, structure and schema checks
├── assets/
│   └── template/                the reference .docx, unpacked and de-identified
├── reference/
│   ├── format-spec.md           every format property, with XML evidence
│   ├── resume-notes.md          ATS, justification, A4, fonts, page counts
│   └── make-template.py         how the template was derived
└── tests/
    ├── run_tests.py             81-check acceptance suite
    └── fixture_*.txt            fictional sample resumes
```

## How it works

Template-clone. `assets/template/` holds the reference document unpacked.
`styles.xml`, `numbering.xml`, `settings.xml`, `fontTable.xml` and `theme/` are
copied into every build byte-for-byte; only `word/document.xml`'s `<w:body>`
and the `mailto:` relationship are regenerated. Because the style definitions
are never re-derived, no formatting property can be lost by oversight —
including ones nobody thought to write down.

Builds are deterministic: the same input produces a byte-identical `.docx`.

## Sharing

The folder is self-contained. Zip it and send it, or push it as a git repo:

```bash
zip -r resume-formatter.zip resume-formatter
```

The recipient drops it in `~/.claude/skills/`, or in a project with an
`AGENTS.md` pointer, and it works — no configuration, no dependencies, no
reference document needed.

It carries **no personal data**. Every name, email, phone number and employer
comes from input at runtime; the template was stripped of author metadata,
revision ids and editing fingerprints; sample data in the tests is fictional.
`tests/run_tests.py` asserts this on every run.

## Bookman Old Style

Ships with Microsoft Office; absent on most Linux systems and Office-less Macs.
The `.docx` is correct either way — Word uses the real font wherever it exists,
and elsewhere the viewer substitutes a serif, which shifts line breaks and can
change the page count. To freeze the layout for everyone, export to PDF from a
machine that has the font. See `reference/resume-notes.md`.

## Tests

```bash
python3 tests/run_tests.py                              # fixtures
python3 tests/run_tests.py --reference path/to/ref.docx # + idempotence
```

Covers idempotence against the original document, structural variation
(2 jobs / 6 jobs / extra sections / no EDUCATION), dirty input (smart quotes,
CRLF, mixed bullet markers, ampersands, non-breaking hyphens), every CLI flag,
failure modes, portability from a fresh copy, and the no-personal-data
guarantee.
