In the last couple of months, two different people asked me if I would share my Japanese-style resume. Not the English one. The 履歴書 and 職務経歴書. The formats a Japanese company actually asks for when you apply.

Each time the answer was the same awkward "let me get back to you on that." My English resume was in decent shape. My Japanese one was a set of half-finished notes, out of date the moment I touched the English version. Rewriting both by hand every job cycle, in a second language, was not going to happen.

So I built JPResume, a small Swift CLI that turns a western-style resume into both Japanese formats and keeps them in sync.

Why a Japanese Resume Is a Different Problem

If you have only ever written a western resume, it is tempting to think the Japanese versions are just a translation job. They are not.

A 履歴書 (rirekisho) is a grid form. Name, furigana, a photo slot, a specific layout for education and work history with year and month columns, licenses, a 志望動機 (motivation) box, and 本人希望記入欄 (your own preferences or requests). There are conventions about how to mark the current role, how to end the work history section (「現在に至る」 in the final row), and whether to use western years or reiwa dates. Some companies care about these details. Some do not. None of it is obvious the first time you encounter it.

A 職務経歴書 (shokumukeirekisho) is the free-form counterpart. This is where the actual career story lives. 職務要約 up top, then per-role detail, skill groupings, achievements, and a 自己PR (self-PR). The phrasing conventions are their own skill. A metric like "drove a 29.8% increase in sign-ups" reads fine in English and clunky in Japanese. Something like 新規会員登録数の29.8%増加に寄与 sits more naturally.

Then there is the alignment problem. Your English resume and your Japanese resume both have to tell the same story, with the same dates, the same role titles, and the same emphasis. When one drifts, you notice in the worst possible moment, usually during a screening call.

That is the part a tool can actually help with.

How It Works

The pipeline is boring on purpose. No magic. Just steps you can pause between:

flowchart LR
    parse[Parse]
    normalize["Normalize (LLM)"]
    repair[Repair]
    validate[Validate]
    generate["Generate (LLM)"]
    render[Render]

    parse --> normalize --> repair --> validate --> generate --> render

Parse is deterministic. It takes an input resume as markdown, DOCX, or PDF, extracts the text, and produces a rough structured version. Markdown goes through a markdown parser. DOCX and PDF go through a text preprocessing path, because those are not clean structured documents once the text is out of them. For scanned PDFs, Vision OCR kicks in as a fallback.

Normalize is where the LLM first shows up. It takes the parsed resume plus a jpresume_config.yaml (kanji name, furigana, address, education timeline, certifications, the ground-truth Japan-specific fields) and produces a normalized structure with real date integers, bullets classified as achievements or responsibilities, and skills grouped into categories. The LLM is told explicitly not to invent anything. If a date is ambiguous, it is expected to flag it with a low confidence rather than guess.

Repair is deterministic again. It sorts roles chronologically, reconciles overlapping dates, fixes is_current inconsistencies. Validate surfaces the remaining issues as warnings: overlapping roles, low-confidence entries, suspicious gaps, total years that look off.

Generate is where the rirekisho and shokumukeirekisho JSON gets produced. Render converts those to markdown and to native PDFs through CoreGraphics and Hiragino Sans, so the Japanese typography actually looks right rather than falling back to a system font that was never designed for it.

If you have worked with AI-assisted tooling for a while, the shape will be familiar: deterministic where you can be, LLM where you must be, and a pause point in the middle where a human or an agent can look at the intermediate JSON before anything downstream commits to it.

The Workflow I Actually Use

The CLI can run end to end with jpresume convert resume.md --provider claude-cli --format both. That works. For most changes, though, I use the agent skill instead.

Why? Because of a feature I leaned on hard while building this: external mode.

In external mode, the LLM stages do not call a provider at all. They write a prompt bundle to disk and exit. The agent (Claude Code, Cursor, Codex, whichever you prefer) reads that bundle, produces the response JSON, writes it back, and then re-runs the stage with --ingest. The agent becomes the model. It sees every prompt. It sees every response. It can pause, ask me a question, fix a bad field, and re-ingest without re-running the whole pipeline.

The practical effect is that the normalize step stops being a black box. When the agent normalizes a role, I see exactly how it interpreted the dates before anything downstream uses them. When something looks off, the fix is a JSON edit, not a re-prompt. After validate, the workflow pauses on purpose, the agent shows me the warnings, and nothing generates until I have looked at them.

This is the part I would have gotten wrong if I had built it as a one-shot tool. For generating a first draft, a one-shot is fine. For a document you are actually going to send to a company, the review loop is the feature.

The Honest Limits

Here is the part I want to be clear about: so far, JPResume has really only been tested on one resume. Mine.

That resume has a specific shape. One country of origin. A career mostly in software engineering, mostly at companies with names Japanese recruiters will recognize. Dates that are relatively clean. A JLPT certification that maps neatly to the standard Japanese phrasing. None of the edge cases that come from career switches, international graduate programs, long intentional gaps, unusual role titles, or industries where the Japanese conventions are very different from tech.

I would not be surprised if the first resume someone else feeds in uncovers something the normalizer does not handle well, or a phrasing pattern the generate stage does not produce gracefully. The pipeline is set up to make those kinds of fixes cheap (edit a JSON artifact, re-run one stage), but I have not actually seen them yet. My sample size is one.

So if you try it and something looks wrong, or you can think of a shape of resume the tool should handle and does not, I would genuinely like to hear about it. Issues, pull requests, or a note in any form. The install instructions and an agent skill you can drop into your coding assistant are both in the README.

What I Would Keep Even If the Tool Changes

The part of this I expect to outlast the current implementation is the shape. Deterministic parsing, LLM normalization with a strict no-invention rule, a deliberate pause after validate, external mode so the agent can act as the model and review its own output. That pattern holds up whether the document is a Japanese resume, a regulatory filing, or a contract. Anywhere the stakes of a fabricated field are high and a fully automated one-shot is the wrong abstraction.

For now, it converts a markdown resume into a 履歴書 and 職務経歴書 that I would actually hand to a company. Which, a couple of months ago, was the thing I could not do.