mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-11-09 00:17:39 +00:00
fix(reset): clear the writing-style patterns /setup infers from past cover letters (#825)
/setup Path A (Step A5) adds a "## Patterns Observed in Past Applications" section to 03-writing-style.md from the user's past cover letters. /reset profile never removed it and its preview listed 03 as framework-only, so inferred candidate data survived a confirmed RESET. Step 1 now reads 03, the preview names the section, Step 3 removes it and leaves the writing rules intact, and the NOT-touched list names 08 and 09 instead. Adds TestResetCoversPathAInferredContent, derived from the Step A5 inference bullets, so the next Path A write-target cannot be missed silently. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
4db9ed7308
commit
2b3f613fda
2 files changed
+82
-2
No files matched your search
@@ -40,13 +40,14 @@ Read the current state of these files and report whether each has content or is
|
||||
|
||||
- `.claude/skills/job-application-assistant/01-candidate-profile.md`
|
||||
- `.claude/skills/job-application-assistant/02-behavioral-profile.md`
|
||||
- `.claude/skills/job-application-assistant/03-writing-style.md` *(any "Patterns Observed in Past Applications" section only — the writing rules are preserved)*
|
||||
- `.claude/skills/job-application-assistant/04-job-evaluation.md` *(personalized match areas, career goals, and life-situation constraints only — the scoring framework is preserved)*
|
||||
- `.claude/skills/job-application-assistant/05-cv-templates.md` *(profile statements section and the contact block inside the LaTeX template only — framework structure is preserved)*
|
||||
- `.claude/skills/job-application-assistant/06-cover-letter-templates.md` *(contact line and signature inside the LaTeX template only — framework structure is preserved)*
|
||||
- `.claude/skills/job-application-assistant/07-interview-prep.md` *(STAR examples and STAR candidates sections only — framework structure is preserved)*
|
||||
- `.claude/skills/job-scraper/search-queries.md` *(role titles, domain keywords, and location terms only — query structure is preserved)*
|
||||
|
||||
This list must stay in step with what `/setup` Step 3 populates: every skill file it writes candidate data into is cleared here.
|
||||
This list must stay in step with what `/setup` writes in Step 3 and infers in Path A's Step A5: every skill file that receives candidate data from either path is cleared here. Known gap: the opening patterns Path A adds to `06-cover-letter-templates.md` are not labelled, so they cannot yet be told apart from the shipped templates and are kept.
|
||||
|
||||
Present as:
|
||||
|
||||
@@ -59,6 +60,10 @@ Present as:
|
||||
- 02-behavioral-profile.md — [has content / already empty]
|
||||
Full file will be replaced with a blank template.
|
||||
|
||||
- 03-writing-style.md — [has observations from past applications / nothing to clear]
|
||||
Any "Patterns Observed in Past Applications" section that /setup drew from your
|
||||
own cover letters will be removed. The writing rules are preserved.
|
||||
|
||||
- 04-job-evaluation.md — [has personalized criteria / already blank]
|
||||
Your match areas, career goals, energizing/draining tasks, and life-situation
|
||||
constraints will be restored to placeholders. The scoring framework (dimensions,
|
||||
@@ -78,7 +83,8 @@ Present as:
|
||||
restored to placeholders. The query structure and filter sections are preserved.
|
||||
|
||||
The following files are NOT touched (they contain framework rules, not candidate data):
|
||||
- 03-writing-style.md
|
||||
- 08-application-forms.md
|
||||
- 09-web-research.md
|
||||
|
||||
Outside the profile scope, still holding your personal data: CLAUDE.md and
|
||||
cv/main_example.tex. This scope covers skill files only.
|
||||
@@ -197,6 +203,8 @@ Every skill file under `.claude/skills/job-application-assistant/` opens with a
|
||||
## Using This in Applications
|
||||
```
|
||||
|
||||
**For `03-writing-style.md`**, remove any `## Patterns Observed in Past Applications` section, which `/setup` Path A writes from the user's own cover letters. The shipped file has no such section, so leave every other section of `03-writing-style.md` intact: its rules are framework, not candidate data.
|
||||
|
||||
**For `04-job-evaluation.md`**, restore the values `/setup` Step 3.4 personalized back to their placeholder tokens, leaving every surrounding line untouched:
|
||||
|
||||
| Line to restore | Token |
|
||||
|
||||
@@ -131,6 +131,78 @@ def setup_step3_skill_files():
|
||||
return files
|
||||
|
||||
|
||||
def setup_path_a_inference_rules():
|
||||
"""(skill file, section headings) pairs /setup Path A's Step A5 writes into.
|
||||
|
||||
Step A5's inference rules are bullets of the form
|
||||
'- **`<file>`** ...: ... under "## <Heading>" ...'. Path A writes from the
|
||||
user's own documents and past applications, outside Step 3's headings, so
|
||||
deriving coverage from Step 3 alone missed these targets.
|
||||
"""
|
||||
a5 = section(SETUP.read_text(encoding="utf-8"), "### Step A5:", "### Step A6:")
|
||||
rules = []
|
||||
for line in a5.splitlines():
|
||||
match = re.match(r"^- \*\*`([^`]+\.md)`", line)
|
||||
if match:
|
||||
rules.append((match.group(1), re.findall(r'"(## [^"]+)"', line)))
|
||||
return rules
|
||||
|
||||
|
||||
class TestResetCoversPathAInferredContent(unittest.TestCase):
|
||||
"""/reset profile must clear what /setup Path A infers from past applications.
|
||||
|
||||
The regression: Path A adds '## Patterns Observed in Past Applications' to
|
||||
03-writing-style.md from the user's own cover letters, while reset.md listed
|
||||
03 as 'NOT touched (they contain framework rules, not candidate data)' - so
|
||||
the user's past phrasing survived a reset that said the file held none.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.text = RESET.read_text(encoding="utf-8")
|
||||
self.rules = setup_path_a_inference_rules()
|
||||
self.files = {name for name, _ in self.rules}
|
||||
# Guard against a vacuous pass if Step A5's bullet format changes.
|
||||
self.assertGreaterEqual(len(self.files), 6, self.rules)
|
||||
self.assertIn("03-writing-style.md", self.files)
|
||||
|
||||
def test_step1_reads_every_path_a_file(self):
|
||||
"""The preview's has-content/nothing-to-clear verdicts come from Step 1's read list."""
|
||||
inventory = section(self.text, "Read the current state of these files", "Present as:")
|
||||
missing = sorted(f for f in self.files if f not in inventory)
|
||||
self.assertEqual(missing, [], f"Step 1 never reads these Path A targets, so their preview line is unchecked: {missing}")
|
||||
|
||||
def test_preview_lists_every_path_a_file(self):
|
||||
preview = section(
|
||||
self.text, "### If scope includes `profile`:", "### If scope includes `documents`:"
|
||||
)
|
||||
listed = section(preview, "## Profile reset will clear:", "The following files are NOT touched")
|
||||
missing = sorted(f for f in self.files if f not in listed)
|
||||
self.assertEqual(missing, [], f"Path A writes into these, but the preview never offers to clear them: {missing}")
|
||||
|
||||
def test_preserved_list_claims_no_path_a_file_is_framework_only(self):
|
||||
preserved = section(self.text, "The following files are NOT touched", "```")
|
||||
mislabeled = sorted(f for f in self.files if f in preserved)
|
||||
self.assertEqual(mislabeled, [], f"listed as framework-only, but Path A writes candidate data into: {mislabeled}")
|
||||
|
||||
def test_execution_removes_every_section_path_a_adds(self):
|
||||
"""Each heading must be named in the Step 3 instruction for its own file.
|
||||
|
||||
An instruction runs from its '**For `<file>`**' line to the next one, so
|
||||
a heading attached to another file's instruction does not count.
|
||||
"""
|
||||
execution = section(self.text, "### Profile reset", "### Documents reset")
|
||||
blocks = {}
|
||||
for match in re.finditer(r"^\*\*For `(?:[^`]*/)?([^`/]+)`\*\*(.*?)(?=^\*\*For `|\Z)", execution, re.S | re.M):
|
||||
blocks[match.group(1)] = blocks.get(match.group(1), "") + match.group(2)
|
||||
missing = sorted(
|
||||
f"{name}: {heading}"
|
||||
for name, headings in self.rules
|
||||
for heading in headings
|
||||
if heading not in blocks.get(name, "")
|
||||
)
|
||||
self.assertEqual(missing, [], f"Path A adds these sections, but no Step 3 instruction for that file removes them: {missing}")
|
||||
|
||||
|
||||
class TestResetCoversEveryPersonalizedSkillFile(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.text = RESET.read_text(encoding="utf-8")
|
||||
|
||||
Reference in new issue
Block a user