v1.5.0

ai-job-search-dev/ai-job-searchv1.5.0Aug 12, 2026by MadsLorentzen

Upgrading?

AI Summary

Focuses on fork maintainability and security. Forks can now see what upstream did, and security guards are hardened to prevent malicious hooks.

Key Highlights

  • tools/upstream_triage.py helps forks review upstream changes.
  • .claude/settings.json hooks are now held to a reviewed allowlist.
  • Tracker status vocabulary is unified across readers and writers.
  • /apply archives the posting while it still holds it.

Breaking Changes

  • Status spellings are now standardized (e.g., no_response) if writing custom status.
  • .env file can no longer be committed.

New Features

  • CI discovers portal CLIs dynamically.
  • /upskill reports gitignored paths correctly.
  • /add-portal credential spec added for paid services.
  • Portal CLIs use honest User-Agents.

Full Release Notes

Two themes this release: **your fork now finds out what upstream did**, and **the security guard watches the one key in `settings.json` it used to ignore**.

## Highlights

### Commit-level upstream triage for forks (#305, #320)
`tools/upstream_triage.py` walks the commits your fork is behind and sorts them into "worth reviewing" vs "probably skip". Cherry-picks you already ported drop off on their own (matched by `git patch-id`, no bookkeeping), commits touching files you removed are set aside, and a flat `.github/upstream-wontport.txt` keeps declined SHAs from resurfacing. It ships with a weekly `.github/workflows/upstream-watch.yml` that posts the report to a rolling issue in *your own fork* - guarded to no-op on the upstream template (pinned by a test), `GITHUB_TOKEN` only, and report-only by design: it prints ready-to-run `git cherry-pick` lines but never merges or opens a PR, because on a fork "applies cleanly" isn't "correct". It's the commit-history companion to `check_upstream_updates.py`'s version stamps; SETUP.md 8 now introduces the two side by side. Thanks @anjolok1997 - from design discussion (#305) to shipped PR (#320).

### `.claude/settings.json` hooks are now held to a reviewed allowlist (#313)
The security guard read `permissions.allow` and nothing else, so a `hooks` block in the same file passed silently. A hook is strictly more dangerous than a pre-approved permission: it runs unconditionally when its event fires, with no prompt and no model decision in between - the exact vector the Shai-Hulud worm's August 2026 wave used, planting a `SessionStart` hook in `.claude/settings.json`. `ALLOWED_HOOKS` ships empty, the check runs before the permissions shape guards so a malformed block can't skip it, and unrecognised hook layouts fail closed. Thanks @Haseeb-1698.

### One tracker status vocabulary, defined once (#298, #299)
The tracker CSV's `status` column had six independent restatements with inconsistent spellings, and two real bugs to show for it: `/html-report` silently dropped `no response`/`offer declined` rows from the rejection-rate denominator, and `/gmail-sync` kept chasing applications that were already closed. `/outcome` (the only writer) now owns the canonical set; every reader references it and accepts both spellings on read. Diagnosis @jakob1379, implementation @oscarbol09.

### `/apply` archives the posting while it still holds it (#306, #307)
The full posting text now lands in `documents/applications/<company>_<role>/job_posting.md` at draft time - never a re-fetch of a URL the spec itself expects to die, and pasted-text postings finally have a recovery path. Diagnosis and implementation @jakob1379.

## Also in this release

- **CI discovers portal CLIs dynamically** (#310) - a portal added with `/add-portal` gets its typecheck and tests run by CI automatically, on this repo and on any fork, no workflow edit. Thanks @ayobamiseun.
- **`/upskill` reports gitignored at the path the skill actually writes them** (#317) - the old rule was rooted; the report (your skill gaps, measured against named employers) could land unignored one directory deeper. New rule pins the report-file prefix at both depths, guard-enforced. Thanks @ayobamiseun.
- **Phantom `evaluated` status dropped from `seen_jobs.json`'s documented vocabulary** (#315, #316) - it never had a writer or a reader. Thanks @ayobamiseun.
- **`/add-portal` credential spec** (#304) - if a portal is only reachable through a paid fetching service, the command now surfaces the billing *before* scaffolding, requires the token env-only (`<SERVICE>_API_TOKEN`), and keeps the whole path explicitly subordinate to the robots/ToS decision. Spec only; no shipped portal needs a credential. Thanks @Haseeb-1698.
- **`/add-portal`'s fetching contract line now states the honest-UA posture** - generated skills default to `Mozilla/5.0 (compatible; <portal>-cli/1.0)`, the convention every shipped CLI follows.

## Updating a personalized fork

Versioned files touched since v1.4.0:

| File | v1.4.0 | v1.5.0 |
|---|---|---|
| `job-application-assistant/SKILL.md` | 1.3.0 | 1.3.2 |

Run `python3 tools/check_upstream_updates.py` for per-file diffs - and from this release, `python3 tools/upstream_triage.py` for the commit-level view.

Three things to check after pulling:

1. **Status spellings** - if your personalized `/outcome` writes `no response` or `offer declined` (space forms), switch the write path to `no_response`/`offer_declined`. Existing rows keep working; every reader now accepts both forms on read.
2. **`upstream-watch.yml` arrives in-tree** - it no-ops on the upstream template and only runs on your fork if you enable Actions, so activation stays a deliberate act. Don't want it? Delete the file or add its SHA to `.github/upstream-wontport.txt`.
3. **New required gitignore rule** - `**/upskill/report-*.md` is now in `REQUIRED_IGNORE_RULES`; if your fork trimmed `.gitignore`, `security_guards.py` will name what's missing.

## Thanks

@anjolok1997, @Haseeb-1698, @ayobamiseun, @jakob1379, @oscarbol09 - five contributors, and every item above started as a community issue, discussion, or PR.

Related Repositories