mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-11-02 13:12:50 +00:00
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:
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.
|
||||
|
||||
|
||||
+1
-1
@@ -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,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
@@ -10,7 +10,7 @@
|
||||
<a href="https://trendshift.io/repositories/43622?utm_source=trendshift-badge&utm_medium=badge&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>
|
||||
|
||||
[](https://github.com/MadsLorentzen/ai-job-search/actions/workflows/ci.yml)
|
||||
[](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
@@ -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.
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in new issue
Block a user