mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-11-02 13:12:50 +00:00
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.
This commit is contained in:
1 parent
895c02191d
commit
760bbc34cf
5 files changed
+65
-2
No files matched your search
@@ -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]*
|
||||
|
||||
---
|
||||
|
||||
@@ -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: <company>_<role>]*. **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.
|
||||
|
||||
@@ -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`,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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/."""
|
||||
|
||||
|
||||
Reference in new issue
Block a user