Commit Graph
37 Commits
Author SHA1 Message Date
93f37dde0b fix(upstream-triage): skip new upstream files inside a directory the fork removed (#656)
* fix(upstream-triage): skip new upstream files inside a directory the fork removed

A path absent from both HEAD and the merge base was always counted as new
upstream work, so a file added inside a tree the fork deleted (a test added
to a dropped portal) put the commit under "Worth reviewing" with a
cherry-pick that can only conflict. The nearest existing ancestor directory
now decides: present in HEAD means new work in a kept directory, present
only at the base means the fork removed the tree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* changelog: place the #656 entry under [Unreleased]

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Mads Lorentzen <madslorentzen_17@hotmail.com>
2026-10-19 20:04:43 +02:00
09a8c31b24 fix(jobdanmark-search): filter search by the administrative regions locations returns (#650)
* fix(jobdanmark-search): filter search by the administrative regions locations returns

`locations` lists two kinds of region: landsdele such as Østjylland
(category `region`) and the administrative regions such as Region
Midtjylland (category `administrativeRegion`), and the search API
filters each by its own type. `--region` always sent type `region`, so
an administrative region - and SKILL.md's own `--region Midtjylland`
example - returned a successful search with zero jobs.

Add `--administrative-region`, sent as the `administrativeRegion`
filter, map each locations category to its flag in SKILL.md and the
README, and give `--region` examples that exist.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* changelog: place the #650 entry under [Unreleased]

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Mads Lorentzen <madslorentzen_17@hotmail.com>
2026-10-19 20:03:09 +02:00
a869a5a2dc fix(jobnet-search): filter search by the ESCO occupation that occupations prints (#649)
* fix(jobnet-search): filter search by the ESCO occupation that occupations prints

SKILL.md and the CLI README told users to pass the conceptUriDa UUID
from `occupations` to `search --occupation-area` / `--occupation-group`.
Those flags take the numeric area and group codes (10000, 10060), so a
UUID there returned a successful search with zero ads. The API filters
one occupation through its `occupations` parameter, which returns the
830 nurse ads for the sygeplejerske UUID.

Add `search --occupation`, sent as `occupations`, accepting the full
conceptUriDa or its trailing UUID, and point the docs at it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* changelog: place the #649 entry under [Unreleased]

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Mads Lorentzen <madslorentzen_17@hotmail.com>
2026-10-19 20:01:08 +02:00
f6063fd8a1 fix(jobindex-search): reject a --sort other than score or date instead of sending it to the portal (#647)
* fix(jobindex-search): reject a --sort other than score or date instead of sending it to the portal

--sort was a free z.string(), and jobindex does not reject an unknown sort:
--sort newest came back exit 0 in an order that is neither relevance nor
date, so the newest-first sort a caller asked for was silently not applied.
Make it an enum of the two documented values, like --format, so any other
value fails schema validation before a request is sent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* changelog: place the #647 entry under [Unreleased]

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Mads Lorentzen <madslorentzen_17@hotmail.com>
2026-10-19 19:59:10 +02:00
ce2755e5aa fix(linkedin-search): require a value for --query instead of dropping the keywords (#646)
* fix(linkedin-search): require a value for --query instead of dropping the keywords

parseFlags stores a bare -q as true and -q "" as an empty string, and the
search took --query only when it was a non-empty string. A bare flag, an
empty argument or a blank query therefore returned every posting in the
location with exit 0. Reject a present --query without keywords with
BAD_ARG before any request; omitting the flag stays a location-only search,
matching the Danish portals (#573) and freehire-search (#568).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* changelog: place the #646 entry under [Unreleased]

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Mads Lorentzen <madslorentzen_17@hotmail.com>
2026-10-19 19:57:08 +02:00
5f0b799479 fix(linkedin-search): reject an unknown --remote mode instead of searching every workplace type (#645)
* fix(linkedin-search): reject an unknown --remote mode instead of searching every workplace type

workTypeFlag() maps a mode it does not recognise to null, and the search URL
then carries no f_WT. So --remote remote,hybrid, a misspelt mode or a bare
--remote returned onsite postings too, with exit 0 and an empty stderr.
Validate the mode before dispatch and exit 1 with BAD_ARG; remote, hybrid,
onsite and the accepted on-site spelling keep their f_WT values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* changelog: place the #645 entry under [Unreleased]

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Mads Lorentzen <madslorentzen_17@hotmail.com>
2026-10-19 19:55:31 +02:00
971f26499b fix(salary): skip prose notes tabs instead of listing sentences as companies (#644)
* fix(salary): skip prose notes tabs instead of listing sentences as companies

A workbook's notes tab ("Each row is one company in the survey.") has no
table header, so the strict header pass finds nothing and the fallback
takes the first line mentioning a company word. With no salary column
beside it, every following sentence was emitted as a company entry and
showed up in --list-all and lookups. A fallback-detected header with no
salary columns now skips the sheet with a warning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(changelog): record fix for #644

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* changelog: place the #644 entry under [Unreleased]

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Mads Lorentzen <madslorentzen_17@hotmail.com>
2026-10-19 19:52:23 +02:00
1ec64d243c fix(salary): collapse line breaks in wrapped workbook headers (#643)
* fix(salary): collapse line breaks in wrapped workbook headers

A wrapped header cell ("Median\nsalary", "Løn-\nindeks") kept its line
break, so the converter derived a category name containing a newline:
the column no longer paired with its count column, and salary_lookup
printed the category label across two lines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(changelog): record fix for #643

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* changelog: place the #643 entry under [Unreleased]

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Mads Lorentzen <madslorentzen_17@hotmail.com>
2026-10-19 19:50:53 +02:00
51c7dfdbd7 fix(rank): keep every --exclude key out of the sweep when the list has spaces (#641)
* fix(rank): keep every --exclude key out of the sweep when the list has spaces

`sweep --exclude "a_b, c_d"` split on bare commas, so " c_d" matched no
entry and every key after the first was swept. On an --all run that
expired a re-scored entry whose fresh deadline apply then re-ranked, so
the report listed it as both expired and shortlisted. Strip each key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* changelog: place the #641 entry under [Unreleased]

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Mads Lorentzen <madslorentzen_17@hotmail.com>
2026-10-19 19:49:24 +02:00
4cbe273b56 fix(rank): report a tracker that is not UTF-8 instead of a traceback (#639)
* fix(rank): report a tracker that is not UTF-8 instead of a traceback

A job_search_tracker.csv saved with a spreadsheet's plain "CSV" type is
written in the ANSI code page, so one accented company name raised a
UnicodeDecodeError traceback in `rank_state.py candidates` and stopped
/rank at Step 1. Exit 1 with a one-line message instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* changelog: place the #639 entry under [Unreleased]

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Mads Lorentzen <madslorentzen_17@hotmail.com>
2026-10-19 19:32:59 +02:00
9b24a8a123 fix(rank): report malformed scoring results in apply instead of a traceback (#635)
* fix(rank): report malformed scoring results in apply instead of a traceback

A result that is not an object, a non-string key or a non-object scores value
raised AttributeError/TypeError and discarded the batch. They now become
errors rows and valid results in the same file still apply.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* changelog: place the #635 entry under [Unreleased]

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Mads Lorentzen <madslorentzen_17@hotmail.com>
2026-10-19 19:31:34 +02:00
3c97e7bc93 fix(job-key): report a non-string url in --audit instead of a traceback (#634)
* fix(job-key): report a non-string url in --audit instead of a traceback

A numeric, list or object url crashed the duplicate-URL pass with an
AttributeError on .rstrip. Report it with a one-line error naming the key,
as the other malformed-state branches do.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* changelog: place the #634 entry under [Unreleased]

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Mads Lorentzen <madslorentzen_17@hotmail.com>
2026-10-19 19:30:01 +02:00
fc83b3bd53 fix(freehire-search): reject --description-format on detail instead of ignoring it (#648)
* fix(freehire-search): reject --description-format on detail instead of ignoring it

The flag was on detail's known-flag list, but runDetail never reads it and
always prints the detail endpoint's HTML body stripped to plain text, so
detail <slug> --description-format markdown passed the unknown-flag guard,
exited 0 and printed text. Drop it from detail's list so it fails with
UNKNOWN_FLAG before any request; search keeps the flag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* changelog: place the #648 entry under [Unreleased]

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Mads Lorentzen <madslorentzen_17@hotmail.com>
2026-10-19 19:22:32 +02:00
Rudy Mizrahi CelekliandClaude Sonnet 5.5 9c802a4aa2 fix(job-key): report a non-object state file in --audit instead of a traceback (#626)
doc.get("seen", doc) raised AttributeError on a top-level array, and
entry.get("url") raised it on an entry that was not an object, so the
existing "expected an object of job entries" message was never reached.
Guard both and exit 1 with a one-line message.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 08:14:09 +02:00
Rudy Mizrahi Celekli 12b757189a fix(jobnet-search): preserve full occupation identifiers in tables (#586)
* fix(jobnet-search): preserve full occupation identifiers in tables

The documented ESCO concept URI is 72 characters long, but the table renderer cut every identifier to 70, losing the last two UUID characters. Size the identifier column from its contents, retaining the existing minimum width and readable label alignment.

Exercise the actual documented occupations CLI with the README API response served over loopback. Table output must retain the same complete URI and UUID as JSON and plain output; no live portal requests.

* test(jobnet-search): cover Windows occupation fixture newlines
2026-10-18 07:36:47 +02:00
Rudy Mizrahi Celekli 176b675080 fix(framework-version): distinguish marker changes from YAML formatting (#578)
* fix(framework-version): distinguish marker changes from YAML formatting

* fix(framework-version): normalize Windows paths for Git base reads
2026-10-18 07:36:13 +02:00
Rudy Mizrahi Celekli d590aa1b10 fix(reset): clear document entries while preserving scaffolds (#580)
* fix(reset): clear document entries while preserving scaffolds

* fix(reset): preserve scaffolds across Bash versions

* docs(reset): keep document inventory before presentation heading
2026-10-18 07:33:41 +02:00
Rudy Mizrahi Celekli 3b16f80595 docs(jobindex-search): align search examples with the actual CLI contract (#579)
* docs(jobindex-search): align search examples with the actual CLI contract

The posted-today skill example omits the query that the shipped CLI requires, so copying it exits MISSING_REQUIRED. Supply a keyword and make the required flag explicit. Replace stale JSON-endpoint/card-parser search documentation with the implemented HTML Stash route and emitted field behavior.

Run the example read directly from SKILL.md through a real CLI subprocess with loopback HTTP fixtures. Keep a separate query/page routing control; no live portal requests or production-code changes.

* test(jobindex-search): cover Windows documentation newlines
2026-10-18 07:31:12 +02:00
Rudy Mizrahi Celekli 894e3917ba fix(salary): preserve Unicode company identity in lookups (#570)
* fix(salary): preserve Unicode company identity in lookups

* fix(salary): restore accent folding after Nordic spellings

Address MadsLorentzen review feedback on #570: keep Unicode identity normalization and restore ASCII-typed accented-name controls on the documented CLI path.
2026-10-18 07:28:42 +02:00
Rudy Mizrahi Celekli 5a29fdc711 fix(upstream): preserve selected refs in companion commands (#591)
* fix(upstream): preserve selected refs in companion commands

* fix(upstream): quote companion command arguments

* docs(changelog): record fix for #591
2026-10-18 07:26:10 +02:00
Rudy Mizrahi Celekli 239dad775b fix(upstream): distinguish new paths from fork deletions (#585)
* fix(upstream): distinguish new paths from fork deletions

* docs(changelog): record fix for #585
2026-10-18 07:24:01 +02:00
Rudy Mizrahi Celekli a748d89fe6 fix(upstream): flag incomplete framework comparisons (#583)
* fix(upstream): flag incomplete framework comparisons

* docs(changelog): record fix for #583
2026-10-18 07:21:32 +02:00
Rudy Mizrahi Celekli 991bda5dd2 fix(salary): reject non-finite workbook numbers (#572)
* fix(salary): reject non-finite workbook numbers

* docs(changelog): record fix for #572
2026-10-18 07:19:24 +02:00
Rudy Mizrahi Celekli ef4d12711c fix(salary): parse Unicode whitespace grouping (#592)
* fix(salary): parse Unicode whitespace grouping

* docs(changelog): record fix for #592
2026-10-18 07:16:55 +02:00
Rudy Mizrahi Celekli 8cc2efbf21 fix(salary): prefer exact workbook identity headers (#571)
* fix(salary): prefer exact workbook identity headers

* docs(changelog): record fix for #571
2026-10-18 07:14:26 +02:00
Rudy Mizrahi Celekli 652508a929 fix(salary): skip chart-only workbook tabs (#590)
* fix(salary): skip chart-only workbook tabs

* docs(changelog): record fix for #590
2026-10-18 07:11:57 +02:00
Rudy Mizrahi Celekli b7be711ca5 fix(salary): distinguish absent indexes from suppression (#584)
* fix(salary): distinguish absent indexes from suppression

* docs(changelog): record fix for #584
2026-10-18 07:09:27 +02:00
Rudy Mizrahi Celekli 9940a84037 fix(salary): skip whitespace-only workbook company rows (#576)
* fix(salary): skip whitespace-only workbook company rows

* docs(changelog): record fix for #576
2026-10-18 07:07:18 +02:00
Rudy Mizrahi Celekli 0b22e55e8f fix(salary): validate metadata before rendering lookups (#575)
* fix(salary): validate metadata before rendering lookups

* docs(changelog): record fix for #575
2026-10-18 07:05:10 +02:00
Rudy Mizrahi Celekli 060f2dbe78 fix(portal-clis): reject missing Danish option values before dispatch (#573)
* fix(portal-clis): reject missing Danish option values before dispatch

The Bunli parser discards known bare options before schema validation, silently dropping filters or applying defaults. Extend the existing pre-dispatch guards across the four Danish portals while preserving built-in switches, valued and equals-form arguments, and existing unknown-flag errors.

Actual CLI subprocess regressions deny and record every fetch to demonstrate that missing filters reach request construction on master. Valued-option and help controls keep accepted command forms covered without contacting any portal.

* docs(changelog): record fix for #573
2026-10-18 07:02:41 +02:00
Rudy Mizrahi Celekli 129367dbca fix(freehire-search): require values for named search options (#568)
* fix(freehire-search): require values for named search options

* docs(changelog): record fix for #568
2026-10-18 07:00:12 +02:00
Rudy Mizrahi Celekli 2017521d2e test(portal-clis): isolate valid flag controls from live services (#581)
* test(portal-clis): isolate valid flag controls from live services

The LinkedIn and Freehire positive controls spawned live search CLIs and only checked for the absence of BAD_ARG, so attempted portal requests still left a green suite. Use loopback responses and assert successful output plus the constructed freshness, pagination and result-limit contract.

LinkedIn cards are verbatim excerpts from portapulito's actual captured response in PR #153; fixture provenance and original hashes are retained. Four Danish validation suites already stop at required-argument guards and need no change. This isolates these controls rather than claiming a whole-CI egress policy.

* docs(changelog): record fix for #581
2026-10-18 06:57:43 +02:00
Rudy Mizrahi Celekli 23876bfa95 fix(linkedin-search): preserve description paragraph and list breaks (#567)
* fix(linkedin-search): preserve description paragraph and list breaks

Clean rendered description lines separately so whitespace normalization does not erase paragraph and list boundaries in JSON and plain output. Exercise the documented detail CLI against a loopback-served captured response, with field and closed-status controls.

Credits: portapulito diagnosed this behavior and supplied the captured guest-response fixture in upstream PR #153, which was closed as an accidental personalized-fork submission. This independent implementation revives only that correctness fix and its fixture evidence.

* docs(changelog): record fix for #567
2026-10-18 06:55:15 +02:00
Rudy Mizrahi Celekli b2439a8011 fix(jobbank-search): retain all documented repeated filter values (#577)
* fix(jobbank-search): retain all documented repeated filter values

Declare the six array-capable search options repeatable in Bunli metadata. Without it, the CLI parser overwrites earlier occurrences before the handler receives the array, so documented OR filters only search the final value.

Exercise actual search CLI subprocesses through loopback HTTP and assert every RSS and total-count request retains repeated values. Cover all six filters, mixed equals/comma input, and unchanged single-value input; no live portal requests.

* docs(changelog): record fix for #577
2026-10-18 06:52:46 +02:00
Rudy Mizrahi Celekli 31a3320fe9 docs(portal-clis): correct Danish CLI installation paths (#589)
* docs(portal-clis): correct Danish installation working directories

* docs(changelog): record fix for #589
2026-10-18 06:50:17 +02:00
Rudy Mizrahi Celekli 1b22a38d3f fix(upstream-triage): suggest dependent cherry-picks in ancestry order (#574) 2026-10-17 20:04:06 +02:00
Rudy Mizrahi Celekli 889b82bf13 fix: expire scored jobs with passed preserved deadlines (#560) 2026-10-15 11:39:10 +02:00