From ac923b504651600d66935d387d576cba64c6d724 Mon Sep 17 00:00:00 2001 From: David Bon-Salomon <62355986+David-BS@users.noreply.github.com> Date: Sat, 6 Jun 2026 18:22:25 +0200 Subject: [PATCH] feat(posting-brief): posting brief generator at intake (0.20.0) --- CHANGELOG.md | 14 + SKILL.md | 14 +- modules/posting-brief-generator/GUIDE.md | 162 ++++++++++ .../references/posting_brief_structure.md | 70 +++++ .../scripts/generate_posting_brief.py | 282 ++++++++++++++++++ .../scripts/md_to_pdf.py | 161 ++++++++++ references/assistant_flow.md | 6 + tests/test_examples.py | 6 +- tests/test_posting_brief.py | 209 +++++++++++++ tooling/build_samples.py | 44 +++ 10 files changed, 959 insertions(+), 9 deletions(-) create mode 100644 modules/posting-brief-generator/GUIDE.md create mode 100644 modules/posting-brief-generator/references/posting_brief_structure.md create mode 100644 modules/posting-brief-generator/scripts/generate_posting_brief.py create mode 100644 modules/posting-brief-generator/scripts/md_to_pdf.py create mode 100644 tests/test_posting_brief.py diff --git a/CHANGELOG.md b/CHANGELOG.md index f102410..b9ac77e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,20 @@ fix → patch (`0.3.x`); feature addition → minor (`0.x.0`). > Ajouté → Added · Modifié → Changed · Corrigé → Fixed · Déprécié → Deprecated · > Supprimé → Removed · Sécurité → Security · Validé → Validated · Documentation → Documentation. +## [0.20.0] — 2026-06-06 + +### Added +- **Posting brief (PB-1) — new `posting-brief-generator` sub-module.** At application intake, the orchestrator now produces a Markdown **posting brief** (job-posting dossier) automatically and **first**, before the widget's six deliverables — it is **not** a selection-widget choice. Content: a header (company, position, recruiter, city, capture date, source URL, posting language), the **verbatim** posting body, and a short model-extracted **digest** (key requirements + application deadline). It reuses the offer already read in the single global analysis, so it adds no separate offer read and nothing to the time-to-widget. Generated via `generate_posting_brief.py`; optional styled PDF via the module's `md_to_pdf.py` (also part of the end-of-run PDF batch). Eighth sub-module of the suite. +- **Script-owned filename** for the brief: `generate_posting_brief.py --output-dir
` to each script below (letter = confirmed **offer** language; help-docs = **working** language, or the override). any ISO 639-1 code works (**L6**): there are no per-language assets anymore — the scripts are language-agnostic and the model supplies the labels/strings in the run language.
- **Beyond `--language` (labels contract — L6):** each deliverable's **structure labels** are produced **by the model in the run language** and passed to its script — `--labels-json '{...}'` for the four `.md` generators (the script enforces the **exact** key set — no invented/omitted section), and `subject_label` / `greeting` / `closing` in the letter's `--data-json`. See each module `GUIDE.md` + `references/language_style_generic.md`.
- **Persist (letter only, opportunistic):** when the letter language was confirmed and the application is (or is being) added to the tracker, write it to the tracker `language` column for silent reuse on resume. Letter-only without a tracker → persist nothing. Help-doc language is **never** persisted.
-4. **Generate each chosen deliverable**, in the mandatory order below, **through the module's script** (never by hand). Read the module's `GUIDE.md` for the details, and prefix its paths with `modules//`:
+4. **First, automatically generate the posting brief** (PB-1) — it is **not** a widget choice — then **generate each chosen deliverable**, in the mandatory order below, **through the module's script** (never by hand). Read the module's `GUIDE.md` for the details, and prefix its paths with `modules//`:
+ - `posting_brief` → `modules/posting-brief-generator` (**automatic, first, not a widget box**) — produced from a job posting at intake, **reusing the offer already read in the single global analysis** (sub-step 3), so it adds no separate offer read and nothing to the time-to-widget. Header + **verbatim** body + model-extracted digest (key requirements / deadline). The capture date and the **filename are script-owned** (`--output-dir`, read the printed path; never hand-compose `--output-path`). **Idempotent**: skip if a `Posting_Brief___*` already exists in outputs (STEP 2); regenerate only on an explicit request. Present it via `present_files`, and record its id `posting_brief` in the tracker `deliverables` (at `add_to_tracker`). Trigger = the presence of a job posting (the same trigger as the STEP 8 title); pure config actions produce no brief. Details: its `GUIDE.md`.
- `strategic_playbook` → `modules/strategic-playbook-generator`
- `application_summary` → `modules/application-summary-generator`
- `interview_prep` → `modules/interview-prep-generator`
@@ -112,7 +114,7 @@ Input arrives one of two **equivalent** ways: (a) the **Generate click** — the
5. Respect the dependencies (reference card after the documents it condenses).
6. No intermediate validation between deliverables.
7. Offer PDF export at the end, then, if the user agrees, generate the PDFs of **all** produced deliverables:
- - `.md` deliverables (playbook, summary, interview prep, reference card) → the relevant module's `md_to_pdf.py`;
+ - `.md` deliverables (posting brief, playbook, summary, interview prep, reference card) → the relevant module's `md_to_pdf.py`;
- **cover letter `.docx`** (#7) → `python modules/cover-letter-generator/scripts/docx_to_pdf.py --input --output ` (LibreOffice; if unavailable the script exits with code 3 and the letter stays usable as `.docx`). Don't forget the letter in the PDF batch.
8. **Suggest the canonical conversation title** `📋 YYYY-MM-DD - Company - Position`, **systematically as soon as a deliverable is generated from a job description** — whatever the flow: full guided flow **OR** creation of a single specific document. The trigger is the **presence of a job description** (so not pure configuration actions: profile, signature, which are tied to no posting). **Title date = date of first activity if the application is already tracked, otherwise today's date** (candidate's local date — same locale resolution as the tracker timestamp) → a **time-stable title** (resuming on another day suggests the same title). It is a **suggestion for the user to apply**: the assistant **cannot rename** the conversation itself. This marker makes it findable in the **sidebar** (on desktop, conversation links aren't clickable). If the conversation already has this name, the suggestion lands correctly — zero cost.
diff --git a/modules/posting-brief-generator/GUIDE.md b/modules/posting-brief-generator/GUIDE.md
new file mode 100644
index 0000000..d3cc5ae
--- /dev/null
+++ b/modules/posting-brief-generator/GUIDE.md
@@ -0,0 +1,162 @@
+> **⚠️ MODULE OF THE "candidate-suite" SUITE — PATH RELOCATION**
+> This module lives in `modules/posting-brief-generator/`. All the relative paths below
+> (`scripts/…`, `references/…`) are relative to THIS folder.
+> From the suite root: prefix them with `modules/posting-brief-generator/`
+> (e.g. `python modules/posting-brief-generator/scripts/