From 760bbc34cf99d28b3c45f2c665a6c35e97115a0f Mon Sep 17 00:00:00 2001 From: Ayobami Adegoke Date: Thu, 8 Oct 2026 20:18:08 +0100 Subject: [PATCH] fix(expand,setup): append behavioral findings to sections 02-behavioral-profile.md actually has (#520) Both commands told the model to add to "Strongest Behavioral Traits" and "How I Work Best" / "How [Candidate] Works Best" while also saying "match existing structure", but the shipped file's headings are "Strongest Behaviors" and "How You Work Best", so an inferred trait either opened a duplicate near-identical section or was dropped. The companion to #519, which fixed the same drift in /reset's skeleton. Both bullets now name the shipped headings, and a derived test in each command's suite reads the quoted section names and requires each to be a ## heading of the shipped file; both fail on master. --- .claude/commands/expand.md | 2 +- .claude/commands/setup.md | 2 +- CHANGELOG.md | 11 +++++++++++ tests/test_expand_command.py | 22 ++++++++++++++++++++++ tests/test_setup_command.py | 30 ++++++++++++++++++++++++++++++ 5 files changed, 65 insertions(+), 2 deletions(-) diff --git a/.claude/commands/expand.md b/.claude/commands/expand.md index 46435606..804e83e6 100644 --- a/.claude/commands/expand.md +++ b/.claude/commands/expand.md @@ -189,7 +189,7 @@ Apply only the confirmed items. Use the Edit tool to add to the relevant section For each addition, add a brief source annotation in a comment or parenthetical: *(Coursera — Deep Learning Specialisation)*, *(GitHub — project-name)*, etc. This makes future `/expand` runs idempotent. ### Additions to `02-behavioral-profile.md` -- Soft/behavioral signals → append to the "Strongest Behavioral Traits" or "How I Work Best" section (match existing structure) +- Soft/behavioral signals → append to the "Strongest Behaviors" or "How You Work Best" section (the shipped file's `##` headings - match existing structure) - Always label inferred behavioral additions: *[Inferred from reference letter — Name / review before relying on this]* --- diff --git a/.claude/commands/setup.md b/.claude/commands/setup.md index 34dcbcd0..b305f729 100644 --- a/.claude/commands/setup.md +++ b/.claude/commands/setup.md @@ -167,7 +167,7 @@ For each skill file, compare extracted document content against the current file **Inference rules** (apply when populating from inferred sources): - **`01-candidate-profile.md` (`## Independent Projects`):** Source is `projects/` documents. Extract structured project entries formatted as `- **[PROJECT_NAME]**: [DESCRIPTION with tech stack and measurable outcome]`. Ground all claims in the document text. -- **`02-behavioral-profile.md`:** Source is LinkedIn About + recommendation letters. Extract recurring themes, adjectives, phrases about how the candidate works. Add only to "Strongest Behavioral Traits", "How [Candidate] Works Best", or "Management Style Preferences" sections. Do not overwrite existing scored assessments. Always label inferred additions: *[Inferred from LinkedIn About / Reference letter - review before relying on this]* +- **`02-behavioral-profile.md`:** Source is LinkedIn About + recommendation letters. Extract recurring themes, adjectives, phrases about how the candidate works. Add only to the "Strongest Behaviors", "How You Work Best", or "Management Style Preferences" sections (the shipped file's `##` headings). Do not overwrite existing scored assessments. Always label inferred additions: *[Inferred from LinkedIn About / Reference letter - review before relying on this]* - **`03-writing-style.md`:** Source is `cover_letter.tex` files. Extract recurring patterns. Add as observations under "## Patterns Observed in Past Applications". Do not modify existing rules. Only add if 2+ cover letters show a genuine pattern. - **`04-job-evaluation.md`:** Source is `job_posting.md` + `outcome.md` pairs. If an application reached interview or offer: note role type and sector as a confirmed strong-fit signal. If 2+ applications repeat a no-response or rejection pattern: note it. Add findings under "## Calibration from Past Applications". Do not modify the existing scoring framework. - **`05-cv-templates.md`:** Source is `cv_draft.tex` files. Extract any profile statement that does not already appear in templates. Label with: *[Used for: _]*. **Ground before extracting:** archived drafts are tailored outputs, not source documents - verify every factual claim in an extracted statement (titles, employers, metrics, technologies) against `01-candidate-profile.md` and drop or correct any claim the profile does not support, keeping only the framing. A tailored draft that drifted must never become a template future applications start from. diff --git a/CHANGELOG.md b/CHANGELOG.md index 38485dfa..957569b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,17 @@ per-file diff commands. ### Fixed +- **`/expand` and `/setup` now append behavioral findings to sections `02-behavioral-profile.md` + actually has** (`.claude/commands/expand.md` Step 5, `.claude/commands/setup.md` Step A7, + `tests/test_expand_command.py`, `tests/test_setup_command.py`) - both told the model to add + to "Strongest Behavioral Traits" and "How I Work Best" / "How [Candidate] Works Best" while + also saying "match existing structure", but the shipped file's headings are "Strongest + Behaviors" and "How You Work Best", so an inferred trait either opened a duplicate + near-identical section or was dropped. The companion to #519, which fixed the same drift in + `/reset`'s skeleton; the maintainer asked for this half as its own PR. Both bullets now name + the shipped headings, and a derived test in each command's suite reads the quoted section + names and requires each to be a `##` heading of the shipped file; both fail on master. + - **`jobnet-search` and `jobdanmark-search` search results now carry the contract's `id` field** (`search.ts` and `search-normalization.test.ts` in both CLIs, both `SKILL.md`s) - `/add-portal`'s contract says every search result has at least `id`, `title`, `company`, diff --git a/tests/test_expand_command.py b/tests/test_expand_command.py index 2413fdd5..4456f7b3 100644 --- a/tests/test_expand_command.py +++ b/tests/test_expand_command.py @@ -1,10 +1,12 @@ """Tests for the /expand command specification.""" +import re import unittest from pathlib import Path REPO_ROOT = Path(__file__).resolve().parent.parent EXPAND_COMMAND_FILE = REPO_ROOT / ".claude" / "commands" / "expand.md" +BEHAVIORAL_PROFILE = REPO_ROOT / ".claude" / "skills" / "job-application-assistant" / "02-behavioral-profile.md" class ExpandCommandTests(unittest.TestCase): @@ -39,6 +41,26 @@ class ExpandCommandTests(unittest.TestCase): self.assertIn("Portfolio & projects grounded in code", text) self.assertNotIn("documents/projects/", text) + def test_behavioral_additions_name_sections_the_shipped_file_has(self): + # Step 5 told the model to append to "Strongest Behavioral Traits" or + # "How I Work Best" and to "match existing structure" - but the shipped + # 02-behavioral-profile.md has neither heading (it has "Strongest + # Behaviors" and "How You Work Best"), so the addition either landed + # in a duplicate near-identical section or was dropped. Derived from + # the shipped file so a renamed heading fails here until /expand follows. + text = EXPAND_COMMAND_FILE.read_text(encoding="utf-8") + start = text.index("### Additions to `02-behavioral-profile.md`") + section = text[start: text.index("---", start)] + quoted = re.findall(r'"([^"]+)"', section) + self.assertTrue(quoted, "the 02-behavioral-profile step must name its target sections") + headings = { + line[3:].strip() for line in BEHAVIORAL_PROFILE.read_text(encoding="utf-8").splitlines() + if line.startswith("## ") + } + for name in quoted: + with self.subTest(section=name): + self.assertIn(name, headings, f"02-behavioral-profile.md has no '## {name}' heading") + def test_expand_enforces_additive_and_confirmation_principles(self): text = EXPAND_COMMAND_FILE.read_text(encoding="utf-8") self.assertIn("Additive only", text) diff --git a/tests/test_setup_command.py b/tests/test_setup_command.py index 902d2157..2f5deefe 100644 --- a/tests/test_setup_command.py +++ b/tests/test_setup_command.py @@ -10,6 +10,7 @@ on the drafter noticing. A real user (#420) ran `/setup` and then hand-edited bo files to close the gap. """ import os +import re import unittest from pathlib import Path @@ -90,6 +91,35 @@ class TemplatesStillCarryThePlaceholders(unittest.TestCase): self.assertIn("\\signature{[YOUR_NAME]}", text) +class SetupPathAWritesIntoSectionsTheShippedFilesHave(unittest.TestCase): + """Step A7 named 02-behavioral-profile.md sections that do not exist. + + "Strongest Behavioral Traits" and "How [Candidate] Works Best" are not + headings in the shipped file ("Strongest Behaviors", "How You Work Best" + are), so the "add only to these sections" rule pointed at nothing. The + section names are read from the bullet and checked against the file's + `##` headings, with the placeholder-free spelling required: a reader + cannot match "[Candidate]" against a heading. + """ + + def test_behavioral_profile_bullet_names_real_headings(self): + text = COMMAND.read_text(encoding="utf-8") + bullet = next( + line for line in text.splitlines() + if line.startswith("- **`02-behavioral-profile.md`:**") + ) + quoted = re.findall(r'"([^"]+)"', bullet) + self.assertTrue(quoted, "the 02-behavioral-profile bullet must name its target sections") + headings = { + line[3:].strip() + for line in (SKILL_DIR / "02-behavioral-profile.md").read_text(encoding="utf-8").splitlines() + if line.startswith("## ") + } + for name in quoted: + with self.subTest(section=name): + self.assertIn(name, headings, f"02-behavioral-profile.md has no '## {name}' heading") + + class SetupPathAProjectsIngestion(unittest.TestCase): """Guards for /setup Path A document ingestion of documents/projects/."""