fix(reset): clear the two personalized skill files /reset profile missed (#364) (#365)

/setup Step 3 populates six skill files; /reset profile cleared four.
04-job-evaluation.md was listed by name under "files NOT touched (they
contain framework rules, not candidate data)" while Step 3.4 writes the
user's match areas, career goals, energizing/draining tasks, financial
situation and schedule constraints into it - and ci.yml's
placeholder-integrity job already guards that file under "personal data
may have been committed". job-scraper/search-queries.md, which Step 3.8
fills with their job boards, role titles, domain keywords, city and
commute tiers, appeared nowhere in reset.md at all.

Both are tracked and unignored, so Step 1 asked the user to confirm a
wipe list that omitted them and Step 4 then reported a blank profile
while /rank kept scoring against the old skills and career goals and
/scrape kept running the old city and queries. Re-running /setup does
not necessarily clean them either: Path A skips files whose content is
"no longer placeholder text", and Step 3.8 is phrased as token
replacement, with no tokens left to replace.

Both files are now previewed and cleared, restoring their /setup
placeholders while preserving the scoring framework and the query
structure. 04-job-evaluation.md leaves the preserved list, which keeps
03-writing-style.md and 06-cover-letter-templates.md - the latter
correctly, since its [YOUR_NAME] tokens are LaTeX scaffolding Step 3
never writes to. CLAUDE.md and cv/main_example.tex stay outside the
profile scope, which reset.md:13 defines as skill files only; the
preview and Step 4 now say they still hold personal data instead of
implying a full wipe.

tests/test_reset_command.py gains a profile-scope guard beside its
documents-scope one, deriving the file list from /setup Step 3's own
headings rather than hardcoding it, so a future /setup target that
/reset forgets fails in CI. Against master the three cases fail on
exactly the defect: preview missing search-queries.md, execution
missing both, and the preserved list mislabelling 04-job-evaluation.md
as framework-only - the last of which a filename search alone would
have missed.

Closes #364

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Prince chukwuemeka
2026-08-25 16:55:42 +02:00
committed by GitHub
co-authored by Cursor
parent 8d2786118b
commit d82df2fe51
3 changed files with 178 additions and 13 deletions
+50 -3
View File
@@ -18,7 +18,7 @@ If `$ARGUMENTS` is empty or does not contain a recognized scope keyword, ask:
> **What would you like to reset?**
>
> - **`profile`** — Clears candidate data from the skill files (profile, behavioral, STAR examples, profile statements). The framework structure and writing rules are preserved. Use this to re-run `/setup` from scratch.
> - **`profile`** — Clears candidate data from the skill files (profile, behavioral, STAR examples, profile statements, personalized evaluation criteria, search queries). The framework structure, scoring framework, and writing rules are preserved. Use this to re-run `/setup` from scratch.
>
> - **`documents`** — Deletes all files you've placed in the `documents/` folder (CV PDFs, LinkedIn export, diplomas, references, pasted job postings, past applications). The folder structure and `README.md` are preserved.
>
@@ -40,8 +40,12 @@ 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/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 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.
Present as:
@@ -54,16 +58,27 @@ Present as:
- 02-behavioral-profile.md — [has content / already empty]
Full file will be replaced with a blank template.
- 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,
score bands, weights, Language Gate, Company Research Checklist) is preserved.
- 05-cv-templates.md — [has profile statements / already blank]
Profile statement templates will be cleared. LaTeX structure and tailoring guidelines are preserved.
- 07-interview-prep.md — [has STAR examples / already blank]
STAR examples and any STAR candidate stubs will be cleared. Framework, tough questions, and roleplay guidelines are preserved.
- job-scraper/search-queries.md — [has personalized queries / already blank]
Your job boards, role titles, domain keywords, city, and commute tiers will be
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
- 04-job-evaluation.md
- 06-cover-letter-templates.md
Outside the profile scope, still holding your personal data: CLAUDE.md and
cv/main_example.tex. This scope covers skill files only.
```
### If scope includes `documents`:
@@ -163,6 +178,27 @@ Wait for the user's response.
## Using This in Applications
```
**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 |
|---|---|
| `**Strong match areas:**` | `[YOUR_PRIMARY_SKILLS]` |
| `**Moderate match areas:**` | `[YOUR_SECONDARY_SKILLS]` |
| `**Weak match areas:**` | `[SKILLS_YOU_LACK]` |
| `**Strong:**` (Experience Match) | `[YOUR_DIRECT_EXPERIENCE_DOMAINS]` |
| `**Moderate:**` (Experience Match) | `[YOUR_ADJACENT_EXPERIENCE]` |
| `**Entry-level:**` (Experience Match) | `[ROLES_WITH_LIMITED_EXPERIENCE]` |
| the three `**Career goals:**` bullets | `[YOUR_CAREER_GOAL_1]`, `[YOUR_CAREER_GOAL_2]`, `[YOUR_CAREER_GOAL_3]` |
| `- Tasks that energize:` | `[YOUR_ENERGIZING_TASKS]` |
| `- Tasks that drain:` | `[YOUR_DRAINING_TASKS]` |
| `- **Security**:` | `[YOUR_FINANCIAL_SITUATION_CONTEXT]` |
| `- **Flexibility**:` | `[YOUR_SCHEDULE_CONSTRAINTS]` |
| `- **Professional development**:` | `[YOUR_GROWTH_PRIORITIES]` |
Also remove any `## Calibration from Past Applications` section, which `/setup` Path A writes from the user's own application outcomes.
Leave the rest of `04-job-evaluation.md` intact: the five scoring dimensions and their score bands, the weighting, the Language Gate, the red-flag guidance, the Company Research Checklist and cache schema, and the salary benchmark section. If `/setup` Step 3.4 ever personalizes a value not in the table above, add it here too.
**For `05-cv-templates.md`**, locate the section that begins with `**Profile statement templates` and extends through the role-specific template blocks. Replace only that section with:
```markdown
@@ -187,6 +223,15 @@ Replace with:
Leave all other content in `07-interview-prep.md` intact (STAR format explanation, tough questions, questions to ask interviewers, phone/video tips, follow-up etiquette, roleplay guidelines).
**For `.claude/skills/job-scraper/search-queries.md`**, restore the values `/setup` Step 3.8 personalized back to their placeholder tokens:
- **Search Sites**: the board names back to `[YOUR_JOB_BOARD]`, `[YOUR_INDUSTRY_JOB_BOARD]`, `[YOUR_ADDITIONAL_JOB_BOARD]`, and the LinkedIn filter back to `[YOUR_COUNTRY]` / `[YOUR_CITY]`.
- **Query Categories**: the four priority headings back to `[YOUR_PRIMARY_ROLE_TYPE]`, `[YOUR_DOMAIN_EXPERTISE]`, `[YOUR_ADJACENT_ROLE_TYPE]`, and `Broader Technical / Consulting`; inside the query blocks, the titles, skills, and domain terms back to `[YOUR_PRIMARY_JOB_TITLE_1]`, `[YOUR_PRIMARY_JOB_TITLE_2]`, `[YOUR_ADJACENT_TITLE_1]`, `[YOUR_ADJACENT_TITLE_2]`, `[YOUR_KEY_SKILL]`, `[YOUR_DOMAIN_KEYWORD_1]`, `[YOUR_DOMAIN_KEYWORD_2]`, `[YOUR_DOMAIN]`, and the location terms back to `[YOUR_CITY]`, `[YOUR_COUNTRY]`, `[YOUR_REGION]`.
- **Location Filter**: the commute tiers back to `[YOUR_CITY]`, `[ACCEPTABLE_AREA_1]`, `[ACCEPTABLE_AREA_2]`, `[BORDERLINE_AREA]`, `[TOO_FAR_AREA]`.
- Remove any extra priority categories or translated query duplicates `/setup` added beyond the four shipped tiers.
Leave the rest of the file intact: the portal-CLI and WebSearch-fallback explanation, the Language scope note, the "organize by function, not job title" guidance, and the Language, Date, and Adapting Queries sections.
### Documents reset
For each non-empty document subfolder, delete all files within it using Bash `rm`. Do not delete the folder itself, and do not delete `documents/README.md`.
@@ -219,7 +264,9 @@ After the reset is complete, report:
Then tell the user what to do next based on what was reset:
**If profile was reset:**
> Your candidate profile is now blank. Run `/setup` to repopulate it. The command auto-detects any files in your `documents/` folder and offers to read from there; otherwise it walks you through a CV import or interactive interview.
> The skill files are now blank. Run `/setup` to repopulate them. The command auto-detects any files in your `documents/` folder and offers to read from there; otherwise it walks you through a CV import or interactive interview.
>
> Note that `CLAUDE.md` and `cv/main_example.tex` are outside the `profile` scope and still hold your personal data. If you are handing this fork over or making it public, clear them by hand.
**If documents were reset:**
> The `documents/` folder is now empty. Add your career documents and run `/setup` to populate your profile. See `documents/README.md` for instructions on what to put where.
+22
View File
@@ -51,6 +51,28 @@ per-file diff commands.
### Fixed
- **`/reset profile` left candidate data in two of the skill files it claims to clear**
(#364) - `/setup` Step 3 populates six skill files; the profile scope cleared four.
`04-job-evaluation.md` was listed by name under "files NOT touched (they contain
framework rules, not candidate data)" while Step 3.4 writes the user's match areas,
career goals, energizing/draining tasks, financial situation and schedule constraints
into it - and CI's placeholder-integrity job already guards it under "personal data may
have been committed". `job-scraper/search-queries.md`, which Step 3.8 fills with their
job boards, role titles, domain keywords, city and commute tiers, appeared nowhere in
`reset.md` at all. Both are tracked and unignored, so the Step 1 preview asked the user
to confirm a wipe list that omitted them and Step 4 then reported a blank profile while
`/rank` kept scoring against the old skills and career goals and `/scrape` kept running
the old city and queries. Both files are now previewed and cleared, restoring their
`/setup` placeholders while preserving the scoring framework and the query structure;
`04-job-evaluation.md` is out of the preserved list, which keeps `03-writing-style.md`
and `06-cover-letter-templates.md` (correctly - the latter's `[YOUR_NAME]` tokens are
LaTeX scaffolding Step 3 never writes to). `CLAUDE.md` and `cv/main_example.tex` stay
outside the `profile` scope, which covers skill files only, and the preview and Step 4
now say so instead of implying a full wipe. `tests/test_reset_command.py` gains a
profile-scope guard alongside its documents-scope one, deriving the file list from
`/setup` Step 3's own headings so a future `/setup` target that `/reset` forgets fails
in CI; the third case pins that a personalized file is never labelled framework-only,
which a filename search alone would have missed.
- **`salary_lookup.py` never stripped the dotted "A.M.B.A." legal suffix** (#356) - the
`STRIP_PATTERNS` regex ended in `\.\b`, and a word boundary can't sit between a literal
dot and the space or end-of-string that follows it in real company names, so the
+106 -10
View File
@@ -1,15 +1,31 @@
"""Guards for /reset's documents scope.
"""Guards for /reset's two scopes: documents and profile.
/reset ends its documents pass by telling the user "The `documents/`
folder is now empty." That statement is only true if every personal-data
drop folder is actually covered by both the Step 1 preview and the
Step 3 delete block. `documents/postings/` was missing from both while
being documented in documents/README.md and protected as personal data
by tools/security_guards.py (review finding F26, 2026-08-19), so a reset
silently kept the user's hand-pasted job postings.
Both scopes have the same failure mode - /reset promises a clean slate it
does not deliver, because something that writes personal data is missing
from the Step 1 preview the user confirms and from the Step 3 execution.
The folder list is derived from the repository tree, so adding a new
drop folder under documents/ fails this test until /reset covers it.
Documents scope: /reset ends its documents pass by telling the user "The
`documents/` folder is now empty." That statement is only true if every
personal-data drop folder is actually covered by both the Step 1 preview
and the Step 3 delete block. `documents/postings/` was missing from both
while being documented in documents/README.md and protected as personal
data by tools/security_guards.py (review finding F26, 2026-08-19), so a
reset silently kept the user's hand-pasted job postings.
Profile scope: the same class of gap, one scope over. /setup Step 3
populates six skill files, and /reset profile cleared four of them -
`04-job-evaluation.md` (the user's match areas, career goals, financial
situation and schedule constraints) was listed by name as containing
"framework rules, not candidate data", and `job-scraper/search-queries.md`
(their role titles, city and commute tiers) appeared nowhere in reset.md.
Both are tracked and unignored, and CI's placeholder-integrity job guards
04-job-evaluation.md under "personal data may have been committed", so a
"blank" profile left /rank scoring against the old skills and /scrape
running the old city.
Both file lists are derived - the documents folders from the repository
tree, the profile files from /setup Step 3's own headings - so a new drop
folder or a new /setup target fails this test until /reset covers it.
"""
import re
import subprocess
@@ -18,6 +34,7 @@ from pathlib import Path
REPO = Path(__file__).resolve().parent.parent
RESET = REPO / ".claude" / "commands" / "reset.md"
SETUP = REPO / ".claude" / "commands" / "setup.md"
def tracked_document_subfolders():
@@ -68,5 +85,84 @@ class TestResetCoversEveryDocumentsSubfolder(unittest.TestCase):
)
def section(text: str, start: str, end: str) -> str:
"""The slice of text from the start marker up to the end marker."""
begin = text.index(start)
return text[begin : text.index(end, begin)]
def setup_step3_skill_files():
"""Skill files /setup Step 3 populates, derived from its own headings.
Step 3's targets are written as '### <n>. <verb> `<target>`', where the
target is either a bare filename resolved against .claude/skills/ or a
repo-relative path. Non-skill targets (CLAUDE.md, cv/main_example.tex)
are dropped: /reset profile's scope is skill files only.
"""
step3 = section(SETUP.read_text(encoding="utf-8"), "## Step 3:", "## Step 4:")
files = set()
for target in re.findall(r"^###\s+\d+\.\s+\w+\s+`([^`]+)`", step3, re.MULTILINE):
if (REPO / target).exists():
if target.startswith(".claude/skills/"):
files.add(Path(target).name)
continue
matches = list((REPO / ".claude" / "skills").glob(f"*/{target}"))
if matches:
files.add(Path(target).name)
return files
class TestResetCoversEveryPersonalizedSkillFile(unittest.TestCase):
def setUp(self):
self.text = RESET.read_text(encoding="utf-8")
self.files = setup_step3_skill_files()
# /setup must actually still name these targets, or every assertion
# below would pass vacuously against an empty set.
self.assertGreaterEqual(len(self.files), 6, self.files)
self.assertIn("04-job-evaluation.md", self.files)
self.assertIn("search-queries.md", self.files)
def test_preview_lists_every_personalized_skill_file(self):
preview = section(
self.text, "### If scope includes `profile`:", "### If scope includes `documents`:"
)
missing = sorted(f for f in self.files if f not in preview)
self.assertEqual(
missing,
[],
"reset.md's profile preview never mentions these files that /setup "
"Step 3 writes candidate data into, so the user types RESET against "
f"a list that omits them: {missing}",
)
def test_execution_clears_every_personalized_skill_file(self):
execution = section(self.text, "### Profile reset", "### Documents reset")
missing = sorted(f for f in self.files if f not in execution)
self.assertEqual(
missing,
[],
"reset.md's Step 3 profile pass has no instruction for these files, "
'yet the command then reports the skill files are "now blank": '
f"{missing}",
)
def test_preserved_list_claims_no_personalized_file_is_framework_only(self):
"""A file /setup personalizes must never be listed as framework-only.
This is the specific regression: 04-job-evaluation.md was named in the
"NOT touched (they contain framework rules, not candidate data)" list,
so merely searching reset.md for the filename would have found it.
"""
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,
[],
"reset.md tells the user these files contain 'framework rules, not "
"candidate data', but /setup Step 3 writes candidate data into them: "
f"{mislabeled}",
)
if __name__ == "__main__":
unittest.main()