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:
DivyaandClaude Fable 5.1 authored and GitHub committed 2026-11-08 19:34:38 +01:00
1 parent 4db9ed7308
commit 2b3f613fda
2 files changed
+82 -2

No files matched your search

+10 -2
View File
@@ -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 |
+72
View File
@@ -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")