mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-09-17 00:26:26 +00:00
fix(apply): run the page-count check Step 5b claimed Step 5d already ran (#476)
Step 5b's prose said "Page count is not checked here - that is verify_pdf.py --pages's job, and Step 5d already runs it", and verify_layout.py's docstring declines to measure page count for the same reason. Step 5d's only verify_pdf.py call is --dump-text, and no step in the workflow passed --pages at all (only the upstream-only CI assertion on the stock examples does), so the hard 2-page CV and 1-page cover letter limits were enforced by nothing but the visual PDF read - the "measure first, then look" failure 5b was written to stop. 5b now runs verify_pdf.py --pages 2 on the CV and --pages 1 on the cover letter ahead of verify_layout.py, names the ACTIVE-TEMPLATE page limit as the substitute for a custom template, and the deferral sentence points at those lines instead of at 5d. tests/test_apply_page_count.py pins the invocations, their counts, their order relative to the layout measurement, and that no prose defers the check to a step that does not run it; all four cases fail on master.
This commit is contained in:
@@ -248,11 +248,15 @@ If either compile fails, fix the error and re-compile until clean.
|
||||
**Measure first, then look.** A visual read catches gross breakage but cannot tell you that a page is 40% empty, and the failure below survives both a clean compile and a correct page count:
|
||||
|
||||
```bash
|
||||
python tools/verify_pdf.py cv/main_<company>_<role>.pdf --pages 2
|
||||
python tools/verify_pdf.py cover_letters/cover_<company>_<role>.pdf --pages 1
|
||||
python tools/verify_layout.py cv/main_<company>_<role>.pdf
|
||||
python tools/verify_layout.py cover_letters/cover_<company>_<role>.pdf
|
||||
```
|
||||
|
||||
The script reports, per page, where the text starts and stops, bottom whitespace as a share of page height, and the largest vertical gap between lines. It exits 1 on: a hole over 100pt (~7 blank lines), a non-final page ending more than 25% early, body text colliding with the page-number footer, a final page more than 35% empty, and an entry header or section heading stranded at a page break. Page count is **not** checked here — that is `verify_pdf.py --pages`'s job, and Step 5d already runs it.
|
||||
The two `--pages` lines are the page-count check: exactly 2 pages for the CV and exactly 1 for the cover letter (the hard limits in `05-cv-templates.md` and `06-cover-letter-templates.md`), exit 1 otherwise. With a custom template active, substitute its declared **Page limit** from the `ACTIVE-TEMPLATE` block. Nothing else runs this check - `verify_layout.py` deliberately leaves page count to it, and Step 5d's extraction call passes no `--pages` - so if these lines are skipped, the page budget is enforced by nothing but the visual read below.
|
||||
|
||||
The layout script reports, per page, where the text starts and stops, bottom whitespace as a share of page height, and the largest vertical gap between lines. It exits 1 on: a hole over 100pt (~7 blank lines), a non-final page ending more than 25% early, body text colliding with the page-number footer, a final page more than 35% empty, and an entry header or section heading stranded at a page break. Page count is **not** checked here — that is `verify_pdf.py --pages`'s job, and the two `--pages` lines above run it.
|
||||
|
||||
The hole check is the one a visual read misses. A moderncv `\cventry` renders as a `tabular`, so it is an **unbreakable block**: when it does not fit in the space left, the whole entry jumps to the next page and leaves a hole behind, while the document still compiles and still reports the right page count. Fix it by shortening the entry that follows the hole, not by stretching the page.
|
||||
|
||||
|
||||
@@ -62,6 +62,20 @@ per-file diff commands.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **`/apply` Step 5b now actually runs the page-count check it claimed Step 5d ran**
|
||||
(`.claude/commands/apply.md`, `tests/test_apply_page_count.py`) - the 5b prose said
|
||||
"Page count is not checked here - that is `verify_pdf.py --pages`'s job, and Step 5d already
|
||||
runs it", and `verify_layout.py`'s docstring declines to measure page count for the same
|
||||
reason. Step 5d's only `verify_pdf.py` call is `--dump-text`, and no step in the workflow
|
||||
passed `--pages` at all (only the upstream-only CI assertion on the stock examples does),
|
||||
so the hard 2-page CV and 1-page cover letter limits were enforced by nothing but the
|
||||
visual PDF read - the "measure first, then look" failure 5b was written to stop. 5b now
|
||||
runs `verify_pdf.py --pages 2` on the CV and `--pages 1` on the cover letter ahead of
|
||||
`verify_layout.py`, names the `ACTIVE-TEMPLATE` page limit as the substitute for a custom
|
||||
template, and the deferral sentence points at those lines. Four spec tests pin the
|
||||
invocations, their counts, their order relative to the layout measurement, and that no
|
||||
prose defers the check to a step that does not run it; all four fail on master.
|
||||
|
||||
- **`salary_lookup.py` prints the privacy footnote only when a row actually carries `N/A*`**
|
||||
- the `* N/A = Too few employees to publish (privacy)` line was appended under every
|
||||
category table, including one where every row has an index, so the output asserted a
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
"""Guard for /apply Step 5b's page-count check.
|
||||
|
||||
The 2-page CV and 1-page cover letter limits are the hard rules of
|
||||
05-cv-templates.md and 06-cover-letter-templates.md, and two places defer
|
||||
their enforcement to `tools/verify_pdf.py --pages`: `verify_layout.py`'s
|
||||
docstring ("page count is verify_pdf.py's job, and CI runs it") and Step 5b's
|
||||
own prose, which used to say "Step 5d already runs it". Step 5d's only
|
||||
invocation is `--dump-text`, and no other step passed `--pages` at all, so
|
||||
the one rule with a mechanical check had zero runnable implementations in
|
||||
the workflow. These tests pin that the invocations exist where the prose says
|
||||
they do, with the counts the guides require, and that no step defers the
|
||||
check to another step that does not run it.
|
||||
"""
|
||||
import re
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path(__file__).resolve().parent.parent
|
||||
APPLY = REPO / ".claude" / "commands" / "apply.md"
|
||||
VERIFY_LAYOUT = REPO / "tools" / "verify_layout.py"
|
||||
|
||||
|
||||
def section(path, heading):
|
||||
"""The body of one markdown section, up to the next heading of any depth."""
|
||||
text = path.read_text(encoding="utf-8")
|
||||
start = text.index(heading) + len(heading)
|
||||
rest = text[start:]
|
||||
end = re.search(r"^#{1,4} ", rest, re.MULTILINE)
|
||||
return rest[: end.start()] if end else rest
|
||||
|
||||
|
||||
def page_count_invocations(text):
|
||||
"""(document path, page count) for every runnable verify_pdf --pages line."""
|
||||
return re.findall(
|
||||
r"^python tools/verify_pdf\.py (\S+) --pages (\d+)\s*$", text, re.MULTILINE
|
||||
)
|
||||
|
||||
|
||||
class ApplyRunsThePageCountCheck(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.step_5b = section(APPLY, "### 5b. Inspect layout")
|
||||
|
||||
def test_step_5b_checks_both_documents_with_the_guides_page_limits(self):
|
||||
invocations = dict(page_count_invocations(self.step_5b))
|
||||
self.assertEqual(
|
||||
invocations.get("cv/main_<company>_<role>.pdf"),
|
||||
"2",
|
||||
"Step 5b must run verify_pdf.py --pages 2 on the CV - the hard "
|
||||
"2-page limit has no other mechanical check",
|
||||
)
|
||||
self.assertEqual(
|
||||
invocations.get("cover_letters/cover_<company>_<role>.pdf"),
|
||||
"1",
|
||||
"Step 5b must run verify_pdf.py --pages 1 on the cover letter",
|
||||
)
|
||||
|
||||
def test_page_count_runs_before_the_layout_measurement(self):
|
||||
# verify_layout.py's own docstring declines to check page count because
|
||||
# verify_pdf.py --pages does; the deferral only holds if that runs first.
|
||||
first_pages = self.step_5b.index("--pages")
|
||||
first_layout = self.step_5b.index("verify_layout.py")
|
||||
self.assertLess(first_pages, first_layout)
|
||||
|
||||
def test_no_step_defers_the_check_to_a_step_that_does_not_run_it(self):
|
||||
text = APPLY.read_text(encoding="utf-8")
|
||||
self.assertNotIn(
|
||||
"Step 5d already runs it",
|
||||
text,
|
||||
"Step 5d's only verify_pdf call is --dump-text; the page-count "
|
||||
"invocation lives in 5b and the prose must point there",
|
||||
)
|
||||
|
||||
def test_layout_tools_deferral_is_backed_by_a_runnable_invocation(self):
|
||||
docstring = VERIFY_LAYOUT.read_text(encoding="utf-8")
|
||||
self.assertIn("verify_pdf.py --pages", docstring)
|
||||
self.assertGreaterEqual(
|
||||
len(page_count_invocations(APPLY.read_text(encoding="utf-8"))),
|
||||
2,
|
||||
"verify_layout.py defers page count to verify_pdf.py --pages, so "
|
||||
"/apply must actually invoke it",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user