# Changelog All notable changes to this project are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Releases are vetted checkpoints of `master`. If you maintain a personalized fork, prefer updating to a tagged release over pulling raw `master` (see [SETUP.md, section 8](SETUP.md#8-pulling-upstream-updates-into-your-fork)). The `framework_version` markers on methodology files tell you which of your customized files a release touched; `python3 tools/check_upstream_updates.py` lists them with per-file diff commands. ## [Unreleased] ### Added - **One cross-platform command installs every portal CLI: `python3 tools/install_portal_clis.py`** (#540, `README.md` quick start step 2, `SETUP.md` section 3, `tests/test_install_portal_clis.py`) - the README and SETUP.md each carried a PowerShell and a Bash loop over a hand-copied list of the six shipped portals, four copies in all, while CI's `discover-clis` job globs `.agents/skills/*/cli/package.json` precisely so a portal added in a fork needs no list edit. A portal generated by `/add-portal` or borrowed from another fork was therefore skipped by the documented install on every fresh clone of that fork. The script uses CI's glob, installs each CLI independently so one failure does not stop the rest, and skips - with a message naming the reason - any manifest that declares a lifecycle script or `trustedDependencies`, the same set `security_guards.py` forbids in-tree, so a borrowed portal carrying one is read before it runs. `--dry-run` lists what it would install. ### Fixed - **CV template guidance aligns geometry scale with master template** (#553, `.claude/skills/job-application-assistant/05-cv-templates.md`, `tests/test_latex_guidance.py`) - the preamble snippet in `05-cv-templates.md` specified `\usepackage[scale=0.77]{geometry}`, whereas the master reference template `cv/main_example.tex` configures `scale=0.80`. The guide has been aligned to `scale=0.80`, preventing tighter margins and unwanted page overflow during CV generation, and a test now guards that the two files stay in sync. - **`.claude/settings.local.json` is ignored in `.gitignore` and pinned in security guards** (#551, `.gitignore`, `tools/security_guards.py`) - Claude Code stores machine-local settings and ad-hoc tool approvals in `.claude/settings.local.json`. The file is now excluded from version control in `.gitignore` and pinned in `REQUIRED_IGNORE_RULES` to prevent fork users from accidentally committing local session configurations. - **`/rank` retires scored jobs whose preserved deadline has passed** (`tools/rank_state.py`, `tests/test_rank_state.py`) - a fresh `null` deadline preserves the stored date, but `apply` previously wrote the job as `ranked` even after that date had passed. The later sweep excludes re-scored keys, so it could not correct the state or shortlist. Apply now expires the job before changing rank metadata; current-day deadlines, explicit future extensions, invalid dates, dry runs, and scoring errors keep their behavior. - **`/reset profile` keeps the `framework_version` frontmatter on the two files it rewrites** (`.claude/commands/reset.md` Step 3, `tests/test_reset_command.py`) - the full-file skeletons for `01-candidate-profile.md` and `02-behavioral-profile.md` started at the `#` title, while the shipped files open with a `---` block carrying `framework_version`. Applied as written, one `/reset profile` left both files failing `tools/check_framework_version.py` ("missing 'framework_version' in frontmatter") and invisible to `tools/check_upstream_updates.py`, which records a configuration error for a file with no local version to compare - the fork update path the README sends every fork through. The step now says to keep the frontmatter block and replace only what follows, with a note under "Profile reset" that the marker is framework data, never candidate data; the version is not hardcoded into the skeleton, where it would go stale on the next bump. A derived test applies each skeleton the way the step says and asserts the shipped file's version still parses out unchanged; it fails on master. Found while re-checking #519's class of drift. - **`/reset profile` now targets the headings the shipped skill files actually have** (`.claude/commands/reset.md` Step 3, `tests/test_reset_command.py`) - two of its Step 3 targets had drifted. The `05-cv-templates.md` step anchored on a section beginning `**Profile statement templates`, a line the guide has never contained (its heading is `**Create 2-3 profile statement templates for your main role types:**`), so the wipe found nothing: the user's role-tailored profile statements, and any `*[Used for: ...]*` statements `/setup` Path A extracted from archived applications, survived a reset that reported success. The `02-behavioral-profile.md` skeleton used headings the shipped file does not have (`Strongest Behavioral Traits`, `How I Work Best`, a truncated `Growth Areas`) and dropped `## Core Behavioral Drives` - the assessment table the shipped file carries - so one reset left the file's headings permanently different from every other fork's, with nothing to recreate the table. The anchor now names the real heading and the replacement re-emits it; the skeleton carries the shipped headings in order. `01-candidate-profile.md` and `07-interview-prep.md` were already correct. Two new tests derive both checks from the shipped files, so a renamed heading fails until `/reset` follows it; both fail on master. - **The `/apply` host-check test derives its installed-portal hosts from the skills tree instead of a hand-copied set** (#497, `tests/test_apply_host_check.py`) - `SHIPPED_PORTAL_HOSTS` was six names typed into the test and nothing tied it to anything: a portal added by `/add-portal` fetches from a host the copy never heard of, so its postings reached `/apply` classified "unverified" while the suite stayed green, and a dropped portal left a stale name behind. Rule 1 in `apply.md` defines an installed portal as "any configured job portal in `.agents/skills/`", so that tree is now the source: the host literals in each `*-search/cli/src/**/*.ts` (one `BASE_URL` per shipped CLI, `www.` dropped so apex and subdomain both match), discovery by installation rather than by the `enabled:` flag because an installed-but-disabled board is still a legitimate source for a pasted posting. `classify_posting_host`'s default is that discovered set, and a new test class asserts that every installed portal has a discoverable host and that each host classifies as `installed_portal` at the apex, under `www.` and under a subdomain. The ATS apex list stays the fixed set it is; parsing `apply.md`'s example list was deliberately not done, since a parser keyed on prose wording is the next thing to go stale. Reported by Holo-Eter. - **`/expand` and `/setup` now append behavioral findings to sections `02-behavioral-profile.md` actually has** (`.claude/commands/expand.md` Step 5, `.claude/commands/setup.md` Step A7, `tests/test_expand_command.py`, `tests/test_setup_command.py`) - both told the model to add to "Strongest Behavioral Traits" and "How I Work Best" / "How [Candidate] Works Best" while also saying "match existing structure", but the shipped file's headings are "Strongest Behaviors" and "How You Work Best", so an inferred trait either opened a duplicate near-identical section or was dropped. The companion to #519, which fixed the same drift in `/reset`'s skeleton; the maintainer asked for this half as its own PR. Both bullets now name the shipped headings, and a derived test in each command's suite reads the quoted section names and requires each to be a `##` heading of the shipped file; both fail on master. - **`jobnet-search` and `jobdanmark-search` search results now carry the contract's `id` field** (`search.ts` and `search-normalization.test.ts` in both CLIs, both `SKILL.md`s) - `/add-portal`'s contract says every search result has at least `id`, `title`, `company`, `location`, `date`, `url`, and the four other shipped CLIs emit `id`. When #340 added the `company`/`location`/`date`/`url` aliases to these two, `id` was left out: jobnet exposed the value only as `jobAdId`, jobdanmark only as `slug`, so a consumer reading every portal's JSON through one shape had to special-case both to call `detail`. Purely additive: `id` equals `jobAdId` on jobnet and `slug` on jobdanmark, the native keys stay, nothing is renamed. The additive-contract test in each CLI now asserts `id` and its equality with the native key; both fail on master. - **`jobdanmark-search detail` renders a JSON-LD description as text instead of passing the markup through** (`.agents/skills/jobdanmark-search/cli/src/commands/detail.ts`, `tests/detail-jsonld.test.ts`) - the JSON-LD branch emitted `jobPosting.description` verbatim, so `detail --format plain` printed `description:

Vi søger en udvikler til R&D.