mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-09-17 00:26:26 +00:00
/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:
co-authored by
Cursor
parent
8d2786118b
commit
d82df2fe51
@@ -18,7 +18,7 @@ If `$ARGUMENTS` is empty or does not contain a recognized scope keyword, ask:
|
|||||||
|
|
||||||
> **What would you like to reset?**
|
> **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.
|
> - **`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/01-candidate-profile.md`
|
||||||
- `.claude/skills/job-application-assistant/02-behavioral-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/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-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:
|
Present as:
|
||||||
|
|
||||||
@@ -54,16 +58,27 @@ Present as:
|
|||||||
- 02-behavioral-profile.md — [has content / already empty]
|
- 02-behavioral-profile.md — [has content / already empty]
|
||||||
Full file will be replaced with a blank template.
|
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]
|
- 05-cv-templates.md — [has profile statements / already blank]
|
||||||
Profile statement templates will be cleared. LaTeX structure and tailoring guidelines are preserved.
|
Profile statement templates will be cleared. LaTeX structure and tailoring guidelines are preserved.
|
||||||
|
|
||||||
- 07-interview-prep.md — [has STAR examples / already blank]
|
- 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.
|
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):
|
The following files are NOT touched (they contain framework rules, not candidate data):
|
||||||
- 03-writing-style.md
|
- 03-writing-style.md
|
||||||
- 04-job-evaluation.md
|
|
||||||
- 06-cover-letter-templates.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`:
|
### If scope includes `documents`:
|
||||||
@@ -163,6 +178,27 @@ Wait for the user's response.
|
|||||||
## Using This in Applications
|
## 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:
|
**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
|
```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).
|
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
|
### 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`.
|
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:
|
Then tell the user what to do next based on what was reset:
|
||||||
|
|
||||||
**If profile 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:**
|
**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.
|
> 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.
|
||||||
|
|||||||
@@ -51,6 +51,28 @@ per-file diff commands.
|
|||||||
|
|
||||||
### Fixed
|
### 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
|
- **`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
|
`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
|
dot and the space or end-of-string that follows it in real company names, so the
|
||||||
|
|||||||
+106
-10
@@ -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/`
|
Both scopes have the same failure mode - /reset promises a clean slate it
|
||||||
folder is now empty." That statement is only true if every personal-data
|
does not deliver, because something that writes personal data is missing
|
||||||
drop folder is actually covered by both the Step 1 preview and the
|
from the Step 1 preview the user confirms and from the Step 3 execution.
|
||||||
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.
|
|
||||||
|
|
||||||
The folder list is derived from the repository tree, so adding a new
|
Documents scope: /reset ends its documents pass by telling the user "The
|
||||||
drop folder under documents/ fails this test until /reset covers it.
|
`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 re
|
||||||
import subprocess
|
import subprocess
|
||||||
@@ -18,6 +34,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
REPO = Path(__file__).resolve().parent.parent
|
REPO = Path(__file__).resolve().parent.parent
|
||||||
RESET = REPO / ".claude" / "commands" / "reset.md"
|
RESET = REPO / ".claude" / "commands" / "reset.md"
|
||||||
|
SETUP = REPO / ".claude" / "commands" / "setup.md"
|
||||||
|
|
||||||
|
|
||||||
def tracked_document_subfolders():
|
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__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user