Commit Graph
245 Commits
Author SHA1 Message Date
Ayobami Adegoke da2c3bbec9 test(cli): cover Jobindex and Jobnet error contracts (#172)
Extend the offline CLI contract tests to the two remaining Danish
portal CLIs. Both implement the documented error contract (JSON
errors on stderr, exit 1) but had no test locking it in:

- jobindex-search: search without --query, detail without an ID,
  and bunli numeric-option validation (--page not-a-number)
- jobnet-search: detail without an ID, occupations without
  --search-string, suggestions without --query, and numeric-option
  validation

All asserted paths exit before any network request, matching the
no-live-portal-requests CI policy. Assertions were written against
observed CLI output, not assumed shapes.
2026-07-17 21:56:13 +02:00
Mads Lorentzen fb91be7a0b security: treat job postings as untrusted input across /apply and /rank (#175)
Prompt-injection hardening from the dataflow analysis in #173 by @Defaultuser361: data-not-instructions rules in /apply and /rank, reviewer research constrained to the user-confirmed company identity, writing-style verify rule tightened to independently located sources (framework_version 1.1.0), SECURITY.md private reporting channel, README note. Closes #173.
2026-07-17 21:48:59 +02:00
Jaewon Chung ac6a734e16 fix(apply): name CVs main_<company>_<role> to avoid overwrites (#171)
CVs from /apply were named cv/main_<company>.tex, so a second role at the same company overwrote the first (cover letters already carried the role). Aligns CV naming to main_<company>_<role>.tex across apply, add-template, the CV template guide, CLAUDE.md, and SETUP.md; /outcome and /interview fallbacks glob main_<company>*.tex to match both legacy and new names. framework_version bumped on both touched framework files.
2026-07-17 21:26:04 +02:00
Adri f1ed475d59 feat(notion-sync): one-way read-only pipeline view in Notion via MCP (#169)
Adds /notion-sync per the conditions agreed in discussion #166: tool-agnostic sync contract with Notion as the in-tree reference binding, silently optional (covers unconfigured, headless, and unauthenticated states), read-only toward the repo with the gitignored sync-state file as its only local write, write-once page bodies, documents sync as filenames only. Complements /html-report: deep local dashboard vs glanceable anywhere-view.
2026-07-17 21:26:02 +02:00
Mads LorentzenandClaude Fable 5 dd6d7efea6 docs: warn fork authors about GitHub's default PR base (#167)
Three personalized-fork PRs (#155, #162, #165) were filed against
upstream by accident in one week - GitHub points new fork PRs at the
upstream repo by default, and nothing warned about it at the moment of
filing. Adds a PR template with the heads-up in the compose box (plus
the review norms the process asks for anyway) and one sentence in
CONTRIBUTING's fork section naming the mechanism.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 22:26:45 +02:00
Yuan Chen 848eddbecc feat(html-report): add /html-report command for application tracker dashboard (#131)
Self-contained HTML dashboard generated from job_search_tracker.csv and the application archives: stat cards, status/sector/channel/funnel charts as hand-generated inline SVG (no CDN, fully offline), HTML-escaped interpolation throughout, and a filterable applications table. Includes Python guards for the command file and the reports/ gitignore rule.
2026-07-16 21:48:59 +02:00
Mads LorentzenandClaude Fable 5 be427a7607 docs: codify the runtime policy - Claude Code first, runtime forks welcome (#163)
Writes down the architecture decision from the #78 discussion (2026-07-15):
Claude Code is the reference runtime; other agent runtimes are supported at
the edges via the portable portal skills in .agents/skills/, the root
AGENTS.md signpost, and thin-pointer community forks. Per-runtime command
trees stay in forks for the same reason market portals do.

README gets one line in Prerequisites; CONTRIBUTING gets the policy section
beside the market-skills rule it mirrors, including the explicit revisit
conditions.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 22:34:10 +02:00
Jovin Nicholas 1db48568b3 feat(agents-config): add root AGENTS.md thin-pointer specification (#159)
Root AGENTS.md pointer file per the architecture decision in discussion #78: documents both config roots (.agents/skills/ portable portal skills, .claude/ orchestration) and the profile entry points, carries a framework_version marker registered in both version tools.

Design case made by @erikpr1994 in the #78 architecture thread; implementation by @jovin-nicholas.
2026-07-15 22:33:42 +02:00
Erik Pastor Rios 37595187d6 feat(job-scraper): per-portal enabled toggle honored by /scrape (#160)
Implements the mechanism approved in discussion #93: enabled: true|false frontmatter on portal skills (missing key = enabled), honored during /scrape portal discovery, with skipped portals reported visibly in the run summary.
2026-07-15 20:20:37 +02:00
Alaa-TaiebandTunic Assistant 55ba1c1652 fix(salary): validate category shape and add --validate preflight (#156)
validate_data() accepted category values that are not {count?, index?}
objects. They slipped through to format_entry(), which then raised
AttributeError on a normal table lookup (or serialized a malformed shape
under --json). It also accepted duplicate company names silently.

- collect_validation_issues() now also flags a non-object category value
  (and non-numeric count / non number-or-string index) as a hard error,
  and duplicate company names as a warning.
- validate_data() keeps its eager-fail behavior (same messages), so
  existing tests and load_data() are unchanged.
- --validate runs the checks standalone and prints an actionable report
  (exit 1 on errors, 0 on warnings-only/clean), letting users pre-flight
  their BYO salary_data.json.

Reproduced on master: validate_data({'companies':[{'company':'Acme',
'categories':{'eng':'not_a_dict'}}]}) returns without error, but
format_entry then raises AttributeError.

Co-authored-by: Tunic Assistant <assistant@tunic.local>
2026-07-15 07:53:01 +02:00
Carlos Mestre CebriánandClaude Fable 5 47118dcbf2 docs(readme): make install loops failure-isolated per iteration (#157)
A failed iteration in the Quick start install loops (e.g. bun missing
from PATH) skipped the cd back to the repo root, so every remaining
tool's cd failed in cascade and the shell ended up stranded inside
.agents/skills/<tool>/cli with nothing else installed. Run each bash
iteration in a subshell and use Push-Location/Pop-Location in
PowerShell so a failure stays contained to its own tool and the loop
always returns to the repo root.


Claude-Session: https://claude.ai/code/session_015EQ2xeixvVdnvbihce3aSt

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 07:52:07 +02:00
Rasika d1e707ef1f feat(job-scraper): referral-contact LinkedIn search links for high/medium-fit jobs (#149)
* added contact skill

* refactor(job-scraper): fold referral-contacts into Step 4.5, drop contacts cache
2026-07-15 07:52:04 +02:00
Alaa-Taieb 1417e3cbdf fix(salary): skip non-numeric and identifier columns in Excel conversion (#152)
parse_sheet treated every column that was not company/city as a salary category, with no check that the column actually held numeric salary data. This turned free-text columns (e.g. Notes) into bogus string categories and numeric identifier columns (e.g. Id) into mistaken salary indexes.

- Drop identifier headers (ID_PATTERNS = {id, personnummer}) at classification time.

- Skip non-numeric standalone values and fully-null count/index pairs at row-processing time.

- Adds regression tests (skips_free_text_column, skips_numeric_identifier_column, keeps_numeric_salary_column) that fail on master and pass after the fix.
2026-07-14 20:11:11 +02:00
Alaa-Taieb 4128ca0318 fix(salary): detect company column from header token, not exact match (#151)
convert_salary_excel.py detected the company column via exact membership
in COMPANY_PATTERNS, so common real-world headers like "Company Name" or
"Employer Name" were never matched. parse_sheet then returned [] for that
sheet, silently dropping it from salary_data.json (or exiting with no
output for a single-sheet file).

Route company-column detection through the existing header_matches()
token matcher (already used for count/index detection). This only adds
detections; inputs that already worked (bare "Company"/"Firma"/...) are
unaffected.

Adds a regression test in tests/test_convert_salary_excel.py that fails
on master (returns []) and passes after the fix.
2026-07-14 14:35:45 +02:00
Yash Rajeshbhai DarjiandCursor 0a8fc194e5 docs: sync onboarding with linkedin-search + freehire-search reality (#150)
/setup still told non-DK users that built-in CLIs are Denmark-specific.
Align setup, /add-portal, and search-queries.md with shipped country-agnostic
CLIs and /scrape auto-discovery (post #85 / #95 / #102).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-14 14:30:49 +02:00
Erik Pastor Rios b20467df4a feat(versioning): framework_version markers, CI version guard, and fork update checker (#144)
* Add framework_version markers to assistant skills and implement CI version guard and update checker

* Address review feedback: update ci.yml based on latest upstream, gate CI version guard to upstream repo, and remove non-ASCII characters from check_upstream_updates.py
2026-07-14 14:29:24 +02:00
Ayobami Adegoke 03798ec974 test(cli): cover Jobbank and Jobdanmark contracts (#148)
Jobbank and Jobdanmark each had only one narrow test, leaving required-argument errors, RSS normalization, JSON-LD variants, and malformed-page handling unprotected.

Add network-free fixture and subprocess tests for repeated RSS filters, description and ID parsing, stderr JSON errors, Bunli numeric validation, JSON-LD objects and arrays, optional fields, not-found pages, and parse failures.

The suites now cover eight Jobbank cases and six Jobdanmark cases without making live portal requests.
2026-07-13 20:45:38 +02:00
Yiğit Ö. Ünver fca1c4234e docs(setup): add needspace to the minimal TeX install list (#147) 2026-07-13 20:45:36 +02:00
Ayobami Adegoke a03529f894 fix(lint): report malformed settings shapes without crashing (#146)
Valid JSON such as [] or {"permissions": []} caused lint_skills.py to raise AttributeError because it assumed both values were objects.

Validate the top-level settings value and permissions object before reading nested keys. Malformed settings now produce clear lint errors and exit 1 without a traceback.

Add subprocess regression tests covering invalid JSON, malformed root values, invalid permissions values, and non-list permissions.allow values.
2026-07-13 20:45:22 +02:00
Ayobami Adegoke 160b479868 ci: verify PDF page counts and text layers via poppler (#145) 2026-07-13 16:36:46 +02:00
Ayobami Adegoke e341d19abd fix(salary): validate salary data shape before lookup (#141) 2026-07-13 15:55:55 +02:00
Ayobami Adegoke 99cf1000d6 ci: run CLI fixture/mock test suites (#134) 2026-07-12 18:02:23 +02:00
Ayobami Adegoke ff78dae48a test(jobnet): extract pure helpers and add normalization tests (#133)
* docs: clean all add-template compile artifacts

* Add Jobnet CLI normalization tests
2026-07-12 18:02:17 +02:00
Mads LorentzenandClaude Fable 5 1526afcbfe docs: add the maintainer's story to the README (#138)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 16:34:58 +02:00
Mads LorentzenandClaude Fable 5 855b6725f3 brand: tagline line + the hired-moment coffee ask (#136)
* feat(brand): tagline under the H1 and the hired-moment coffee line

Two items from the branding strategy: the positioning line lands in the
README itself (the repo description already carries it), and /outcome's
hired path gets its single, value-framed donation ask - once per hire,
never nagging, never effort-framed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(brand): pay-it-forward framing for the hired-moment line

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 11:17:48 +02:00
Mads LorentzenandClaude Fable 5 013b90132b chore: slim brand assets to just the mascot GIF (#135)
The regeneration pipeline, AI source sheets, retired sprites, avatar,
social card, and internal design/plan docs are maintainer tooling, not
template content - archived in the maintainer's private workspace. Fork
users get the 30 KB animation and nothing they didn't ask for. The
.gitignore PNG allowlist is dropped along with the PNGs it served.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 10:30:55 +02:00
Mads LorentzenandClaude Fable 5 09f0417d78 brand: meet Pip, the courier bird (#132)
* docs: add mascot & brand design spec (Pip the courier bird)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: Pip wears a tie - update mascot spec to v5 flight loop

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: add Pip brand PR implementation plan

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(plan): v7 master GIF - drop frame scaling, add enclosed-hole transparency

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(plan): v8 master GIF - fix hole classification (chest stays opaque)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(plan): scrub stale v5 references

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(plan): v10 master GIF - line-fitted envelope border clipping

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(plan): label pipeline as v10

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(plan): v16 master GIF - targeted removal of gap blob

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(plan): v17 master GIF - drop envelope border clipping, keep blob removal

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(plan): v19 final master GIF - user-approved thin outline repair

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(brand): add Pip mascot assets and regeneration pipeline

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(brand): Pip takes over the README header

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(spec): scrub stale scaling line

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(brand): avatar, social card, and mascot sources (PNG allowlist)

The global *.png personal-data rule silently excluded the mascot's source
sheets and generated PNGs; allowlist the upstream-controlled assets/mascot/
paths without weakening the fork-protecting rule.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 10:21:36 +02:00
Ayobami Adegoke 8bee3ddb97 docs(add-template): clean all test compile artifacts (#130) 2026-07-11 22:06:04 +02:00
Ayobami Adegoke 379abad503 docs(add-template): clarify --use activation metadata (#129) 2026-07-11 22:05:05 +02:00
Ayobami Adegoke 569b1df371 fix(security): harden guard JSON shape handling (#128) 2026-07-11 22:04:54 +02:00
Mads LorentzenandClaude Fable 5 543d1a733e docs: add no-crypto/no-token disclaimer (#127)
The repo's owner has received multiple pump.fun token-sponsorship
solicitations this week. This pre-empts any 'the developer
acknowledged the token' narrative and protects users from
unauthorized schemes trading on the project's name.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 20:12:42 +02:00
Mads LorentzenandClaude Fable 5 22d8b48dcf docs: add Trendshift #1-repository-of-the-day badge (#126)
Commemorates #1 on GitHub Trending (all languages), July 7 2026.
Official Trendshift embed, linked to the tracked stats page.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 17:24:43 +02:00
♦ jabarii♦ c134eef553 refactor(salary): optimize search match scoring and normalize Excel category keys (#101)
This commit improves the performance and consistency of the salary tools:

- Redundant query normalization and word extraction are eliminated in salary_lookup.py by pre-calculating representations once before the search loop.
- A match_score_optimized helper is introduced to perform the comparison using the pre-calculated query data, preserving full backward compatibility for match_score.
- Normalization in tools/convert_salary_excel.py is unified: paired column headers now consistently substitute spaces and dashes with underscores (e.g. 'software_engineering') to match the single-column formatting.
- Unit test coverage is significantly expanded in tests/test_salary_lookup.py and tests/test_convert_salary_excel.py to cover normalization, anglicization, search filtering, and matching behaviors.
2026-07-10 15:24:20 +02:00
Erik Pastor RiosandClaude Opus 4.8 7e8df35819 docs(job-scraper): make search queries market-neutral (#95)
Rebased onto current master. The SKILL.md description and "How It Works"
copy were already generalized by #102, so this drops those now-redundant
edits and keeps only the unique remaining value: replacing the hardcoded
Danish job boards (jobindex.dk, karriere.dk, jobfinder.dk, akademikernes.dk)
and Danish location terms in search-queries.md with configurable
placeholders and an /add-portal pointer, so the query templates apply to
any market.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 08:06:54 +02:00
Erik Pastor RiosandClaude Opus 4.8 a278ad7a50 refactor(salary): make compound-word matching locale-agnostic (#94)
* refactor(salary): make compound-word matching locale-agnostic

The Excel column detector hardcoded a DANISH_COMPOUND_PATTERNS set inside
header_matches(), so the compound-word matching that helps Danish headers
(e.g. "lønindeks") was baked into the algorithm by name and unavailable to
any other locale without editing the source.

Rename it to COMPOUND_PATTERNS and pass it as a parameter (default
unchanged, so the Danish demonstration data behaves identically). A
different-locale spreadsheet can now supply its own compound tokens via
header_matches(..., compound_patterns=...). Add a test covering both the
preserved default and the parameterized path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(salary): drop unused compound_patterns parameter

Per review: keep the DANISH_COMPOUND_PATTERNS -> COMPOUND_PATTERNS
rename (universal template naming, defaults still Danish), but remove
the compound_patterns= parameter. No caller passes a custom set, and a
fork adapting another locale edits the module-level constant either way,
so parameterizing it is speculative generality per CONTRIBUTING.md.

header_matches() now reads COMPOUND_PATTERNS directly. Test updated to
verify compound-vs-whole-token matching against the constant.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 08:06:05 +02:00
Ayobami Adegoke cd7c22325b ci: run the Python test suite — CONTRIBUTING.md asks for tests CI never executes (#100)
* ci: run the Python test suite - CONTRIBUTING.md asks for tests CI never executes

CONTRIBUTING.md tells contributors to put Python tool tests in tests/
and run the relevant suites, and tests/ now holds real ones
(test_salary_lookup.py, test_convert_salary_excel.py from #75) - but no
CI job executes them. A suite that never runs in CI can't gate a PR and
silently rots. New python-tests job: unittest discover over tests/,
stdlib only, no new dependencies. Future test files run without any
workflow change.

Also lands tests/test_security_guards.py, which missed #84's merge
window (pushed to the branch as #84 was being merged; the merge took
2a6cb8c, the tests were 260c37a). 13 unittest cases in the existing
tests/ style: each copies the guard script into a synthetic repo tree
and runs it as a subprocess - the same way CI invokes it - asserting
real exit codes and messages. Every forbidden state fails (Bash(*) and
Bash(curl:*) additions, each personal-data gitignore rule removed one
at a time, each forbidden lifecycle script, trustedDependencies,
invalid settings JSON, zero manifests); every non-event passes (dropped
shipped permission, extra ignore rules, benign scripts, hostile
manifest inside node_modules); and the real repo passes its own guards.

22 tests total, all passing locally via the exact command the job runs.

* test: use benign lifecycle-script values in fixtures - AV heuristics flag attack-shaped strings

Review found the curl-pipe-to-sh fixture value matches a real Defender
signature (Trojan:Script/Stealer.HAX!MTB): Windows quarantines the temp
package.json mid-test, making the suite flaky for any Windows
contributor who runs it - while proving nothing extra, since the guard
flags the script KEY and never inspects the value.

Fixture values are now 'echo test' (also in the node_modules-ignored
test, same class of string), with a comment on the key-only test
explaining why the value must stay benign so a future 'make the fixture
realistic' cleanup doesn't reintroduce the quarantine flake. Coverage
is unchanged: same keys, same assertions, 13 tests passing.
2026-07-10 08:06:00 +02:00
Kienne d0846aad1d Add Jobdanmark detail HTML fallback (#115) 2026-07-10 08:05:40 +02:00
Kienne 1bc119dffd Report Jobbank Cloudflare blocking clearly (#114) 2026-07-10 08:05:24 +02:00
Sai Sridhar Tarra 44fa00c8c6 test: add coverage for match_score and search_company (#106) (#109) 2026-07-10 08:05:02 +02:00
Sai Sridhar Tarra 30e8b44759 docs: mention /interview as next step after /apply (#108) (#110) 2026-07-10 08:04:23 +02:00
Sai Sridhar Tarra fea59fd8df docs: document complete seen_jobs.json schema including rank fields (#107) (#111) 2026-07-09 21:32:43 +02:00
student-mayank 429e32f7c0 fix(salary): handle missing/null city & resolve custom baseline percentage bug (#98)
* fix: handle None value for city key in salary lookup

* fix: calculate correct percentage difference for non-100 baselines in salary lookup
2026-07-09 21:14:15 +02:00
fooSynapticandfooSynaptic 6a1240648f docs: document freehire-search in README and SETUP (#103)
The freehire-search CLI is typechecked in CI but was missing from install
instructions, the file tree, and the country-agnostic portal overview.

Co-authored-by: fooSynaptic <19420328+fooSynaptic@users.noreply.github.com>
2026-07-09 21:13:31 +02:00
Ayobami Adegoke 779a33aa20 docs: make CONTRIBUTING.md PR/issue references clickable (#91)
GitHub auto-links #N only in issue/PR comments and commit messages, not
in rendered markdown files - so all 26 precedent references in
CONTRIBUTING.md ((#30), (#55, #56), ...) render as plain text, and the
reader can't jump to the precedent being cited.

Converted every bare #N to a reference-style link ([#N] in prose, URL
definitions collected at the bottom of the file). Definitions use the
/issues/N form, which GitHub redirects to /pull/N when the number is a
PR, so each link resolves correctly whether it points at an issue or a
pull request. No prose changed; existing inline links (the Discussions
URLs) untouched.
2026-07-09 21:12:30 +02:00
Yash Rajeshbhai DarjiandCursor 3bb4688e5d Use CLI detail in scraper Step 2; broaden skill description (#102)
Follow-up to #65 feedback after #52 merged CLI-first search. Step 2 now
uses each portal's detail command for CLI-sourced jobs (WebFetch only for
WebSearch fallbacks). Skill description reflects market-agnostic portal
CLIs instead of Danish-only wording.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-09 21:12:15 +02:00
Mads LorentzenandClaude Fable 5 6e92a4358a docs: add credit norm to CONTRIBUTING.md (#89)
Co-authored-by for incorporated code; named commit/PR mention for
independently written fixes derived from reports. Prompted by the
question in #66.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 06:10:16 +02:00
Sheroy Cooper 22a4438dab docs: prefer python3 in setup examples (#86) 2026-07-09 06:05:16 +02:00
Ilya Strelov b8d35a4b69 Add freehire-search: country-agnostic freehire.dev aggregator skill (#85)
* feat(freehire-search): add country-agnostic freehire.dev aggregator skill

Adds a portal-search skill over the freehire.dev public JSON API — an
open-source IT job aggregator normalizing ~50 ATS platforms across many
markets into one schema. Like linkedin-search it is country-agnostic and
zero-dependency (plain bun + fetch), but it queries a JSON API rather than
scraping HTML, so results carry structured facets (skills/seniority/region).

Honors the portal-skill contract: search + detail commands, --format
json|table|plain, stderr JSON errors with exit 1, backoff on 429/5xx. Reads
are public (no API key) — the same zero-signup bar as linkedin-search. The
hosted-service dependency (best-effort, no SLA) is labeled prominently in
SKILL.md, and FREEHIRE_API_URL swaps the base URL for a self-hosted backend.

Scoped tech-first: triggers cover software/data/engineering roles, where the
faceted filtering is strong; non-tech coverage exists but is still maturing.

Network-free tests (mocked fetch + pure reshape/parse functions); CI matrix
updated to typecheck the new CLI.

* refactor(freehire-search): clarity pass on cli flag parsing

No behavior change. Replace a nested ternary and a comma-operator side effect
in a ternary with explicit if/else, and fix a comment that described facets
while sitting on the alias map.

* refactor(freehire-search): tighten to boundary contracts, trim comments

- Validate/normalize at boundaries, trust the declared types inside: drop the
  redundant '?? []' guards on facet arrays the wire contract already guarantees,
  and the re-filter in buildQuery (commaList already stripped empties).
- Model enrichment as always-present (an unenriched job serializes it as {}),
  removing the '?? {}' guard.
- Replace the positional table-row builder with a declarative column list; add a
  shared shortDate and a labeled-field helper for detail's plain output.
- Extract stringFlag for the string-or-bare-boolean flags (--remote/--query/...).
- Dedup the response parse in apiGet to a single tolerant read (drop safeJson).
- SKILL.md: document partial data + the 'none' unspecified-region facet.
- Trim restating comments to the reference skills' density.
2026-07-09 06:04:35 +02:00
Ayobami Adegoke e16afac7b9 ci: add supply-chain guards — permission allowlist, gitignore rules, manifest checks, pinned actions (#84)
* ci: add supply-chain guards - permission allowlist, gitignore rules, manifest checks, pinned actions

This template's threat model is unusual: it ships pre-approved Claude Code
permissions (.claude/settings.json) and CLI code that every fork user
executes via those permissions. A plausible-looking PR could therefore
ship risk to every forker: widen a permission to Bash(*), weaken the
personal-data gitignore rules, or smuggle code execution into bun install
via a lifecycle script. Nothing checked for these mechanically.

New job security-guards runs tools/security_guards.py (stdlib only):

- settings.json: every permissions.allow entry must be in an exact,
  in-repo allowlist. The guard makes permission changes loud, not
  impossible - a PR that intentionally widens permissions must update
  the allowlist in the same diff, so the widening is explicit and
  reviewable
- .gitignore: the personal-data rules (tracker, documents/**, cv/main_*,
  salary data, seen_jobs) must all still be present - the mirror image
  of the placeholder-integrity job
- .agents/**/package.json: no lifecycle scripts (preinstall/install/
  postinstall/prepare/prepack) and no trustedDependencies, which would
  execute arbitrary code during bun install on users' machines

New job dependency-review (PRs only): actions/dependency-review-action
flags newly introduced vulnerable or malicious dependencies,
fail-on-severity high.

Workflow hardening: explicit top-level permissions: contents: read
(least-privilege token), and all actions pinned to commit SHAs resolved
from the same major tags already in use (checkout v4, setup-python v5,
setup-bun v2), with the tag recorded in a comment.

Honest limit, recorded in the workflow header: a PR can edit this
workflow itself, so these guards catch accidents and casual attempts,
not a determined author. Branch protection with required checks and
human review of workflow/settings diffs remain the real backstop.

Verified locally: positive run passes; injecting Bash(*) into
settings.json, deleting the tracker gitignore rule, and adding a
postinstall script each fail the guard with the intended message, and
reverting restores a clean pass.

* ci: scope dependency-review to upstream PRs - forks lack Dependency graph by default

Verified on a fork: the action fails with 'Dependency review is not
supported on this repository' until Dependency graph is manually enabled,
and forks don't inherit it. Guarded with the same
github.repository == upstream condition the other upstream-only jobs use.
With the graph enabled the action passes, so the config itself is sound.

* ci: probe Dependency graph before dependency-review - warn and pass when unavailable

The upstream PR run showed Dependency graph is disabled on the upstream
repo too (the action hard-fails: 'Dependency review is not supported on
this repository'), not just on forks. Only the repo owner can enable it,
so a hard red X here is friction, not signal.

The job now probes the dependency-graph SBOM endpoint with the workflow
token first: HTTP 200 runs the real review; anything else emits a
::warning:: naming the setting to flip (Settings -> Advanced Security ->
Dependency graph) and passes. Same graceful-skip pattern the workflow
uses for optional tools - the check self-activates the moment the graph
is enabled, no workflow change needed.
2026-07-09 06:03:01 +02:00
Kushida 9e26de2c67 Fix salary tool edge cases (#75)
* fix: handle salary tool edge cases

* fix: preserve Danish salary compounds
2026-07-08 21:12:36 +02:00