diff --git a/.claude/commands/reset.md b/.claude/commands/reset.md index 55d99951..c308853c 100644 --- a/.claude/commands/reset.md +++ b/.claude/commands/reset.md @@ -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 | diff --git a/tests/test_reset_command.py b/tests/test_reset_command.py index aa1326eb..387015d5 100644 --- a/tests/test_reset_command.py +++ b/tests/test_reset_command.py @@ -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 + '- **``** ...: ... under "## " ...'. 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 ``**' 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")