chore: point every in-tree reference at ai-job-search-dev/ai-job-search (#776)

The repository is moving from the MadsLorentzen personal account to the
ai-job-search-dev organization. GitHub redirects the old slug, but the
upstream-watch guard compares github.repository against the literal slug
and would start running the fork-only triage job on upstream itself, and
the upstream-check tool and its tests pin the slug too. README, CONTRIBUTING,
SECURITY, SETUP, the CI workflow, the issue-template config and the
CHANGELOG links follow.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Mads LorentzenandClaude Fable 5.1 authored and GitHub committed 2026-10-26 14:19:53 +01:00
1 parent e16acee3ab
commit 7f9edf54c1
18 files changed
+94 -86

No files matched your search

@@ -23,7 +23,7 @@ export function writeError(error: string, code: string): void {
// cli/src, so a github.com URL here made every github.com link classify as an
// installed job board instead of an unverified host. The owner/project name
// already identifies the repo on GitHub.
const UA = "MadsLorentzen/ai-job-search/1.0 (freehire-search)"
const UA = "ai-job-search-dev/ai-job-search/1.0 (freehire-search)"
/** The shared API response envelope: {data, meta, error}. */
export interface Envelope<T> {
@@ -138,7 +138,7 @@ points users to (`?q=<role>` scopes the counts). Example:
serves HTML, which `cleanHtml` (`cli/src/helpers.ts`) strips into readable text.
Search descriptions arrive already rendered by the API and are passed through
verbatim — stripping them again would undo the Markdown structure.
- Fetch sends `User-Agent: MadsLorentzen/ai-job-search/1.0 (freehire-search)` (no contact
- Fetch sends `User-Agent: ai-job-search-dev/ai-job-search/1.0 (freehire-search)` (no contact
URL: a host literal in `cli/src` makes `/apply` treat that host as this portal's board)
and `Accept: application/json`, and uses exponential backoff with jitter on 429/5xx (max 6 retries). A connection error (API
unreachable) fails fast with a clear message — no retry, since it is not
@@ -2,7 +2,7 @@
`detail-orbis-excerpt.html` contains verbatim excerpts from the guest response for
posting `4345841651`, captured by **portapulito** through this CLI's `htmlFetch`
on 2026-07-14 and published in [upstream PR #153](https://github.com/MadsLorentzen/ai-job-search/pull/153).
on 2026-07-14 and published in [upstream PR #153](https://github.com/ai-job-search-dev/ai-job-search/pull/153).
That PR was closed because it was accidentally submitted to upstream instead of
the author's personalized fork; it was not merged.
@@ -2,7 +2,7 @@
`search-cards.json` stores the HTML string for the first two verbatim `<li>` cards from a real
LinkedIn guest search response captured by **portapulito** through the CLI's
`htmlFetch` on 2026-07-14 and published in [PR #153](https://github.com/MadsLorentzen/ai-job-search/pull/153).
`htmlFetch` on 2026-07-14 and published in [PR #153](https://github.com/ai-job-search-dev/ai-job-search/pull/153).
That PR was closed as an accidental personalized-fork submission, not merged.
Source: `.agents/skills/linkedin-search/cli/test/__fixtures__/search-list.html`
+1 -1
View File
@@ -1,7 +1,7 @@
blank_issues_enabled: true
contact_links:
- name: Filing from a personalized fork? Read this first
url: https://github.com/MadsLorentzen/ai-job-search/blob/master/SETUP.md#2-fork-and-clone
url: https://github.com/ai-job-search-dev/ai-job-search/blob/master/SETUP.md#2-fork-and-clone
about: >-
The gh CLI in a fork clone targets THIS public repo by default. Personal
application tracking, evaluations, and incident logs belong in your own
+3 -3
View File
@@ -50,7 +50,7 @@ jobs:
- run: pip install pyyaml
- run: python tools/lint_skills.py
- name: Framework version guard (upstream template only)
if: github.repository == 'MadsLorentzen/ai-job-search'
if: github.repository == 'ai-job-search-dev/ai-job-search'
run: python tools/check_framework_version.py
security-guards:
@@ -203,7 +203,7 @@ jobs:
python3 tools/verify_pdf.py cv/main_example.pdf --min-chars 100
python3 tools/verify_pdf.py cover_letters/cover_example.pdf --min-chars 100
- name: Assert stock PDF structure (upstream template only)
if: github.repository == 'MadsLorentzen/ai-job-search'
if: github.repository == 'ai-job-search-dev/ai-job-search'
run: |
python3 tools/verify_pdf.py cv/main_example.pdf \
--pages 2 \
@@ -269,7 +269,7 @@ jobs:
placeholder-integrity:
name: Placeholder integrity (upstream template only)
if: github.repository == 'MadsLorentzen/ai-job-search'
if: github.repository == 'ai-job-search-dev/ai-job-search'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
+2 -2
View File
@@ -38,7 +38,7 @@ jobs:
name: Triage upstream commits
# No-op on the upstream template itself. Pinned by
# tests/test_upstream_triage.py so a template clone never runs it by surprise.
if: github.repository != 'MadsLorentzen/ai-job-search'
if: github.repository != 'ai-job-search-dev/ai-job-search'
runs-on: ubuntu-latest
# Issue write is granted to this one job, not the workflow, so a job added
# later starts read-only.
@@ -57,7 +57,7 @@ jobs:
- name: Fetch upstream template
run: |
git remote add upstream https://github.com/MadsLorentzen/ai-job-search.git 2>/dev/null || true
git remote add upstream https://github.com/ai-job-search-dev/ai-job-search.git 2>/dev/null || true
git fetch --quiet upstream master
- name: Build triage report
+26 -18
View File
@@ -25,6 +25,14 @@ per-file diff commands.
### Changed
- **The repository moved to the `ai-job-search-dev` organization** - the canonical URL is now
`https://github.com/ai-job-search-dev/ai-job-search`. GitHub redirects the old
`MadsLorentzen/ai-job-search` address for web links, `git` remotes, raw files and the API, so
existing clones and forks keep working; update your `upstream` remote when convenient:
`git remote set-url upstream https://github.com/ai-job-search-dev/ai-job-search.git`. Every
in-tree reference (README, CONTRIBUTING, SECURITY, the upstream-check tool and its tests, the
workflows' upstream guard) now points at the new slug.
- **`freehire-search` documents the `OR` and `title:` query operators** (#674,
`.agents/skills/freehire-search/SKILL.md`, `url-reference.md`, `cli/README.md`, `--help`) -
freehire's search parses an uppercase `OR` between up to five alternatives and a leading `title:`
@@ -272,7 +280,7 @@ per-file diff commands.
`github.com` among them, so a "posting" published in any GitHub repo or gist classified as an
installed portal instead of getting the `⚠ Unverified source host` warning. The User-Agent keeps
the `owner/project/version` name freehire's API docs ask for and drops the optional URL:
`MadsLorentzen/ai-job-search/1.0 (freehire-search)`. The host-check test now lists `github.com` and
`ai-job-search-dev/ai-job-search/1.0 (freehire-search)`. The host-check test now lists `github.com` and
`gist.github.com` postings among the hosts that must stay unverified.
- **The `/reset` replay tests find a Bash that actually runs the script on Windows** (#671,
@@ -528,7 +536,7 @@ per-file diff commands.
`.agents/skills/freehire-search/cli/src/helpers.ts`, `url-reference.md`) - the old
`freehire-search-skill/1.0 (+https://freehire.me)` pointed at the API itself, so freehire's
operators could not tell whose client it was. It now sends
`MadsLorentzen/ai-job-search/1.0 (+https://github.com/MadsLorentzen/ai-job-search; freehire-search)`,
`ai-job-search-dev/ai-job-search/1.0 (+https://github.com/ai-job-search-dev/ai-job-search; freehire-search)`,
the `owner/project` form freehire's API docs ask for. Nothing behaves differently; the API does
not gate on it.
@@ -2539,7 +2547,7 @@ per-file diff commands.
Step 5 and then discarded. Both arrays are now stored verbatim and replaced (never
accumulated) on `--all` re-ranks, so downstream consumers of `seen_jobs.json` can read
real triage findings instead of re-deriving them. See
[discussion #258](https://github.com/MadsLorentzen/ai-job-search/discussions/258).
[discussion #258](https://github.com/ai-job-search-dev/ai-job-search/discussions/258).
- **`/upskill` aggregate mode now ingests `/rank`'s recorded gaps** - previously it only
read `job_search_tracker.csv` and *guessed* required skills from the `role`/`sector`/
`notes` columns, even though `/rank` had already fetched and scored postings that never
@@ -2549,7 +2557,7 @@ per-file diff commands.
skill list wherever both exist. The heatmap's Gap Source column now shows the
recorded-vs-inferred split per skill, and the report header states how many jobs came
from each source. Depends on #263 (`/rank` persisting `gaps`/`strengths`); see
[discussion #258](https://github.com/MadsLorentzen/ai-job-search/discussions/258).
[discussion #258](https://github.com/ai-job-search-dev/ai-job-search/discussions/258).
### Security & privacy
@@ -2559,7 +2567,7 @@ per-file diff commands.
documents the safe alternative (a private repository with this repo as `upstream`), and
`/setup` ends with a matching privacy note the moment profile data first lands in
tracked files. Prompted by
[discussion #266](https://github.com/MadsLorentzen/ai-job-search/discussions/266).
[discussion #266](https://github.com/ai-job-search-dev/ai-job-search/discussions/266).
- **The gitignore guard now covers two more personal-data rules** - `security_guards.py`
requires `cover_letters/Cover_*.*` (the uppercase cover-letter naming variant `/apply`
recognizes) and `cv/*.txt` (ATS text extractions of tailored CVs) in `.gitignore`, so a
@@ -2586,7 +2594,7 @@ per-file diff commands.
- CONTRIBUTING's "run what CI runs" list is now complete - it previously omitted
`tools/security_guards.py` and the exact `unittest` invocation, the precise checks a
contributor PR had already failed on. Prompted by
[issue #262](https://github.com/MadsLorentzen/ai-job-search/issues/262).
[issue #262](https://github.com/ai-job-search-dev/ai-job-search/issues/262).
## [1.1.0] - 2026-07-30
@@ -2677,15 +2685,15 @@ At this baseline the framework provides:
- **Cross-runtime support** - a root `AGENTS.md` pointer so Codex and Antigravity can
discover the portable portal skills, with Claude Code as the reference runtime.
[Unreleased]: https://github.com/MadsLorentzen/ai-job-search/compare/v1.7.3...HEAD
[1.7.3]: https://github.com/MadsLorentzen/ai-job-search/compare/v1.7.2...v1.7.3
[1.7.2]: https://github.com/MadsLorentzen/ai-job-search/compare/v1.7.1...v1.7.2
[1.7.1]: https://github.com/MadsLorentzen/ai-job-search/compare/v1.7.0...v1.7.1
[1.7.0]: https://github.com/MadsLorentzen/ai-job-search/compare/v1.6.0...v1.7.0
[1.6.0]: https://github.com/MadsLorentzen/ai-job-search/compare/v1.5.0...v1.6.0
[1.5.0]: https://github.com/MadsLorentzen/ai-job-search/compare/v1.4.0...v1.5.0
[1.4.0]: https://github.com/MadsLorentzen/ai-job-search/compare/v1.3.0...v1.4.0
[1.3.0]: https://github.com/MadsLorentzen/ai-job-search/compare/v1.2.0...v1.3.0
[1.2.0]: https://github.com/MadsLorentzen/ai-job-search/compare/v1.1.0...v1.2.0
[1.1.0]: https://github.com/MadsLorentzen/ai-job-search/compare/v1.0.0...v1.1.0
[1.0.0]: https://github.com/MadsLorentzen/ai-job-search/releases/tag/v1.0.0
[Unreleased]: https://github.com/ai-job-search-dev/ai-job-search/compare/v1.7.3...HEAD
[1.7.3]: https://github.com/ai-job-search-dev/ai-job-search/compare/v1.7.2...v1.7.3
[1.7.2]: https://github.com/ai-job-search-dev/ai-job-search/compare/v1.7.1...v1.7.2
[1.7.1]: https://github.com/ai-job-search-dev/ai-job-search/compare/v1.7.0...v1.7.1
[1.7.0]: https://github.com/ai-job-search-dev/ai-job-search/compare/v1.6.0...v1.7.0
[1.6.0]: https://github.com/ai-job-search-dev/ai-job-search/compare/v1.5.0...v1.6.0
[1.5.0]: https://github.com/ai-job-search-dev/ai-job-search/compare/v1.4.0...v1.5.0
[1.4.0]: https://github.com/ai-job-search-dev/ai-job-search/compare/v1.3.0...v1.4.0
[1.3.0]: https://github.com/ai-job-search-dev/ai-job-search/compare/v1.2.0...v1.3.0
[1.2.0]: https://github.com/ai-job-search-dev/ai-job-search/compare/v1.1.0...v1.2.0
[1.1.0]: https://github.com/ai-job-search-dev/ai-job-search/compare/v1.0.0...v1.1.0
[1.0.0]: https://github.com/ai-job-search-dev/ai-job-search/releases/tag/v1.0.0
+33 -33
View File
@@ -43,7 +43,7 @@ Reviews here are empirical. Bug reports are reproduced on master before the fix
## Building for your own market? Do this instead
1. Fork the repo and run `/add-portal` with your local job board - it scaffolds a portal skill matching the shipped contract, and `/scrape` picks it up automatically.
2. Announce your fork in the pinned [Community forks & adaptations](https://github.com/MadsLorentzen/ai-job-search/discussions/78) discussion so others can find it.
2. Announce your fork in the pinned [Community forks & adaptations](https://github.com/ai-job-search-dev/ai-job-search/discussions/78) discussion so others can find it.
3. Run the framework update checker (`python3 tools/check_upstream_updates.py`) in your fork to check if upstream has updated any framework files and compare them with your personalized variants.
Market-specific skills are genuinely valuable - they just live in forks, where their maintainers can test them and their users can find them.
@@ -60,9 +60,9 @@ What upstream maintains for other runtimes instead:
- The root `AGENTS.md` points any agent at the canonical workflow specs and the profile entry point.
- Framework instruction files carry `framework_version` markers, so a runtime fork can track methodology changes precisely (`python3 tools/check_upstream_updates.py`).
Announce your runtime fork in the pinned [Community forks & adaptations](https://github.com/MadsLorentzen/ai-job-search/discussions/78) discussion and it gets listed alongside the market adaptations. The proven shape is a thin pointer: reference the specs here instead of copying them, so upstream improvements reach your fork on rebase.
Announce your runtime fork in the pinned [Community forks & adaptations](https://github.com/ai-job-search-dev/ai-job-search/discussions/78) discussion and it gets listed alongside the market adaptations. The proven shape is a thin pointer: reference the specs here instead of copying them, so upstream improvements reach your fork on rebase.
This is a decision, not a dogma: if cross-runtime standards mature to the point where these specs run unmodified elsewhere, or the community's center of gravity moves to runtime forks, the trade-off gets re-evaluated. Background: the architecture thread in [Community forks & adaptations](https://github.com/MadsLorentzen/ai-job-search/discussions/78).
This is a decision, not a dogma: if cross-runtime standards mature to the point where these specs run unmodified elsewhere, or the community's center of gravity moves to runtime forks, the trade-off gets re-evaluated. Background: the architecture thread in [Community forks & adaptations](https://github.com/ai-job-search-dev/ai-job-search/discussions/78).
## Practical notes
@@ -70,34 +70,34 @@ This is a decision, not a dogma: if cross-runtime standards mature to the point
- **Personal-use boundaries**: portal skills that touch ToS-restricted sources carry a prominent personal-use-only warning, and CI deliberately makes no live portal requests. Don't "fix" that.
- **LaTeX changes**: both templates must compile (`lualatex` for the CV, `xelatex` for the cover letter) and hold their exact page counts. CI smoke-checks this.
Questions and proposals are welcome in [Discussions](https://github.com/MadsLorentzen/ai-job-search/discussions) - an Idea thread costs nothing and can save you building the wrong thing :-)
Questions and proposals are welcome in [Discussions](https://github.com/ai-job-search-dev/ai-job-search/discussions) - an Idea thread costs nothing and can save you building the wrong thing :-)
[#17]: https://github.com/MadsLorentzen/ai-job-search/issues/17
[#30]: https://github.com/MadsLorentzen/ai-job-search/issues/30
[#31]: https://github.com/MadsLorentzen/ai-job-search/issues/31
[#35]: https://github.com/MadsLorentzen/ai-job-search/issues/35
[#36]: https://github.com/MadsLorentzen/ai-job-search/issues/36
[#37]: https://github.com/MadsLorentzen/ai-job-search/issues/37
[#39]: https://github.com/MadsLorentzen/ai-job-search/issues/39
[#41]: https://github.com/MadsLorentzen/ai-job-search/issues/41
[#43]: https://github.com/MadsLorentzen/ai-job-search/issues/43
[#44]: https://github.com/MadsLorentzen/ai-job-search/issues/44
[#49]: https://github.com/MadsLorentzen/ai-job-search/issues/49
[#52]: https://github.com/MadsLorentzen/ai-job-search/issues/52
[#54]: https://github.com/MadsLorentzen/ai-job-search/issues/54
[#55]: https://github.com/MadsLorentzen/ai-job-search/issues/55
[#56]: https://github.com/MadsLorentzen/ai-job-search/issues/56
[#59]: https://github.com/MadsLorentzen/ai-job-search/issues/59
[#60]: https://github.com/MadsLorentzen/ai-job-search/issues/60
[#63]: https://github.com/MadsLorentzen/ai-job-search/issues/63
[#64]: https://github.com/MadsLorentzen/ai-job-search/issues/64
[#66]: https://github.com/MadsLorentzen/ai-job-search/issues/66
[#67]: https://github.com/MadsLorentzen/ai-job-search/issues/67
[#68]: https://github.com/MadsLorentzen/ai-job-search/issues/68
[#72]: https://github.com/MadsLorentzen/ai-job-search/issues/72
[#73]: https://github.com/MadsLorentzen/ai-job-search/issues/73
[#75]: https://github.com/MadsLorentzen/ai-job-search/issues/75
[#76]: https://github.com/MadsLorentzen/ai-job-search/issues/76
[#155]: https://github.com/MadsLorentzen/ai-job-search/pull/155
[#162]: https://github.com/MadsLorentzen/ai-job-search/pull/162
[#165]: https://github.com/MadsLorentzen/ai-job-search/pull/165
[#17]: https://github.com/ai-job-search-dev/ai-job-search/issues/17
[#30]: https://github.com/ai-job-search-dev/ai-job-search/issues/30
[#31]: https://github.com/ai-job-search-dev/ai-job-search/issues/31
[#35]: https://github.com/ai-job-search-dev/ai-job-search/issues/35
[#36]: https://github.com/ai-job-search-dev/ai-job-search/issues/36
[#37]: https://github.com/ai-job-search-dev/ai-job-search/issues/37
[#39]: https://github.com/ai-job-search-dev/ai-job-search/issues/39
[#41]: https://github.com/ai-job-search-dev/ai-job-search/issues/41
[#43]: https://github.com/ai-job-search-dev/ai-job-search/issues/43
[#44]: https://github.com/ai-job-search-dev/ai-job-search/issues/44
[#49]: https://github.com/ai-job-search-dev/ai-job-search/issues/49
[#52]: https://github.com/ai-job-search-dev/ai-job-search/issues/52
[#54]: https://github.com/ai-job-search-dev/ai-job-search/issues/54
[#55]: https://github.com/ai-job-search-dev/ai-job-search/issues/55
[#56]: https://github.com/ai-job-search-dev/ai-job-search/issues/56
[#59]: https://github.com/ai-job-search-dev/ai-job-search/issues/59
[#60]: https://github.com/ai-job-search-dev/ai-job-search/issues/60
[#63]: https://github.com/ai-job-search-dev/ai-job-search/issues/63
[#64]: https://github.com/ai-job-search-dev/ai-job-search/issues/64
[#66]: https://github.com/ai-job-search-dev/ai-job-search/issues/66
[#67]: https://github.com/ai-job-search-dev/ai-job-search/issues/67
[#68]: https://github.com/ai-job-search-dev/ai-job-search/issues/68
[#72]: https://github.com/ai-job-search-dev/ai-job-search/issues/72
[#73]: https://github.com/ai-job-search-dev/ai-job-search/issues/73
[#75]: https://github.com/ai-job-search-dev/ai-job-search/issues/75
[#76]: https://github.com/ai-job-search-dev/ai-job-search/issues/76
[#155]: https://github.com/ai-job-search-dev/ai-job-search/pull/155
[#162]: https://github.com/ai-job-search-dev/ai-job-search/pull/162
[#165]: https://github.com/ai-job-search-dev/ai-job-search/pull/165
+7 -7
View File
@@ -10,7 +10,7 @@
<a href="https://trendshift.io/repositories/43622?utm_source=trendshift-badge&amp;utm_medium=badge&amp;utm_campaign=badge-trendshift-43622" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/trendshift/repositories/43622/daily" alt="MadsLorentzen%2Fai-job-search | Trendshift" width="250" height="55"/></a>
</p>
[![CI](https://github.com/MadsLorentzen/ai-job-search/actions/workflows/ci.yml/badge.svg)](https://github.com/MadsLorentzen/ai-job-search/actions/workflows/ci.yml)
[![CI](https://github.com/ai-job-search-dev/ai-job-search/actions/workflows/ci.yml/badge.svg)](https://github.com/ai-job-search-dev/ai-job-search/actions/workflows/ci.yml)
An AI-powered job application framework built on [Claude Code](https://claude.com/claude-code). Fork it, fill in your profile, and let Claude evaluate job postings, tailor your CV, write cover letters, and prepare you for interviews.
@@ -61,7 +61,7 @@ The framework encodes career guidance best practices, including structured evalu
## Prerequisites
- [Claude Code](https://claude.com/claude-code) (CLI). Claude Code has no free tier: you need a Claude Pro/Max/Team subscription or Anthropic API credits (pay-per-token, usually cheaper for occasional use). Using a different agent tool (Codex, Antigravity, Gemini CLI)? Start at [`AGENTS.md`](AGENTS.md) - the portal search skills work there out of the box, and [community forks](https://github.com/MadsLorentzen/ai-job-search/discussions/78) adapt the full workflow.
- [Claude Code](https://claude.com/claude-code) (CLI). Claude Code has no free tier: you need a Claude Pro/Max/Team subscription or Anthropic API credits (pay-per-token, usually cheaper for occasional use). Using a different agent tool (Codex, Antigravity, Gemini CLI)? Start at [`AGENTS.md`](AGENTS.md) - the portal search skills work there out of the box, and [community forks](https://github.com/ai-job-search-dev/ai-job-search/discussions/78) adapt the full workflow.
- Python 3.10+
- [Bun](https://bun.sh) (for job search CLI tools)
- LaTeX distribution with `lualatex` and `xelatex`: [TeX Live](https://tug.org/texlive/), [MacTeX](https://tug.org/mactex/), [TinyTeX](https://yihui.org/tinytex/), or [MiKTeX](https://miktex.org/). The CV compiles with `lualatex` (pdflatex often fails on modern MiKTeX installs with `fontawesome5` font-expansion errors); the cover letter compiles with `xelatex` because `cover.cls` requires `fontspec`. If using a minimal TeX install such as TinyTeX or BasicTeX, install the extra packages listed in [SETUP.md](SETUP.md#minimal-tex-install-tinytexbasictex).
@@ -74,7 +74,7 @@ The framework encodes career guidance best practices, including structured evalu
### 1. Fork and clone
```bash
gh repo fork MadsLorentzen/ai-job-search --clone
gh repo fork ai-job-search-dev/ai-job-search --clone
cd ai-job-search
```
@@ -302,7 +302,7 @@ The four Danish CLI tools in `.agents/skills/` (Jobbank, Jobdanmark, Jobindex, J
Give it your local job board's URL. The command investigates the portal (search-URL pattern, result-page structure, robots.txt/access rules), scaffolds a CLI skill with the same structure, commands, and output contract as the shipped ones, and test-runs a live query before registering anything. Auth-walled portals are declined, and portals with restrictive terms get a prominent personal-use-only warning in the generated skill. The generated skill is market-specific and lives in your fork; the generator itself is the universal part.
Maintaining a fork adapted to your market or language? Add it to the [Community forks & adaptations](https://github.com/MadsLorentzen/ai-job-search/discussions/78) thread so others can find it.
Maintaining a fork adapted to your market or language? Add it to the [Community forks & adaptations](https://github.com/ai-job-search-dev/ai-job-search/discussions/78) thread so others can find it.
For **country-agnostic** starting points outside Denmark, the repo ships two portal skills alongside the Danish demos:
@@ -313,11 +313,11 @@ For **country-agnostic** starting points outside Denmark, the repo ships two por
Everything above adds up to an extension model, so here it is stated plainly. The framework has three extension points, and none of them require touching upstream:
1. **Portal skills** - the module system for job boards. Every `*-search` skill is a self-contained folder under `.agents/skills/` with the same contract (a `search`/`detail` CLI, `--format json|table|plain` output, an `enabled:` flag in its `SKILL.md`, its own tests). `/scrape` auto-discovers any installed skill that follows the contract - nothing to register, nothing to wire up. `/add-portal` generates new ones; the [community portal index](https://github.com/MadsLorentzen/ai-job-search/discussions/78) catalogs the ones other forks have built.
1. **Portal skills** - the module system for job boards. Every `*-search` skill is a self-contained folder under `.agents/skills/` with the same contract (a `search`/`detail` CLI, `--format json|table|plain` output, an `enabled:` flag in its `SKILL.md`, its own tests). `/scrape` auto-discovers any installed skill that follows the contract - nothing to register, nothing to wire up. `/add-portal` generates new ones; the [community portal index](https://github.com/ai-job-search-dev/ai-job-search/discussions/78) catalogs the ones other forks have built.
2. **Document templates** - `/add-template` registers any CV or cover-letter toolchain that compiles to PDF from the command line, LaTeX or otherwise.
3. **Evaluation criteria** - deal-breakers and preferences in your profile are free-form, and the evaluation rubric scores against whatever you put there. "Strong parental-leave terms", "minimum salary X per my union's scale", "no on-call" - each is one profile line, no code, and it carries real weight in `/rank` and `/apply` fit evaluations. Language is the one deal-breaker type with dedicated, structured handling: `/setup` captures every language you work in and your level (asked directly, or inferred from your CV/LinkedIn export) into a `Languages` table, and the Language Gate (`04-job-evaluation.md`) hard-rejects a posting that requires a language you haven't declared at all, while flagging - not auto-rejecting - one that asks for a higher level than you declared in a language you do work in, so a borderline case (a strict "fluent" bar against your own B1/B2, say) gets your judgment instead of a silent drop.
**Borrowing a portal skill from another fork** is the intended way to get a board that upstream doesn't ship: find it in the [portal index](https://github.com/MadsLorentzen/ai-job-search/discussions/78), open that fork, and copy the one folder into your own `.agents/skills/`. Before you run it:
**Borrowing a portal skill from another fork** is the intended way to get a board that upstream doesn't ship: find it in the [portal index](https://github.com/ai-job-search-dev/ai-job-search/discussions/78), open that fork, and copy the one folder into your own `.agents/skills/`. Before you run it:
- **Read the code.** All of it - these CLIs run pre-approved on your machine (`.claude/settings.json` allowlists them) against your career data. Check that the only network calls go to the job board it claims to search, that `package.json` has no `dependencies` and no lifecycle scripts (`postinstall` etc.), and that nothing reads or writes outside its own folder.
- **Run its tests offline** (`bun test` in the skill's `cli/` directory) - a well-built skill's tests pass with no network access.
@@ -325,7 +325,7 @@ Everything above adds up to an extension model, so here it is stated plainly. Th
The copy step is manual on purpose. Your settings already allow installed portal skills to run without asking each time - so an installer that fetched them from third-party repos for you would skip the one check that matters: you, reading the code first. There isn't one, and that's a security decision rather than a missing feature.
Market-specific *data sources* (a national salary database, local award-rate tables) follow the same pattern as portals: they belong in a market fork, shared via [#78](https://github.com/MadsLorentzen/ai-job-search/discussions/78), not upstream.
Market-specific *data sources* (a national salary database, local award-rate tables) follow the same pattern as portals: they belong in a market fork, shared via [#78](https://github.com/ai-job-search-dev/ai-job-search/discussions/78), not upstream.
### Salary benchmarking
+2 -2
View File
@@ -2,7 +2,7 @@
## Reporting a vulnerability
Please report security findings privately via **[GitHub private vulnerability reporting](https://github.com/MadsLorentzen/ai-job-search/security/advisories/new)** rather than a public issue. You will get a response within a few days, credit in the fix unless you prefer otherwise, and public disclosure coordinated with the patch.
Please report security findings privately via **[GitHub private vulnerability reporting](https://github.com/ai-job-search-dev/ai-job-search/security/advisories/new)** rather than a public issue. You will get a response within a few days, credit in the fix unless you prefer otherwise, and public disclosure coordinated with the patch.
If the private form is unavailable, open a public issue that describes the *class* of problem without a working recipe, and note that you have details to share privately.
@@ -47,4 +47,4 @@ Instruction-level defenses raise the bar; permission-level defenses bound the da
## Scope notes
- Portal CLI skills make live requests only when you run them; CI never does.
- Community fork skills listed in the [forks index](https://github.com/MadsLorentzen/ai-job-search/discussions/78) are **not** covered by this policy - review the code you copy, as the index itself says.
- Community fork skills listed in the [forks index](https://github.com/ai-job-search-dev/ai-job-search/discussions/78) are **not** covered by this policy - review the code you copy, as the index itself says.
+3 -3
View File
@@ -166,7 +166,7 @@ If a command still uses `pdftotext -layout`, it must pass `-enc UTF-8` as well.
## 2. Fork and clone
```bash
gh repo fork MadsLorentzen/ai-job-search --clone
gh repo fork ai-job-search-dev/ai-job-search --clone
cd ai-job-search
gh repo set-default <your-github-username>/ai-job-search
```
@@ -307,10 +307,10 @@ Upstream keeps improving the methodology files your fork has personalized, so pl
**Prefer releases over raw `master`.** Tagged [releases](../../releases) are vetted checkpoints, each described in [CHANGELOG.md](CHANGELOG.md). Updating to a tag pulls a stable, documented state instead of whatever `master` happens to be mid-review. Fetch tags with `git fetch upstream --tags` and merge a release (for example `git merge v1.0.0`) when you want stability; pull `master` directly only when you specifically want the latest unreleased changes. The steps below apply either way - substitute the release tag for `upstream/master` where you see it.
1. **Commit your personalization - but know where those commits land.** `/setup` edits CLAUDE.md and the profile skill files in place; those edits are *yours*, and committing them is what lets updates merge cleanly. But a GitHub **fork of this repo is public** - forks of public repositories cannot be made private - so anything you commit *and push to a fork* is visible to anyone. If you want your profile in a remote at all, don't push it to a fork: create a **private** repository, push there, and add this repo as the `upstream` remote (`git remote add upstream https://github.com/MadsLorentzen/ai-job-search.git`) to keep receiving updates. Committing locally without pushing is also fine. The genuinely sensitive files (tracker, salary data, `documents/`, application archives) are gitignored and never enter git either way. An uncommitted working tree is the most common reason `git pull` refuses to merge at all (`Your local changes ... would be overwritten`).
1. **Commit your personalization - but know where those commits land.** `/setup` edits CLAUDE.md and the profile skill files in place; those edits are *yours*, and committing them is what lets updates merge cleanly. But a GitHub **fork of this repo is public** - forks of public repositories cannot be made private - so anything you commit *and push to a fork* is visible to anyone. If you want your profile in a remote at all, don't push it to a fork: create a **private** repository, push there, and add this repo as the `upstream` remote (`git remote add upstream https://github.com/ai-job-search-dev/ai-job-search.git`) to keep receiving updates. Committing locally without pushing is also fine. The genuinely sensitive files (tracker, salary data, `documents/`, application archives) are gitignored and never enter git either way. An uncommitted working tree is the most common reason `git pull` refuses to merge at all (`Your local changes ... would be overwritten`).
2. **Preview what changed before pulling:**
```bash
git remote add upstream https://github.com/MadsLorentzen/ai-job-search.git # first time only, if you cloned your own fork
git remote add upstream https://github.com/ai-job-search-dev/ai-job-search.git # first time only, if you cloned your own fork
git fetch upstream # or origin, if you cloned the template directly
python3 tools/check_upstream_updates.py
```
+1 -1
View File
@@ -10,7 +10,7 @@ import os
import subprocess
from pathlib import Path
UPSTREAM = "MadsLorentzen/ai-job-search"
UPSTREAM = "ai-job-search-dev/ai-job-search"
REPO = Path(__file__).resolve().parent.parent
+1 -1
View File
@@ -10,7 +10,7 @@ from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
SCRIPT = REPO_ROOT / "tools" / "check_upstream_updates.py"
TEMPLATE_URL = "https://github.com/MadsLorentzen/ai-job-search.git"
TEMPLATE_URL = "https://github.com/ai-job-search-dev/ai-job-search.git"
FORK_URL = "https://github.com/octocat/ai-job-search.git"
FRAMEWORK_FILES = [
+1 -1
View File
@@ -158,7 +158,7 @@ class ToolsWriteUtf8(unittest.TestCase):
subprocess.run(["git", "init", "-b", "master"], cwd=repo, check=True, capture_output=True)
subprocess.run(["git", "config", "user.name", "Test"], cwd=repo, check=True, capture_output=True)
subprocess.run(["git", "config", "user.email", "test@example.com"], cwd=repo, check=True, capture_output=True)
subprocess.run(["git", "remote", "add", "upstream", "https://github.com/MadsLorentzen/ai-job-search.git"], cwd=repo, check=True, capture_output=True)
subprocess.run(["git", "remote", "add", "upstream", "https://github.com/ai-job-search-dev/ai-job-search.git"], cwd=repo, check=True, capture_output=True)
(repo / "file.txt").write_text("hello\n", encoding="utf-8")
subprocess.run(["git", "add", "file.txt"], cwd=repo, check=True, capture_output=True)
subprocess.run(["git", "commit", "-m", "init"], cwd=repo, check=True, capture_output=True)
+8 -8
View File
@@ -16,7 +16,7 @@ except ImportError: # `python -m unittest discover -s tests` imports test modul
import _upstream
from _upstream import is_upstream_checkout
UPSTREAM = "MadsLorentzen/ai-job-search"
UPSTREAM = "ai-job-search-dev/ai-job-search"
def _no_github_env(testcase):
@@ -81,16 +81,16 @@ class UpstreamCheckoutTests(unittest.TestCase):
def test_origin_url_forms(self):
cases = [
("https://github.com/MadsLorentzen/ai-job-search", True),
("https://github.com/MadsLorentzen/ai-job-search.git", True),
("git@github.com:MadsLorentzen/ai-job-search", True),
("git@github.com:MadsLorentzen/ai-job-search.git", True),
("ssh://git@github.com/MadsLorentzen/ai-job-search.git", True),
("ssh://git@github.com/MadsLorentzen/ai-job-search", True),
("https://github.com/ai-job-search-dev/ai-job-search", True),
("https://github.com/ai-job-search-dev/ai-job-search.git", True),
("git@github.com:ai-job-search-dev/ai-job-search", True),
("git@github.com:ai-job-search-dev/ai-job-search.git", True),
("ssh://git@github.com/ai-job-search-dev/ai-job-search.git", True),
("ssh://git@github.com/ai-job-search-dev/ai-job-search", True),
("ssh://git@github.com/someone-else/ai-job-search.git", False),
("https://github.com/someone-else/ai-job-search.git", False),
("git@github.com:someone-else/ai-job-search", False),
("https://gitlab.com/MadsLorentzen/ai-job-search.git", False),
("https://gitlab.com/ai-job-search-dev/ai-job-search.git", False),
]
for url, expected in cases:
with self.subTest(url=url):
+1 -1
View File
@@ -9,7 +9,7 @@ from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
SCRIPT = REPO_ROOT / "tools" / "upstream_triage.py"
WORKFLOW = REPO_ROOT / ".github" / "workflows" / "upstream-watch.yml"
UPSTREAM_SLUG = "MadsLorentzen/ai-job-search"
UPSTREAM_SLUG = "ai-job-search-dev/ai-job-search"
def git(root: Path, *args: str) -> str:
+1 -1
View File
@@ -35,7 +35,7 @@ FRAMEWORK_FILES = [
"AGENTS.md",
]
UPSTREAM_REPO_SLUG = "MadsLorentzen/ai-job-search"
UPSTREAM_REPO_SLUG = "ai-job-search-dev/ai-job-search"
def run_git(args: list[str]) -> tuple[int, str, str]:
res = subprocess.run(