Changelog
GitHubOgni cambiamento rilevante, release dopo release. Lo stesso file è distribuito nel repository ed è tradotto in tutte le 17 lingue.
Changelog (Italiano)
Questo changelog inizia dalla v1.85.0 — la versione in cui è stata aggiunta la localizzazione italiana. Per le versioni precedenti vedi 🇬🇧 CHANGELOG.md.
[1.236.0] — 2026-09-18
Parità con il padre — career-ops main @ 6a9c84c (VERSION 1.33.0, 128 commit). Due nuove sorgenti, e tre correzioni replicate — due delle quali qui perdevano silenziosamente le offerte.
Aggiunto
Due sorgenti per lo scanner, 92 → 94 (89 EN + 5 RU). Python.org Jobs (pythonorg) legge il feed pubblico della bacheca; l’attribuzione del datore di lavoro è obbligatoria, quindi una riga il cui titolo non nomina alcun datore di lavoro viene scartata invece di essere completata con il nome della voce del portale. Generalist World (generalist-world) esegue lo scraping delle schede della bacheca e fallisce rumorosamente su un markup che non riesce a interpretare — una scheda la cui forma è cambiata solleva un’eccezione invece di leggersi come una bacheca vuota, che è la modalità di fallimento che rende inaffidabile una sorgente ottenuta tramite scraping. Entrambe compaiono nel filtro Sorgente di #/scan, entrambe hanno l’host fissato (regex ancorata, solo HTTPS), ed entrambe sono guidate end-to-end da suite a trasporto fittizio (43 e 36 casi).
Corretto
Un ruolo Workday aperto in più città veniva scartato da un filtro sede. L’endpoint delle liste di Workday risponde a un’offerta multi-sede con un conteggio dove ogni altra offerta porta una sede: "53 Locations". Non è una sede, e buildLocationFilter confronta per sottostringa senza distinzione tra maiuscole e minuscole — quindi un ruolo aperto ad Austin e in altre 52 città non corrispondeva a nessuna voce allow: [austin] e veniva scartato, mentre lo stesso ruolo elencato singolarmente passava. Misurato a monte su tre tenant: 53 su 291 offerte erano segnaposto, quindi questo è il caso ordinario, non uno marginale. Le sedi reali dell’offerta esistono solo nel documento CXS di dettaglio, quindi ogni segnaposto ora costa una GET aggiuntiva verso di esso, con un tetto di 200 per annuncio — oltre il tetto il segnaposto viene lasciato visibile invece che falsificato, perché un’offerta non risolta non deve mai leggersi come risolta. Un dettaglio fallito o senza sede lascia l’offerta esattamente come restituita dalla lista: una "53 Locations" visibile è onesta su ciò che non si è potuto risolvere, mentre una sede svuotata passerebbe silenziosamente ogni filtro. La stessa GET porta anche startDate, quindi un’offerta arricchita viene datata esattamente invece che con la prosa relativa dell’endpoint delle liste ("Posted 30+ Days Ago" → undefined); tutto ciò che non è un semplice YYYY-MM-DD viene lasciato invariato invece di essere passato a Date.parse, il cui fallback non-ISO dipende dall’implementazione.
Un livello dichiarato non fa più collassare due requisizioni in un unico repost. "Insurance Specialist II" è la dichiarazione stessa del datore di lavoro che questa requisizione non è sorella dell’altra — una fascia retributiva, un ambito e un numero di requisizione diversi sotto un unico titolo di base. Il tokenizzatore non riusciva a vederlo: w.length > 3 scarta ogni numero romano fino a VIII e ogni cifra singola, quindi "Insurance Specialist" e "Insurance Specialist II" venivano tokenizzati in modo identico, ottenevano un rapporto di Jaccard perfetto, e il rilevatore di repost segnalava il secondo come una ripubblicazione del primo. role-matcher.mjs di web-ui non aveva alcuna gestione dei livelli, quindi questo porta l’intero meccanismo del padre: i livelli vengono estratti ripiegando romano e arabo su un unico numero ("Nurse II" e "Nurse 2" sono la stessa dichiarazione — e quella coppia prima differiva erroneamente), due livelli dichiarati devono concordare, e un livello presente solo su un lato è trattato come una riscrittura libera a meno che ciascun titolo non porti anche una parola che manca all’altro, il che costituisce due disaccordi e non una decorazione.
Una bacheca Avature fissata a un filtro percorreva comunque l’intera bacheca globale. L’interfaccia di ricerca di Avature genera parametri di faccetta opachi (?42386=[812132] è un filtro paese), e un operatore che fissa una bacheca ristretta in portals.yml si aspetta che la scansione percorra quella. resolveSearch ricostruiva l’URL come ${origin}${path} e scartava del tutto la query string — quindi ogni richiesta, prima inclusa, percorreva l’intera bacheca del tenant: più pagine, più richieste, e offerte che l’operatore aveva deliberatamente filtrato via. Le faccette ora accompagnano ogni richiesta paginata, con il nostro jobOffset applicato per ultimo, così che una faccetta omonima vagante non possa mai dirottare la paginazione.
Sicurezza
devalue 5.8.1 → 5.9.2 (in modo transitivo tramite astro), che chiude l’unico avviso Dependabot aperto — una denial of service su input malformato. Dipendenza solo del sito; nessun codice a runtime modificato.
Note
Non portato, ciascuno con la propria motivazione. Il padre ha dato a lever un timeout di 30s e a manfred un timeout di 25s contro il suo default condiviso di 10s — un no-port: il trasporto di scansione di web-ui consente già 60s (DEFAULT_SCAN_TIMEOUT_MS), quindi entrambe le bacheche hanno qui più margine di quanto la correzione conceda là. Anche il prefisso careers- raddoppiato di iCIMS non può verificarsi qui: web-ui prende l’origin del tenant direttamente da api/careers_url e non costruisce mai un host a partire da un nome di tenant; il suo nuovo percorso di fallback-host è una funzionalità per la configurazione più permissiva del padre, non la correzione di un difetto. deutschebahn e manfred hanno ottenuto il retry-on-transient-abort, che il tetto di 60s di web-ui rende molto meno urgente — annotato invece che portato, così un futuro timeout su una scansione lunga ha già una prima cosa nota da provare. Tutto il resto del delta di 128 commit è CLI, verificatore, dashboard o web/ (il frontend proprio del padre): liveness-browser, check-jd-archive, verify-ats/verify-portals/verify-cv-facts, update-system, set-status, audit-portals, discover-ats, scaffolder, test-all, manifest dei plugin.
Divergenze del fork riverificate dopo il pull: la correzione cirillica \p{L} in providers/telegram-channel.mjs, il providers/telegram.mjs proprio del fork, hermes in web/src/lib/clis.ts. L’upstream career-ops-hq/main è completamente unito (0 commit mancanti). 3066 → 3164 test; la guida ora dichiara 94 adattatori (89 EN + 5 RU) in tutte e 17 le lingue, e la struttura dei titoli resta invariata a 32 H2 / 122 H3.
[1.235.0] — 2026-09-16
Parità con il padre — career-ops main @ 68e6b94 (VERSION 1.33.0, 42 commit). Due correzioni replicate; una delle quali qui perdeva silenziosamente le offerte MokaHR.
Corretto
Una seconda offerta dalla stessa bacheca MokaHR spariva invece di essere aggiunta. url-key.mjs riduce l’URL di un annuncio a una chiave di confronto, e scartava il frammento in modo incondizionato — ragionevole, dato che quasi ogni frammento è solo presentazionale (#apply, #section-2). MokaHR è l’eccezione nel nostro stesso registro: i suoi annunci sono …/social-recruitment/{tenant}/{id}#/job/{n}, dove ogni offerta di un tenant condivide lo stesso percorso e l’id dell’annuncio esiste solo nel frammento. Quindi tutte si normalizzavano sulla stessa chiave. Misurato prima della correzione: incollando due offerte MokaHR diverse nella pipeline si otteneva una sola riga — nessun errore, nessun avviso, nulla da notare; il filtro di freschezza dello scanner (en-scanner.mjs) legge la stessa chiave, quindi una volta che un’offerta di quel tenant era nella cronologia delle scansioni, ogni altra veniva scartata come «già vista». normalizeUrl ora promuove una rotta riconosciuta #/job/{id} o #/jobs/{id} a una chiave di query solo di confronto prima che il frammento venga scartato — MokaHR mantiene il suo nome consolidato mokahr_job_id così le chiavi già scritte restano confrontabili, qualsiasi altro host riceve un _career_ops_fragment_job_id interno. L’id promosso viene aggiunto, non impostato: un URL che porta già quel parametro contiene l’id di un’altra offerta, e sovrascriverlo fonderebbe esattamente le due cose che la chiave esiste per tenere separate. Ogni frammento non riconosciuto continua a collassare, che è la metà che era già corretta.
accept-encoding è ora fissato su ogni richiesta verso le sorgenti. Se non impostato, Node negozia qualunque cosa offra la sua build di undici; da Node ≥ 23 questo include zstd, e la risposta zstd di amazon.jobs torna troncata a esattamente 1024 byte con stato 200 — quindi il fallimento si legge come Unterminated string in JSON at position 1024, come un’API malformata piuttosto che un bug di trasporto. Ogni richiesta ora invia gzip, deflate, br. Sui nostri runtime questo è preventivo, non una riparazione: Node 18/20/22 — la matrice CI e il server (22.22.1) — inviano gzip, deflate, verificato direttamente e non presunto, quindi qui nulla era troncato; ciò che cambia oggi è che viene offerto br, che quelle versioni non richiedono di default ma decodificano correttamente. Il default viene applicato senza distinzione tra maiuscole e minuscole, che è la parte che vale la pena rivedere: i nomi degli header non distinguono maiuscole/minuscole e fetch unisce due voci con lo stesso nome in un unico valore separato da virgole, quindi un chiamante che passava Accept-Encoding: identity altrimenti si sarebbe visto inviare gzip, deflate, br, identity.
Note
Gli header restano un oggetto semplice al confine di fetchImpl, dove il padre passa a fetch un’istanza Headers. È una divergenza deliberata, non una svista: gli helper fetch di web-ui accettano un fetchImpl iniettabile, e 45 suite delle sorgenti leggono opts.headers['User-Agent'] da ciò che catturano — un’istanza Headers non ha accesso tramite parentesi quadre, quindi il port fedele è quello del comportamento (override senza distinzione tra maiuscole/minuscole, vince il chiamante) e non dell’implementazione. L’oggetto del chiamante viene copiato, mai mutato, poiché diverse sorgenti condividono un’unica costante di header. La correzione gemella del padre — applicare uno user-agent predefinito senza distinzione tra maiuscole e minuscole — è un non-port: web-ui non inietta alcuno UA predefinito, ogni sorgente imposta quello richiesto dalla propria bacheca, quindi non esiste un default con cui lo User-Agent di un chiamante possa entrare in collisione.
Due nuove suite e tre nuovi casi fissano la coppia: tests/http-accept-encoding.test.mjs (il valore fissato esatto, ciascuno dei quattro helper fetch, e un override del chiamante in tre capitalizzazioni — verificando che sopravviva una singola chiave accept-encoding, che è ciò di cui il bug di merge ne produceva due) e tests/parsers-pipeline-hash-route.test.mjs, che lavora sulla giuntura visibile all’utente piuttosto che sulla chiave: due offerte MokaHR devono entrambe raggiungere la pipeline, un reincollaggio della stessa offerta in quattro grafie diverse deve comunque collassare in una riga, e un frammento cosmetico non deve diventare un’identità. Tutti sono stati confermati falliti per primi. Il pin vive nel trasporto, non solo in quegli helper: 25 delle 92 sorgenti chiamano direttamente il fetchImpl iniettato e non ci passano mai, quindi lo applica makeTimeoutFetch — il wrapper che entrambi gli scanner iniettano davvero — ed è l’unico punto di passaggio comune a tutte le sorgenti. (Il progetto padre non ha bisogno di un equivalente: providers/_http.mjs è il suo unico trasporto.) Una guardia strutturale ora fa fallire la CI se una nuova sorgente chiama fetch direttamente invece di usare il trasporto che le è stato consegnato, e un caso comportamentale porta greenhouse — una delle 25 — attraverso il wrapper per dimostrare che l’header arriva davvero sul filo.
Non portati, con la motivazione: liveness-browser.mjs (guardia di riutilizzo di Playwright headed — web-ui non guida un browser headed), check-jd-archive.mjs (nessun relay in web-ui), test-all.mjs (l’harness del padre; la divergenza del fork su vpFixtureEnv resta intatta), web/ (il frontend proprio del padre, non rispecchiato), i manifest dei plugin e SIGNATURES.md (solo di repository). batch/batch-runner.sh (le e commerciali vengono sottoposte a escape prima dell’interpolazione sed) e jd-skill-gap.mjs (risolve cv.md rispetto alla radice dei dati) sono inoltrati in sola lettura — web-ui esegue in shell le copie del padre, quindi entrambi arrivano senza alcuna modifica al codice qui, e quello di jd-skill-gap rende più corretto il relay di /api/jds/:id/skill-gap: cv.md ora si risolve dalla radice dei dati invece che dalla directory da cui il processo era stato avviato. Divergenze del fork riverificate dopo il pull: la correzione cirillica \p{L} in providers/telegram-channel.mjs, il providers/telegram.mjs proprio del fork, hermes in web/src/lib/clis.ts. Il conteggio delle sorgenti resta invariato a 92 (87 EN + 5 RU), quindi la guida non è toccata. 3042 → 3066 test.
[1.234.0] — 2026-09-15
Parità con il padre — career-ops main @ 56cce8f (VERSION ancora 1.32.0, 58 commit oltre la 1.32.0). Una correzione replicata su 15 sorgenti, più una guardia perché non possa più tornare.
Corretto
Un singolo id di offerta malformato non svuota più l’intera pagina. encodeURIComponent solleva un URIError su un singolo surrogato UTF-16 isolato, e un payload JSON può portarne uno — JSON.parse('"\\uD800"') lo conserva. Quindici sorgenti costruivano l’URL di ogni annuncio a partire da un id o slug controllato dall’host dentro il ciclo di parsing, quindi un singolo annuncio malformato interrompeva il ciclo e perdeva tutte le offerte di quella pagina; nulla veniva registrato come fallimento della sorgente perché l’eccezione risaliva lo stack prima che il catch per-azienda avesse qualcosa da segnalare. Il padre l’ha corretto in providers/_safe-url.mjs (safeEncodeURIComponent → null su URIError, rilancia qualsiasi altra eccezione) instradandovi l’id di ogni ciclo, scartando solo l’annuncio in questione quando torna null. Quell’helper ora è server/lib/sources/_safe-url.mjs, e la stessa modifica è applicata alle tredici sorgenti replicate — alibaba, arbeitsagentur, bamboohr, feishu-jobs, garena, jibeapply, manfred, meituan, mokahr, phenom, thehub, tkms, vdab — e a due sorgenti solo di web-ui che la nuova guardia ha fatto emergere alla prima esecuzione, jobstreet e trudvsem, che avevano la stessa forma di ciclo e lo stesso throw latente. L’ambito è deliberato: solo gli id di provenienza API passano per l’helper. I valori derivati dalla configurazione mantengono la codifica rigorosa e falliscono rumorosamente — l’office di garena (un segmento di portals.yml) resta sull’urlSegment che solleva eccezioni, mentre il suo id da API riceve un idUrlSegment che restituisce null; il corpName di csod e lo slug di 4dayweek validato da SLUG_RE sono in allowlist per nome, con la relativa motivazione.
Il fallback del titolo di rheinmetall poteva sollevare un’eccezione su un href malformato. Quando una scheda non ha un titolo, questo viene ricostruito dallo slug dell’URL tramite decodeURIComponent, che solleva un’eccezione su una sequenza percentuale non valida nell’href estratto — la stessa forma “una riga malformata uccide la pagina”, questa volta sul lato della decodifica. Ora decodifica in modo difensivo e ripiega sullo slug grezzo.
Il registro delle sorgenti salta i file con prefisso _. L’auto-discovery importava ogni .mjs in server/lib/sources/ tranne se stesso e avvisava per qualsiasi file privo di export const meta — quindi l’helper produceva un avviso all’avvio. Viene adottata la convenzione del padre: un underscore iniziale significa non è una sorgente; questi file non vengono né importati né segnalati. Un file non dichiarato e privo di meta continua a generare un avviso, e il test di discovery ora verifica entrambe le metà (confermato rosso contro il vecchio filtro). Lo script di build del sito sync-assets.mjs conta i file degli adapter su disco con la stessa regola, quindi l’helper non fa più scattare la sua guardia contro le perdite silenziose.
Note
tests/sources-url-encoding-surrogate.test.mjs rispecchia la suite del padre in tre parti: il contratto dell’helper (un surrogato in coppia — una normale emoji — viene comunque codificato; un’eccezione sollevata dal toString stesso del valore è un bug del chiamante e si propaga), il comportamento per-sorgente per tutte e quindici (un id malformato + un id valido → nessuna eccezione, il valido mantenuto, il malformato scartato, ciascuno guidato attraverso il parser puro esportato dalla sorgente o, per thehub, il suo fetcher con un trasporto fittizio), e una guardia a livello di sorgente: ogni file convertito deve importare l’helper e invocarlo, nessun file può referenziarlo senza importarlo, e nessun file non revisionato può costruire un URL di offerta con un encodeURIComponent nudo — una nuova sorgente con la vecchia forma fallisce la CI indicando il numero di riga incriminato. Tutti e quindici i casi per-sorgente sono stati confermati falliti prima del port.
Tutto il resto del delta del padre non è portato, con la motivazione: batch/batch-runner.sh --cli è un flag solo-CLI (web-ui non invoca il batch runner via shell); invite-match.mjs ora risolve set-status.mjs dalla radice del codice — web-ui non lo inoltra; company-funded.mjs ha ricevuto internamente la stessa modifica safe-url e la forma del suo output --json, che /api/company-funded analizza, è invariata; test-all.mjs ha solo spostato un budget di timeout (la divergenza del fork su vpFixtureEnv resta intatta); web/ è il frontend proprio del padre e non è rispecchiato; AGENTS.md/SIGNATURES.md sono solo di repository. Divergenze del fork riverificate dopo il pull: la correzione cirillica \p{L} in providers/telegram-channel.mjs, il providers/telegram.mjs proprio del fork, hermes in web/src/lib/clis.ts. Il conteggio delle sorgenti resta invariato a 92 (87 EN + 5 RU), quindi la guida non è toccata. 3022 → 3042 test.
[1.233.2] — 2026-09-10
Corretto — due avvisi CodeQL js/remote-property-injection ad alta severità.
Corretto
Il ciclo che copia i valori salvati in process.env scorreva Object.entries(safe) — un oggetto costruito dal corpo della richiesta — con una guardia esplicita contro le chiavi di prototipo. Il codice era sicuro, perché safe viene riempito solo da KNOWN_KEYS, ma l’analizzatore non può seguire quel vincolo attraverso due cicli. Ora scorre l’array costante, quindi il nome della proprietà è dimostrabilmente un letterale di modulo. Il comportamento è identico.
Note
Il test che fissava la guardia era un grep del testo sorgente. È stato sostituito da uno strutturale e uno comportamentale che invia __proto__ come JSON grezzo — un letterale di oggetto avrebbe impostato il prototipo invece di creare la proprietà. 3021 → 3022 test.
[1.233.1] — 2026-09-10
Corretto — la guida non descriveva il controllo aggiunto dalla v1.233.0, e la stessa sezione portava ancora istruzioni non più vere da 200 release.
Corretto
DOC-1 — la voce «Usa il valore predefinito (…)» esprime lo stato «la chiave non è impostata»; la differenza dallo scegliere lo stesso valore dall’elenco è invisibile e rilevante. Tre punti nel ### Behavior esistente ×17.
DOC-2 — una riga della tabella non nominava alcuna chiave: la cella conteneva (server uses default UA), che descriveva HH_USER_AGENT, rimosso nella v1.19.0, e invitava a registrarsi su dev.hh.ru — inutile dalla v1.65.0. Le tre menzioni obsolete spariscono; la riga di risoluzione problemi è riscritta, non cancellata: da luglio 2026 hh.ru restituisce HTTP 451 agli IP non russi.
La tabella delle chiavi ne elenca 6 su 45 mentre il titolo promette un registro. Una frase ×17 la dichiara una selezione e rimanda a #/config.
Note
Il nuovo help-recognized-keys.test.mjs confronta la prima colonna delle tabelle di chiavi con il KNOWN_KEYS vivo. 3018 → 3021 test; la guida resta a 32 H2 / 122 H3.
[1.233.0] — 2026-09-10
Corretto — tre riscontri nel percorso di salvataggio di #/config; CONFIG-3 è stato causato dalla correzione stessa della v1.232.1.
Corretto
CONFIG-3 — una scelta deliberata che coincideva con il valore seminato veniva scartata come «non toccata». initial confonde due origini: seminato da .env e seminato da defaultValue, e nel secondo caso l’uguaglianza è ambigua. Corretto registrando seededFromFile accanto a initial; dove il seme viene dal file nulla cambia, dove viene dal default dirty diventa un secondo segnale valido.
Rimuovere una chiave riportava «· 0» perché il server restituiva solo ciò che aveva scritto. Ora risponde anche con removed, e il messaggio conta entrambi.
Un menù a tendina poteva essere impostato ma mai svuotato: nessuno dei 18 aveva un’opzione vuota. Ora ognuno inizia con «Usa il valore predefinito (…)». Una nuova chiave i18n ×17.
Note
Base unitaria invariata (3018); browser 111 → 116.
[1.232.1] — 2026-09-10
Corretto — Salva su #/config scriveva campi che l’utente non aveva mai toccato.
Corretto
CONFIG-2. La v1.57.1 semina un controllo non impostato con il suo defaultValue perché il campo mostri ciò che il server usa davvero, mentre save() consultava dirty solo per i segreti e inviava sempre tutti gli altri campi. Il valore messo lì per la VISUALIZZAZIONE partiva quindi come scelta dell’utente: cambiare un menù a tendina inviava 28 chiavi e ne fissava 18 mai aperte. Ciò che cambia è lo stato di ogni impostazione: da «non impostata, segui il default del progetto» a «fissata nel .env».
Note
Confrontare con cfg.values non basta: un select non impostato mostra il suo defaultValue mentre il valore salvato è ''. Ora si confronta con ciò con cui ogni controllo è stato seminato. Il numero di chiavi scritte torna nel messaggio. 105 → 111 test browser.
[1.232.0] — 2026-09-09
Corretto — due difetti da una passata di QA nel browser; nessuno dei due viene dalla v1.231.5.
Corretto
PROFILE-1 — #/profile faceva scorrere lateralmente l’intera pagina. Il valore era un <div> anonimo con soli stili di carattere inline, quindi il suo overflow-wrap restava normal. Un URL LinkedIn non offre al CSS alcun punto di interruzione: la larghezza min-content è l’intera stringa, e min-width: auto sugli elementi di griglia rifiuta di restringersi oltre — la scheda ha fatto scoppiare la traccia, poi la riga e il documento (23 px di scorrimento reale a 1280 px). Corretto con la classe condivisa .card-value e overflow-wrap: anywhere, più .card-row > * { min-width: 0 }. anywhere, non break-word: solo anywhere conta nel calcolo del min-content.
CONFIG-1 — POST /api/config accettava qualunque valore per un campo select. {"LLM_PROVIDER":"not-a-provider"} restituiva 200 e finiva nel .env; poi nulla falliva, perché il resolver ripiegava in silenzio. LLM_PROVIDERS era già esportato da env-config.mjs — il validatore non lo consultava.
Note
Solo LLM_PROVIDER è validato: il dominio reale dei modelli appartiene al fornitore e cambia tra le nostre release. Un valore già salvato viene tollerato, perché il form rispedisce ogni campo non segreto a ogni salvataggio. Aggiunto anche il descrittore di OLLAMA_API_KEY. 3013 → 3018 test.
[1.231.5] — 2026-09-09
Sicurezza — sette avvisi chiusi, uno dei quali un’esecuzione di codice remoto critica. Nessun file sorgente è cambiato.
Sicurezza
Unito da Dependabot: multer 2.2.0 → 2.3.0 (alta — denial of service per perdita di descrittori di file all’interruzione) e svgo 4.0.2 → 4.1.0 nel sito (alta e media — removeScripts lasciava passare link eseguibili).
Poi altri cinque, emersi dalla riscansione innescata da quelle unioni. Ogni intervallo interessato era già coperto dallo ^ esistente, quindi serviva un aggiornamento, non una modifica al manifesto: astro 7.1.0 → 7.3.2 — critica, esecuzione di codice remoto tramite l’ottimizzazione delle immagini AVIF (corretta in 7.2.8), e un bypass di autorizzazione (7.2.4); sharp 0.35.3 → 0.35.4 — alta, vulnerabilità di libheif; e js-yaml 4.3.1 → 4.3.2 in entrambi i manifesti — alta.
Note
“Solo dipendenze” qui non equivale a “senza rischio”. js-yaml analizza ogni configurazione che l’app legge, multer accetta ogni caricamento, e Astro è avanzato di una versione minore. Verificato: test:ci 3013/3013, suite browser 4/4, e una ricostruzione completa del sito con le stesse 86 pagine e i 17 mirror. npm audit: 0 vulnerabilità.
[1.231.4] — 2026-09-07
Corretto — la v1.231.3 ha sistemato le conseguenze del clic su Doctor, non il momento. Segnalato di nuovo, da mobile e da desktop.
Corretto
withSpinner riscriveva ancora il pulsante mentre la richiesta era in volo. La v1.231.3 gli ha fatto catturare i nodi figli e ripristinarli nel finally, e tornavano davvero — ma il segnale di occupato eseguiva comunque replaceChildren con il testo '⏳ ' + original. Per tutta la durata di doctor.mjs il quadrato da 36 px conteneva quindi ⏳ 🩺Doctor e diventava una pillola larga che rompeva la riga; su desktop appariva come un ⏳🩺Doctor ammassato. Un pulsante con figli elemento non viene più riscritto: il suo segnale è la classe .is-loading, che ora scambia l’icona con una clessidra in CSS, così la geometria non può cambiare: 36 px a riposo, 36 px in volo, 36 px dopo.
Note
La lezione riguarda l’asserzione, non il codice. Il gate della v1.231.3 attendeva che aria-busy sparisse e solo allora misurava: per costruzione vedeva solo lo stato già riparato dal finally. Il nuovo controllo tiene aperta la promessa da sé e campiona a metà volo. 3012 → 3013 test.
[1.231.3] — 2026-09-07
Corretto — cliccare Doctor rompeva la barra superiore su mobile. Segnalato da un telefono il giorno stesso in cui è uscita la v1.231.2.
Corretto
UI.withSpinner appiattiva i figli del pulsante che decorava. Il suo segnale di occupato passava per textContent: salvare button.textContent, scrivere '⏳ ' + original, riscriverlo. Assegnare textContent sostituisce tutti i figli con un unico nodo di testo — e la v1.231.2 aveva appena diviso ogni azione della barra in .btn-ico + .btn-label, con l’etichetta nascosta sotto max-width: 900px. Così il primo tocco su Doctor distruggeva entrambi gli <span> per sempre: le regole mobile non avevano più nulla da agganciare, l’etichetta tornava come testo nudo e il quadrato da 36 px cresceva in una pillola 🩺Doctor che si sovrapponeva all’interruttore del tema. Non guariva da solo — solo un ricaricamento ripristinava il markup. La correzione cattura i nodi figli e li ripristina con replaceChildren; i pulsanti di solo testo, cioè la maggior parte dei 24 punti di chiamata, si comportano esattamente come prima.
- FIND-3 — il campo di ricerca si era ridotto a 8 px su un telefono da 320 px. Portando la barra su una riga sola, il searchbar è rimasto l’unico elemento flessibile, e il changelog precedente lo diceva: «può ridursi a nulla». E si è ridotto a nulla: 8 px di campo a 320 px, 28 px a 340 px, un carattere di un segnaposto da 21. Cliccabile e digitabile, ma illeggibile come campo. Nessun
min-widthlo risolve: a 320 px la barra tiene 32 px di padding, un menu da 40 px e 162 px di azioni, senza 120 px da cedere. Perciò sotto i 420 px — la larghezza in cui il campo supera finalmente i 100 px da solo — si nasconde dietro una lente e si espande su tutta la barra al tocco: 182 px a 320 px, 222 px a 360 px, 252 px a 390 px, senza overflow ad alcuna larghezza. Il pulsante conserva un unico nome accessibile e comunica lo stato conaria-expanded, e lo scambio 🔍/✕ è CSS puro. Sopra i 420 px non cambia nulla. Trovato da una passata di regressione in browser sulla build live.
Note
È la stessa classe di difetto della trappola applyI18n() annotata nella v1.231.2 — codice che assegna textContent a un elemento che ora ha figli elemento — raggiunta da un chiamante diverso. La lezione si generalizza: dividere il contenuto di un elemento in <span> figli rende ogni scrittore di textContent già esistente su quell’elemento un bug latente.
Bloccato due volte: node:vm sul vero withSpinner e un clic in un browser reale a 320 px. Entrambi confermati fallire sul codice precedente. 3009 → 3012 test.
[1.231.2] — 2026-09-06
Corretto — la barra superiore mobile andava a capo su due righe, il server eseguiva un padre più vecchio e il redirect di cvstart.ru dava 404 su qualsiasi percorso che già nominasse una lingua.
Corretto
- La barra superiore mobile torna su una sola riga, azioni a sole icone. I pulsanti venivano forzati su una seconda riga a tutta larghezza: un telefono mostrava
[☰ · ricerca]sopra[🔔 🌙 Doctor Apri Scan]— due pillole larghe su una riga propria. Ora sono quadrati da 36 px (🩺 / ⚡) accanto alla campanella e all’interruttore del tema, e tutto sta su una riga a 320 px. Misurato, non stimato a occhio: la larghezza del documento eguaglia il viewport a 320, 360, 390 e 430 px e su tutta la fascia 560–760 px, dove prima i pulsanti si sovrapponevano alla barra di ricerca. La lingua non incide più sul layout mobile, perché l’etichetta è nascosta e il pulsante è un quadrato fisso; sopra i 900 px le etichette tornano invariate. - Il server eseguiva il padre 1.31.0 benché la versione fosse costruita su 1.32.0.
/api/healthsu resumecraft.ru riportavaparentVersion: 1.31.0. Sincronizzati 95 file di runtime, due rimossi. Rilevato da QA da browser sulla superficie in produzione. cvstart.ruanteponeva/ru/a qualsiasi percorso, senza guardare cosa ci fosse già./ru/helpdiventava/ru/ru/helpe/en/helpdiventava/ru/en/help; entrambi 404. Il primo è ciò che si ottiene copiando un URL dal sito principale; il secondo escludeva del tutto il visitatore anglofono dalla versione inglese. Ora un percorso che nomina già uno dei 16 prefissi passa così com’è,enviene rimosso (l’inglese sta nella radice) e il resto riceve ancora/ru/.
Modificato
- 98 prompt QA superati spostati in
qa/archive/superseded-prompts/.qa/README.mddocumenta dalla v1.137.0 che al livello superiore resta solo il prompt della versione corrente — la regola non è stata applicata per 95 versioni. - Altri cinque prompt hanno lasciato il livello superiore: si dichiaravano attuali senza esserlo.
REGRESSION-FINAL.mdsi definiva «l’unico prompt di regressione autorevole e indipendente dalla versione», ma i suoi §§11–15 sono cinque registri di cicli chiusi (v1.55.x → v1.59.7) e la baseline dell’aiuto dichiarata era 28 H2 / 103 H3 su 16 lingue contro 32 / 122 e 17 oggi: un agente QA che lo seguisse sarebbe caduto al §9 al primo passo.QA-REGRESSION-PROMPT.mdaveva un nome senza versione e contenuti di v1.226.0.QA-REGRESSION-PROMPT-v1.76.0-FULL.mdera fissato 155 release indietro,REGRESSION-PROMPT-FINAL.mdchiude il ciclo v1.58.52 → v1.59.10 eQA-FULL-REGRESSION.mdli duplicava tutti su base v1.131.1. Con loro è andatoPARENT-SYNC-WORKLIST-v1.26.0.md(il progetto padre è ormai a 1.32.0) e il datatoUX-AUDIT-2026-07-06.mdè passato inreports/. Restano quattro driver, con le proprie baseline aggiornate ai valori reali di v1.231.2.qa/README.mdè riscritto per descrivere la cartella che esiste.
Note
- Due trappole di specificità ora commentate in
app.css: le regole desktop sono dichiarate più in basso del bloccomax-width: 900px, quindi a parità di specificità vincono. applyI18n()assegnael.textContent, perciò la chiave i18n è passata a uno<span>interno; sul pulsante avrebbe cancellato l’icona a ogni cambio lingua.- Il primo rsync del padre si è interrotto a metà su un file non più presente in locale, lasciando un deploy parziale che riportava successo. I deploy del padre si verificano per checksum, mai per codice di uscita.
[1.231.1] — 2026-09-06
Corretto — la scansione dei contatori della v1.231.0 ha riscritto il nome dell’account in tutti i link.
Corretto
- Ogni link
Fighter90è diventatoFighter92. La scansione del contatore di sorgenti in v1.231.0 sostituiva90con92sotto una guardia di confine numerico, che scarta correttamente290e901ma corrisponde al90dentroFighter90. 562 link nei 17 README, nei 17 pacchetti di aiuto e nei mirror di aiuto del sito, più 138 nel wiki, puntavano a un account inesistente. Segnalato da un utente, per l’immagine mancante. - Cinque riferimenti storici a versioni sono slittati. La scansione
85 → 87ha trasformatov1.85.0inv1.87.0, inventando una versione mai rilasciata.
Note
- Un confine numerico non basta: protegge un numero dalle cifre vicine, non dall’essere parte di un identificatore o di una stringa di versione. I contatori sono corretti: 92 sorgenti, 87 adattatori EN, 3009 test.
- Il repository padre non è stato toccato.
[1.231.0] — 2026-09-05
Aggiunto — due sorgenti di scansione dal career-ops padre v1.32.0 (Collage, Telegram rigoroso), la modalità REST di Gem e un difetto che il port ha portato alla luce nel codice del padre stesso.
Aggiunto
- Due nuove sorgenti, 90 → 92 (87 EN + 5 RU),
ALL_ADAPTERS85 → 87. Entrambe senza token.- Collage (
provider: collage) — l’API pubblica dei siti annunci di Collage HR. Il segmento di percorso è l’indirizzo del sito che il tenant ha scelto, non uno slug del nome azienda: viene quindi letto daapi:o da un URLsecure.collage.co/jobs/<indirizzo>e mai indovinato — un indirizzo indovinato scansionerebbe la bacheca di qualcun altro. - Telegram (rigoroso) (
provider: telegram-channel) — legge le stesse anteprimet.me/s/<canale>della sorgentetelegramesistente, con lo scambio opposto: un post diventa una riga solo se nomina un datore di lavoro E rimanda a una pagina di annuncio. Digest, post con datore nascosto e post di solo contatto vengono scartati. Bassa copertura per scelta — a monte è stato misurato il 32-77% dei post che passano — ma ogni riga porta un datore reale e un link reale. Entrambe vengono distribuite, come nel padre.
- Collage (
- Gem ha guadagnato una modalità REST opzionale accanto al percorso GraphQL (
api: https://api.gem.com/job_board/v0/<board>/job_posts). Non viene mai derivata da un id di bacheca, e il percorso GraphQL resta il predefinito. Portato dal padre #3783.
Corretto
- Il filtro di luogo di
telegram-channelnon vedeva il cirillico e nominava città come datori di lavoro. IlLOCATIONISH_REdel padre usava\b, un confine solo ASCII che non scatta mai accanto al cirillico: tutte le sue alternative russe erano irraggiungibili eSenior Engineer | Москваrestituiva «Москва» come datore di lavoro. È esattamente la sostituzione di campo che l’intera politica di attribuzione esiste per impedire, e colpiva il pubblico principale del provider. Il port di web-ui usa controlli Unicode, gli stessi che il padre già applica una costante più sotto inROLE_WORD_RE. Fissato da un test che fallisce sulla forma ASCII. Non ancora corretto a monte.
Note
- Il recupero per faccette di Workday nel padre (#3851, #3874) non richiedeva port — web-ui non pagina Workday affatto, un solo POST a
offset: 0. providers/_types.jsè cambiato solo nella documentazione.- Test: 2962 → 3009 (+47).
[1.230.0] — 2026-09-05
Modificato — tre correzioni ai provider dal career-ops padre v1.32.0, e un difetto che il port stesso ha portato alla luce.
Aggiunto
- Welcome to the Jungle accetta un’espressione
filtersdi Algolia applicata lato server (wttj: { filters: "offices.country_code:FR AND contract_type:full_time" }). Una parola chiave da sola non restringe una bacheca globale: a decidere qualimax_hitsrisultati arrivano è il ranking di pertinenza di Algolia, non i filtri dello scanner — i nostri agiscono dopo, su ciò che è già arrivato, quindi tutto oltre il tetto è invisibile per quanto bene corrisponda. Un filtro riduce l’insieme invece di riordinarlo, perciò il tetto per query sale da 200 a 1000. Con un filtro,queriesdiventa facoltativo: la query vuota significa «tutto ciò che passa il filtro». Portato dal padre #3757.
Corretto
- Radancy non avvisa più «truncated» quando un tenant serve semplicemente meno annunci di quanti ne dichiari il suo stesso banner. Il padre ha confrontato
data-total-resultscon nove tenant reali: cinque coincidevano, ma quattro — uno piccolo e uno enorme — hanno consegnato dal 10% al 56% di annunci unici in meno del dichiarato, senza una sola riga duplicata. Il banner sovrastima ciò che l’indice di ricerca del tenant serve davvero, quindi confrontarci il conteggio accumulato dava falsi positivi sulla maggioranza delle bacheche. L’avviso ora compare solo quando il percorso è stato fermato dal nostromax_jobs/max_pages, l’unico caso su cui il chiamante possa agire;totalResultsresta nel messaggio come contesto e non decide mai. Portato dal padre #3839. - Radancy aggira la cache a ogni richiesta di frammento JSON. Uno strato di cache davanti a quella rotta ripete risposte obsolete su alcuni tenant: lo stesso annuncio ricompare pagine dopo mentre un altro non viene mai servito — riprodotto nove volte su nove a monte. Le intestazioni
Cache-Control/Pragmanon hanno cambiato nulla; un parametro casuale per richiesta sì. Deve essere unico per chiamata, mai derivato dal numero di pagina: un parametro deterministico darebbe comunque alla cache una chiave stabile, e con essa la stessa corrispondenza stabile e sbagliata. Portato dal padre #3839. - Gli elenchi di uffici di Greenhouse hanno ordine deterministico. Greenhouse non promette l’ordine di
offices[]tra una risposta e l’altra, e l’insieme ricostruito dall’albero/officessegue il suo ordine di visita. Entrambi finiscono nel campolocationdell’annuncio, quindi una bacheca che riordinasse i propri uffici riscriveva quella stringa — e un annuncio immutato veniva letto come nuovo. Il padre ha corretto l’insieme (#3839); web-ui aveva una seconda istanza dello stesso difetto, assente nel padre — il ripiegooffices[0], usato quando l’annuncio non porta una localizzazione propria — e ora sono ordinati entrambi.
Note
- La correzione iCIMS del padre (#3728) non richiede port. Legge tutte le voci
jobLocationdal JSON-LD della pagina di dettaglio, perché alcuni tenant emettono una voce interamenteUNAVAILABLEprima dell’indirizzo reale. L’iCIMS di web-ui prende la localizzazione dall’HTML della pagina di elenco e non apre mai una pagina di dettaglio. - La modifica a
_http.mjsnon richiede port. EstraeisRefusedRedirectErrorper un secondo consumatore che web-ui non ha; il comportamento diisRetryableErrorè invariato. - La deduplicazione per localizzazione (#3751) non è rispecchiata — vive nella pipeline
scan.mjsdella CLI del padre. - Registro invariato: 90 fonti (85 EN + 5 RU). Test: 2956 → 2962 (+6).
[1.229.0] — 2026-09-03
Aggiunto — quattro fonti di scansione dal career-ops padre v1.31.0: Built In, Feishu Jobs, Garena e MokaHR.
Aggiunto
- Quattro nuove fonti, 86 → 90 (85 EN + 5 RU),
ALL_ADAPTERS81 → 85, tutte senza token. Built In copre i nove mercati della bacheca tech statunitense e non porta query predefinite: una voce senzaqueries:nécategories:non scansiona nulla e lo dice, perché una fonte condivisa non deve portare i termini di ricerca di qualcuno; un mercato fuori elenco viene rifiutato, mai sostituito in silenzio dalla bacheca nazionale. Feishu Jobs accetta due sole forme di host, che usano percorsi diversi per la pagina dell’offerta. Garena è mono-azienda:officemodella il link, non l’elenco. MokaHR restituisce una risposta cifrata (busta AES-128-CBC con la chiave accanto) — offuscamento, non sicurezza, ma senza decifrare non si legge nulla.
Corretto
htmlToTextpoteva lasciar passare nel testo un apri-tag incompleto.safe <img src=x onerror=1usciva comesafe <img src=x onerror=1. Ora ogni decodifica è seguita da una ripulitura e l’apri-tag finale perde la parentesi angolare. Portato dal padre (#3491).
Note
- Dal fix Workday del padre non è stato portato nulla. Recupera i tenant il cui backend blocca la paginazione all’offset 2000 — web-ui non pagina Workday (un POST a
offset: 0, 100 righe). - I fix a
verify-cv-factsemerge-trackernon hanno richiesto port: web-ui esegue quegli script dal checkout del padre. - Test: 2909 → 2956 (+47).
[1.228.5] — 2026-08-30
Corretto — la versione riportata descriveva il file su disco, non il codice in esecuzione.
Corretto
-
/api/healthrileggevapackage.jsona ogni richiesta, quindi un processo obsoleto riportava una versione che non stava eseguendo. Il QA ha trovato un’istanza locale che rispondevaversion: 1.228.4mentre restituiva 404 per/api/ping, una rotta aggiunta in 1.228.3: avviata prima del deploy, serviva il codice vecchio e riportava la versione del file nuovo. È esattamente così che un deploy che copia i file senza mai riavviare sembra riuscito: l’unica stringa che un operatore controlla è quella che non può vedere il problema. Stessa classe del symlink di v1.228.1, dove rsync si interrompeva a metà trasferimento e riportava comunque successo.La versione ora viene catturata al caricamento del modulo, quindi descrive il codice in esecuzione. Un processo obsoleto riporta la versione vecchia — la verità, visibile subito.
parentVersionresta per richiesta: descrive il checkout del progetto padre, che questo processo non carica e che può legittimamente cambiare sotto un server in esecuzione.
[1.228.4] — 2026-08-30
Corretto — la sonda di liveness non autenticata faceva lavoro su disco a ogni richiesta.
Corretto
GET /api/pingleggeva e analizzavapackage.jsona ogni chiamata. È l’unico endpoint raggiungibile senza credenziali, quindi una lettura da disco più un parsing JSON per richiesta è una leva di denial of service consegnata a chiunque — CodeQL l’ha segnalata come mancanza di limitazione di frequenza. La versione ora si legge una sola volta alla registrazione della rotta; non può cambiare senza riavvio, quindi il gestore non fa alcun I/O. Limitare la frequenza avrebbe contenuto il danno; togliere il lavoro toglie la leva.- Il mascheramento del nome reale del titolare del profilo fuori dal loopback è ora fissato da un test.
/api/healthlo sostituiva già conhidden, ma solo perché quella riga condivide la guardiahidden ?? valuecon la radice del progetto. Nulla lo verificava: una modifica che le desse un valore proprio avrebbe fatto trapelare il nome di una persona reale sulla LAN senza che nulla se ne accorgesse.
[1.228.3] — 2026-08-30
Fixed — the docs assistant could not answer in English, Telegram company names arrived truncated, and a monitor had no way to see the service was up.
Fixed
-
The docs assistant answered “no matching help sections were found” in English while the same question worked in Russian. Retrieval was fine; the context builder was not.
buildAskPromptdidif (ctx.length + sec.body.length > MAX_CONTEXT) break— abreak, so one oversized section ended the loop and left the context empty, and the assistant truthfully reported it had nothing. The English §5 had grown to 16 081 bytes against a 14 336-byte budget (partly because v1.228.0 addedtelegram_channelsto it); the Russian §5 was 13 009 and fit. Same ranking in both languages — §5 was first — only the size differed.Two changes: a section past 6 KB is now split at its
###boundaries, sotelegram_channelsis its own retrievable chunk (1 340 bytes) that ranks first in both languages; and an oversized section is skipped or truncated rather than ending the loop. The guide went from 32 chunks to 75. -
A Telegram company label yielded the sentence after it, not the name.
Компания: FinCore Technology (продуктовая разработка, высоконагруженные системы)becameFinCore Technology (продуктовая разработка, высоконагруженны— capped at 60 characters, cut mid-word. Three of 141 rows. The name now ends at the first separator that introduces a description — a spaced dash (soCoca-Colasurvives), a comma, semicolon, pipe or opening parenthesis — and any remaining cap falls on a word boundary. This source attributes a company as fact; a truncated sentence is as wrong as a guessed name, only less obviously so.
Added
GET /api/ping— a liveness probe that leaks nothing.{ ok, version }and no more./api/healthreports absolute paths, the profile owner’s real name and which API keys are set: legitimate for an authenticated operator, never for the public internet. A monitor behind a Basic-auth gate previously had no way to see the service was up at all.
[1.228.2] — 2026-08-30
Fixed — a two-word search in /api/scan-results matched the phrase, not the words, and under-reported badly.
Fixed
-
?q=now matches every term independently instead of the literal phrase. The query was a singleincludes(q)over title + company + location, soпродакт менеджерfound only rows carrying those two words in that exact adjacency — 32 of the 162 that carry both.Менеджер продуктаandМенеджер по развитию продуктаwere invisible to it: the same words, reordered or with filler between them.This is how a narrow match becomes a wrong answer. The Telegram assistant asked for all Продуктовый менеджер reported 9 — faithfully, because 9 was what the endpoint said. The count it quoted was honest; the endpoint’s was not. Word order and the words between are not something a person typing two words intended to pin.
Matching is AND across terms, so widening precision costs nothing: a row must still carry every term.
менеджер разработчикreturns zero rather than the union.
[1.228.1] — 2026-08-29
Fixed — a dangling symlink in the repo that silently truncated deploys.
Fixed
-
.claude/skills/refero-designwas a tracked symlink to a path that has never existed here. It pointed at../../.agents/skills/refero-design, which resolves to.agents/at the repo root; there is no such directory, and the link arrived in the v1.118.0 parity pack from a machine where the surrounding folders nested differently. Anyone cloning the repo has been getting a broken link ever since.It was not inert. rsync aborts on it mid-transfer — a deploy today reported success while the server kept running the previous version, and the mismatch only surfaced because the version was checked afterwards. The parent project’s
node test-all.mjsalso crashes outright when a web-ui checkout sits inside its tree, because its fixture copy walks the working directory and cannot stat the link. Nothing referenced it: the onlyreferomatches in the repo are the Polish word preferowany.
[1.228.0] — 2026-08-29
Aggiunto — i canali Telegram come fonte di scansione e una modalità di query paginata su /api/scan-results.
Aggiunto
-
I canali Telegram sono ora una fonte scansionabile. Un blocco
telegram_channels:inportals.ymlli elenca; ognuno viene letto dall’anteprima pubblicahttps://t.me/s/<canale>. Registro: 85 → 86 fonti (81 EN + 5 RU),ALL_ADAPTERS80 → 81.Telegram non pubblica RSS e la Bot API non può leggere un canale che il bot non amministra: nessuna delle due vie ovvie funziona. L’anteprima
/s/è semplice HTML reso dal server: senza autenticazione, senza JS, senza cookie. Verificato su 16 canali: 15 hanno restituito 20 post ciascuno con una GET nuda.channel:accetta qualsiasi forma di scrittura. Il parser si ancora alle tre cose che un redesign di Telegram sposterebbe per ultime:data-post, il contenitore del testo e la data ISO<time datetime>. -
GET /api/scan-resultsguadagna una modalità di query paginata.?q=<testo>&limit=50[®ion=ru|en][&set=filtered|fresh][&offset=N]restituisce{ total, returned, offset, limit, set, rows }, dovetotalè il conteggio PRIMA della paginazione. Senza parametri la rotta restituisce ancora lo snapshot intero: la tabella#/scandipende da quella forma.
Corretto
- L’assistente Telegram rispondeva da una frazione dei dati. Il suo skill puntava già a
/api/scan-results, ma la rotta sapeva restituire solo lo snapshot intero — circa 2 MB — che non entra nel contesto di un agente. Alla richiesta «trova tutti i Product Manager» ha riportato 9 corrispondenze dove lo snapshot ne aveva centinaia, dicendo di «lavorare con un indice locale limitato». Era vero: vedeva l’endpoint ma non poteva consumarlo. Con la modalità paginata la stessa domanda costa 2 KB e restituisce untotalonesto. Anche lo skill è stato riscritto: chiedeva di riassumere i post nuovi, cioè puntava afreshinvece che afiltered.
Note
- Un post di canale è prosa, non una scheda di lavoro. Non ci sono campi strutturati: il titolo viene dalla prima riga sostanziale, azienda/luogo/retribuzione da etichette esplicite. Separare le offerte vere da pubblicità e digest resta al
title_filteresistente, lo stesso già tarato per le altre fonti. Sono spunti, non un elenco. - L’azienda non viene mai indovinata. Senza etichetta la riga è attribuita al canale (
@rabotaphp): un datore di lavoro sbagliato entra nel tracker come un fatto. - Un parse vuoto è un errore, non «nessuna offerta». t.me risponde con un redirect per un canale privato o inesistente; restituire zero righe si leggerebbe come una giornata tranquilla e nasconderebbe per sempre un refuso nella configurazione.
jobGeeksfa esattamente questo (HTTP 302) e deliberatamente non è configurato. - Il rilevamento del remoto usa una frontiera di parola Unicode anziché
\b, che è solo ASCII — la stessa trappola del filtro sui titoli in v1.227.3, qui colta da un test prima del rilascio. - 15 canali configurati e verificati dal vivo: 299 post, 15/15 canali. 30 nuovi test. Test: 2865 → 2895.
- The README test counts jumped by hundreds, and no, this release did not add them. The localized READMEs carried unit-test totals frozen at 1856 / 1945 / 1955 / 549 / 419 / 284 / 2527 depending on the file — each stale since a different release, and several contradicting the badge at the top of the same page. They now all read 2895, verified by running the suite rather than by copying a badge. Playwright (70 / 90 / 12 → 101), e2e smoke (20 → 21), route modules (12 / 32 → 37), smoke flows (12 → 22) and the test-file count (218 → 329) were stale in the same way. The
Totalrow also repeated the unit count, so it under-summed the three suites below it; it now reads the real sum (3040).
[1.227.5] — 2026-08-29
Corretto — la scansione regionale esauriva la memoria del server e una suite Playwright falliva sul proprio smontaggio.
Corretto
- La scansione regionale uccideva il server per OOM.
ru-scanneraccumulava ogni risultato grezzo di ogni query e deduplicava e filtrava solo alla fine. L’elenco di query è volutamente pieno di quasi sinonimi (Golang,Go разработчик,Golang разработчик,Senior Go…), quindi la stessa offerta torna una volta per query e l’array tratteneva un multiplo del conteggio univoco — descrizioni comprese, che sono il grosso dell’oggetto e che i filtri scartano quasi del tutto. Misurato: 742 MB di heap su un’esecuzione reale da 21 query. Il server limita l’heap di Node a 490 MB (V8 lo dimensiona dai 956 MB di RAM della macchina), perciò la scansione abbatteva il servizio conFATAL ERROR: Reached heap limit— quattro volte in un giorno. Ora deduplicazione e filtraggio avvengono per query, portando la stessa esecuzione a 177 MB, il 76% in meno. I conteggi riportati non cambiano: il totale univoco è portato da unSetdi stringhe URL. net::ERR_SOCKET_NOT_CONNECTEDfaceva fallire la CI a caso. Tre suite Playwright chiamanowindow.stop()per fermare le richieste in volo prima di affermare che non ci sono stati errori di console. Chromium riporta quell’unico atto con tre nomi a seconda dello stato del socket:ERR_ABORTEDall’annullamento,ERR_EMPTY_RESPONSEin volo,ERR_SOCKET_NOT_CONNECTEDquando il socket era già sparito. Il filtro condiviso ne elencava solo due. È emerso nella scansione dei locale sutr, mentreplaywright-formseplaywright-smokeerano a un’esecuzione dallo stesso guasto.
Note
- Perché solo ora. L’elenco è passato da 14 a 21 query con l’aggiunta delle ricerche product manager. La memoria della vecchia implementazione scalava col totale dei risultati: +50% di query ha superato il tetto della macchina.
- La modifica alla deduplicazione preserva il comportamento, ed è testato.
tests/ru-scanner-dedup-order.test.mjsfissa la direzione scomoda — un duplicato successivo che NON passa i filtri deve sfrattare uno precedente che era passato — ed esegue 200 verifiche randomizzate di equivalenza contro la vecchia implementazione. - Il filtro della console resta stretto. Solo la famiglia delle richieste annullate è benigna; connessione rifiutata, errore DNS, reset, 5xx ed eccezioni JS continuano a far fallire l’asserzione.
- Due ipotesi sono state testate e scartate anziché spedite: un timeout di Caddy (configurazione pulita,
flush_interval -1corretto per SSE) e ilrequestTimeoutpredefinito di 5 minuti di Node (una riproduzione in scala ha mostrato che non interrompe una lunga risposta in streaming). La prova vera è stataReached heap limitnel journal. Test: 2860 → 2865.
[1.227.4] — 2026-08-29
Corretto — word: / stem: erano ancora ignorati da content_filter; la v1.227.3 aveva sistemato solo il filtro sui titoli.
Corretto
content_filternon rispettava i prefissi. La v1.227.3 ha insegnato atitle_filteri prefissiword:/stem:del progetto padre, ma ha lasciatocontent_filteral suolower.includes(k)grezzo: lì una voce con prefisso veniva ancora confrontata con il testo letterale"word:java"— lo stesso no-op silenzioso, un filtro più in là.content_filterlegge la DESCRIZIONE dell’offerta e il suo default è sempre stata una semplice sottostringa: per questo un negativo nudojavascarta tutto ciò che si limita a menzionare «JavaScript»; il prefisso è il modo di sottrarre una singola voce a quella regola. Portato ilcompileContentKeyworddel padre, che condivide la macchina dei prefissi ma deliberatamente non ancora le parole corte: il filtro sui titoli ancora le sigle di 2–3 lettere perché «COO» dentro «Coordinator» è sempre sbagliato, mentre una sequenza breve dentro un paragrafo di prosa è spesso voluta (aws,gcp,sql,go).
Note
- Individuato nella revisione IA della PR v1.227.3, che ha notato come il codice non coprisse ciò che il suo stesso commento affermava: diceva «
title_filter/content_filter» mentre era collegato solo il percorso dei titoli. Ora il commento distingue esplicitamente: entrambi i filtri rispettano i prefissi, solo quello sui titoli ancora le sigle. - Verificato anch’esso da quella revisione e invariato: passare la regola delle sigle di 2–3 lettere da
\balla frontiera Unicode è identico per l’ASCII e più severo solo dove serve —COO_leadecoo1restano mantenuti (trattino basso e cifra sono caratteri di parola in entrambi),Coordinatormantenuto, «Директор COO» e «VP «Продукт»» correttamente scartati. Verificati 14 casi limite. - Le voci di
content_filtersenza prefisso mantengono la corrispondenza precedente byte per byte, una descrizione vuota o assente passa ancora, e unword:/stem:vuoto non corrisponde a nulla anziché a tutto. 6 nuovi casi intests/content-filter.test.mjs. Test: 2852 → 2858.
[1.227.3] — 2026-08-29
Corretto — i prefissi word: / stem: del filtro sui titoli venivano ignorati in silenzio, rendendo quelle righe di filtro inefficaci.
Corretto
- web-ui non implementava i prefissi
word:/stem:del progetto padre.title_filter(econtent_filter) confrontano per impostazione predefinita sottostringhe senza distinzione di maiuscole: per questo un negativo nudointernscarta anche «International Product Manager» e «Internal Tools Engineer». Anziché ribaltare quel default — una rottura per ogni installazione già configurata — il padre ha reso la precisione opzionale per singola voce:word:interncorrisponde solo alla parola intera estem:agentdeve iniziare una parola potendo proseguire (separando «Agentforce» da «Reagents»). web-ui non aveva né l’uno né l’altro, quindi confrontava la voce con il testo letterale"word:intern", che non compare in nessun titolo di offerta. La riga diventava un no-op silenzioso: lo stessoportals.ymlfiltrava correttamente dalla CLI e per niente qui, così ogni annuncio di stage escluso raggiungeva comunque la tabella#/scan. Portato daltitle-keywords.mjsdel padre, inclusa la frontiera di parola compatibile con Unicode (\p{L}\p{M}\p{N}_) al posto di\b, che è solo ASCII e corrispondeva a metà parola nei titoli accentati o cirillici.
Note
- Trovato controllando un
portals.ymlreale che aveva appena adottatoword:internproprio perché uninternnudo smettesse di scartare «International Product Manager». Il prefisso funzionava nella CLI; web-ui teneva silenziosamente tutti gli stage. Ora il comportamento è identico. - I default non cambiano. Una parola chiave senza prefisso mantiene il confronto precedente: sottostringa per le frasi e per tutto ciò che contiene caratteri non alfabetici (
.NET,SAP,L&D), ancoraggio ai confini di parola per le sigle di 2–3 lettere. Unword:/stem:vuoto è considerato un refuso e non corrisponde a nulla: come negativo, uno schema vuoto corrisponderebbe a ogni titolo e annullerebbe un’intera scansione per via di due punti sfuggiti. - La regola delle sigle di 2–3 lettere usa ora la stessa frontiera Unicode: prima usava
\b, quindicooeword:coodivergevano sui titoli non ASCII — due grafie della stessa regola. - 6 nuovi casi in
tests/title-filter.test.mjs. Test: 2846 → 2852.
[1.227.2] — 2026-08-29
Corretto — #/config scorreva lateralmente sui telefoni stretti.
Corretto
- MOBILE-1:
#/configandava in overflow orizzontale su viewport ≤ 352 px. Ogni campo di tipo select portava unmin-width: 300pxinline. Uno stile inline batte il foglio di stile, quindi il controllo non poteva restringersi insieme al contenitore: con un viewport da 320 px il bordo destro del select finiva a 353 px, la pagina otteneva una barra di scorrimento orizzontale e i controlli uscivano a destra. Interessati tutti e 18 i select di provider/modello (cfg-llm-providere ognicfg-*-model). Oramin-width: min(300px, 100%)+max-width: 100%, così i comodi 300 px valgono solo quando c’è spazio. Dispositivi alla soglia o sotto: iPhone SE (1ª gen., 320 px), Android datati, schermo esterno del Galaxy Fold.
Note
- Perché tutte le passate mobili precedenti l’hanno mancato. 360 px e 375 px hanno giusto il margine per nascondere l’overflow, e le suite Playwright girano in una finestra da 1280 px. Nulla in CI aveva mai misurato un viewport più stretto di 375 px. Il nuovo
tests/playwright-narrow-viewport.mjspercorre le rotte con form a 320 px e fallisce su qualsiasi overflow orizzontale, nominando nel messaggio l’elemento più largo; un secondo caso fissa che nessun select di#/configsupera il viewport. È stato prima verificato che riproducesse il bug (scrollWidth 353 > clientWidth 320, 18 select con bordo destro 353). Playwright: 99 → 101. - Noto e volutamente non ancora sotto gate: 280 px (schermo esterno del Galaxy Fold) va ancora in overflow di ~5 px, per una causa diversa — contenuto lungo dentro i blocchi
<details>della card di configurazione e la sidebar da 256 px contro un viewport da 280 px, non ilmin-widthinline che questa release corregge. Rifluire a una larghezza che nessun telefono diffuso dichiara è lavoro a parte; la listaWIDTHSdella suite è già pronta. - Corretto anche il prompt QA della v1.227.1, che chiamava la rotta di follow-up
#/modes/followup. Le mode-page registrano lo slug nudo (Router.register(cfg.slug)), quindi la rotta è#/followupe#/modes/followuprestituisce correttamente 404. Solo documentazione. - Suite unitaria invariata a 2846 (la nuova copertura è a livello browser).
[1.227.1] — 2026-08-29
Corretto — la ripartizione delle fonti nella guida non tornava con sé stessa e il banner GitHub veniva stampato come markup grezzo nell’app.
Corretto
- §17 dava un totale e una ripartizione che non sommavano. Tutti i bundle della guida dicevano «il registro
server/lib/sources/include 85 adapter — 78 inglesi + 5 russi». 78 + 5 = 83, non 85; in ja/ko la deriva arrivava a 77 (= 82). Ogni nuova fonte alzava il totale lasciando intatto l’addendo, e i due numeri divergevano da diverse release. Ora la ripartizione è 80 EN + 5 RU in tutte e 17 le lingue. Conta più dell’estetica:docs/help/<lang>.mdè l’UNICO corpus su cui si fonda «Ask the docs», quindi chi chiedeva quante fonti fossero supportate riceveva due numeri contraddittori dalla stessa frase. - La stessa frase fissava un’ancora di versione obsoleta. «A partire da v1.213.0 il registro …» è sopravvissuta in 16 lingue mentre il contatore si muoveva due volte (83 in v1.219.0, 85 in v1.226.0). L’ancora era già stata riparata a mano in v1.210.1 ed è semplicemente derivata di nuovo. Viene rimossa a favore di una formulazione senza versione: il contatore è verificato contro il registro vivo, che non può invecchiare.
- Il banner dei provider veniva stampato come markup escapato in cima a
#/help. v1.225.0/v1.225.1 lo davano per «rimosso nell’app daUI.md()(nessun supporto immagini)». Questo confonde due cose:UI.md()fa escape per primo — esegue l’escape di ogni byte della sorgente prima di qualsiasi trasformazione markdown, che è il confine XSS corretto ma l’opposto della rimozione. Nessuno rimuoveva il banner perché nessuno ci provava, e tutte e 17 le lingue aprivano#/helpcon 268 caratteri di<p align="center"><img src="https://…letterale. Un nuovo ingresso condiviso (server/lib/help-markdown.mjs::stripGithubOnlyBlocks) scarta in lettura i blocchi HTML immagine isolati, sia perGET /api/help/:langsia per il corpus del docs-assistant. I file mantengono il banner, quindi GitHub continua a mostrare la vetrina. - La colonna «#» della bacheca della cadenza era vuota dalla v1.117.0. Leggeva
entry.appNum, mentrefollowup-cadence.mjsemettenum—appNumnon è mai esistito nel payload. Ora vengono letti entrambi. La riga «Next up» della v1.227.0 aveva ereditato lo stesso errore ed è corretta insieme.
Note
- Tutti e quattro sono passati inosservati perché nulla li presidiava. I gate della guida (
canonical-docs-coverage,help-ui,help-ru-config-section) contano le INTESTAZIONI — 32 H2 / 121 H3 — e nessun test aveva mai letto un numero dentro un paragrafo né guardato cosa serve davvero l’ingresso della guida. Due nuove suite chiudono la falla:tests/help-source-counts.test.mjs(il totale dichiarato è pari aSOURCES.length; gli addendi corrispondono alla divisione EN/RU viva e sommano al totale; il paragrafo non fissa versioni) etests/help-banner-strip.test.mjs(il banner resta su disco per GitHub, non sopravvive mai allo strip, la prima riga renderizzata di ogni bundle è la sua intestazione, lo strip resta stretto ed entrambi i consumatori vi passano). Entrambe verificate in rosso contro esattamente questi bug prima del commit. Suite di test: 2837 → 2846. - Nessuna modifica a registro, scanner o contratti di route; le fonti restano 85 (80 EN + 5 RU) e
ALL_ADAPTERS80.
[1.227.0] — 2026-08-28
Aggiunto — SEEK Hong Kong (JobsDB), un filtro solo-in-scadenza sui follow-up e i datori di lavoro riservati mostrano l’agenzia che li ha presentati.
Aggiunto
- JobsDB Hong Kong tramite la sorgente Jobstreet/SEEK esistente. Hong Kong opera con il marchio JobsDB ma poggia sulla stessa piattaforma SEEK dietro lo stesso endpoint v5, quindi non serve una sorgente separata — bastano
hk.jobsdb.comnella allowlist degli host esiteKey: HK-Main. Il registro resta invariato: 85 sorgenti (80 EN + 5 RU) eALL_ADAPTERS80 — è un nuovo MERCATO su un adapter esistente, non un adapter nuovo. - Filtro solo-in-scadenza sulla bacheca della cadenza (
#/modes/followup). Una nuova casella restringe la bacheca a ciò su cui agire adesso. La scadenza si legge dall’urgencygià calcolata dalfollowup-cadence.mjsdel progetto padre (urgent/overdue), mai dallostatusdel tracker, che valeapplied/responded/interviewe non combacerebbe con nulla. Quando non c’è nulla in scadenza, la bacheca indica il follow-up programmato più vicino invece di mostrare un vuoto. La logica vive nel modulo puropublic/js/lib/followup-view.js, specchio delfollowup-view.mjsdel padre. - I datori di lavoro riservati vengono attribuiti all’agenzia che li ha presentati. Un annuncio con datore di lavoro celato viene registrato come
?nel tracker, con il recruiter nella colonnaVia.#/trackermostrava un?nudo, così più righe riservate erano indistinguibili e il risolutore dei loghi non aveva nulla su cui agganciarsi. Ora una riga simile si legge «Riservato · tramite <agenzia>», risolve il logo dal nome dell’agenzia ed è rintracciabile con quel nome nella ricerca. Solo presentazione — il campo canonicocompanynon viene mai sovrascritto con il recruiter. Nuovo modulo puropublic/js/lib/company-presentation.js, specchio delcompany-presentation.mjsdel padre.
Corretto
- Jobstreet/SEEK chiamava un endpoint deprecato. La sorgente puntava ancora a chalice-search v4 (
/api/chalice-search/v4/search), che SEEK ha ritirato; il padre è migrato a/api/jobsearch/v5/searche web-ui non ha mai seguito. Ora ogni voce Jobstreet/SEEK interroga v5. La forma dell’item v5 è letta nativamente (URL costruita daid,locations[0].label,advertiser.description,salaryLabel), il parametro di proiezionesolrFieldsproprio della v4 sparisce, e una voce il cuiportals.ymlfissi ancora il percorso v4 morto conserva l’hostname del suo mercato mentre il percorso viene ricostruito su v5 — senza modifiche da parte dell’utente.
Note
- Parità con career-ops 1.30.0 (HEAD del padre
8d64f65→6cc46a4; il suoVERSIONsegna ancora 1.30.0 — release-please è indietro rispetto amain). Non portato, con motivazioni: riempimento della località da JSON-LD in iCIMS — la sorgente iCIMS di web-ui non ha alcun hookenrichDate()(solo pagine di elenco), quindi non c’è alcun percorso da correggere; stderr CLI non fatale (#1974) — il runner di web-ui decide il fallimento solo dal codice di uscita e non ha mai avuto quell’euristica; tracciamento dei percorsi Turbopack — web-ui non ha bundler; correzionicv-sync-check— rilanciate in sola lettura e fail-soft; sequenzializzazione di Playwright inscan.mjs, risposte Block H, guardie updater/doctor/update-system, il controllo di sintassi ricorsivo, la dashboard in Go e le auto-verifiche aggiunte ajd-skill-gap/verify-cv-facts— superfici solo-CLI che web-ui non invoca, o auto-test senza cambi di comportamento. Suite di test: 2818 → 2837.
[1.226.0] — 2026-08-27
Aggiunto — due sorgenti di lavoro vietnamite: ITviec e CareerViet.
Aggiunto
- ITviec + CareerViet (Vietnam). Due board vietnamite con host fissato si uniscono allo scanner tramite voci
provider: itviec/provider: careerviet: ITviec (itviec.com, tech) e CareerViet (careerviet.vn, ampia copertura). Entrambi sono scraper HTML senza token, con host-pin anti-SSRF, solo-HTTPS, fail-soft per azienda e limiti di pagina. Il registro ora conta 85 sorgenti (80 EN + 5 RU),ALL_ADAPTERS80 — entrambi compaiono nel filtro Sorgente di#/scan.
Note
- Parità con career-ops 1.30.0 (parentVersion 1.29.0 → 1.30.0). Non portato (solo CLI/community o già coperto): la Hired Wall,
/calibrate, clean-markers, pacchetti di template, doctor, pdf/LaTeX e le CLI eval/tracker/scan che web-ui non invoca; il fix_html-to-texteroleFuzzyMatchc’erano già; il prefissotitle-filter stem:è rimandato. Suite di test: 2818.
[1.225.1] — 2026-08-27
Corretto — il banner dei loghi dei provider ora compare nel README e nella guida.
Corretto
- Il banner della v1.225.0 non compariva su GitHub: il file
images/providers.svgautonomo aveva unfont-familynon valido (virgolette doppie annidate) che GitHub rifiuta, l’<img>usava unsrcrelativo (l’HTML<img>non risolve percorsi relativi su GitHub) e al blocco<p>mancava una riga vuota finale. Ora punta a unimages/providers.pngrenderizzato tramite URL assoluto, con separazione a righe vuote; anche l’SVG è corretto in XML valido.
Note
- Solo docs; nessun cambiamento di codice o test. La vetrina di cvstart.org e i riquadri dell’app erano già corretti (SVG inline, non questo file). 2784
[1.225.0] — 2026-08-27
Aggiunto — un banner dei loghi dei provider nel README e nella guida.
Aggiunto
- Banner dei loghi dei provider. Un unico
images/providers.svgautonomo — i 18 provider LLM, con il logo reale del brand (simple-icons, CC0) per gli 11 che ne pubblicano uno e un monogramma con il colore del brand per i 7 restanti — apre ora il README (×17) e la guida in-app. (L’app disegna da sé i riquadri con il logo reale; il banner è reso su GitHub e rimosso nell’app dal renderer sicuro per l’XSS.)
Note
- Solo docs — nessun cambiamento di codice o test; l’SVG è inline (nessuna risorsa remota). Sorgenti di scansione invariate: 83. Suite di test: 2784.
[1.224.0] — 2026-08-26
Modificato — i riquadri dei provider LLM ora mostrano i loghi reali dei brand.
Modificato
- Loghi reali dei provider. I riquadri del provider — campi chiave API nelle Impostazioni, le righe di Utilizzo, il chip della dashboard, il riepilogo “Attivo” delle Impostazioni, il risultato della ⚡ eval e il banner di benvenuto — ora mostrano il logo reale del brand per gli 11 provider che pubblicano un’icona open source (Anthropic · Gemini · OpenAI · Qwen · OpenRouter · GitHub · DeepSeek · Kimi · MiniMax · Mistral · Ollama; SVG a percorso singolo da simple-icons, CC0). I 7 senza icona pubblicata (Hermes · GLM/Z.ai · Grok · Together · Fireworks · BytePlus Ark · Volcengine Ark) mantengono il monogramma con il colore del brand. Sempre sicuro per la CSP per costruzione: il percorso del logo è una costante statica incorporata — nessuna risorsa remota, nessun
innerHTML.
Note
- Nessun cambiamento di rotta o comportamento; la stessa API
ProviderLogo.el(slug).provider-logo.jsè cresciuto (i percorsi dei loghi incorporati). Sorgenti di scansione invariate: 83. Suite di test: 2784.
[1.223.0] — 2026-08-26
Corretto — una valutazione ⚡ live riuscita veniva colorata come errore; inoltre installa il coworker OpenWorker direttamente dal suo URL GitHub o .zip.
Corretto
- Tinta del badge di risultato della ⚡ eval. Una valutazione live riuscita non è più colorata come fallimento. I provider in-process (OpenRouter, DeepSeek, …) non restituiscono un
codedi sottoprocesso, quindi lo strettocode === 0era falso e ogni successo appariva rosso (pur mostrando “exit 0”). La tinta ora segue l’esito — solo un sottoprocesso che esce con codice diverso da 0 lo colora di rosso; un risultato live senza codice èbadge-ok, e il suffisso “· exit N” appare solo con un vero codice di uscita.
Modificato
- Installa il coworker OpenWorker dal suo URL GitHub / .zip. La documentazione del coworker ora descrive l’installer a un comando (
curl … | bash, idempotente — riusa un career-ops / web-ui esistente) e i tre percorsi di installazione in OpenWorker (URL GitHub ·.zip· import di un singolo file) indocs/integrations/openworker.md, la guida in-app §32 (×17) e il README.
Note
- Chiusa una vecchia lacuna di copertura: la sorgente Recruitee ora ha un test end-to-end del fix dell’angolo tra virgolette di v1.214.2 (un
>dentro un attributo di tag non deve trapelare nella descrizione). Sorgenti di scansione invariate: 83. Suite di test: 2783.
[1.222.0] — 2026-08-26
Modificato — i suggerimenti dei campi dei provider estesi (DeepSeek … Volcengine Ark) sono ora localizzati in tutte le 17 lingue.
Modificato
- Suggerimenti dei campi dei provider localizzati. Ogni
config.<slug>Hintdegli 11 provider compatibili con OpenAI aggiunti in v1.216.0–v1.217.0 (DeepSeek · GLM/Z.ai · Kimi · MiniMax · Mistral · Grok · Together · Fireworks · Ollama · BytePlus Ark · Volcengine Ark) — i suggerimenti di chiave API, modello e URL base in Impostazioni → Chiavi API — è ora tradotto in tutte le 17 lingue anziché ripiegare sull’inglese. URL di registrazione, id dei modelli e il marcatore ⚡ della valutazione live sono conservati alla lettera.
Note
- Nessun cambiamento di comportamento — il
hintFallbackinglese infield-specs.jsprotegge ancora una chiave mancante, e un nuovo test di contratto verifica che ognihintKeydei descrittori di campo si risolva in tutte le 17 lingue. Fonti di scansione invariate: 83. Suite di test: 2779.
[1.221.0] — 2026-08-26
Sicurezza — difesa in profondità contro il DNS-rebinding sul percorso di fetch dello scanner.
Sicurezza
- Il core HTTP dello scanner (
fetchJson/fetchTextinserver/lib/http-json.mjs) ora risolve l’host di ogni sorgente sul percorso di rete reale e rifiuta di connettersi se si risolve a un indirizzo privato, loopback, link-local, CGNAT o di metadati cloud — chiudendo un vettore di DNS-rebinding. Gli host dello scanner sono già ancorati a domini pubblici, quindi è difesa in profondità; il percorso degli URL utente aveva già un ancoraggio di connessione più forte (safe-fetch.mjs). La guardia gira solo sul trasportofetchreale — unfetchImpldi test iniettato non viene mai risolto.
Note
- Nessun cambio di comportamento per una scansione sana. Fonti di scansione invariate: 83. Test: 2775.
[1.220.0] — 2026-08-26
Aggiunto — scansiona più categorie di Get on Board da una sola voce.
Aggiunto
- Una voce
getonbrdpuò ora impostarecategories: [programming, operations-management, machine-learning-ai](o un singolocategory:) invece del solo feedprogramming; gli annunci sono deduplicati per URL tra le categorie, con un limite di 12. Le voci esistenti restano identiche (il default restaprogramming).
Note
- Solo server; l’host-pin SSRF (www.getonbrd.com) e
redirect:'error'sono invariati, e uno slug di categoria non valido viene rifiutato prima di qualsiasi richiesta. Fonti di scansione invariate: 83. Test: 2771.
[1.219.0] — 2026-08-26
Aggiunto — Torre entra nello scanner e ora ogni etichetta di provider è corretta.
Aggiunto
- Torre (
provider: torre) — il marketplace di talenti pan-latinoamericano di torre.ai, una ricerca pubblica senza autenticazione con ruoli remoti che non arrivano a Greenhouse/Lever/Ashby; una richiesta limitata per voce. Il registro conta ora 83 sorgenti (78 inglesi + 5 russe). Inoltre, monogramma del provider attivo nel banner di benvenuto.
Corretto
- La scansione a16z Speedrun si fermava troppo presto su una board che ruota (una pagina corta a metà passata veniva presa per l’ultima, troncando a 149 di ~300); ora si fida del
total_pagesdel feed. E il banner di benvenuto risolveva il provider da una mappa di 4 voci (mostrando lo slug grezzo per gli altri 14) — ora usa etichetta+monogramma dei 18; la nota provider nelle Impostazioni passa da 7 a 18.
Note
- La nuova opzione multi-categoria per voce di Get on Board non è ancora collegata qui (richiede un refactor a livello di sorgente). Test: 2768.
[1.218.0] — 2026-08-26
Aggiunto — badge dei provider (e nomi corretti) ovunque venga mostrato un provider.
Aggiunto
- Monogrammi dei provider sul chip della dashboard, nel riepilogo “Attivo” delle Impostazioni e nell’intestazione del risultato della ⚡ valutazione.
Corretto
- Etichette obsolete: tre schermate risolvevano il nome da una mappa di 5 voci (il risultato della valutazione etichettava come “Gemini” ogni provider non-Anthropic); ora usano
ProviderStatus.label(18). Il conteggio delle chiavi mostrava “/ 7” → 18. L’elenco di slug del suggerimentoLLM_PROVIDERha aggiuntoark/arkcnin tutte le 17 lingue.
Note
- Solo client, sicuro per la CSP. Nessuna modifica al server. Fonti di scansione: 82. Test: 2758.
[1.217.1] — 2026-08-26
Rafforzamento dei test — copertura degli endpoint per l’intero catalogo di provider.
Aggiunto
- Un test
run<Provider>parametrizzato che verifica endpoint, modello predefinito e autenticazione Bearer per Kimi, MiniMax, Mistral e Fireworks.
[1.217.0] — 2026-08-26
Aggiunto — Ark si aggiunge all’elenco LLM (18 provider). BytePlus Ark e Volcengine Ark — i modelli Doubao di ByteDance — sono ora provider ⚡ dal vivo, con lo stesso core compatibile con OpenAI e un URL di base per regione.
Aggiunto
- BytePlus Ark (
ARK_API_KEY, internazionale) e Volcengine Ark (ARK_CN_API_KEY, Cina) — Chat Completions compatibile con OpenAI viarunOpenAICompatible(). ImpostaARK_MODEL/ARK_CN_MODEL(un nome di modello Doubao o il tuo id endpointep-…);ARK_BASE_URL/ARK_CN_BASE_URLcambiano regione. Entrambi si uniscono all’ordineauto(per ultimi), con il monogramma nelle Impostazioni e in Utilizzo. L’interfaccia web copre ora 18 provider dal vivo.
Note
- Stesso percorso di configurazione attendibile + controllo schema http(s) del resto (mai il validatore SSRF degli URL di lavoro). Fonti di scansione invariate: 82. Test: 2755.
[1.216.0] — 2026-08-26
Aggiunto — altri nove provider LLM, ciascuno con la propria targhetta di marca. Le tue valutazioni ⚡ dal vivo possono ora usare DeepSeek, GLM (Z.ai), Kimi (Moonshot), MiniMax, Mistral, Grok (xAI), Together, Fireworks o un Ollama completamente locale — a una chiave di distanza, ciascuno segnato da un monogramma accanto al suo campo nelle Impostazioni.
Aggiunto
- Nove provider compatibili con OpenAI. DeepSeek, GLM (Z.ai), Kimi (Moonshot), MiniMax, Mistral, Grok (xAI), Together AI, Fireworks AI e Ollama (completamente locale, senza chiave). Imposta una chiave — o
OLLAMA_BASE_URLper Ollama — nelle Impostazioni e si unisce all’ordineautodopo Hermes; fissane uno conLLM_PROVIDER. Together ospita anche Inkling di Thinking Machines (thinkingmachines/Inkling). L’interfaccia web copre ora 16 provider dal vivo. - Monogrammi dei provider. Una piastrella con l’iniziale nel colore del marchio, sicura per la CSP, segna ogni provider accanto al suo campo chiave nelle Impostazioni e nella sua riga della pagina Utilizzo — SVG inline, nessun logo esterno, nulla lascia il tuo dispositivo.
Note
- Gli URL di base dei provider sono configurazione attendibile che passa per
runOpenAICompatible()con un controllo dello schema http(s) (il loopback di Ollama è consentito), mai il validatore SSRF degli URL di lavoro. Fonti di scansione invariate: 82. Test: 2752.
[1.215.0] — 2026-08-26
Aggiunto — esegui tutta la tua ricerca di lavoro da OpenWorker. Un nuovo coworker guida questa pipeline dall’app di coworker IA open source di Andrew Ng, e la guida in-app ora lo copre così l’assistente Chiedi ai docs può accompagnarti.
Aggiunto
- Coworker OpenWorker. Un coworker senza codice di OpenWorker — Fighter90/career-ops-coworker — esegue tutta la ricerca di lavoro (scansione, punteggio, adattamento, tracciamento, follow-up) e può avviare questa dashboard, tutto dal tuo desktop. È una persona in un solo file distribuita in 17 lingue, verificata installabile con il loader di OpenWorker stesso.
- Guida §32 “Run it from OpenWorker” (tutte le 17 lingue) lo documenta, così l’assistente in-app Chiedi ai docs e il pulsante di aiuto fluttuante possono rispondere. Collegato anche dal footer della landing, dal README, dalla wiki e da
docs/integrations/openworker.md.
Note
- Solo docs + sito — nessuna modifica al codice dell’app. Fonti di scansione invariate a 82. Suite di test: 2742.
[1.214.2] — 2026-08-25
Corretto — la pipeline HTML→testo condivisa (descrizioni Greenhouse / Recruitee) non fa più trapelare attributi di tag nella descrizione quando un valore di attributo contiene un carattere >.
Corretto
- Le descrizioni HTML con
>dentro un attributo vengono ripulite correttamente. Il rimuovi-tag dietro le descrizioni Greenhouse e Recruitee usava un match ingenuo che si fermava al primo>, così un tag come<a title="salary > 100k">lasciava il testo di attributo finale (100k">) nella descrizione. Ora rispetta i valori di attributo tra virgolette (singole o doppie), rimuove il markup prima di decodificare le entità (così una virgoletta codificata dentro un attributo non diventa un falso delimitatore), e preserva ancora un<>letterale in testo semplice.
Note
- Suite di test: 2742. Fonti di scansione invariate a 82. Nient’altro di nuovo dal padre stavolta: le altre modifiche sono ritrasmesse in sola lettura (i fix follow-up / weekly-digest / rejection-latency / salary-gap non richiedono modifiche a web-ui) o non sono superfici web-ui (un plugin esterno X/Xquik BYO-key, fix CLI/verify). Le voci opzionali — descrizioni di dettaglio SmartRecruiters, salto aggregatori in detect-reposts, prefissi di titolo
stem:/word:, irrobustimento anti-DNS-rebinding — restano in coda.
[1.214.1] — 2026-08-25
Corretto — le descrizioni Ashby sono ora limitate alla stessa lunghezza di Greenhouse e Recruitee, così un filtro di contenuto si comporta identico sulle tre board e una board non può gonfiare la cache di scansione.
Corretto
- Le descrizioni Ashby ora rispettano lo stesso limite di lunghezza delle altre board. La v1.214.0 alimentava il corpo dell’annuncio Ashby al filtro di contenuto, ma — a differenza di Greenhouse e Recruitee, che limitano a 4000 caratteri — quello di Ashby era mappato diretto senza limite (alcuni 4× più lunghi). Così la stessa parola di
content_filterpoteva corrispondere su Ashby ma non su Greenhouse solo in base a dove cadeva nel testo, e una board poteva aggiungere centinaia di KB alla cache di scansione caricata da#/scan. Ashby è ora limitato agli stessi 4000 caratteri (il suo testo è già in chiaro, quindi viene troncato, mai ripulito dai tag).
Note
- Suite di test: 2738. Fonti di scansione invariate a 82.
[1.214.0] — 2026-08-25
Corretto — le descrizioni di Greenhouse, Ashby e Recruitee ora alimentano il filtro di contenuto, così i filtri parole / paese / visto corrispondono davvero a quelle board; e “Associate
Corretto
- I filtri di contenuto ora funzionano su Greenhouse, Ashby e Recruitee. Queste board portano ora il testo completo di ogni annuncio come sua descrizione, così un
content_filter(o un filtro parole paese / visto) corrisponde al corpo dell’offerta invece di lasciar passare la board alla cieca. Ashby e Recruitee forniscono il corpo nel payload di lista gratis; Greenhouse lo faceva già. Tutte e tre passano per un’unica pipeline HTML→testo condivisa. - “Associate Director” / “Associate Professor” è senior, non entry-level. Il classificatore di seniority (usato da
skip_tiers:) trattava la parola “associate” come marcatore entry-level anche quando prefissa un titolo senior. Ora legge “Associate” — inclusi i ranghi accademici (Associate Professor, Associate Dean) — come senior, mantenendo le vere varianti junior (Associate Attorney, Associate Editor) come entry-level, e classifica ancora un “Intern, Associate Dean” iniziale come tirocinio.
Note
- Suite di test: 2736. Fonti di scansione invariate a 82. In coda per dopo: arricchimento opzionale delle descrizioni SmartRecruiters (una richiesta di dettaglio per annuncio), il salto delle board
aggregator: truedi detect-reposts e i prefissi di titolo a parola ancoratastem:/word:— ciascuno una configurazione opzionale, non un cambio di comportamento predefinito. L’irrobustimento anti-DNS-rebinding resta in coda per la sua release di sicurezza dedicata.
[1.213.0] — 2026-08-22
Aggiunto — MyCareersFuture, la banca del lavoro nazionale di Singapore, come fonte di scansione. Corretto — le offerte Greenhouse ora portano il testo completo perché i filtri di contenuto funzionino, e le offerte Ashby da remoto non restano più nascoste dietro una sede solo-città.
Aggiunto
- MyCareersFuture (Singapore) (mycareersfuture.gov.sg) — una nuova fonte di scansione senza token per la banca del lavoro pubblica nazionale di Singapore, gestita da Workforce Singapore. Selezionala nel filtro Fonte in
#/scan, o aggiungi un’azienda conprovider: mycareersfuturee una listakeywordsfacoltativa (altrimenti usa i ruoli target del tuo profilo, come Job Bank). Legge l’API di ricerca pubblica, con host fissato, senza chiave.
Corretto
- Le offerte Greenhouse ora sono filtrabili per contenuto. Le board Greenhouse vengono recuperate con il corpo completo dell’annuncio, decodificato in testo semplice come descrizione — così un
content_filter(o un filtro per parole paese/visto) che legge la descrizione ora corrisponde davvero alle offerte Greenhouse invece di lasciarle passare alla cieca. - Le offerte Ashby da remoto non vengono più scartate da un filtro città. Ashby tiene il modello di lavoro (Remote/Hybrid/Onsite) separato dalla città dell’ufficio, quindi un ruolo totalmente da remoto si leggeva ancora come es. “San Francisco” — e un filtro sede che blocca quella città nascondeva un ruolo accettabile. Ora “Remote” viene aggiunto alla sede quando il ruolo è da remoto, e
workplaceTypevince su unisRemoteobsoleto per non etichettare male un ruolo ibrido ancorato all’ufficio.
Note
- Fonti di scansione: 82 (77 inglesi + 5 russe). Suite di test: 2724. Un irrobustimento anti-DNS-rebinding (validare l’indirizzo risolto di un host prima di connettersi) è in coda per una release dedicata — richiede un design proprio di web-ui, non un port diretto.
[1.212.1] — 2026-08-21
Corretto — la landing di cvstart.org contava per difetto le fonti di lavoro dello scanner (mostrava 80 e ometteva Job Bank (Canada)); ora torna a combaciare con le 81 dell’app, e la build del sito fallisce rumorosamente se le due divergono.
Corretto
- Il conteggio “Fonti di lavoro” della landing è di nuovo sincronizzato con l’app. Dopo la v1.212.0, cvstart.org mostrava 80 board e mancava il nuovo chip Job Bank (Canada), mentre app, menu a tendina di scansione e guida elencavano tutti 81. La landing costruisce l’elenco caricando il registro live dello scanner, e una fonte non si è caricata in quella build per come importava una dipendenza YAML — quindi è stata scartata in silenzio. Ora Job Bank carica quella dipendenza in modo pigro, come fa il resto dell’app al momento della scansione, così compare sempre.
- La build del sito ora rifiuta di pubblicare un conteggio di fonti incoerente. Se il registro enumera meno fonti di quante esistano su disco (la firma di una fonte non caricata), la build fallisce con un messaggio chiaro invece di pubblicare in silenzio il numero sbagliato.
Note
- Il comportamento dell’app è invariato — lo scanner ha sempre avuto tutte le 81 fonti; solo la landing era interessata. Fonti di scansione: 81 (76 inglesi + 5 russe) — invariato. Suite di test: 2687.
[1.212.0] — 2026-08-21
Aggiunto — Job Bank (Canada), la job board nazionale federale. Rimosso — EchoJobs (il suo feed è ora bloccato da anti-bot). Corretto — le board Consider restituiscono di nuovo risultati e le offerte Lever multi-sede non nascondono più metà delle loro sedi.
Aggiunto
- Job Bank (Canada) (jobbank.gc.ca) — una nuova fonte di scansione senza token per il servizio nazionale federale per l’impiego del Canada, una board ad alto volume che nessun aggregatore copre bene. Selezionala nel filtro Fonte in
#/scan, o aggiungi un’azienda conprovider: jobbankcae una listakeywordsfacoltativa (altrimenti usa i ruoli target del tuo profilo). Legge il feed ATOM pubblico, con host fissato, senza chiave.
Rimosso
- EchoJobs — ritirata. Il suo feed pubblico è ora dietro protezione anti-bot e non restituisce nulla, quindi tenerla sprecava solo uno slot di scansione.
Corretto
- Le board Consider restituiscono di nuovo risultati. Consider ora richiede un handshake anonimo (una GET che semina un cookie di sessione + token CSRF) prima di accettare la ricerca; senza, la richiesta veniva rifiutata in silenzio e la board sembrava vuota.
- Le offerte Lever multi-sede non nascondono più metà delle loro sedi. Lever mette una città primaria in
locatione il resto inallLocations; leggere solo la primaria faceva sembrare un’offerta aperta a Barcellona E Montevideo come solo-Barcellona (e la faceva scartare per errore da un filtro di sede). Ora entrambe vengono unite.
Note
- Ritmo tra pagine più delicato (250 ms, da 150) sulle board paginate, per cortesia verso i siti carriera single-host. Fonti di scansione: 81 (76 inglesi + 5 russe) — invariato (Job Bank entra, EchoJobs esce). Suite di test: 2685.
[1.211.0] — 2026-08-19
Aggiunto — Yourator, job board tech taiwanese. Corretto — le entità accentate di titolo/azienda ora si decodificano ovunque, e un’azienda con un accento nel nome non viene più segnalata per errore.
Aggiunto
- Yourator (yourator.co) — una nuova fonte di scansione senza token per il mercato del lavoro tech e digitale di Taiwan. Selezionala nel filtro Fonte in
#/scan, o aggiungi un’azienda conprovider: yourator. Legge l’API JSON pubblica (nessuna chiave, nessun browser), percorre ogni pagina della board ed emette il vero link del datore di lavoro di ogni annuncio (il suo ATS) con i parametri di tracciamento rimossi.
Corretto
- Le entità con nome accentato ora si decodificano ovunque. Il decoder HTML condiviso ha acquisito le lettere Latin-1 (
é→ é,ç→ ç, …), quindi una board europea che scriveDéveloppeuroFrançaisnon lascia più quel letterale in un titolo, nel tracker o in un documento generato. (Le maiuscole restano maiuscole —Éè É, non é — e una ricerca come&constructor;ora si risolve in se stessa.) - Un’azienda con un accento nel nome non viene più segnalata per errore per essere sul proprio dominio. “Işık” ora si ripiega in “isik” e corrisponde a isik.com.tr; “Société Générale” corrisponde a societegenerale.com. Il vecchio controllo cancellava le lettere accentate invece di ripiegarle sulla loro base ASCII.
Note
- Fonti di scansione: 81 (76 inglesi + 5 russe). Suite di test: 2667.
[1.210.1] — 2026-08-19
Corretto — i titoli degli annunci e i nomi azienda di Habr Career con “&” o virgolette non arrivano più corrotti.
Corretto
- La fonte Habr Career ora decodifica le entità HTML nel titolo e nel nome azienda prima che proseguano. Le card renderizzate lato server arrivano con entità (“Changellenge >>”, “Demand Forecasting & Inventory Optimization”, “ООО "М-ТЕХ"”), quindi una “&” non decodificata falliva in silenzio il tuo stesso filtro del titolo con “&” — lo stesso sintomo che la release precedente ha chiuso su altre cinque board — e i nomi azienda arrivavano corrotti al tracker e ai report. La decodifica delle entità è ora completa su tutte e sei le fonti interessate.
Note
- Suite di test: 2644.
[1.210.0] — 2026-08-19
Aggiunto — Senjob, la prima job board africana dello scanner (Senegal); corrispondenza dei titoli più precisa su altre cinque board.
Aggiunto
- Senjob (senjob.com) — una nuova fonte di scansione senza token per il Senegal, la prima board africana dello scanner. Selezionala nel filtro Fonte in
#/scan, o aggiungi un’azienda conprovider: senjob. Legge l’elenco pubblico via HTTP semplice (nessuna chiave, nessun browser), fissa ogni richiesta a senjob.com e — analizzando HTML — tratta un elenco che di colpo non restituisce nulla come una board rotta (un errore visibile) anziché un paese senza offerte.
Corretto
- I titoli con “&” non fanno più cadere offerte su cinque board — su beesite, Cornerstone (csod), Hacker News “Who is hiring”, Phenom e TKMS i titoli arrivano con entità HTML, quindi una “&” con escape in un ruolo come “R&D Engineer” falliva la tua stessa parola chiave “r&d” e l’offerta spariva in silenzio (nemmeno un veto “sales & marketing” scattava). Ora i titoli — e le località di Phenom — vengono decodificati prima del filtro.
Note
- Fonti di scansione: 80 (75 inglesi + 5 russe). Suite di test: 2643.
[1.209.0] — 2026-08-17
Aggiunto — la guida nell’app ora copre come registrare l’esito di una candidatura, e «Chiedi alla guida» può portarti lì.
Aggiunto
- La guida del Tracker (§11) ha guadagnato una sezione «Registrare un esito» in tutte le 17 lingue, che illustra il pulsante Esito: scegli cos’è successo (rifiutato / offerta / assunto / rifiutata / ghostato / passato al colloquio), visualizza l’anteprima di cosa farà, poi registra — il che annota il risultato, archivia il CV e la lettera che hai inviato e sincronizza lo Stato della riga per te. L’assistente fluttuante «Chiedi alla guida» legge la guida, quindi ora ti indirizza a quel pulsante invece di suggerirti solo di modificare lo Stato a mano.
Note
- Ogni bundle della guida ora è 31 H2 / 119 H3 (era 118); le guardie di parità sono state alzate. Solo documentazione — nessuna modifica al codice o al comportamento. Suite: 2625.
[1.208.2] — 2026-08-16
Corretto — su un telefono i pulsanti di notifiche e tema non stanno più sopra il campo di ricerca.
Corretto
- La v1.208.1 ha impedito ai pulsanti della barra superiore di sovrapporsi al titolo della pagina, ma su un telefono stretto — pur non essendo il più stretto — e soprattutto nelle lingue con etichette più lunghe, l’intera barra si stipava ancora in una sola riga, così i pulsanti 🔔 e 🌙 potevano finire sopra il campo di ricerca. Ora i pulsanti d’azione (notifiche, tema, Diagnostica, Apri Scan) scendono sempre su una loro seconda riga a tutta larghezza sul telefono, così il campo di ricerca resta pienamente leggibile e niente si sovrappone.
Note
- Sul telefono i pulsanti d’azione della barra passano a una seconda riga a tutta larghezza, eliminando la fragile fascia di “riga quasi piena” in cui il layout distribuiva lo spazio negativo residuo come sovrapposizione. Un guard di Playwright riproduce ora il trigger esatto — una lingua con etichette lunghe nella fascia 565–640px — e verifica che i controlli della barra non condividano mai pixel. Suite: 2621.
[1.208.1] — 2026-08-16
Corretto — su un telefono i pulsanti della barra superiore non si sovrappongono più alla pagina.
Corretto
- La v1.208.0 mandava i pulsanti della barra superiore (Diagnostica, Apri Scan, notifiche, tema) su una seconda riga sugli schermi stretti, ma la barra manteneva un’altezza fissa, così la riga a capo debordava e si posava sul titolo della pagina. Ora la barra cresce per accogliere le sue righe e il contenuto scorre sotto.
Note
- L’
heightfissa della barra è diventata unamin-height, così cresce con il contenuto a qualsiasi larghezza (il desktop è invariato). Un guard di Playwright verifica ora anche che la barra non debordi sulla pagina. Suite: 2621.
[1.208.0] — 2026-08-16
Corretto — l’app ora sta nello schermo di un telefono: niente più scorrimento laterale.
Corretto
- Su uno schermo stretto l’intera app scivolava di lato — la barra superiore, le tabelle, gli articoli di aiuto e le schede delle impostazioni sforavano il bordo destro. Ora ogni pagina sta in qualsiasi larghezza: i pulsanti della barra superiore vanno a capo su una seconda riga, tabelle e blocchi di codice larghi scorrono nel proprio riquadro, l’aiuto impila l’indice sopra l’articolo, le righe di pulsanti/schede vanno a capo, e percorsi o URL lunghi si spezzano invece di allungare la pagina.
Note
- La causa era la classica trappola min-width: auto di flex/grid più un paio di elementi larghi non incapsulati; risolto con
min-width: 0sugli elementi della griglia,overflow-wrapsu markdown/titoli, una tabella markdown scorrevole e l’impilamento della griglia di aiuto al breakpoint mobile. Un guard Playwright verifica 0 overflow orizzontale a 375 px sulle rotte principali.tests/playwright-smoke.mjs. Suite: 2621.
[1.207.2] — 2026-08-16
Corretto — i piani IA e i profili di orientamento non vengono più mostrati come un dump di codice grezzo.
Corretto
- Alcuni modelli avvolgono l’intera risposta in un recinto di codice
markdown …. Quando accadeva, il piano di sviluppo e il profilo di orientamento apparivano come un blocco di codice a spaziatura fissa invece di un documento con titoli ed elenchi. Ora il recinto avvolgente viene rimosso — solo quando avvolge l’intera risposta e il linguaggio è esplicitamentemarkdown/md, così una vera risposta inpython/js/``` senza linguaggio resta intatta.
Note
- Gestito una volta sola nel passo condiviso di pulizia LLM (
cleanLlmMarkdown), così ne beneficiano tutte le route IA e i blocchi di codice interni alla risposta avvolta sopravvivono.tests/llm-output.test.mjs(+3). Suite: 2621.
[1.207.1] — 2026-08-16
Corretto — la landing page non deborda più di lato sui telefoni piccoli.
Corretto
- Su un telefono stretto l’hero — il titolo, la riga introduttiva e il terminale di installazione — poteva essere tagliato sul bordo destro perché un comando di installazione lungo e le colonne del layout non si restringevano allo schermo. Ora stanno in qualsiasi larghezza; il comando di installazione scorre dentro il proprio terminale.
Note
- È stato inoltre irrobustito un controllo E2E instabile che poteva fallire per un 404 transitorio di una risorsa: ora ignora il rumore di rete benigno (favicon / connessione / risorsa fallita) come i controlli vicini, pur continuando a rilevare veri errori di script. Nessun cambiamento nel comportamento dell’app. Suite: 2618.
[1.207.0] — 2026-08-15
Aggiunto — registra l’esito di una candidatura direttamente dal tracker.
Aggiunto
- Ogni riga del tracker ottiene un’azione Esito: scegli cosa è successo (rifiutato, offerta ricevuta, assunto, offerta rifiutata, nessuna risposta, passato al colloquio), aggiungi una nota facoltativa, visualizza in anteprima lo stato risultante e registralo. La registrazione archivia gli artefatti del CV e della lettera inviati e sincronizza il tracker allo stato canonico — un’azione deterministica invece della modifica manuale.
Note
- Nuova
POST /api/outcomeche inoltra la CLI degli esiti:dryRun:trueè un’anteprima in sola lettura (individua la riga, riporta lo stato risultante, non scrive nulla); una chiamata reale lo registra. Sicurezza in scrittura: il tipo di esito è limitato all’insieme noto e ogni campo di testo viene rifiutato se contiene caratteri di controllo prima della chiamata (argomenti come array, spawn — nessuna shell).tests/outcome-route.test.mjs. Suite: 2618.
[1.206.0] — 2026-08-15
Documentazione — la guida in-app ora copre le cinque funzionalità più recenti, in tutte e 17 le lingue.
Aggiunto
- La guida in-app — e l’assistente «Chiedi alla guida» che risponde basandosi su di essa — ora documenta cinque funzionalità aggiunte di recente: Doctor di configurazione (Impostazioni — controlla CV e profilo per lacune e dati di esempio rimasti), Scopri board ATS (Portali — trova automaticamente il portale carriere di un’azienda), il controllo «ancora aperta?» (Tracciamento — se un annuncio è ancora aperto), il suggerimento «riusare un CV precedente?» (CV Studio — segnala quando un CV già adattato va bene per un nuovo annuncio) e il Registro competenze (Analisi — registrare punteggi di autovalutazione). Cinque nuove sottosezioni, tradotte in tutte e 17 le lingue.
Note
- La struttura della guida cresce a 31 H2 / 118 H3, con parità garantita in ogni lingua. Documentazione di riferimento aggiornata:
docs/architecture/API.mddocumenta le cinque route di queste funzionalità, e i contatori di route/versione inCLAUDE.mdedocs/sdd/CONVENTIONS.mdsono aggiornati (36 moduli di route). Suite: 2610.
[1.205.0] — 2026-08-15
Aggiunto — un Registro competenze per annotare i risultati di test/valutazioni.
Aggiunto
- Un nuovo Registro competenze (Analisi → Registro competenze) permette di annotare un’autovalutazione — azienda, piattaforma, competenza, punteggio % e una nota facoltativa — aggiunta a
data/assessments.tsv, con un elenco delle voci precedenti (più recenti prima). Zero token, deterministico; il formato del file è gestito dalla CLI del progetto padre.
Note
- Nuova
GET /api/assessments(inoltra la lista JSON predefinita diassessment-log.mjs; fallimento morbido{available:false}) +POST /api/assessments(scrittura esplicita: campi passati come argomenti array aassessment-log.mjs add). Sicurezza in scrittura: ogni campo testo con un carattere di controllo viene rifiutato (un TAB romperebbe una colonna, un a-capo inietterebbe una riga) → 400 prima di scrivere; punteggio/soglia limitati a 0–100, lunghezze limitate.tests/assessments-route.test.mjs. Suite: 2610.
[1.204.0] — 2026-08-15
Aggiunto — un pannello “Dottore di configurazione” nelle Impostazioni che segnala un CV/profilo incompleto o con dati di esempio.
Aggiunto
- Impostazioni → Dottore di configurazione ora esegue un controllo a zero token del tuo
cv.mdeconfig/profile.ymled elenca i problemi bloccanti (file/campi mancanti) e gli avvisi (dati di esempio residui, metriche fisse) — così cogli una configurazione incompleta prima che indebolisca scansioni e adattamenti. Sola lettura; ri-esegui con un clic.
Note
- Nuova rotta di sola lettura
GET /api/cv-sync-checkche inoltracv-sync-check.mjsdel progetto padre, che stampa testo + un codice di uscita (nessun--json); la rotta effettua un parsing leggero delle sue righe stabiliERROR:/WARN:in{ok, errors[], warnings[]}— a decidere il successo è il banner, non il codice di uscita. Fallimento morbido{available:false}sulle installazioni autonome.tests/cv-sync-check-route.test.mjs. Suite: 2602.
[1.203.0] — 2026-08-15
Aggiunto — un suggerimento “riutilizzare un CV precedente?” in CV Studio.
Aggiunto
- Quando apri un annuncio salvato in CV Studio, l’app ora lo confronta con i tuoi altri annunci salvati (sovrapposizione di parole deterministica, zero token) e ti dice se il più simile basta per riutilizzare quel CV su misura, riutilizzarlo con modifiche o prepararne uno nuovo — così non riparti da zero per un ruolo già mirato.
Note
- Nuova rotta di sola lettura
GET /api/jds/:name/reuseche inoltrajd-similarity.mjsdel progetto padre (sovrapposizione Jaccard + guardia di seniority; JSON{decision, score, reason}) una volta per annuncio precedente (fan-out limitato a 25, vince il migliore); fallimento morbido{available:false}se mancano lo script o gli annunci precedenti.tests/jd-similarity-reuse-route.test.mjs. Suite: 2594.
[1.202.0] — 2026-08-15
Aggiunto — scopri la bacheca ATS di un’azienda da #/portals e inizia a seguirla.
Aggiunto
- Su #/portals, digita il nome di un’azienda e l’app sonda Greenhouse, Ashby e Lever per la sua bacheca pubblica — zero LLM, nessun browser — e mostra le bacheche che esistono e listano ≥1 offerta. Un clic aggiunge la bacheca scelta alle aziende monitorate dallo scanner. Il sondaggio è di sola lettura; la scrittura su
portals.ymlavviene solo al clic su Aggiungi.
Note
- Nuovo
server/lib/discover-ats.mjs(sonda di slug con host fisso e charset validato tramitesafeGetcon DNS fissato, ≤12 sonde/richiesta) +POST /api/portals/discover(sola lettura) ePOST /api/portals/track(scrittura esplicita:withFileLock+ innesto testuale + guardia di ri-parsing + rinomina atomica; solo host ATS noti, idempotente). Riusa il registro di adattatori dello scanner. i18n ×17.tests/discover-ats-resolver.test.mjs+tests/discover-ats-route.test.mjs. Suite: 2588.
[1.201.0] — 2026-08-15
Corretto — un tracker con intestazioni di colonna localizzate o varianti non appare più vuoto.
Corretto
- Se il tuo
data/applications.mdusa intestazioni non inglesi o varianti — spagnoloempresa/puesto/estado/fecha/enlace, oposition/stage/link— il tracker le leggeva con le chiavi sbagliate e mostrava colonne Azienda / Ruolo / Stato / Data / Link vuote. Ora queste intestazioni vengono ripiegate sui nomi di campo canonici e il tracker si visualizza correttamente. Un tracker tutto in inglese viene elaborato come prima.
Note
- Nuova mappa
HEADER_ALIASES+ una piegatura di normalizzazione inparseApplications(server/lib/parsers.mjs); intestazioni sconosciute o già canoniche passano invariate.tests/tracker-header-aliases.test.mjs. Suite: 2563.
[1.200.0] — 2026-08-15
Aggiunto — un controllo “ancora attiva?” con un clic per gli annunci su ATS nel tuo tracker.
Aggiunto
- Su #/tracker, una candidatura il cui URL è un annuncio Greenhouse / Lever / Ashby / Workday / SmartRecruiters mostra ora un pulsante “Ancora attiva?”. Un clic interroga il JSON pubblico dell’ATS stesso — zero token, nessun browser — e mostra Attiva / Scaduta / Sconosciuta, per scovare annunci morti senza aprirli uno a uno. Conservativo per scelta: solo un 404/410 netto vale Scaduta; ogni ambiguità resta Sconosciuta (mai una falsa Scaduta).
Note
- Nuovi
server/lib/liveness-core.mjs+liveness-api.mjse una rotta di sola letturaGET /api/liveness?url=(nessuna scrittura, nessun LLM). Sicuro rispetto a SSRF: l’URL passa perisValidJobUrl, poi l’API dell’ATS è raggiunta solo viasafeGet(DNS fissato) con host fisso e segmenti validati.tests/liveness-core.test.mjs+tests/liveness-route.test.mjs. Suite: 2557.
[1.199.0] — 2026-08-15
Corretto — le tabelle larghe ora scorrono lateralmente invece di essere tagliate.
Corretto
- Nella pagina Scan (e in tutte le altre tabelle — Tracker, Statistiche, Utilizzo, Dashboard) una tabella più larga della finestra veniva tagliata senza barra di scorrimento, lasciando irraggiungibili le ultime colonne. Ora le tabelle larghe mostrano una barra di scorrimento orizzontale quando serve, così ogni colonna resta raggiungibile a qualsiasi larghezza.
Note
.table-wrapinpublic/css/components.cssè passato daoverflow: hiddenaoverflow-x: auto(come il contenitore.reports-scrollesistente); il bordo arrotondato è preservato.tests/table-wrap-scroll.test.mjs. Suite: 2540.
[1.198.0] — 2026-08-15
Aggiunto — i ritentativi di scansione ora usano backoff esponenziale, jitter e rispettano il Retry-After di un limitatore di frequenza.
Aggiunto
- Quando una bacheca di annunci limita la frequenza o fallisce brevemente (HTTP 429 / 5xx) durante una scansione, il ritentativo ora attende con backoff esponenziale + jitter invece di un ritardo breve fisso — una bacheca occupata non viene martellata alla stessa cadenza e i ritentativi concorrenti non si ri-scontrano all’unisono. Un
Retry-Afterdella bacheca viene rispettato (ma limitato, così unRetry-After: 86400ostile non può bloccare l’intera scansione). Gli errori permanenti (404, redirect rifiutati) falliscono ancora subito — invariato.
Note
- Nuove
parseRetryAfterMs()e la puracomputeRetryDelayMs()inserver/lib/http-json.mjs;fetchJsonora cattura.retryAftersu una risposta non-ok efetchJsonWithRetryaccetta unmaxDelayMsopzionale (predefinito 8000).tests/http-json.test.mjs(+9). Suite: 2536.
[1.197.0] — 2026-08-14
Aggiunto — segui una bacheca di annunci di un fondo Getro con la sola careers_url; l’id della collezione si risolve da solo.
Aggiunto
- Una bacheca Getro seguita (b2venture, Earlybird, Point Nine, …) non ha più bisogno di un
getro_collectionnumerico cercato a mano. Fornisci lacareers_urldella bacheca stessa e l’id si risolve automaticamente da quella pagina alla prima scansione — un’unica GET sicura contro l’SSRF legge ilnetwork.idnumerico direttamente dai dati incorporati nella pagina. Ungetro_collectionesplicito ha ancora la precedenza e salta del tutto il recupero.
Note
- Nuove
httpsCareersUrl(),extractCollectionId()e laresolveCollectionId()asincrona inserver/lib/sources/getro.mjs; la pagina della bacheca viene recuperata tramitesafeGet(DNS fissato, dimensione limitata), e l’id risolto resta ancorato all’hostapi.getro.comdaassertGetroUrl. L’adattatore ora corrisponde a una voceprovider: getroche porta unacareers_urlhttps anche senza id.tests/sources-getro.test.mjs(+8). Suite: 2527.
[1.196.0] — 2026-08-14
Corretto (sicurezza) — l’adapter Workday valida un endpoint api in base al suo hostname, non a una sottostringa.
Corretto
- Un valore
api:Workday inportals.ymlora è accettato solo quando il suo hostname èmyworkdayjobs.com(o un sottodominio.myworkdayjobs.com). Il vecchio controllo era una corrispondenza di sottostringa, quindi qualsiasi URL che contenesse soltanto la stringa — es.https://example.com/?x=myworkdayjobs.com— passava e sarebbe stato usato come endpoint. Gli endpoint Workday reali non sono interessati. (Segnalato da CodeQL, #443.)
Note
- Il nuovo
isWorkdayApi()analizza l’URL e verifica l’host (server/lib/portals/adapters/workday.mjs).tests/workday-adapter-endpoint.test.mjs(+1). Suite: 2522.
[1.195.0] — 2026-08-14
Prestazioni (scanner) — il rilevamento dei repost resta veloce su cronologie di scansione grandi.
Prestazioni
- Il rilevamento di annunci duplicati non degrada più a O(N²) su un grande
scan-history.tsv. Il raggruppamento dei titoli per azienda era un ciclo annidato che pagava unroleFuzzyMatchcompleto per ogni coppia; ora è un indice invertito — raggruppa le righe per titolo esatto in una passata, poi fa il match fuzzy solo tra bucket DISTINTI che condividono un token discriminante (non di base). L’output è identico — gli stessi cluster di repost — dimostrato da un test differenziale contro il vecchio algoritmo su 200+ cronologie casuali.
Note
groupRowsByTitleinserver/lib/detect-reposts.mjs(esportato per il test differenziale).tests/detect-reposts-grouping.test.mjs(+2). Suite: 2521.
[1.194.0] — 2026-08-14
Corretto (scanner) — le pagine carriere Workday con URL a segmento singolo ora vengono scansionate correttamente.
Corretto
- L’adapter Workday ora analizza gli URL carriere il cui percorso è un singolo segmento — es.
https://parsons.wd5.myworkdayjobs.com/Search,.../KBR_Careers,.../Careers. Prima il sito ripiegava suExternal, l’adapter colpiva l’endpoint CXS sbagliato e una sonda poteva sembrare sana senza restituire nulla. Ora prende il primo segmento non vuoto del percorso come sito (scartando un prefisso di lingua comeen-US); il caso documentato/en-US/Externalè invariato. (Segnalato in #255.)
Note
- Analisi strutturale del percorso in
server/lib/portals/adapters/workday.mjs.tests/workday-adapter-endpoint.test.mjs(+7). Suite: 2519.
[1.193.0] — 2026-08-14
Aggiunto (statistiche) — una scheda “Silenzio dopo il colloquio” che fa emergere i colloqui da sollecitare.
Aggiunto
- Una scheda Silenzio dopo il colloquio in
#/stats: colloqui rimasti in silenzio oltre una finestra di cortesia (30 giorni di default), unendo i tuoi colloqui attivi e il tracker — con da quanto ciascuno è in silenzio, la data dell’ultimo colloquio e il motivo. Un elenco gentile di sollecito/chiusura; solo suggerimenti, mai un’affermazione di rifiuto. Senza token.
Note
- Nuova rotta
GET /api/stats/rejection-latency(fail-soft{available:false}).tests/stats-rejection-latency-route.test.mjs(+2). +10 chiavi i18n ×17; help-hint di#/statsda 7→8 schede. Suite: 2510.
[1.192.0] — 2026-08-14
Aggiunto (cv-studio) — un controllo “Verifica i fatti del tuo CV” che scova numeri che non hai mai avuto.
Aggiunto
- Una scheda Verifica i fatti del tuo CV in
#/cv-studio: incolla un CV o una lettera su misura e verifica ogni metrica e fatto rispetto al tuo CV, profilo e two-pager reali. Ottieni un verdetto pass / warn / block più le metriche inventate, i fatti non supportati e le frasi vietate / di avviso esatte. Senza LLM; non viene scritto nulla.
Note
- Nuova rotta
POST /api/cv-studio/verify-facts: scrive il testo in un file temporaneo usa-e-getta ed esegueverify-cv-facts.mjs, fidandosi del verdetto JSON anche se lo script esce con 1 su un block.tests/cv-studio-verify-facts-route.test.mjs(+4). +15 chiavi i18n ×17. Suite: 2508.
[1.191.0] — 2026-08-14
Aggiunto (statistiche) — una scheda “Cosa imparare dopo” che ordina le competenze da imparare prima.
Aggiunto
- Una scheda Cosa imparare dopo in
#/stats: un riepilogo su tutto il tracker — le competenze mancanti che più spesso hanno affondato una bassa compatibilità, pesate (per 5−punteggio di compatibilità su ogni report) e in livelli Critical / High / Medium — più quelle già coperte dal tuo CV/profilo. Sola lettura, solo suggerimenti, senza token.
Note
- Nuova rotta
GET /api/stats/upskill(campo{ error }quando i dati sono pochi; fail-soft{available:false}).tests/stats-upskill-route.test.mjs(+3). +15 chiavi i18n ×17. Suite: 2504.
[1.190.0] — 2026-08-14
Aggiunto (tracker) — un pannello “Cronologia azienda” che ti dice quali aziende ti rispondono davvero.
Aggiunto
- Una scheda Cronologia azienda su
#/tracker: scegli un’azienda e ottieni prove in sola lettura — quanto è stata reattiva con te (in silenzio con te / misto / ha già risposto) e se lo stesso ruolo viene ripubblicato — unendo tracker, follow-up e cronologia di scansione. Nessun token; lo scanner non viene mai chiamato.
Note
- Nuova rotta
GET /api/stats/company-history[?company=](fail-soft{available:false}).tests/stats-company-history-route.test.mjs(+3). +18 chiavi i18n ×17. Suite: 2501.
[1.189.0] — 2026-08-14
Corretto (scanner) — i livelli di anzianità scritti in numeri romani ora contano anche nei titoli non latini.
Corretto
- Il classificatore di livello dietro
skip_tiersora legge un suffisso di livello in numeri romani (I / II / III / IV / V) dopo la parola del ruolo in qualsiasi scrittura — “Инженер III”, “エンジニア I”, “Ingénieur IV” — non solo dopo parole ASCII. Prima, un numero di livello dopo una parola non latina veniva ignorato e l’offerta cadeva su mid, quindiskip_tiers: [senior]o[entry]le mancava.
Note
- Lookbehind indipendente dalla scrittura in
server/lib/classify-tier.mjs; rimosso un matcherSr.duplicato morto.tests/classify-tier.test.mjs(+1). Suite: 2498.
[1.188.0] — 2026-08-14
Corretto (UI) — i pulsanti d’azione principali non sono più attaccati al sottotitolo della pagina.
Corretto
- La riga di azione / controllo principale in Riepilogo settimanale dei colloqui, Aziende finanziate, Portali, Piano di carriera e Orientamento professionale ora ha un margine superiore adeguato, così il pulsante respira sotto il sottotitolo invece di appoggiarvisi.
Note
- Guardia di regressione
tests/lead-row-top-margin.test.mjs(+5). Suite: 2497.
[1.187.0] — 2026-08-14
Corretto (scanner) — l’impostazione skip_tiers funziona di nuovo: le offerte che hai chiesto di saltare per seniority vengono scartate.
Corretto
- Un elenco
skip_tiers:inportals.yml(es.skip_tiers: [intern, entry]) è ora rispettato dalla scansione. Il titolo di ogni offerta è classificato in un livello (intern / entry / mid / senior) e scartato se il livello è nel tuo elenco. Prima la scansione applicava i filtri titolo / località / contenuto / affidabilità ma senza filtro di livello, quindiskip_tiersveniva ignorato in silenzio. I titoli senza parola di livello ricadono su mid (quindiskip_tiers: [mid]scarta anche la maggior parte delle offerte comuni), e il classificatore legge la parola di livello PIÙ A SINISTRA.
Note
- Nuovo modulo puro
server/lib/classify-tier.mjs(classifyTier+buildTierFilter), collegato alle catene di filtri degli scanner EN e RU.tests/classify-tier.test.mjs(+7). Suite: 2492.
[1.186.0] — 2026-08-14
Aggiunto (CV Studio) — un pannello “Divario di competenze”: quali competenze richieste da un’offerta il tuo CV nomina, implica o gli mancano.
Aggiunto
- Un nuovo pannello Divario di competenze in CV Studio. Scegli una descrizione di lavoro salvata e classifica ogni competenza richiesta in nominata nel tuo CV, implicita nel tuo CV o mancante — confronto di parole senza LLM, nulla viene scritto. Una nota di bassa affidabilità appare quando l’offerta non aveva una sezione requisiti chiara.
Note
- Nuovo
GET /api/jds/:name/skill-gap(il nome dell’offerta è sanificato e confermato sottojds/prima di diventare un argomento; fallback morbido a{available:false}senza lo script). +13 chiavi i18n ×17. Test:tests/jds-skill-gap-route.test.mjs(+4, incl. rifiuto del path-traversal). Suite: 2485.
[1.185.0] — 2026-08-14
Aggiunto (statistiche) — una scheda “Imbuto e velocità”: come il tuo imbuto si confronta col mercato e quanto velocemente avanzi tra le fasi.
Aggiunto
- Una nuova scheda Imbuto e velocità in Statistiche mostra i tuoi tassi di risposta e colloquio accanto agli intervalli di riferimento di mercato (con le avvertenze su campione piccolo e bias di selezione), una lista d’attesa delle candidature in corso oltre la finestra tipica di prima risposta, e i giorni mediani per fase (Candidato → Risposto → Colloquio → Offerta) — le righe lente sono censurate a destra per non distorcere le mediane. Sola lettura e zero token; legge solo il tuo tracker.
Note
- Nuovo
GET /api/stats/funnel(fallback morbido a{available:false}senza lo script). +18 chiavi i18n ×17. Test:tests/stats-funnel-route.test.mjs(+2). Suite: 2481.
[1.184.0] — 2026-08-14
Corretto (UI) — le tessere di azione rapida della Dashboard ora si allineano in una griglia uniforme.
Corretto
- Nella Dashboard (Centro di comando) un gruppo di 3 tessere veniva reso più largo di uno da 4, lasciando le sezioni con un bordo destro irregolare. Ogni gruppo usa ora colonne di uguale larghezza (4 su schermo ampio, fino a 3 / 2 / 1 al restringersi della finestra), così tutte le tessere hanno la stessa dimensione e i loro bordi destri si allineano.
Note
- Solo CSS (
.qa-grid:repeat(N, minmax(0,1fr))fisso invece diauto-fill). Protetto datests/dashboard-grid-align.test.mjs(+2). Suite: 2479.
[1.183.0] — 2026-08-14
Aggiunto (scanner) — rilevamento duplicati più intelligente: la stessa offerta ripubblicata con un link di tracciamento non compare più due volte.
Aggiunto
- Lo scanner ora riconosce un’offerta tramite una chiave URL canonica, così la stessa offerta ripubblicata con un parametro di tracciamento (
?utm_…,gclid, …), suhttpvshttps, o con una barra finale /#frammentoè trattata come l’unica offerta che è — nessuna riga duplicata nei risultati o nel pipeline e nessuna valutazione sprecata su un’offerta già vista. Offerte davvero diverse (un id funzionale mantenuto comegh_jid) contano ancora separatamente.
Note
- Nuovo
server/lib/url-key.mjs, collegato al dedup di entrambi gli scanner e allo scrittore del pipeline. Normalizza volutamente poco — non fonde mai due offerte distinte. Test:tests/url-key.test.mjs(+5),tests/parsers.test.mjs(+1). Suite: 2477 (+6).
[1.182.0] — 2026-08-14
Corretto (scanner) — le fasce salariali ora si leggono uguali in ogni lingua.
Corretto
- Gli importi salariali nelle righe di scan e tracker usano i simboli neutri ≥ e ≤ (es.
≥ 120000 EUR,≤ 90000) invece delle parole inglesi “from” / “up to”, che trapelavano non tradotte nelle interfacce non inglesi. Vale per ogni bacheca con fascia a un solo lato (Getro, Remotli, Manfred, Agentic Jobs, JustJoin, Jobicy); le fasce a due lati (100000–150000 USD) erano già neutre.
Note
- Solo visualizzazione — il filtro salari del client analizza i numeri a prescindere dal prefisso, quindi il filtraggio non cambia. Suite: 2471.
[1.181.0] — 2026-08-14
Aggiunto (scanner) — le bacheche Getro ora mostrano stipendio, tutte le località e posizioni da remoto.
Aggiunto
- Lo scanner Getro (bacheche delle reti di talenti dei fondi) ora mostra uno stipendio su ogni posizione (fascia annuale + valuta), elenca tutte le località invece della sola prima e contrassegna le posizioni da remoto. Una posizione Getro nel tuo scan e tracker porta ora lo stesso dettaglio di stipendio + località delle altre bacheche.
Note
- Solo scanner; nessuna nuova dipendenza, nessun cambiamento a route / CSP / SSRF. Test:
tests/sources-getro.test.mjs(+5). Suite: 2470 (+5).
[1.180.0] — 2026-08-14
Corretto (MEDIO, report) — l’elenco #/reports è ora una tabella e viene recuperato un punteggio reale che un segnaposto di Machine Summary nascondeva.
Corretto
- L’elenco
#/reportsè una tabella (Report · Data · Legittimità · Punteggio), non una griglia di 4 schede. Un lungo chip “Punteggio non rilevato” comprimeva la colonna del titolo quasi a zero, e l’overflow-wrap: anywheredel titolo della scheda spezzava il nome del report un carattere alla volta. Ora ogni campo ha la propria colonna, la cella del nome va a capo per parole e la tabella scorre orizzontalmente su viewport stretti (nuovo contenitore.reports-scroll). Nuova chiave i18nrep.colReport×17. - Un punteggio reale nel corpo (
**Итоговый балл:** 1.8 / 5) non è più nascosto da un segnaposto di Machine Summary (score: —). Quando il blocco## Machine Summaryportava un punteggio non numerico o fuori intervallo, occupava lo slot del punteggio analizzato e bloccava il fallback forma-valore in grassetto, così il report mostrava “Punteggio non rilevato” nonostante unX / 5reale nel corpo.parseReportHeaderora recupera la forma-valore dal corpo quando non sopravvive alcun numero utilizzabile (passo 4.5).
Note
- Solo client + parser; nessun cambiamento a route / CSP / SSRF / scrittura sul padre. Test:
tests/reports-table.test.mjs(+5),tests/report-header-locale.test.mjs(+2). Suite: 2465 (+7).
[1.179.0] — 2026-08-13
Modificato (LOW, scanner) — 20 decoder di entità HTML duplicati consolidati sul modulo condiviso (follow-up di parità, chiude la worklist).
Modificato
- 20 sorgenti di scansione avevano ciascuna il proprio
decodeEntities/decodeXmlEntities(+ un helperfromCodePoint) — copie che erano derivate (tre potevano sollevare unRangeError, corretto in v1.172.0; altre ammettevano NUL/C0 o interpretavano malea2;). Ora passano tutte per l’unicoserver/lib/html-entities.mjs(decoder sicuro secondo Char di XML 1.0), rimuovendo ~237 righe di duplicazione. Le 8 sorgenti in stile RSS hanno guadagnato la decodifica di (prima gestivano solo 5 entità); la doppia decodifica voluta di cryptocurrencyjobs è preservata tramite un alias.hhmantiene il proprio decoder (gestisce—/–, fuori dalle 6 condivise). Un nuovo test di guardia fallisce se una sorgente ricrea un decoder locale.
Note
- Refactoring che preserva il comportamento; nessun cambiamento a route / CSP / SSRF / scrittura sul padre. Test:
tests/decoder-consolidation.test.mjs(+2). Suite: 2458 (+2).
[1.178.0] — 2026-08-13
Corretto (LOW, parità col padre) — due costanti obsolete allineate al padre (PARENT-SYNC GAP #4 + #5).
Corretto
- User-Agent del browser (GAP #4) —
BROWSER_LIKE_USER_AGENT(inviato da workable/workday/oraclecloud/a16z/eightfold per superare i gate WAF/bot) portato da Chrome 131 a 151, allineando iluser-agent.mjsdel padre; una versione vecchia è più spesso bloccata. Protetto da un testChrome major ≥ 151. - FALLBACK degli stati del tracker (GAP #5) — il
FALLBACKdi ultima istanza instates.mjs(usato solo quando iltemplates/states.ymllive è illeggibile — clone nuovo / root isolata in CI) ha guadagnato gli alias di stato turchi del padre (#2615): değerlendirildi, başvuruldu, yanıt verildi, mülakat, teklif, reddedildi, iptal edildi, uygun değil, kabul edildi/işe alındı. In produzione il file live li forniva già.
Note
- Solo due costanti; nessun cambiamento a route / CSP / SSRF / scrittura sul padre. Test:
tests/http-json.test.mjs(+1) +tests/states.test.mjs(+1). Suite: 2456 (+2).
[1.177.0] — 2026-08-13
Corretto (MEDIUM, scanner) — csod (Cornerstone) restituiva 0 offerte sui tenant che proteggono l’API di ricerca con cookie di sessione (parent #2769, PARENT-SYNC GAP #1).
Corretto
- Alcuni tenant Cornerstone impostano cookie di sessione sulla home del sito carriere e rispondono
401 CSOD Unauthorizedall’API di ricerca se quei cookie non tornano insieme al token bearer anonimo.sources/csod.mjsora legge il bootstrap tramite un nuovo helperfetchResponse, costruisce un headerCookiedai suoi valoriSet-Cookie(cookieHeaderFrom— solo nome=valore, semantica jar) e lo ripete sul POST di ricerca. Solo stessa origine (host fissato +redirect:'error'), quindi i cookie di sessione non raggiungono mai terze parti; un tenant senza cookie si comporta come prima.
Note
- Nuovo
server/lib/http-json.mjs::fetchResponse(additivo; sorgenti esistenti intatte). Nessun cambiamento a route / CSP / SSRF / scrittura sul padre. Test:tests/sources-parity-v1118a.test.mjs(+1). Suite: 2454 (+1).
[1.176.0] — 2026-08-13
Corretto (MEDIUM, report) — un punteggio sotto un’etichetta in grassetto che la tabella RU non elenca mostrava ancora “Score not detected” (FIND-5).
Corretto
- Due report RU scrivevano il punteggio come
**Итоговый балл:** 1.8 / 5/**Скор:** 1.8 / 5— etichette in grassetto cheREPORT_LABELS.runon enumera (conosce solo “Оценка”/“Балл”), quindi il punteggio restava non analizzato. Invece di ampliare l’elenco di sinonimi,parseReportHeaderora ripiega sulla forma del valore: una frazione sulla rubrica /5 sotto QUALSIASI etichetta in grassetto. È indipendente dalla lingua, immune a un’intestazione (niente**, niente valore/5) e rifiuta una data come5/5/2026(lookahead negativo sul denominatore).
Note
- Solo parser del server; nessun cambiamento a route / CSP / SSRF / scrittura sul padre. Test:
tests/report-header-locale.test.mjs(+2). Suite: 2453 (+2).
[1.175.0] — 2026-08-13
Corretto (LOW, irrobustimento) — una guardia di regressione per la descrizione SEO di FIND-3 + uno strip di legittimità sicuro ai valori nulli (follow-up dell’AI-review).
Corretto
- Guardia di parità della descrizione SEO — la correzione di v1.174.0 che ha sostituito un “~55” fisso nel
meta.descdi ogni lingua con un placeholder{adapters}derivato dal registro non aveva test, quindi poteva regredire in silenzio alla prossima modifica di una locale. Il nuovotests/site-meta-desc-parity.test.mjs(isolato in CI) fallisce se uno dei 17site/src/i18n/*.jsonperde il placeholder o rifissa un conteggio, o seLanding.astrosmette di interpolarlo nelle tre metadescrizioni. - Strip di legittimità sicuro ai nulli —
stripEmphasisrestituisce''per un input nullish invece della stringa “undefined” (i campi sono inizializzati a stringa, quindi è difesa in profondità).
Note
- Test + una guardia di una riga nel parser; nessun cambiamento a route / CSP / SSRF / scrittura sul padre. Test:
tests/site-meta-desc-parity.test.mjs(+3). Suite: 2451 (+3).
[1.174.0] — 2026-08-13
Corretto (HIGH, report) — i report localizzati mostravano “Score not detected”; la descrizione SEO era obsoleta.
Corretto
- Parsing del punteggio (FIND-1) — un report non inglese il cui H1 contiene la parola dell’etichetta del punteggio (
# Оценка вакансии: <titolo>) non scambia più quel titolo per il punteggio.parseReportHeaderora si ancora all’etichetta in grassetto localizzata (**Оценка:** 1.5 / 5), salta le righe di intestazione e richiede l’etichetta adiacente ai due punti — così i report RU che mostravano “Score not detected” espongono il punteggio reale. - Chip di legittimità (FIND-2) — l’enfasi Markdown viene rimossa dal valore, così il chip mostra “High Confidence” invece di “** High Confidence”.
- Overflow del punteggio — una riga di punteggio con testo di stato in coda (“1.8, Status: Evaluated, …”) viene compattata al solo punteggio;
.score-pillottiene un limite no-wrap/overflow e la colonna del titolo può restringersi, così un chip colorato non esce mai dal bordo della card. - Descrizione SEO (FIND-3) — le descrizioni meta / OG / Twitter di cvstart.org (tutte le 17 lingue) fissavano “Scan ~55 job boards” mentre il corpo contava il registro reale (“~75”). Ora la descrizione interpola il conteggio derivato dal registro, così non deriva più.
Note
- Parser del server + render/CSS del client + i18n del sito; nessun cambiamento a route / CSP / SSRF / scrittura sul padre. Test:
tests/report-header-locale.test.mjs(+4). Suite: 2448 (+4).
[1.173.0] — 2026-08-13
Aggiunto (LOW, configurazione) — Hermes entra nell’elenco delle CLI di IA rilevate (parità con career-ops).
Aggiunto
- La scheda
#/config→ “Strumenti CLI di IA” ora rileva Hermes (Nous Research), il runtime di agente appena supportato dal progetto padre (binariohermes). L’elenco fisso inserver/lib/routes/cli-detect.mjscresce da 10 a 11 strumenti; il rilevamento resta una scansione di PATH in sola lettura (nessun binario viene mai eseguito).
Note
- Nessun cambiamento a i18n / route / CSP / SSRF / scrittura sul padre; l’elenco è fisso, mai un input. Suite: 2444 (il canary di cli-detect aggiornato da 10 a 11).
[1.172.0] — 2026-08-13
Corretto (MEDIUM, scanner) — un’entità HTML malformata poteva mandare in crash una sorgente di scansione (parità con career-ops #2150).
Corretto
- Le sorgenti
oraclecloud,gemedassaultdecodificavano le entità HTML numeriche con un debole controlloNumber.isFiniteprima diString.fromCodePoint— un riferimento superiore a0x10FFFF(es.�da un feed malformato o ostile) sollevava unRangeErrornon catturato e interrompeva l’intero parsing di quella sorgente. Un modulo condivisoserver/lib/html-entities.mjs(che rispecchia il_html-entities.mjsdel progetto padre) ora limita i riferimenti numerici all’insieme Char di XML 1.0 §2.2, cosìString.fromCodePointnon può mai sollevare eccezioni, e distingue esadecimale e decimale separatamente affinchéa2;non venga più interpretato male. Le tre sorgenti lo importano.
Note
- Nessun cambiamento per i feed validi; nessun cambiamento a JS / i18n / route / CSP / SSRF / scrittura sul padre. Il consolidamento delle ~20 copie del decoder rimaste nelle sorgenti è tracciato in
qa/PARENT-SYNC-WORKLIST-v1.26.0.md. - Test:
tests/html-entities.test.mjs(+7). Suite: 2444 (+7).
[1.171.0] — 2026-08-13
Modificato (BASSA, design-system) — token di scala tipografica + livelli z-index (D-4, primo passo). Dimensioni e impilamento erano letterali per componente.
Modificato
- Livelli z-index — introdotti token
--z-*(--z-topbar…--z-skiplink) e migrato ogni letterale z-index. Valori preservati, impilamento identico; un nuovo canarino vieta nuovi numeri magici. - Scala tipografica — rampa
--font-size-*(xs 11…2xl 28, base = Inter 15px); migrate le dimensioni centrali (nessun cambiamento visivo). I valori fuori rampa migrano in modo incrementale (docs/UX-ROADMAP.md).
Note
- Solo token CSS; nessuna modifica a comportamento/JS/i18n/route/CSP/SSRF/scrittura. Nessun cambiamento di pixel.
tests/design-tokens-scale.test.mjs(+3). Suite: 2437 (+3).
[1.170.0] — 2026-08-13
Aggiunto (BASSA) — suggerimenti ETA onesti sulle lunghe generazioni IA (P4-ETA). Le generazioni pesanti (piano di carriera ~40 s, orientamento / mercato / networking ~30 s, two-pager ~20 s) mostravano solo “Generazione…” senza percezione della durata.
Aggiunto
- Accanto a ogni pulsante di generazione lunga compare ora un suggerimento attenuato
⏱ ~Ns(come l’ETA di#/auto). Stile.eta-hintcondiviso + due chiavi generiche (common.eta~{n}s,common.etaTitle).
Note
- Solo client; nessuna modifica a route/CSP/SSRF/scrittura. +2 chiavi i18n ×17 (snapshot 1219 → 1221).
tests/generation-eta-hint.test.mjs(+2). Suite: 2434 (+2).
[1.169.0] — 2026-08-13
Aggiunto (BASSA) — anteprima PDF inline (D-5). GET /api/output/pdfs/:name forzava Content-Disposition: attachment, quindi anche il pulsante “Apri” su #/cv scaricava invece di mostrare.
Aggiunto
?inline=1serve lo STESSO file sanificato conContent-Disposition: inline, per un’👁 Anteprima in una nuova scheda; il default resta il download. Nessuna nuova route; stesse protezioni sul nome.- Il primo pulsante della lista PDF su
#/cvè ora 👁 Anteprima accanto a ⬇ Scarica.cv.openPdf“Apri” → “Anteprima” ×17.
Note
- Nessuna modifica CSP/SSRF — stesso
sanitizePathName. Una chiave i18n riformulata ×17 (snapshot 1219).tests/output-pdfs.test.mjs(+3). Suite: 2432 (+3).
[1.168.0] — 2026-08-13
Corretto (BASSA, a11y) — le righe delle checkbox rispettano ora il minimo 24×24 di WCAG 2.5.8 (D-2). Le etichette di checkbox/radio su #/scan, #/config, #/evaluate e #/cv-studio stavano in una banda di ~22 px.
Corretto
- Una regola circoscritta
label:has(> input[type="checkbox"/"radio"]) { min-height: 24px }garantisce una banda ≥24 px. Solomin-height— le etichette sono già flex, nulla si sposta;.apply-checklist(32 px) era già conforme.
Note
- Solo CSS; nessuna modifica a comportamento/JS/i18n/route/CSP/SSRF/scrittura.
tests/checkbox-target-size.test.mjs(+1). Suite: 2429 (+1).
[1.167.0] — 2026-08-13
Corretto (BASSA, design-system) — le superfici rialzate ora si distinguono dai filetti (D-3). I token --panel-2 / --surface-elev1 si risolvevano in --slate, lo stesso valore dei filetti --line / --border, senza separazione visiva.
Corretto
- Un token dedicato e consapevole del tema
--elev(#eef1f6chiaro /#1e232escuro, distinto da--slatein entrambi i temi) sostiene ora le superfici rialzate; i filetti restano su--slate. Gli altri rilievi (D-2, D-4, D-5, P4-ETA) sono backlog indocs/UX-ROADMAP.md.
Note
- Solo token CSS; nessuna modifica a comportamento/JS/i18n/route/CSP/SSRF/scrittura.
tests/elevation-token.test.mjs(+2). Suite: 2428 (+2).
[1.166.0] — 2026-08-13
Corretto (BASSA) — la terminologia della rubrica ora rispecchia i docs canonici. career-ops.org/docs descrive “cinque dimensioni più un punteggio globale olistico”, ma il web-ui, cvstart.org e il wiki dicevano “rubrica a sei dimensioni” (5 + 1 = 6, ma il lessico divergeva).
Corretto
- Adottata la formulazione dei docs — “cinque dimensioni più un punteggio globale olistico” — in modo coerente in README ×17, nel sito cvstart.org ×17, nella guida ×17, in
docs/career-ops-canonical.mde nel wiki (Home ×17 + Features).
Note
- Solo docs/marketing; nessuna modifica a codice/chiave i18n/route/CSP/SSRF/scrittura.
tests/rubric-terminology.test.mjs(+2). Suite: 2426 (+2).
[1.165.0] — 2026-08-13
Corretto (BASSA) — il termine “Two-pager” è ora coerente all’interno di ogni lingua. In arabo la barra laterale mostrava il latino “Two-pager” mentre l’<h1> era completamente localizzato — l’unica stringa latina in una navigazione RTL altrimenti speculare.
Corretto
- Decisione applicata: per lingua,
nav.twoPageretwoPager.titleconcordano sul termine (entrambi in latino o entrambi localizzati). Solo l’arabo era diviso; la sua etichetta di navigazione è ora localizzata (“الصفحتان”). Un nuovo canarino fallisce se una lingua li separa di nuovo.
Note
- Solo testo; nessuna modifica a route/CSP/SSRF/scrittura. Un valore i18n modificato (ar); nessuna nuova chiave (snapshot 1219).
tests/two-pager-term-consistency.test.mjs(+2). Suite: 2424 (+2).
[1.164.0] — 2026-08-13
Corretto (BASSA) — il placeholder della ricerca non trabocca più in nessuna lingua. “Find a company, role or URL…” veniva troncato (nowrap) quando la barra si restringeva; la metà “…or URL” non era mai visibile.
Corretto
top.search(×17) è ora il breve “Cerca o incolla una URL” (≤24 caratteri in ogni lingua), sta anche in una barra stretta e mantiene il riferimento all’URL. Il fallback inindex.htmlcorrisponde; l’aria-labelconserva il dettaglio completo.
Note
- Solo testo; nessuna modifica a route/CSP/SSRF/scrittura. Una chiave i18n riformulata ×17 (nessuna nuova; snapshot 1219).
tests/search-placeholder-fit.test.mjs(+2). Suite: 2422 (+2).
[1.163.0] — 2026-08-13
Corretto (BASSA) — l’assistente in-app “Chiedi ai docs” ora copre l’esportazione di un report in PDF. Rispondeva che la guida non lo copriva, benché #/reports/:slug abbia un pulsante 📄 Generate PDF funzionante.
Corretto
- Aggiunto un H3 “Esportare un report in PDF” sotto §10 Report in tutti i 17 bundle di aiuto (dov’è il pulsante, il file finisce in
output/*.pdf, richiede Playwright, rivedere prima di inviare). Il recupero dell’assistente ora mostra la sezione Report.
Note
- Solo docs/aiuto; nessuna modifica a codice/route/CSP/SSRF/scrittura. Soglia aiuto 112 → 113 H3 (31 H2 invariato).
tests/help-reports-pdf-section.test.mjs(+2). Suite: 2420 (+2).
[1.162.0] — 2026-08-13
Corretto (MEDIA) — il “?” di aiuto è ora un bersaglio di ≥24×24 (WCAG 2.5.8). .help-hint misurava 18×18 px con padding:0, sotto il minimo, su ogni intestazione.
Corretto
- Il box
.help-hintè ora 24×24 (il bersaglio misurabile) mentre l’anello visibile resta a 18px — disegnato da un::beforecentrato, quindi il glifo e la linea di base dell’<h1>sono invariati. Gli stati hover/attivo/focus seguono l’anello; margine 6→3px per mantenere lo spazio.
Note
- Solo CSS; nessuna modifica a JS/i18n/route/CSP/SSRF/scrittura.
tests/help-hint-target-size.test.mjs(+2). Suite: 2418 (+2).
[1.161.0] — 2026-08-13
Corretto (MEDIA) — #/reports mostra un chip “Punteggio non rilevato” invece di spazio vuoto. Dopo il parser multilingua di v1.159.0, un report senza punteggio analizzabile mostrava un’area vuota, indistinguibile da un errore.
Corretto
- La cella del punteggio ora si ramifica: con punteggio → pillola di tono; senza → chip
.score-mutedattenuato (“Punteggio non rilevato”, ×17) con tooltip “Apri il report…”. La card resta unrole="link"operabile da tastiera e la data si mostra. - Riusa il token neutro esistente; nessun colore nuovo.
Note
- Solo client; nessuna modifica a route/CSP/SSRF/scrittura. +2 chiavi i18n ×17 (snapshot 1217 → 1219). Suite: 2416 (+3).
[1.160.0] — 2026-08-13
Corretto (ALTA) — il testo sui provider non contraddice più la promessa dei 7 provider. #/config diceva che la valutazione live “usa la tua chiave Anthropic o Gemini” e che quella OpenAI “non è usata dal web UI”; la dashboard diceva “Score Anthropic-first” — falso dalla cascata a 7 provider (v1.157.0).
Corretto
config.providerModelNote(×17): ora dice che la ⚡ valutazione live gira headless con una qualsiasi delle tue sette chiavi (Anthropic · Gemini · OpenAI · Qwen · OpenRouter · GitHub Models · Hermes), ordinate automaticamente con fallback. Rimossa la frase falsa su OpenAI.dash.quick.evaluateSub(×17): neutro (“Punteggio di idoneità 0–5”).Keys: N / 5→N / 7.
Note
- Solo testo; nessuna modifica a route/CSP/SSRF/scrittura. Nessuna nuova chiave i18n (snapshot 1217). Suite: 2413 (+3).
[1.159.0] — 2026-08-13
Corretto (ALTA) — i metadati del report non dipendono più dalla lingua. I report generati in una lingua diversa dall’inglese mostravano una striscia di metadati vuota su #/reports, perché parseReportHeader riconosceva solo le etichette in grassetto in inglese.
Corretto
parseReportHeaderora analizza il blocco YAML invariante## Machine Summary(score:/legitimacy:/date:— la stessa fonte cheauto-pipelinegià legge): etichette inglesi → blocco Machine Summary → etichette localizzate (REPORT_LABELS, 17 lingue). I report in inglese restano identici byte per byte.- Analisi numerica tollerante (
1.5/5,1,5/5,1.5 из 5,4.5 out of 5); la data ripiega sul mtime del file quando manca.
Note
- Solo lettura/analisi; nessuna modifica a route, CSP, SSRF o scrittura sul padre. Nessuna nuova chiave i18n. Suite: 2410 (+8).
[1.158.0] — 2026-08-12
Corretto — due bug cosmetici di visualizzazione (un «?» che trapelava nel titolo della scheda e un conteggio dei provider errato nella landing). Solo visualizzazione; nessuna modifica a comportamento, sicurezza o flusso dei dati.
Corretto
- Il «?» di HelpHint non trapela più in
document.title. Il router derivava il titolo della scheda dalh1.textContentgrezzo, mostrando «Vacancy search?» invece di «Vacancy search».router.js::focusNewViewora clona l’intestazione, rimuove.help-hinte poi legge il testo; il «?» visibile resta intatto. - cvstart.org mostrava «17 AI providers» invece di «7». L’helper
sub()inFeatures.astroriscriveva ogni{n}con il numero di lingue (17) prima della sostituzione per scheda; ora{n}è risolto per scheda (provider → 7, lingue → 17).
Note
- Nessuna modifica a server, route, CSP, SSRF o chiavi i18n; forma di
facts.jsoninvariata. Suite: 2402 test (+1).
[1.157.0] — 2026-08-12
Corretto — le valutazioni live ora girano con QUALSIASI provider configurato, non solo Anthropic/Gemini. Un utente con solo OPENROUTER_API_KEY veniva forzato erroneamente in modalità manuale.
Corretto
- Causa radice: un pin
LLM_PROVIDERsenza chiave (es.LLM_PROVIDER=claudedainit) finiva in un vicolo cieco; ora ripiega sull’ordine auto tra i provider configurati (inselectActiveProvider+ entrambe le cascate di dispatch). - Il gating lato client (
#/deep+ viste mode-page) usa orawindow.ProviderStatus(/api/status/providers, tutti e 7) invece del probe obsoleto Anthropic/Gemini; testi riscritti (deep/eval × 17) + badge «Valutazioni live» della dashboard +config.llmProviderHint.
Note
- Nessun cambiamento di sicurezza. Suite: 2401 test (+5).
[1.156.0] — 2026-08-12
Refactor — dividere scan.js sotto il limite di dimensione (P-16) + un fix CodeQL. scan.js era di 906 righe; estratte due factory che preservano il comportamento → 648. Completa la coppia di divisioni delle view P-15/P-16.
Modificato
- Nuovi
scan/runner.js(motore di esecuzione dello scan) escan/filters.js(macchina a stati dei filtri) tramite bagctx/refs;scan.jscollega entrambi.
Corretto
- CodeQL
js/useless-assignment-to-local(#428) inconfig/tab-controller.js:let n = i;→let n;.
Note
- Refactor puro, nessun cambio di comportamento; 4 test che leggono il sorgente ripuntati. Entrambe le view grandi ora sotto 800 (P-15/P-16 fatto). Suite: 2396 test.
[1.155.0] — 2026-08-12
Refactor — dividere config.js sotto il limite di dimensione (P-15). config.js era di 1030 righe (oltre il limite di 800); estratti due moduli che preservano il comportamento, portandolo a 783.
Modificato
- Nuovi
config/field-specs.js(dati dei campi + liste di modelli) econfig/tab-controller.js(factory della barra delle schede);config.jsli referenzia, la logica di render è invariata.
Note
- Refactor puro, nessun cambio di comportamento; 6 test che leggono il sorgente sono stati ripuntati.
scan.js(906) resta com’è (già parzialmente diviso; nucleo troppo accoppiato per una divisione meccanica pulita). Suite: 2396 test.
[1.154.0] — 2026-08-12
Nuova guida — “Esegui l’intero stack nel cloud.” career-ops non ha una propria storia cloud/server, quindi ne aggiungiamo una: una ricetta passo passo per mettere la pipeline padre career-ops, questo visualizzatore career-ops-ui e il motore IA (un abbonamento Claude via Claude Code, un Hermes locale, o chiavi API) su un piccolo server sempre attivo. Arriva come Aiuto §31 in 17 lingue, una sezione del README e una pagina wiki.
Aggiunto
- Aiuto §31 “Esegui l’intero stack nel cloud” (× 17) — le tre parti, provisioning + installazione, scelta del motore, esposizione sicura (reverse proxy HTTPS + auth + le invarianti CSP/SSRF/XSS/nessun-segreto). Il bundle di aiuto cresce a 31 H2 / 112 H3.
- README — una sezione “Esegui l’intero stack nel cloud” (× 17) + una pagina Cloud-Deployment nel wiki.
Note
- Solo docs — nessuna rotta, server o modifica client; nessuna nuova chiave i18n. I 4 test di aiuto passano al contratto 31 H2 / 112 H3. Suite: 2396 test (invariata).
[1.153.0] — 2026-08-12
Scanner Jobvite migrato al feed XML pubblico (sync con il parent). Il parent ha ritirato l’API JSON di Jobvite (ora restituisce zero offerte); il source di web-ui usava lo stesso endpoint morto, quindi ogni azienda Jobvite tracciata scansionava vuota in silenzio. Porta il fix del parent (#2623): il source ora legge il feed XML pubblico per-tenant, con chiave companyEId.
Corretto
- Il source chiamava l’API JSON ritirata e restituiva zero offerte; ora chiama
https://app.jobvite.com/CompanyJobs/Xml.aspx?c={companyEId}e fa il parse dell’XML<result><job>…(CDATA + entità,detail-urlprima diapply-url).
Modificato
- Risoluzione di
companyEId: (1)company_eid:sul portale, (2) ilc=di unapi:esplicito, (3) discovery dalla pagina del board.fetchText(http-json.mjs) allega.location/.retryAfterall’errore non-ok (sola lettura, retrocompatibile).
Note
- Sicurezza — due host (
jobs.jobvite.com,app.jobvite.com) fissati daassertJobviteUrl: solo https, allowlist stretta, nessun redirect seguito. IlcompanyEIdè solo un valore?c=; conteggio dei source invariato. - Suite: 2396 test (+4).
[1.152.0] — 2026-08-12
Provider Hermes — cablaggio completato + allineamento docs. Una revisione del codice dell’integrazione Hermes di v1.151.0 ha trovato due lacune reali e quattro punti di completezza; tutti corretti qui, e il roster dei provider LLM dell’intera app è portato ai sette completi su tutte le superfici di docs e le 17 lingue.
Corretto
#/confignon poteva forzare Hermes — il menuLLM_PROVIDERelencava solo sei provider, quindi si poteva impostareHERMES_API_KEYma non forzare Hermes dalla UI. Orahermesè l’8ª opzione, e un nuovo test di parità impedisce al menu di divergere di nuovo daLLM_PROVIDERS.- Le chiavi locali corte venivano rifiutate in silenzio — la soglia di 20 caratteri di
isUsableKeyera tarata per chiavi cloud;hasHermesKeyusa ora una soglia allentata di 8 (l’esempio dei docs Hermes è di 19 caratteri).
Modificato
- Il roster dei provider è stato normalizzato ai sette completi in README (× 17), guida in-app (× 17), dict
config.llmProviderHint(× 17) edocs/sdd;hermesChatUrlcompleta un host senza percorso; il testo di fallback manuale cita Hermes.
Note
- Sicurezza invariata — nessuna nuova rotta né modifica SSRF/CSP; health/doctor guadagna una riga
HERMES_API_KEY. - Suite: 2392 test (+2).
[1.151.0] — 2026-08-12
Hermes è ora un provider LLM collegato (Phase 5) — lo spike di analisi della Phase 5 ha confermato che il Hermes di Nous Research include un API Server compatibile con OpenAI (hermes gateway → POST /v1/chat/completions), quindi career-ops-ui ora esegue valutazioni live tramite un Hermes locale esattamente come OpenAI/Qwen. Imposta HERMES_API_KEY in Impostazioni app ed entra nell’ordine auto (ultimo). Chiude l’ultimo punto aperto della roadmap — Phase 5, Shape A.
Aggiunto
- Provider LLM Hermes (Shape A) —
runHermessul client condivisorunOpenAICompatible(server/lib/openai.mjs), in entrambe le cascate (llm-dispatch.mjs+routes/llm.mjs), in coda all’ordine auto + il pinLLM_PROVIDER=hermes,/api/status/providersellm-pricing.mjs. Raggiunge una base URL locale configurabile (defaulthttp://127.0.0.1:8642/v1) con auth Bearer — è un endpoint di provider CONFIGURATO (come OpenRouter/Qwen), non un URL di offerta dell’utente, quindi non tocca il guard SSRF. - Campi
#/config—HERMES_API_KEY(segreto) +HERMES_BASE_URL+HERMES_MODEL(defaulthermes-agent), con 6 nuove chiavi i18n × 17 lingue (snapshot 1208 → 1214).
Modificato
- Lo spike di analisi è risolto:
docs/integrations/HERMES.md, l’aiuto in-app §30 (× 17), il teaser del README (× 14), la skillhermes-bridgee la roadmap passano da “pianificato / non ancora collegato” a collegato (Shape A). Shape B (un relay su misura del runtime di agente) non è stato necessario.
Note
- Sicurezza: il fetch del provider è un endpoint configurato, della stessa categoria degli altri provider compatibili con OpenAI — nessuna nuova superficie SSRF, nessuna modifica a CSP/sanitizer.
HERMES_API_KEYè unaSECRET_KEY(mai mostrata). - Test (isolati in CI, trasporto simulato):
tests/hermes-provider.test.mjs(+5); il canarino “nessun ramo Hermes” di v1.146.0 è invertito per affermare che C’È; i test di superficie dei provider aggiornati all’ordine a 7 provider. - Suite: 2390 test (+5).
[1.150.0] — 2026-08-12
Stati vuoti coerenti (rifinitura Phase 4) — ogni pannello “ancora niente qui” ora si renderizza tramite l’unico stile condiviso .empty, invece che alcune viste ridichiarino l’aspetto inline con un 40px magico. Piccola correzione di coerenza visiva; gli stati vuoti di #/activity, #/cv-studio, #/stats e #/usage ora combaciano con tutti gli altri (padding di 48px tokenizzato + bordo tratteggiato).
Modificato
#/activity,#/cv-studio,#/stats,#/usagehanno rimosso il lorostyle: { padding: '40px', textAlign: 'center', color: 'var(--foggy)' }inline sui pannelli vuoti — tutte e tre le proprietà sono già fornite dalla classe condivisa.empty(--space-7= 48px, centrato, attenuato, bordo tratteggiato). Così questi quattro si renderizzano identici agli altri ~25 pannelli.empty.- Gli override legittimi per vista (
#/dashboardwidth:100%,#/pipelineborder:none) non sono toccati — rimosse solo le ridichiarazioni puramente ridondanti.
Note
- Solo pulizia dell’uso di CSS lato client — nessuna modifica a rotta, server, chiave i18n o regole CSS (la classe
.emptyè invariata); snapshot del dizionario 1208. Verificato nel browser (il pannello vuoto di#/usagecalcola 48px di padding + bordo tratteggiato, 0 errori di console). - Il nuovo canarino
tests/empty-state-consistency.test.mjsmantiene.emptycome unica fonte di verità. La Phase 5 (provider Hermes) resta bloccata. - Suite: 2385 test (+2:
tests/empty-state-consistency.test.mjs).
[1.149.0] — 2026-08-12
Portali spostati nelle Impostazioni (Phase 4) — #/portals ora si trova nel gruppo di navigazione Setup, accanto a Impostazioni app, invece che sotto Sourcing. Dalla v1.144.0 è una superficie di configurazione (abilita/disabilita le aziende monitorate + una sonda di salute dell’ATS), non un’azione di sourcing — quindi è lì che deve stare. Solo modifica di navigazione; la pagina e la sua rotta non cambiano.
Modificato
- Elemento di navigazione
#/portals→ gruppo Setup (inpublic/index.html), collocato subito dopo Impostazioni app. Rimosso dal gruppo Sourcing (che conserva Scan / Pipeline / Auto-pipeline / Aziende finanziate). La rotta#/portals, la vista e l’etichettanav.portalsnon cambiano — si è spostata solo la posizione nella barra laterale.
Note
- Solo markup di navigazione — nessuna modifica a rotta, vista, chiave i18n o server. Verificato nel browser (0 errori di console); protetto da
tests/portals-nav-placement.test.mjs. - Suite: 2383 test (+2:
tests/portals-nav-placement.test.mjs).
[1.148.0] — 2026-08-12
Filtri di ricerca più chiari (Phase 4) — il pannello dei filtri è ora una griglia ordinata — il pannello dei filtri di #/scan è passato da un flex-wrap irregolare di riquadri rigidi di larghezza variabile a una griglia responsiva, e le azioni Applica / Reimposta ora occupano una loro riga separata e allineata a destra. Stessi filtri, stesso comportamento — solo più leggibili. Una rifinitura di design (senza parent-sync).
Modificato
- Pannello dei filtri di
#/scan→ griglia responsiva —.scan-filtersè oradisplay: gridcon colonnerepeat(auto-fill, minmax(180px, 1fr))e spaziature uniformi, così gli 11 filtri etichettati si allineano in colonne ordinate a qualsiasi larghezza invece di andare a capo in una riga irregolare. - Azioni Applica / Reimposta occupano l’intera griglia su una loro riga, separate da una sottile linea e allineate a destra. Rimosso il vecchio trucco dell’etichetta nascosta + il wrapper flex interno in
scan.js.
Note
- Solo CSS + una piccola pulizia del DOM — ogni id di filtro (
#scan-filter-*,#scan-apply) e il cablaggio diSR.render()sono invariati, quindi il flusso Playwright non è toccato. Nessuna nuova chiave i18n. - Verificato nel browser (0 errori di console); protetto da
tests/scan-filters-grid.test.mjs. - Suite: 2381 test (+3:
tests/scan-filters-grid.test.mjs).
[1.147.0] — 2026-08-12
Hermes & Telegram — la sezione di aiuto in-app + la superficie su cvstart.org (Phase 5b, parte 2) — la seconda e ultima parte del lavoro di documentazione di Hermes: la guida pratica ora vive dentro la guida di aiuto dell’app stessa, in tutte le 17 lingue, e l’assistente di documentazione integrato risponde alle domande su Hermes a partire da essa. Resta solo documentazione — il percorso del provider LLM Hermes rimane pianificato / non ancora collegato (Phase 5).
Aggiunto
- Aiuto in-app §30 “Hermes & Telegram” × 17 lingue — una nuova sezione della guida (cos’è Hermes + le due forme di integrazione; esecuzione su un server cloud; Telegram tramite Hermes + la regola “cosa NON esporre”), raggiungibile da
#/help. Il grounding didocs-assistant/DocsFabla recupera automaticamente, dato che entrambi leggonodocs/help/<lang>.md. - cvstart.org — un link alla guida di Hermes che punta al documento su GitHub.
Modificato
- Soglia del bundle di aiuto alzata 29 → 30 H2 / 105 → 108 H3 (
canonical-docs-coverage,help-ui,help-ru-config-section); §30 aggiunge 3 H3.
Note
- Ancora nulla chiama Hermes. Il nuovo canarino
tests/help-hermes-section.test.mjsverifica che ogni lingua contenga la §30 con le sue àncore indipendenti dalla lingua (docs/integrations/HERMES.md,hermes-bridge,#/help,127.0.0.1, Telegram). Il provider resta bloccato in attesa del contratto API della Phase 5. - Questo chiude il deliverable documentazione + skill della Phase 5b; l’integrazione del provider (Phase 5) resta un elemento separato e bloccato.
- Suite: 2378 test (+2:
tests/help-hermes-section.test.mjs).
[1.146.0] — 2026-08-12
Agente Hermes + Telegram — la guida di integrazione + una skill (Phase 5b, parte 1) — puoi eseguire career-ops-ui su un server cloud e collegare i suoi eventi (una scansione completata, un nuovo report, un follow-up urgente) a Telegram tramite un agente Hermes di Nous Research. Questa versione porta la documentazione di design + deployment e una skill hermes-bridge; il percorso del provider LLM Hermes resta pianificato / non ancora collegato (bloccato dallo spike sul contratto API della Phase 5). Documentazione intenzionalmente davanti al codice.
Aggiunto
docs/integrations/HERMES.md— l’analisi approfondita: le due forme di integrazione (endpoint compatibile con OpenAI vs. runtime dell’agente), il deployment su server cloud (reverse proxy + HTTPS + systemd, il contratto di sola lettura con il parent su una macchina headless), Telegram tramite Hermes, e un elenco da modello di minaccia «cosa NON esporre» (niente CV / stipendio / corpo dei report / chiavi verso il canale).- Il teaser
## Hermes agent + Telegramnel README — un breve rimando + link, nel README in inglese e replicato nei README tradotti di ogni lingua. - Una skill
hermes-bridge(.claude/skills/hermes-bridge/) che rende operativa la guida — controlli di prerequisiti e di ambito (Node ≥ 18, chiavi presenti, raggiungibilità dell’endpoint tramite il percorso sicuro da SSRF), non scrive mai segreti su disco/log, e si rifiuta di inventare un endpoint Hermes o di affermare che il provider sia collegato. - Una sezione Integrations in
docs/architecture/OVERVIEW.mdcollega alla guida.
Note
- Per ora nulla chiama Hermes. Un test canarino (
tests/hermes-docs.test.mjs) verifica i marcatori di onestà «pianificato / non ancora collegato» e chellm-dispatch.mjsnon abbia alcun ramo Hermes/Nous — quindi collegare il provider in futuro dovrà aggiornare documentazione + roadmap nella stessa modifica. - Rinviato a v1.147.0 (Phase 5b, parte 2): la sezione di aiuto in-app «Hermes & Telegram» H2 × 17 lingue e la superficie di marketing di cvstart.org.
- Suite: 2376 test (+4:
tests/hermes-docs.test.mjs).
[1.145.0] — 2026-08-12
Statistiche perspicaci (segue): un grafico ricostruibile — la scheda “Tendenza ruoli target” su #/stats ha ora un widget Crea un grafico: scegli una metrica × dimensione e si ridisegna in tempo reale. Richiesta UX dell’utente (senza parent-sync).
Aggiunto
- Grafico ricostruibile metrica × dimensione — scegli una metrica (Offerte / Stipendio mediano / Stipendio medio) e una dimensione (Per paese / Per ruolo), e il grafico a barre si ridisegna all’istante. Le metriche salariali rispettano la valuta + l’interruttore all’anno ⇄ al mese; le offerte sono un semplice conteggio.
- 8 nuove chiavi i18n × 17 lingue; snapshot 1200 → 1208.
Note
- Verificato nel browser (0 errori console). Suite: 2372 test (+2).
[1.144.0] — 2026-08-12
Impostazioni e filtri (Fase 4, parte 1): abilita/disabilita i portali seguiti — ora puoi accendere o spegnere un’azienda monitorata da #/portals, e lo scanner lo rispetta. Richiesta UX dell’utente (senza parent-sync).
Aggiunto
- Interruttore Abilita/Disabilita per azienda su
#/portals— un clic spegne un portale (lo scanner EN salta già le aziende conenabled: false, quindi un portale disabilitato esce da tutte le scansioni future) o lo riaccende, con un avviso ottimistico. POST /api/portals/toggle— una scrittura esplicita dell’utente che commuta chirurgicamente e con validazione di parsing il flagenableddi un’azienda inportals.yml(commenti, ordine e altri campi preservati). 5 nuove chiavi i18n × 17 lingue; snapshot 1195 → 1200.
Note
- La modifica allo scanner è stata zero —
en-scanner.mjsfiltra giàenabled !== false. Suite: 2370 test (+3).
[1.143.0] — 2026-08-12
Comprensibile (segue): suggerimenti ? sulle viste di lavoro principali — il ? di aiuto copre ora le nove pagine d’azione principali, in tutte le lingue. Aggiustamento UX segnalato dall’utente (senza parent-sync).
Aggiunto
- Suggerimento
?su altri 9 titoli di viste —#/scan,#/evaluate,#/cv-studio,#/tracker,#/config,#/deep,#/batch,#/auto,#/applyricevono un?inline (viaHelpHint.title) che apre un popover localizzato «cosa fa / come usarlo / cosa aspettarsi» — lo stesso componente sicuro per la CSP di v1.139.0. - 9 nuove chiavi i18n × 17 lingue (
help.hint.scan/…/apply); snapshot 1186 → 1195.
Note
- Verificato nel browser (0 errori console). Suite: 2365 test (+1).
[1.142.0] — 2026-08-12
Correzione: basta con l’archetipo di carriera “Unknown” — #/orientation ora classifica sempre tra gli otto vettori di carriera nominati invece di rispondere talvolta “Unknown” e consigliarti di “puntarci di più”. Correzione segnalata dall’utente (senza parent-sync).
Corretto
#/orientation— il prompt IA vieta ora un archetipo fuori insieme. Il modello DEVE classificare i primi tre tra esattamente gli otto vettori nominati e non mai rispondere “Unknown”/“N/A”/“dati insufficienti” né inventare un’etichetta. Con un CV scarno nomina comunque i tre più vicini con minore fiducia e dice quali prove mancano.
Note
- Solo modifica del prompt server (
buildOrientationPrompt); nessuna modifica i18n/schema. Suite: 2364 test (+1).
[1.141.0] — 2026-08-12
Statistiche perspicaci (segue): arricchimento delle aziende finanziate — #/funded è ora più visivo: loghi aziendali, un grafico per importo di finanziamento e schede con round / importo / punteggio di scoperta / azione suggerita. Aggiustamento UX segnalato dall’utente (senza parent-sync).
Modificato
#/funded— tabella piatta → griglia di schede. Ogni azienda finanziata di recente è ora una scheda con logo (derivato dal nome viaCompanyLogo, avatar a lettera come ripiego), chip di round + importo, il punteggio di scoperta e l’azione suggerita del progetto principale, oltre a link e data della notizia.- Visualizzazione dell’importo di finanziamento — un grafico a barre delle maggiori aziende per importo divulgato; gli importi in testo libero (“$120M”/“€1.5B”) vengono convertiti in grandezza da un nuovo
parseAmount. 3 nuove chiavi i18n × 17 lingue.
Note
- Ancora sola lettura su
GET /api/company-funded; descrizione e fascia salariale non sono nella fonte di finanziamento. Suite: 2363 test (+2).
[1.140.0] — 2026-08-12
Statistiche perspicaci: cifre salariali più ricche — la ripartizione salariale di “La mia pipeline” su #/stats mostra ora la media (non solo la mediana), un interruttore all’anno ⇄ al mese e una tabella min · media · mediana · max per paese. Prima parte della Fase 3. Aggiustamento UX segnalato dall’utente (senza parent-sync).
Aggiunto
- Salario medio —
RoleStats.salaryStatsora restituisceavgUsdaccanto aminUsd/medianUsd/maxUsd. La mediana resiste agli outlier, la media rivela l’asimmetria — insieme si leggono come una distribuzione. - Interruttore all’anno ⇄ al mese e una tabella min · media · mediana · max per paese nella sezione salari, guidati dai selettori di valuta e periodo. 8 nuove chiavi i18n × 17 lingue.
Note
- Le cifre derivano ancora solo da annunci con salario leggibile e sono normalizzate in USD (indicative). Suite: 2361 test (+1).
[1.139.0] — 2026-08-12
Comprensibile: suggerimenti ? — un pulsante ? riutilizzabile e sicuro per la CSP che, al clic, spiega «cosa fa / come funziona / cosa aspettarsi» nella tua lingua. Aggiustamento UX segnalato dall’utente (senza parent-sync).
Aggiunto
- Popover di suggerimento
?(window.HelpHint) — un?rotondo accanto a un titolo apre un popover leggero, adattato al tema e speculare in RTL, che mostra una spiegazione localizzata tramiteUI.md(); accessibile (role="tooltip",aria-expanded, chiusura con Esc/clic esterno, focus ripristinato) e sicuro per la CSP. ?sulle 5 schede di#/statse su 8 titoli di viste IA/analitiche (career-plan, orientamento, two-pager, networking, colloquio simulato, memoria, funded, riepilogo settimanale) — 14 nuove chiavi i18n × 17 lingue.
Note
- Tutte le viste avevano già un sottotitolo di una riga; il
?aggiunge la spiegazione più approfondita su richiesta e rende gli stati vuoti autoesplicativi. Suite: 2360 test (+4).
[1.138.0] — 2026-08-12
Generazione nella lingua della tua interfaccia — ogni generazione IA ora risponde nella lingua scelta nell’UI, più rafforzamenti dei test emersi dalla revisione. Un aggiustamento UX segnalato dall’utente (senza parent-sync).
Modificato
- Le generazioni IA ora rispettano la lingua dell’interfaccia. Con l’interfaccia in russo, spagnolo, giapponese, … il testo generato torna in quella lingua invece che sempre in inglese. La direttiva di lingua di output attraversa tutti gli endpoint di generazione — piano di carriera, orientamento, report di mercato, colloquio simulato, piano di networking, «chiedi alla documentazione», il suggerimento della nota di memoria e la bozza del two-pager. Codice e identificatori restano in inglese (es. le chiavi YAML del two-pager); solo prosa, titoli e punti vengono localizzati.
Corretto
- Guardia dei ruoli di colore CSS (
tests/css-role-tokens.test.mjs) — un canarino statico che i token alias della modalità scura di v1.137.0 non invertano mai il ruolo: i token di testo (--fg/--danger/--ok/…) mai comebackground, e quelli di superficie (--card/--panel/--line/…) mai comecolordi testo, in tutto il CSS e negli stili inline della SPA. - Auto-sonda del loader XSS di
UI.md()— il test che caricamd()daapi.jsora sondamd('<script>…')subito dopo l’estrazione e solleva un errore se manca l’escape, così un futuro taglio errato fallisce rumorosamente invece di lasciare verde la suite di sicurezza su una funzione troncata. - Guardia di scroll su
#/career-plan— loscrollIntoViewdopo la generazione viene eseguito solo se l’anteprima è ancora connessa al documento.
Note
docs/UX-ROADMAP.mdaggiornato: i suggerimenti?+ descrizioni di pagina + stati vuoti passano a v1.139.0; un provider Nous Research / Hermes — con una guida al deploy su server cloud + Telegram e uno skill Hermes — è tracciato come Fase 5 / 5b.- Suite: 2356 test (+5).
[1.137.0] — 2026-08-11
Correzioni di leggibilità e rendering — contrasto in modalità scura, etichette dei grafici e il piano di carriera. Un intervento UX segnalato da un utente (nessun parent-sync).
Corretto
- Bianco su bianco / nero su nero in modalità scura su molte schermate — quindici proprietà CSS personalizzate referenziate da diverse viste (
--fg,--panel,--panel-2,--ok,--danger,--card, …) non erano mai state dichiarate, quindi ricadevano su valori chiaro/nero hardcoded: corretto in modalità chiara, illeggibile in modalità scura (i chip di panoramica di#/pipeline, la tab attiva di#/stats, “Attivo / Chiavi” + “✓ impostato” di#/config, le sezioni di#/two-pager, il fumetto della domanda di#/mock-interview, il testo di errore). Ora sono aliasate ai veri token theme-aware, quindi seguono automaticamente il tema — 0 violazioni di contrasto WCAG-AA su tutte le 29 viste, verificato da un revisore automatico; la tab attiva di#/configè passata a uno stile con tinta più leggibile. Una guardia di regressione (tests/dark-theme-tokens.test.mjs) mantiene l’aliasing. - Le etichette dei grafici di
#/statsvenivano troncate a metà parola (“Senior Backend Engineer” → “…Enginee”) — ora vengono troncate con ellissi, mantenendo l’etichetta completa come tooltip al passaggio del mouse. #/career-planmostrava il piano generato come Markdown non elaborato — ora viene renderizzato automaticamente come testo formattato e leggibile (il Markdown modificabile resta nella textarea; il pulsante Anteprima lo attiva/disattiva).
Note
#/career-plan,#/two-pager,#/statse il digest settimanale dei colloqui non sono rotti — mostrano stati vuoti finché non generi un piano o non hai dati. Indicazioni più chiare in pagina e suggerimenti di aiuto?sono previsti come prossimo passo (docs/UX-ROADMAP.md).
[1.136.0] — 2026-08-11
Parità con il career-ops padre v1.26.x (mainline post-v1.26.0) — una nuova sorgente a zero autenticazione più un’ondata di port di qualità e robustezza ai mirror di web-ui. Il registro ora conta 79 sorgenti = 74 EN + 5 RU (ALL_ADAPTERS 74).
Aggiunto
eightfold(Eightfold AI, #2684) — bacheche di talent-acquisition tramite l’API a zero autenticazionehttps://<tenant>.eightfold.ai/api/apply/v2/jobs, fissata sull’host*.eightfold.ai(il CNAME brandizzatocareers.<company>.comè deliberatamente rifiutato); paginata con un tetto di sicurezza, errore su bacheca morta, deduplica degli URL. Sorgente + adattatore + suite CI-isolata; compare nel filtro Sorgente di#/scane sulla landing.
Corretto
- Deduplica e chiavi di ruolo sensibili a Unicode (#2569 / #2587 / #2667) — una nuova
normalizeTextKeycondivisa (NFKC, mantiene lettere/segni/cifre di qualsiasi script) sostituisce le chiavi solo-ASCII:detect-repostsora raggruppa le varianti di larghezza/punteggiatura dell’azienda (“Acme, Inc.” ≡ “Acme Inc”) e non collassa mai datori di lavoro distinti non latini, mentrerole-matcherunifica i titoli a larghezza piena e mantiene i token di ruolo non latini invece di eliminarli. fetchJsonWithRetrynon ripete più un reindirizzamento rifiutato (#2657) — una guardiaredirect:'error'che incontra un 3xx è deterministica, quindi ora non è ripetibile e fallisce rapidamente invece di consumare il budget di retry.- Gruppi AND con
+intitle_filter.positive(#2552) — un+delimitato da spazi all’interno di una voce positiva ora richiede che ogni termine appaia nel titolo, in qualsiasi ordine. oraclecloudaccetta gli apici tenant numeratioraclecloud1.com … oraclecloud99.com(#2683) — una famiglia limitata (nessuno zero iniziale, ≤ 2 cifre), mai un apice wildcard.workableirrobustita (#2675) — retry, header simil-browser e serializzazione delle richieste contro l’host dietro Cloudflare.personioricorre a uno scraping HTML quando il feed XML è disabilitato, invece di non restituire nulla.- Alias FALLBACK di
statesrisincronizzati con il padre (#2615).
Note
- Non portato (non specchiato da web-ui, o solo CLI): reply-matcher (#2672), jd-similarity (#2661), jd-skill-gap (#2686), il parsing di scan env-path (#2568) /
--flag=value(#2589), e le modifiche a cover-letter / template CV / doctor / ollama / generate-pdf. Gli avvisi HIGH webjs-yaml/nanoiderano già stati corretti in web-ui v1.135.0.
[1.135.0] — 2026-08-11
Parità con career-ops padre v1.26.0 — cinque nuove sorgenti di scansione a zero autenticazione più correzioni di correttezza a quattro bacheche già presenti in web-ui. Il registro ora conta 78 sorgenti = 73 EN + 5 RU (ALL_ADAPTERS 73).
Aggiunto
- Cinque nuove sorgenti di scansione (ciascuna con una sorgente + adattatore + suite CI-isolata; compaiono nel filtro Sorgente di
#/scane sulla landing di cvstart.org):join(JOIN) — la bacheca JOIN di un’azienda ricavata dal__NEXT_DATA__di Next.js injoin.com/companies/<slug>(host fissato, pagine limitate).getro(Getro) — bacheche di portfolio “talent-network” dei VC tramite l’API pubblica POSTapi.getro.com, paginata dal più recente; ogni lavoro è attribuito all’azienda del portfolio, non al fondo.consider(Consider) — bacheche di portfolio VC di getconsider.com tramite una POST same-origin; l’host configurabile è fissato da una guardia SSRF strutturale (solo host HTTPS pubblico).joinup(JOINUP) — la bacheca svizzera joinup.ch, che legge la pagina più recente renderizzata lato server; si blocca (fail-closed) in caso di rottura dello scraper.remotli(Remotli) — remotli.ch, ruoli da remoto presso aziende svizzere (stipendi in CHF); emette l’URL di candidatura ATS proprio del datore di lavoro, così le inserzioni incrociate vengono deduplicate.
Corretto
- a16z Speedrun non interrompe più l’intera bacheca per un intoppo transitorio — i fetch delle pagine ora passano attraverso un
fetchJsonWithRetrycondiviso (tentativi limitati solo su 429/5xx/timeout transitori, mai su un 4xx permanente), e il budget di pagina è stato ridimensionato per la pagina da 50 lavori. - arbeitsagentur è passata alla API Jobsuche v6 (
/pc/v6/jobs) — il vecchio endpoint v4 restituisce 404; la forma della risposta è stata rinominata e il filtraggio remoto ora si restringe lato server. - thehub è passata alla API v2
jobsandfeatured— le righe non portano una data di pubblicazione e sono esenti dal filtro per età. - hackernews trova ora in modo affidabile il thread mensile “Who is hiring?” filtrando la ricerca Algolia sul tag account
author_whoishiringinvece che su una query in testo libero.
Note
- Non portato (web-ui è già sicuro, assorbito dall’inoltro, o solo CLI): le chiavi di deduplicazione ruoli / corrispondenza aziende Unicode (il raggruppamento dei ripescaggi di web-ui fa già chiave sull’azienda in minuscolo semplice, quindi datori di lavoro distinti non latini non collassano mai); il segnale di latenza di rifiuto del follow-up + le rifiniture di company-funded (inoltrati in sola lettura, fail-soft); i percorsi di scansione sovrascrivibili da variabili d’ambiente e il parsing
--flag=value(web-ui esegue gli scanner in-process); il refactoring di consolidamento dello User-Agent (web-ui lo centralizza già); e le voci solo CLI (elenco contenuti non attendibili, oferta/offer-prep, doctor, modifiche ai template CV/lettera di presentazione).
[1.134.1] — 2026-08-05
Irrobustimento della validazione — correzioni emerse da un audit a livello di intero progetto.
Corretto
successfactorsnon scarta più i lavori raccolti in caso di fallimento a metà scansione (regressione nel port v1.134.0 dell’errore su bacheca morta) — il suo ciclo di paginazione non aveva alcuntry/catch, quindi un fallimento a pagina 2 o successiva (dopo il successo della pagina 1) generava un errore e scartava tutto ciò che era già stato raccolto; e se quel fallimento era un404(unostartrowfuori intervallo),en-scannermetteva in quarantena per giorni un tenant in realtà attivo. Ora rispecchiaphenom/radancy: un fallimento a pagina 0 genera ancora un errore (bacheca morta), ma un fallimento su una pagina successiva mantiene i risultati parziali.- Le chip dei filtri di
#/scansono ora azionabili da tastiera (WCAG 2.1.1) — le chip delle facet (e la chip “pulisci”) erano span con un gestore di click ma senzatabindex/ruolo, quindi gli utenti che usano la tastiera o uno screen reader non potevano raggiungerle né attivarle. Ora portanorole="button",tabindex="0",aria-pressede l’attivazione con Invio/Spazio. - Tre stringhe inglesi hardcoded sono ora localizzate — il tooltip del badge di affidabilità di
#/scan, l’intestazione della colonna di trasferimento di#/scane l’intestazione del punteggio di#/dashboarderano letterali nudi che il gate di parità i18n non poteva rilevare (non erano mai state chiavi), quindi restavano in inglese in ogni localizzazione non inglese. Orascan.trustTip+scan.col.reloc(2 nuove chiavi) più un riuso ditrack.col.score, con una guardia source-static.
[1.134.0] — 2026-08-05
Parità con career-ops padre v1.25.0.
Aggiunto
- Nuova sorgente di scansione: getManfred (
manfred) — un feed a livello di board di ruoli tech spagnoli/UE con stipendi pubblicati, dawww.getmanfred.com/api/v2/public/offers(zero autenticazione, host fissato + solo HTTPS, catalogo completo in una singola richiesta). Sorgente + adattatore + una suite CI-isolata (tests/sources-manfred.test.mjs); il registro ora conta 73 sorgenti = 68 inglesi + 5 russe (ALL_ADAPTERS68). Compare nel filtro Sorgente di#/scane sulla landing di cvstart.org.
Corretto
- Il feed a16z Speedrun si troncava silenziosamente a 50 lavori (#2404) — il feed limita una pagina a 50, ma la sorgente richiedeva
PER_PAGE = 100, quindi la paginazione si fermava dopo la pagina 1. Corretto a 50. - Le bacheche morte ora generano un errore invece di leggersi come “attive ma vuote” (#2379) —
cryptocurrencyjobs,phenom,radancy,successfactors: un fallimento di fetch in cui nessuna richiesta si risolve mai ora genera un errore (così#/portalshealth e la scansione registrano un vero fallimento), invece di ridurlo silenziosamente a un elenco vuoto; un fallimento a metà scansione dopo almeno un successo mantiene i risultati parziali. - workable ora usa l’API pubblica del widget (#5ab8425) — passato a
apply.workable.com/api/v1/widget/accounts/<slug>, che restituisce in un’unica richiesta l’elenco completo degli annunci di un account grande, così gli account grandi non vengono più troncati.
Note
- Non portato (solo CLI o non replicato da web-ui): la riscrittura delle prestazioni con bucket per titolo di detect-reposts #2389; le correzioni per chiavi aziendali Unicode (la deduplicazione del tracker di web-ui è già sicura per caratteri non latini);
scan --since;cv-facts; il passaggio di audit su template CV / PDF;doctor; la direttiva sui contenuti non attendibili delle modalità.
[1.133.1] — 2026-08-02
Corretto
#/funded(Aziende finanziate) ora mostra i risultati — due bug facevano sì che la tabella mostrasse sempre “nessuna azienda finanziata” anche quandocompany-funded.mjsdel padre restituiva un elenco completo. (1) La vista leggeva i risultati sottores.candidates, ma il padre li emette sottocompanies(ciascuna{ company, amount, round, funding: { sources: [{ source, url, observed_date }] } }); il client ora legge la chiave corretta e mappa la forma reale delle prove. (2) La tabella dei risultati passava le proprie celle aUI.el('tr', {}, …)come argomenti variadici, maUI.el(tag, attrs, children)si aspettachildrencome un singolo nodo o un array, quindi veniva renderizzata solo la prima colonna (Azienda) — ora le celle vengono passate come array. Verificato in un browser reale: 11 aziende dai quattro feed vengono renderizzate con le colonne Azienda / Segnale di finanziamento / Fonte / Data e link alle prove funzionanti, zero errori console. Un passaggio vuoto ora mostra anche la diagnostica per fonte, così un giorno di notizie tranquillo si distingue da un feed bloccato.- Guardie di regressione in
tests/parity-routes-v1133.test.mjs: lo script fittizio del padre ora emette la forma reale di outputcompanies(il fixture originale rispecchiava erroneamente la formacandidates— proprio il motivo per cui il bug è stato pubblicato con la suite verde), oltre a canarini source-statici che verificano chefunded.jsleggares.companies(maires.candidates) e costruisca le righe della tabella con figli in forma di array (+1 → 2144).
[1.133.0] — 2026-08-01
Aggiunto
- Scoperta aziende finanziate (
#/funded, parità col padre #2117) — una nuova vista di sola lettura che inoltra lo scriptcompany-funded.mjsdel career-ops padre tramiteGET /api/company-funded: un elenco a revisione preventiva di aziende recentemente finanziate, scoperte da feed pubblici di finanziamento con host fissato (TechCrunch, PR Newswire, The Guardian, Hacker News). L’inoltro esegue lo script con--json --dry-run(JSON su stdout, nessuna scrittura su file), non passa mai l’input dell’utente in--sources, applica un limite di frequenza ed è attivato dall’utente (un pulsante Scopri, mai al montaggio). Nuovo modulo di rottaserver/lib/routes/funded.mjs+public/js/views/funded.js, sotto Sourcing. - Digest settimanale dei colloqui (
#/interview-digest, parità col padre #2129/#2130) — una nuova vista di sola lettura che inoltra lo script zero-LLMweekly-digest.mjsdel padre tramiteGET /api/interview/weekly-digest: un riepilogo meccanico delle note di sessione dei colloqui — con quali aziende e in quali fasi hai fatto colloqui questa settimana, competenze ricorrenti e lacune aperte stimate al meglio. L’intervallo opzionale?from=&to=viene passato solo quando ENTRAMBI sonoYYYY-MM-DDvalidi; un intervallo vuoto è un digestavailable:truevalido. Aggiunto aserver/lib/routes/interview.mjs+public/js/views/interview-digest.js, sotto Analytics. - Entrambi gli inoltri seguono il contratto consolidato fail-soft
available:falsequando lo script del padre è assente (CI, installazioni standalone). 26 nuove chiavi i18n ×17 locale; suite CI-isolatatests/parity-routes-v1133.test.mjs(+5 → 2143).
Note
- Il career-ops padre è avanzato oltre la v1.24.0 con la pagina Follow-up Tracker dell’app web/ Next.js (#1422) e il rendering PDF lato backend (#2182) — non portati: web-ui ha già un proprio inoltro follow-up e i propri runner PDF, e l’irrobustimento sottostante di
followup-cadence.mjsarriva gratis tramite l’inoltro con shell-out. Le modifiche aset-status.mjs/tracker-utils.mjssono interne alla CLI e non vengono replicate.
[1.132.0] — 2026-07-31
Modificato
- Il sottosistema di rendering dei risultati di
#/scanè stato estratto inpublic/js/lib/scan-results.js(debito da contratto sulla dimensione dei file —public/js/views/scan.jsera cresciuto a ~1254 LOC). Il sottosistema —renderResults,buildChipRow,getRows, i builder di righe/faccette, i pittori delle opzioni e lo specchio del registroFALLBACK_SOURCES— si è spostato in una factorywindow.ScanResults.create(ctx)che chiude su un oggetto di contesto fornito dalla vista. Nessuna modifica di comportamento — le funzioni sono state spostate testualmente, le variabili di chiusura ricollegate meccanicamente actx.*;scan.jsè ora ~906 LOC (è pianificato un secondo passaggio di estrazione verso l’obiettivo di 800 LOC). - I test source-static leggono entrambi i file tramite
tests/helpers/scan-src.mjs::loadScanSrc();tests/scan-fallback-sources.test.mjsora legge lo specchio del registro dascan-results.js. - Nuovo gate di regressione in-browser —
tests/playwright-scan-filters.mjssemina undata/last-scan.jsonpredefinito e guida ogni filtro di#/scan, verificando i conteggi esatti delle righe (npm run test:e2e:browser); sono stati aggiunti id stabili per i controlli dei filtri (#scan-filter-*,#scan-apply). - Il banner “Ultima release” del README è stato snellito a un riepilogo di una riga + un link al changelog completo (il lungo muro narrativo multi-versione è stato ritirato). Applicato in tutte le 17 localizzazioni.
[1.131.2] — 2026-07-31
Modificato
app.cssdiviso in tre fogli di stile ordinati (debito da contratto sulla dimensione dei file — il singolo file era cresciuto a ~1990 LOC, ben oltre il limite rigido di 800 LOC). Ora èapp.css(~672 — accessibilità, token di design/tema, sidebar, main, pulsanti, content-shell),components.css(~595 — card, griglie, paginatore, badge, tabelle, form, log/console, markdown, selettore lingua, filtro a chip, banner di connessione) eoverlays.css(~737 — toast, cassetto notifiche, modale, varie/responsive, lo specchio[dir="rtl"], docs-fab, usage-hud), ciascuno entro il limite rigido.- Il taglio è contiguo e in ordine, quindi la cascata è identica byte per byte al file pre-divisione;
index.htmlcarica i tre come<link>ordinati. Nessuna modifica di comportamento, markup o i18n. - I test che verificano il CSS ora leggono la concatenazione tramite un helper condiviso
tests/helpers/css.mjs::loadAppCss(). Il nuovotests/css-modularization.test.mjsblocca la divisione (i file esistono · ciascuno ≤ 800 LOC · ordine dei link in index.html) → suite 2138. Verificato nel browser: tutti e tre i fogli di stile vengono analizzati e le loro regole vengono applicate.
- Il taglio è contiguo e in ordine, quindi la cascata è identica byte per byte al file pre-divisione;
[1.131.1] — 2026-07-31
Corretto
- Coerenza del pinning dell’host degli adattatori sulle due sorgenti v1.130.0 (follow-up di code review, difesa in profondità; nessuna modifica di comportamento per input validi):
- L’adattatore
a16z-speedrun-talentora rivalida l’overrideapi:/a16z-speedrun-talent:inbuildEndpoint(HTTPS + host esattospeedrun-talent-network.com) e torna al feed canonico quando la validazione fallisce — parità con l’adattatorecryptocurrencyjobs, così un valore fuori host non raggiunge mai lo slot di fetch (in precedenza si basava solo sulla guardia a tempo di fetchassertSpeedrunUrl). Il controllo dell’host esatto è ora una singolaSPEEDRUN_TALENT_HOST_REesportata, condivisa dalla guardia e dall’adattatore. - Il parser
cryptocurrencyjobs—cleanUrlora usa la stessa guardia dell’host a corrispondenza esatta diassertCryptocurrencyJobsUrle dell’override dell’adattatore (eraendsWith, che accettava sottodomini). Il parser non è mai più permissivo della guardia SSRF: un link di elementosub.cryptocurrencyjobs.coviene scartato. - +2 test → suite 2135.
- L’adattatore
[1.131.0] — 2026-07-31
Aggiunto
- Bacheca a schede di stadio (CRM) per
#/tracker(portata dalla vista/pipelinedell’app web del padre). La barra a chip del funnel + il menu a tendina di stato del tracker sono sostituiti da una striscia di schede di stadio: una scheda Tutti più una scheda per ogni stato canonico — Evaluated · Applied · Responded · Interview · Offer · Rejected · Discarded · SKIP · Hired — ciascuna con un conteggio live sull’intera cronologia, incluse le fasi a conteggio zero così l’intero funnel è sempre visibile (l’aspetto CRM). La scheda attiva guida il filtro; un secondo clic la deseleziona tornando a Tutti. Le righe mantengono la tonalità del punteggio, la legittimità e le funzioni PDF e report, e la cella dell’azienda ora mostra un logo del brand quando i loghi sono abilitati (disattivati di default → zero richieste extra).- Nuova rotta di sola lettura
GET /api/tracker/stagesrestituisce il funnel canonico (etichette in ordine) + una mappa di fusione degli alias, presa daserver/lib/states.mjs(templates/states.yml, con il fallback integrato) — così il client non codifica mai staticamente la whitelist di stato. La risposta legacy senza parametri diGET /api/trackerè invariata (solo{ rows }). - Nuova libreria client pura e testata a livello di unità
public/js/lib/tracker-stages.jsraggruppa le righe secondo gli stadi del server, tollerando grassetto markdown residuo e alias localizzati (es.aplicado→Applied). Le schede sono accessibili (role tablist/tab, aria-selected, area di tocco ≥44 px, conteggi nel nome accessibile di ciascuna scheda). Nessuna nuova chiave i18n. Suite 2133.
- Nuova rotta di sola lettura
[1.130.0] — 2026-07-31
Aggiunto
- Due nuove sorgenti di scansione portate dal career-ops del padre v1.24.0 (in-process, senza nuove dipendenze; entrambe compaiono nel filtro Sorgente di
#/scane sulla landing di cvstart.org):- a16z Speedrun (
a16z-speedrun-talent, #2231) — il feed JSON dell’intera bacheca della talent-network a16z Speedrun. Fissato all’hostspeedrun-talent-network.com, solo HTTPS, paginazione a indice 0 con un tetto di pagine, threading diq/config per azienda, fail-soft. - Cryptocurrency Jobs (
cryptocurrencyjobs) — il job board Web3cryptocurrencyjobs.co, acquisito tramite il suo feed pubblico RSS 2.0 (zero autenticazione). Decodifica delle entità XML in due passaggi, annunci solo remoti, datore di lavoro estratto dalla coda del titolo"… at <Azienda>". - Il totale del registro è ora 72 sorgenti = 67 inglesi + 5 russe (
ALL_ADAPTERS= 67 adattatori di portali inglesi).
- a16z Speedrun (
Corretto
echojobs— i ruoli ibridi restano distinguibili dal remoto (rispecchia il #2258 del padre). Un marcatorehybridcase-insensitive ora produce"<Città> · Ibrido"(o un sempliceIbridoin assenza di città) eworkplaceType: 'Hybrid', invece di essere collassato inRemoto.radancy— markup legacy TalentBrew + trasporto del frammento di risultati JSON (rispecchia il commit del padre a3e6df9), condizionato da unopts.fetchJsoniniettabile.
Note
- Non portato — funzionalità del padre solo CLI. L’ampia superficie CLI/modalità di career-ops v1.24.0 resta fuori da web-ui, che è un visualizzatore + scrittura passante sottile, non un host di modalità: le tabelle di conformità/giurisdizione, la rubrica contatti + vCard, il debrief da trascrizione colloquio / rilevamento piattaforma di chiamata, l’impostazione stato del ledger, la registrazione degli esiti, il triage a due passaggi, la similarità delle JD, lo schema versionato degli artefatti CV di candidatura, il rilevamento doctor di Playwright-MCP e
portals/fix-slugs.mjs. Le modifiche di orchestrazione della scansione che vivono nelloscan.mjsdel padre — lo scanner Playwright per Interamt.de, lo sweep completo iCIMS reverse-ATS, il filtro di idoneità paese per il remoto, il pacing delle ricerche DNS, la deduplicarltrdi StepStone e la colonna azienda normalizzata nella cronologia scansioni — non si applicano: web-ui esegue gli scanner EN/RU in-process e non passa perscan.mjs. - Già coperto. La correzione di accent-folding di
role-matcher(#2209) è stata portata in v1.127.0, quindi qui è un no-op.
[1.129.1] — 2026-07-29
Corretto
- Follow-up della revisione IA sui port web di v1.128/v1.129 (tutti consultivi, corretti all’origine): precedenza di livello in
job-facets.js(un modificatore esplicito ora batte una parola di gestione —Senior Engineering Manager→senior, primalead); il fallback instates.mjsnon è più bloccato (una lettura riuscita è memoizzata, il fallback è restituito senza cache — un padre momentaneamente non disponibile al boot viene riletto alla chiamata successiva) +console.warnsu un file presente ma malformato;score-tone.js— una riga senza punteggio è neutra (muted), non rossa;domainFromName()salta gli slug non-ASCII prima di/api/logo; +una guardia di isolamento intests/states.test.mjs. +4 test → 2073.
[1.129.0] — 2026-07-29
Aggiunto
- Faccetta Livello + colonna Età su
#/scan— la libreriajob-facets.jsrilasciata in v1.128.0 è ora collegata alla UI di scansione (prima solo logica). Un nuovo menu Livello classifica il titolo di ogni offerta in lead/staff/senior/intermedio/junior/stagista (JobFacets.seniorityFromTitle) e si autopopola da ciò che c’è nei risultati (come la faccetta Paese); i titoli senza parola di livello passano sempre. Mantenuta in ricerche salvate, Reimposta e Applica. La tabella guadagna una colonna Livello (badge) e una colonna Età senza token (oggi/Ng, daJobFacets.daysSince). 12 chiavi i18n ×17, +3 test → 2069.
[1.128.0] — 2026-07-29
Aggiunto
- Quattro soluzioni portate dalla web app propria del padre (
../web/, Next.js), reimplementate in JS vanilla/ESM, senza nuove dipendenze: (1)server/lib/states.mjsleggetemplates/states.ymldal vivo come vocabolario canonico degli stati del tracker (fallback CI) — elimina il re-sync manuale della whitelist a ogni release; POST ripiega gli alias (spagnolo/legacy) sull’etichetta canonica, il funnel di GET raggruppa per stato canonico; (2) loghi aziendali sulle righe con host ATS viadomainFromName()(~90 brand→dominio); (3)score-tone.js— tono di punteggio a 4 livelli (≥4.2/3.8/3.0 + fallback a lettera); (4)job-facets.js— facet seniority/source/days. +21 test.
Note
- Non portato (solo concetto): il livello di azioni agentiche del padre (
actions/registry.ts+api/assistant/route.ts) — progetto per quandodocs-fabdiventerà un copilota. Nessuna nuova sorgente (registro 70), nessuna modifica i18n/help.
[1.127.0] — 2026-07-29
Aggiunto
- Tre nuove sorgenti di scansione (parità career-ops v1.23.0) — il registro conta ora 70 adattatori (65 EN + 5 RU): Flowxtra (aggregatore globale senza auth), VDAB (API per parola chiave del servizio pubblico per l’impiego fiammingo) e iCIMS (portali
careers-<tenant>.icims.com, distinto dajibeapply). Inoltre Cursor torna nel roster delle CLI (parent #2115):cli-detectora rilevacursor(10 strumenti), roster ripristinato in help/README/config ×17.
Corretto
- agenticjobs è passato dallo scraping HTML all’API REST (#2167); Greenhouse recupera la città da
/officesquandolocation.nameè solo un modello di lavoro (#2104); parità role-matcher (#1933/#2164/#2009: prefisso MTS, baselineproduct, ripiegamento accenti, disaccordo sub-baseline).
Note
- Non portato. Gran parte di v1.23.0 è superficie CLI/dashboard che web-ui non usa (batch-tailor, discover-ats, modi NL/PT, temi PDF, dashboard Go, updater/doctor); gli script relayati non cambiano. VERSION del padre → 1.23.0.
[1.126.1] — 2026-07-25
Corretto
- Due punti di deriva del roster CLI che il resync di v1.126.0 ha mancato — (1) l’intro della scheda API keys di
#/config(config.providerModelNote, i18n ×17) elencava solo 7 CLI — ora Antigravity e Grok Build sono inseriti dopo OpenCode; (2) una seconda riga della tabella comparativa nella guida di aiuto (×17) e l’help del sito (costruito in CI) riportavano ancoraInside Claude Code / Codex / Cursor / Gemini CLI— il set obsoleto con Cursor — ora il roster completo. Entrambi usavano separatori slash/punto medio che i pattern dello sweep v1.126.0 non coprivano. Snapshot i18n rigenerato; la suite resta a 1969.
[1.126.0] — 2026-07-25
Aggiunto
- La scheda strumenti AI CLI ora rileva tutte le 8 CLI di prima classe di career-ops — il roster di
#/configè stato sincronizzato con ildocs/SUPPORTED_CLIS.mddel padre:server/lib/routes/cli-detect.mjsguadagna Grok Build CLI (grok) e Kimi CLI (kimi), e Antigravity ora sonda per primo il suo binario canonicoagy. La scansione PATH in sola lettura ora riporta 9 strumenti; continua a non eseguire mai un binario trovato.
Modificato
- Risincronizzazione della documentazione con career-ops.org/docs — ogni superficie di docs è stata riconciliata con le pagine live del padre (lette tutte e 31). Il roster canonico di assistenti IA (help ×17 + README ×17) elenca ora le 8 CLI di prima classe — Claude Code, Codex, OpenCode, Antigravity CLI, Grok Build CLI, Qwen Code, Kimi, GitHub Copilot CLI — più Gemini CLI (wrapper legacy). I bundle di aiuto mantengono la struttura 29 H2 / 105 H3.
[1.125.4] — 2026-07-23
Modificato
- dipendenze di site (dependabot #151–#153) —
sharp0.34.5→0.35.3,svgo4.0.1→4.0.2,fast-uri(dev) 3.1.3→3.1.4 insite/; build Astro verde, nessun impatto su SPA/server.
Note
- Sweep di parità col progetto padre (career-ops
37d17ec..254764a, post-v1.22.0) — niente da portare: il guard della riga sbagliata diset-status(#2108) è solo CLI (in web-ui le righe del tracker si selezionano esplicitamente nella UI e nessuna route invocaset-status.mjs), il Risk Summary dei modi localizzati (#2109) tocca filemodes/<lang>/che web-ui non legge mai (solomodes/*.mddi primo livello), la verifica del manifest diupdate-system(#2111) riguarda solo l’updater, e il resto è documentazione del padre (README turco, SIGNATURES ×4, SCRIPTS.md, accenti es). Il VERSION del padre resta 1.22.0 —parentVersioninvariato.
[1.125.3] — 2026-07-23
Corretto
- I prompt LLM in danese e hindi rispondevano in inglese (segnalato da un utente) —
LOCALE_NAMESe i cinque blocchiSCAFFOLD_STRINGSinserver/lib/prompts.mjsnon erano mai stati estesi adaehi, quindiresolveLocale()ricadeva suene ogni prompt AI — deep research (live e manuale), modalità, valutazione, colloquio, networking, CV Studio — perdeva la direttiva# Output languagein quelle due lingue. Entrambe sono ora di prima classe: direttiva di lingua + impalcatura localizzata. Il gate di regressione intests/locale-scaffold.test.mjsora scorre l’elenco canonico di 17 locale invece di 12 hardcoded, e un nuovo gate strutturale fa fallire qualsiasi chiave dell’impalcatura che ricade sull’inglese — una futura locale che dimentichiprompts.mjsnon può più essere pubblicata (+12 test, la suite è ora 1969).
[1.125.2] — 2026-07-22
Corretto
- Deep research via Gemini: HTTP 502 (
MALFORMED_FUNCTION_CALL) (#145, contributo di @Alien10140) — il prompt live di/api/deepchiedeva al modello di «Use WebFetch / WebSearch» e di salvare il brief su file, ma i provider API headless non hanno un canale strumenti; Gemini rispondeva con una chiamata di funzione invece che con testo, manifestandosi come un 502 vuoto.buildDeepPromptebundleProjectContextaccettano ora un flagheadless: le esecuzioni live (Anthropic/Gemini/cascata di fallback) ricevono un prompt senza strumenti che scrive il brief dal contesto inlineato, mentre il prompt copia-incolla per Claude Code mantiene le istruzioni sugli strumenti. +1 test intests/critical-fixes.test.mjs.
Modificato
- Modelli Gemini predefiniti aggiornati oltre il deprecato
gemini-2.0-flash(#144, contributo di @Alien10140) — il menu a tendina della Configurazione, il fallback server ingemini.mjs(che divergeva in silenzio dal suggerimento), la catena di fallback OpenRouter,config.geminiModelHint×17 e la guida ×17 ora indicano tuttigemini-3.6-flash. Il nuovo gate anti-derivatests/gemini-default-model.test.mjs(+5 test) fissa tutte le superfici sullo stesso letterale — la suite arriva a 1957 test.
[1.125.1] — 2026-07-21
Corretto
- SuccessFactors: i tenant RMK multi-brand mantengono il proprio percorso di brand (parent #2099, post-v1.22.0) — le holding che gestiscono più brand acquisiti da un’unica istanza RMK condivisa li distinguono tramite un segmento di percorso (
careers.nemetschek.com/Bluebeam/contro…/Vectorworks/); l’adattatore in precedenza collassava l’URL configurato alla sua origine, scansionando silenziosamente gli annunci del brand principale. L’endpoint ora preserva il prefisso di brand, rimuovendo solo un segmento finale/search/o/tile-search-results/così che nulla si duplichi mai su se stesso; i tenant a dominio singolo restano invariati byte per byte. Nuovo helper esportatoresolveTenantBase+ 1 blocco di test portato intests/sources-successfactors.test.mjs.
[1.125.0] — 2026-07-21
Aggiunto
- cvstart.org: sezione “Fonti di lavoro” nella landing — una nuova sezione tra gli screenshot e il confronto elenca tutte le 67 sorgenti di scansione come chip cliccabili (62 board/ATS in inglese + le 5 board russe sotto un proprio sottotitolo), ciascuna con link al sito pubblico della sorgente. L’elenco è sincronizzato con il registro degli adattatori dal vivo in fase di build (
sync-assets.mjs→facts.sources), così non può mai disallinearsi dall’app; una mappa di link curata inSources.astroè protetta dalla nuovatests/site-sources.test.mjs. La navigazione dell’header ha guadagnato un’ancora Fonti; 4 nuove chiavi i18n del sito ×17. È stato corretto anche l’elencoinLanguagedel JSON-LD della landing, a cui mancava ancorahi.
[1.124.0] — 2026-07-21
Aggiunto
- Cinque sorgenti di scansione (parità con il parent v1.22.0, #1808/#1572/#2024/#2055) — Welcome to the Jungle (API JSON per l’intero board), Agentic Engineering Jobs (board di ingegneria agentica/IA), Jobvite (ATS per-tenant a zero autenticazione), Gem (ATS per-tenant) e Alibaba Group (API JSON delle pagine carriere, pattern Meituan/Tencent). Ognuna è una coppia sorgente + adattatore ancorata all’host e isolata per la CI; il registro ora conta 67 adattatori (62 inglesi + 5 russi); il fallback del menu a tendina Source di
#/scane il relativo gate di deriva sono aggiornati; cinque nuove suitetests/sources-{wttj,agenticjobs,jobvite,gem,alibaba}.test.mjs.
Corretto
- Arbeitsagentur: full-remote a livello nazionale solo quando
homeofficetypèVOLLSTAENDIG(parent #1981) — la queryhomeoffice=nv_truerestituisce anche ruoli ibridi, quindi il passaggio remoto ora conferma ogni risultato rispetto all’endpoint di dettaglio dell’offerta in piccoli batch e fallisce in modo sicuro (un errore di lookup mantiene la città reale dell’offerta, così i filtri di località continuano ad applicarsi). - SmartRecruiters: gli URL pubblici delle offerte venivano costruiti senza
/postings/(parent #2047) — i link ora atterrano sulla pagina pubblica dell’offerta invece di un 404 per i tenant il cui sito pubblico omette il segmento.
Note
- La v1.22.0 del parent ha anche introdotto modifiche lato CLI che la web UI non richiama via shell o che già copre: il template CV zh-CN + la tipografia del PDF, la modalità
/expand, le ottimizzazioni della cache dei prompt dei provider (Gemini/OpenAI/Ollama), la ripartizione dei token per singolo passo (la web UI ha il proprio contatore di utilizzo), la serializzazione con writer-lock del tracker (la web UI instrada le scritture tramitewithFileLockdalla v1.21), i flag CLIvisa_filtere data di pubblicazione assoluta per lo scan (la web UI ha il proprio filtro “Pubblicato entro”), e il seeding di deduplica delle sorgenti già viste (lo scanner della web UI mantiene la propria deduplica dello storico delle scansioni).
[1.123.0] — 2026-07-17
Aggiunto
- Sorgente di scansione Oracle Recruiting Cloud (parità con il parent v1.21.0, #1929) — l’API REST a zero autenticazione
recruitingCEJobRequisitionsdei siti carriere Oracle Fusion/ORC (JPMorgan Chase, Oracle, BNY Mellon, American Express, Honeywell, …): host ancorato a*.fa[.<region>][.ocs].oraclecloud.com, il numero di sito risolto dalcareers_urldi ciascuna azienda tracciata, paginazione a offset con un limite massimo di pagine, e header simil-browser consapevoli del WAF. Il registro ora conta 62 adattatori (57 inglesi + 5 russi); il fallback del menu a tendina Source di#/scane il relativo gate di deriva sono aggiornati; nuova suite isolata per CItests/sources-oraclecloud.test.mjs.
Corretto
- Rilevatore di repost: i titoli base restano distinti dai fratelli con suffisso specializzato (parent #1922) — “Senior Analytics Engineer” non si raggruppa più con “Senior Analytics Engineer, People Analytics”: quando i token di un titolo sono un sottoinsieme stretto di quelli dell’altro e il token in più è una specializzazione reale (non una parola di base), le due offerte sono trattate come annunci pubblicabili separatamente. Le annotazioni di repost (“(Repost)”, “relisted”) sono ora trattate come stopword di rumore meta. +2 asserzioni in
tests/detect-reposts.test.mjs.
Note
- La v1.21.0 del parent ha anche introdotto modifiche lato CLI che la web UI non richiama via shell o che già copre: l’avviso di ricandidatura per azienda ripetuta (la web UI ha il cooldown di ricandidatura dalla v1.84.0), i flag
--format/--reportdella lettera di presentazione, le modalità del prompt e-mail per red-flag del colloquio / intelligence sul panel / mancata presentazione, la persistenza dei segnali di fiducia della scansione e della salute dei portali (la web UI esegue il proprio scanner in-process contrust-validatore la pagina di salute dei Portali), e le estensioni di statistiche/salary-gap (inoltrate in sola lettura e fail-soft).
[1.122.0] — 2026-07-16
Aggiunto
- Hindi (हिन्दी) — la 17ª lingua — dizionario completo dell’interfaccia (~1.110 chiavi), la guida in-app completa (parità 29 H2 / 105 H3),
README.hi.md, un nuovoCHANGELOG.hi.md(a partire dalla v1.122.0, seguendo il precedente di de/it/tr), le pagine landing di cvstart.org + Metodologia/Licenza/Changelog/Guida, lo switcher lingua (🇮🇳), il rilevamento automatico della lingua del browser e uno screenshot della dashboard localizzato. Ogni gate di parità ×16 ora gira ×17: parità del dizionario i18n + snapshot, i gate H2/H3 della guida, la parità del changelog,check-i18ndella site e lo spazzolamento delle lingue con Playwright.
[1.121.0] — 2026-07-16
Aggiunto
- cvstart.org: pagine Metodologia, Licenza e Changelog — la landing ha guadagnato tre nuove sezioni in tutte le 16 lingue, accanto al blocco Confronto già esistente: /methodology/ (la griglia di punteggio a sei dimensioni da 0.0–5.0, la soglia di candidatura a 4.0 e le regole del “mai fare” — un riassunto localizzato di career-ops.org/methodology), /license/ (il testo canonico MIT con il rimando a NOTICE.md) e /changelog/ (questo file, reso per singola lingua a partire dai 16 CHANGELOG tradotti del repository). Nuova voce Metodologia nell’header e link Risorse nel footer;
sync-assets.mjsora sincronizza anche il CHANGELOG ×16 e la LICENSE nella site in fase di build, così le pagine non possono mai disallinearsi dal repository. - Link alla metodologia in tutta la documentazione — il README (tutte le 16 lingue), l’elenco canonico del §1 della guida in-app (tutte le 16 lingue) e il wiki ora rimandano a career-ops.org/methodology (oltre a FAQ e glossario) accanto alle guide già esistenti su career-ops.org/docs.
Modificato
- Banner e badge di release del README aggiornati (1850 test, release v1.121.0) — il banner annunciava ancora la v1.119.5.
[1.120.0] — 2026-07-16
Aggiunto
- Il Manifesto CareerOps (parità con il padre v1.20.0) — il progetto padre ha pubblicato il Manifesto CareerOps (
MANIFESTO.md· career-ops.org/manifesto) e ora lo mette in evidenza dal proprio README, dall’updater e dalla dashboard Go. La web-ui fa lo stesso: un nuovo link nel footer della barra laterale apre la pagina del manifesto (nuova chiave i18nfooter.manifestoin tutte le 16 lingue), la guida in-app ha guadagnato il §29 «Il Manifesto CareerOps» in tutte le 16 lingue, il README spiega cos’è il manifesto e come firmarlo, e anche il footer della landing cvstart.org vi rimanda.
Note
- Il padre v1.20.0 ha anche corretto la soppressione delle competenze già note nella modalità mirata
upskill, ha reso silenzioso dotenv così che lo stdout discan --jsonresti analizzabile, e ha corretto il template HTML del CV in modo che l’intestazione di un ruolo resti insieme ai suoi punti elenco — superfici lato CLI in cui la web-ui non entra tramite shell; non è stata necessaria alcuna modifica al codice della web-ui.
[1.119.5] — 2026-07-13
Corretto
- Il pulsante lingua della landing non va più a capo — con le bandiere della v1.119.2 l’etichetta dello switcher nell’header (es. «🇷🇺 Русский») poteva spezzarsi fino a tre righe alle larghezze desktop strette; l’etichetta dello switcher e tutte le opzioni del menu ora usano
whitespace-nowrap— bandiera + endonimo sempre su una riga. L’elenco delle lingue nel footer è passato da una griglia rigida a due colonne a una riga avvolgente di voci su una riga — anche «🇧🇷 Português (Brasil)» non si spezza più a metà nome.
[1.119.4] — 2026-07-13
Modificato
- LICENSE nomina l’autore — la riga di copyright ora recita: Sergei Emelianov (Fighter90) https://sergey-cv.com and career-ops-ui contributors (testo MIT canonico intatto). Un nuovo NOTICE.md dettaglia il licenziamento: chi detiene il copyright, cosa copre esattamente la concessione MIT (codice, doc, traduzioni, la landing, la wiki), cosa NON copre (i tuoi dati a runtime, il progetto padre, i contenuti delle job board, i marchi), la tabella dei componenti di terze parti (express/js-yaml — MIT; Astro/Tailwind — MIT; i font Figtree e JetBrains Mono — SIL OFL 1.1; sharp — Apache-2.0) e una riga di attribuzione facoltativa.
[1.119.3] — 2026-07-13
Aggiunto
- SECURITY.md — la security policy a cui puntava CONTRIBUTING ora esiste: versioni supportate, flusso di segnalazione privata (il private vulnerability reporting di GitHub è ora abilitato sul repo — scheda Security → «Report a vulnerability»), il modello di minaccia per un’app mono-utente su localhost (in scope: XSS via annunci ostili / SSRF / path traversal / fuga di segreti / indebolimento della CSP; fuori scope: DoS del proprio localhost e problemi del progetto padre) e la baseline di hardening per i reviewer.
[1.119.2] — 2026-07-13
Aggiunto
- CONTRIBUTING.md — la guida del contributore a cui landing e README puntavano da sempre ora esiste: installazione, mappa del progetto, le regole dure sicurezza/no-build, i livelli di test, il walkthrough dei «due registri» per aggiungere una sorgente di scansione, il contratto i18n ×16, le convenzioni di commit/PR e il processo di release.
- Bandiere delle lingue sulla landing — il selettore di lingue di cvstart.org, la griglia delle lingue nel footer e il banner «leggi nella tua lingua» ora mostrano la bandiera di ogni locale accanto al suo endonimo (lo stesso set di indicatori regionali del
<select>di lingua dell’app; degrada a lettere di regione dove mancano i glifi delle bandiere). - Correzioni del footer della landing — il link morto a Discussions (funzione non abilitata sul repo) ora punta alla wiki del progetto, e il footer accredita l’autore: Sergei Emelianov (sergey-cv.com · LinkedIn).
[1.119.1] — 2026-07-13
Corretto
- Il filtro sorgenti di
#/scanha raggiunto il registro — la lista staticaFALLBACK_SOURCESdietro il menu a tendina Source (usata solo quandoGET /api/scan/sourcesè irraggiungibile) era silenziosamente indietro dalla v1.87.0: mancavano 20 provider nel fallback offline (Amazon, Avature, SAP SuccessFactors, Get on Board, Dassault Systèmes, beesite, HigherEdJobs, JibeApply (iCIMS), softgarden, Cornerstone, Phenom, Radancy, Deutsche Bahn, EchoJobs, TKMS, Heckler & Koch, Rheinmetall, LaraJobs e i nuovi Meituan / Tencent). Sincronizzata con tutti i 61 e ora protetta da un test di deriva che fa fallire la CI quando la lista client diverge dal registro server (valori E etichette). +1 test (1845).
[1.119.0] — 2026-07-13
Parità con il career-ops padre v1.19.0 + aggiornamento della landing cvstart.org.
Aggiunto
- 2 nuovi provider di scansione — Meituan (
zhaopin.meituan.com) e Tencent (careers.tencent.com): le API JSON pubbliche senza autenticazione delle board tech cinesi, rilevate dall’host o selezionate con unprovider:esplicito, con ricerca server-side per parola chiave, paginazione e deduplicazione per URL — ora 61 adapter (56 EN + 5 RU). +20 test (1844). - Blocco dei contributori sulla landing — cvstart.org mostra gli avatar di tutti coloro che hanno contribuito con codice (API GitHub
/contributorsin fase di build, bot filtrati), localizzato nelle 16 lingue, con link al grafo completo dei contributori. - Contatore di stelle GitHub live sulla landing — il badge nell’header ora si aggiorna lato client dall’API GitHub a ogni visita (lo snapshot di build resta come fallback), e una ricostruzione settimanale pianificata di Pages mantiene freschi snapshot e lista dei contributori; le chiamate API in CI sono autenticate con token.
Corretto
- Le richieste Workday CXS portano header da browser (padre #1813) — i tenant dietro Cloudflare (visto dal vivo: geico) rispondono 500 alle richieste prive dei normali UA/
accept-language/origin/referer; il fetcher ora deriva origin e slug del site dall’URL CXS stesso. Le richieste Glints hanno guadagnato lo stesso UA da browser + origin/referer, entrambi dalla costante condivisaBROWSER_LIKE_USER_AGENTdihttp-json.mjs.
[1.118.4] — 2026-07-10
Corretto
- Le scansioni di hh.ru restituivano 0 risultati da un IP russo (link a sottodominio regionale) — da un IP residenziale russo, hh.ru reindirizza la ricerca (302) verso un sottodominio regionale (
sochi.hh.ru,spb.hh.ru, …) e restituisce i link delle offerte su quel sottodominio. Il parser cercava il link del titolo sull’host fissohttps://hh.ru/vacancy/e non ne trovava nessuno tra quelli regionali, quindi una scansione perfettamente funzionante registrava 0 in silenzio. Ora accetta qualsiasi host*.hh.ru(gli annunci suadsrv.hh.ru/click?…restano esclusi — non hanno il percorso/vacancy/<id>) e canonicalizza ogni URL di risultato inhttps://hh.ru/vacancy/<id>. Verificato dal vivo: 17 offerte reali vengono analizzate da una paginasochi.hh.ruche prima dava 0. +1 test (1824).
[1.118.3] — 2026-07-10
Corretto
- hh.ru restituiva silenziosamente 0 risultati (interstitial di verifica VPN) — hh.ru ora reindirizza con 302 le reti che considera VPN/proxy (IP di datacenter) verso un interstitial
/vpncheeck(“VPN мешает работе сайта”) che risponde HTTP 200 senza alcuna scheda di vacancy, quindi la scansione riportava 0 senza alcun errore. Lo scanner ora rileva il redirect tramite l’URL finale della risposta, disabilita hh.ru per il resto dell’esecuzione e stampa un suggerimento onesto: il traffico deve davvero uscire da un IP residenziale — un VPN/proxy di sistema può restare attivo anche con l’interruttore del browser spento. +1 test (1823).
[1.118.2] — 2026-07-10
Manutenzione
- Rifinitura della landing (#118) —
site/README.mdriconciliato con Astro 7 (l’upgrade di sicurezza di #116), import inutilizzato rimosso e +4 guardie eseguibili per gli script di build della landing: il gate di parità i18n fallisce dimostrabilmente con un dizionario rotto esync-assetsnon scrive mai fuori dasite/— suite 1822. Risolti due avvisi CodeQL (uno corretto alla fonte, uno respinto come comportamento di build voluto).
[1.118.1] — 2026-07-10
Corretto
- Scansione di hh.ru fuori dalla Russia — hh.ru ora restituisce HTTP 451 (blocco legale regionale) agli IP non russi sulle pagine pubbliche di ricerca. Lo scanner tratta il 451 come il 403: dopo il primo blocco hh.ru viene disattivato per il resto dell’esecuzione con una riga onesta nel log che indica un IP russo / uscita VPN, senza sprecare le query rimanenti né le altre fonti RU. Guida §7 aggiornata in tutte le 16 lingue. +1 test (1818).
[1.118.0] — 2026-07-09
Pacchetto di parità con il career-ops padre v1.18.0.
Aggiunto
- 9 nuovi provider di scansione — Cornerstone OnDemand (
csod), Phenom (phenom), Radancy (radancy), Deutsche Bahn (deutschebahn), EchoJobs (echojobs), TKMS (tkms), Heckler & Koch (hecklerkoch), Rheinmetall (rheinmetall), LaraJobs (larajobs) — ora 54 adapter. L’adapter di Lever rileva inoltre le board del tenant EU (jobs.eu.lever.co). - Stato
Hirednel tracker (parità con lostates.ymldel padre): le offerte accettate hanno uno stato canonico proprio, un badge celebrativo e un banner «lavoro ottenuto» su#/tracker; funnel e conversioni lo contano come avanzato attraverso tutte le fasi. - Scheda Totale in
#/stats— relay in sola lettura dellostats.mjsdel padre (riepilogo complessivo del tracker, tassi cumulati del funnel, totali dello scanner, copertura dei portali) più le osservazioni sulla retribuzione dasalary-gap.mjs(desiderata vs pubblicizzata vs effettiva, per candidatura). Nuove rotteGET /api/stats/lifetimeeGET /api/stats/salary-gap— shell-out a zero token, degradazione sicura{available:false}senza il progetto padre. - 28 nuove chiavi i18n in tutte le 16 lingue; guida in-app §14/§26 aggiornata in ogni lingua.
Test
- +38 test unitari (tre suite di parità provider + rotte relay/stato) — 1817 in totale.
[1.117.2] — 2026-07-06
Correzione tracker vuoto per gli shell-out di parità. Gli script del padre escono con codice 1 e un JSON {error} strutturato quando il tracker non ha ancora candidature; la bacheca di follow-up e la scheda pattern lo mostravano come «script-error». Entrambe le rotte ora lo inoltrano come uno stato vuoto sano (available:true, empty:true) e la UI mostra il suo onesto messaggio «ancora niente». Verificato dal vivo contro un padre reale.
Nuovo: nessuno.
[1.117.1] — 2026-07-06
Indurimento di v1.117.0 (triage CodeQL). I tre endpoint shell-out (GET /api/followup, POST /api/followup/seed, GET /api/stats/patterns) portano ora il limitatore per-IP condiviso (creano un processo figlio per richiesta; no-op su loopback). L’estrazione del testo da URL di Aggiungi al CV rimuove i tag fino al punto fisso e poi cancella ogni </> residuo — una sanificazione dimostrabilmente completa per testo di prompt LLM. Nessun cambiamento per input validi.
Nuovo: nessuno.
[1.117.0] — 2026-07-06
Pacchetto di parità col padre — sei capacità del career-ops padre portate nella UI. (1) Bacheca di cadenza su #/followup: urgenza per candidatura (🔴/🟠/🟡/🔵) da followup-cadence.mjs, più il pulsante Semina date (followup-seed.mjs --backfill). (2) Pattern di rifiuto: una quarta scheda Statistiche esegue analyze-patterns.mjs (sola lettura) — distribuzione degli esiti, raccomandazioni, tasso di avanzamento per fornitore ATS. (3) Aggiungi al CV: una scheda di CV Studio trasforma un URL o testo incollato in punti ATS basati SOLO su quella fonte (solo suggerimenti, nessuna scrittura; il fetch dell’URL è protetto anti-SSRF). (4) 4 nuovi provider di scansione — beesite, HigherEdJobs (RSS), JibeApply (iCIMS), softgarden — il registro conta ora 50 adattatori (45 EN + 5 RU), tutti nel menu del Scan. (5) Passo di pre-scansione dei disqualificatori nella checklist Apply. (6) Runner reconcile (/api/run/reconcile). Le rotte shell-out degradano onestamente senza gli script del padre.
- Nuovo modulo
server/lib/routes/followup.mjs(31º) + nuove rotte + 8 file source/adapter. Test: 6 + 7 nuovi; suite 1737 → 1750. 41 chiavi i18n ×16. Aiuto §13/§17/§24/§26 esteso ×16.
Nuovo: GET /api/followup, POST /api/followup/seed, GET /api/stats/patterns, POST /api/cv-studio/add-entry, POST /api/run/reconcile.
[1.116.0] — 2026-07-06
Contatore di utilizzo rifatto + primo test end-to-end dei widget. Il contatore di utilizzo IA (v1.114.0) è corretto e ancorato correttamente: ora è fissato in fondo alla barra laterale sinistra (larghezza piena, stessa superficie) e riserva in basso uno spazio pari alla propria altezza così che il menu non venga mai coperto — la navigazione e il piè di versione scorrono sempre liberi sopra di esso. Si aggiorna dal vivo (ogni 15 s, al focus della scheda e al cambio di rotta), e ogni riga di finestra mostra ora i <token> · <costo stimato> reali (le barre si scalano rispetto alla finestra di 30 giorni) invece di una “quota” sempre al 100%. Inoltre: una barriera typeof durevole nell’importatore del CV chiude alla fonte il falso positivo ricorrente di type-confusion di CodeQL, e un nuovo test end-to-end Playwright guida entrambi i widget persistenti in un browser reale.
public/js/lib/usage-hud.js+app.css,server/lib/cv-import.mjs. Test:tests/playwright-widgets.mjs(2 E2E) +tests/usage-hud.test.mjs(10). Aiuto §6 esteso ×16.
Nuovo: nessuno.
[1.115.0] — 2026-07-06
Rifinitura del design (conservativa, brand corallo mantenuto). Un passaggio leggero di rifinitura sul sistema di design condiviso — nessuna ristrutturazione, nessun cambio di palette. Le schede metriche della dashboard ora si sollevano e prendono un bordo corallo al passaggio del mouse (come i riquadri di azione rapida); le schede di contenuto si sollevano di poco; i pulsanti primary / dark / danger guadagnano un’ombra a riposo e un lieve sollevamento all’hover per profondità; i numeri grandi si allineano con tabular-nums; e i controlli interattivi ricevono un alone corallo morbido dietro il nitido anello da tastiera di 2px. Tutto il movimento rispetta prefers-reduced-motion, e l’alone è limitato ai controlli — mai un *:focus-visible globale.
- Solo CSS (
public/css/app.css); nessuna modifica a markup, i18n, rotte o CSP. Test:tests/design-polish-v1115.test.mjs(5). Verificato dal vivo con Playwright.
Nuovo: nessuno.
[1.114.0] — 2026-07-06
Contatore di utilizzo e costo dell’IA nella barra laterale (in basso a sinistra). Una sezione UTILIZZO compatta ora si trova in fondo alla barra laterale (una scheda fissa in basso a sinistra se non c’è barra laterale; in basso a destra in RTL) su ogni pagina. Mostra l’uso di token LLM su finestre 24h / 7g / 30g — ciascuna come <token> · <quota%> con una barra verde (quota sul totale) — più un piè di pagina con il costo stimato delle 24h. I dati sono il riepilogo di sola lettura GET /api/usage di data/llm-usage.jsonl (solo locale), la stessa fonte della pagina #/usage; il costo è una stima e le esecuzioni in modalità manuale sono gratuite e non conteggiate. Comprimibile — l’intestazione commuta e lo stato persiste.
- Nuovo widget client
public/js/lib/usage-hud.jscaricato daindex.html, montato nella barra laterale sopra il piè di versione (fallback ad angolo fisso). Sicuro per la CSP; adattivo al tema e speculare RTL. Nessuna nuova rotta server. Test:tests/usage-hud.test.mjs(8). 3 nuove chiavi i18n ×16.
Nuovo: nessuno.
[1.113.0] — 2026-07-06
Assistente fluttuante “Chiedi alla guida” su ogni pagina. Un pulsante di chat con un robot in gradiente ora fluttua nell’angolo in basso a destra (in basso a sinistra in RTL) di ogni pagina. Toccalo per aprire una chat compatta che risponde a domande d’uso basandosi SOLO sulla guida di aiuto integrata nella tua lingua — lo stesso endpoint della pagina #/docs-assistant (POST /api/docs-assistant/ask), quindi non legge mai il tuo CV, profilo o tracker. Dal vivo con una chiave LLM; senza chiave → un prompt pronto all’uso. L’intestazione mostra un avatar robot + stato online; i chip avviano domande comuni; Esc o clic esterno chiude; si nasconde sulla pagina #/docs-assistant.
- Nuovo widget client
public/js/lib/docs-fab.jsmontato globalmente daindex.html; sicuro per la CSP; stili adattivi al tema e speculari RTL inapp.css. Nessuna nuova rotta server. Test:tests/docs-fab.test.mjs(8). 6 nuove chiavi i18n ×16. Aiuto §1 esteso sul posto.
Nuovo: nessuno.
[1.112.0] — 2026-07-06
Consolidamento docs & QA. Nessuna modifica di codice visibile. Il documento di convenzioni SDD (docs/sdd/CONVENTIONS.md) è aggiornato agli attuali 30 moduli di rotta (erano 24) e alla baseline di test attuale; il prompt QA definitivo dell’intero progetto (qa/QA-REGRESSION-PROMPT.md) è consolidato — meccanica di release ripulita (v1.111, parentVersion 1.17.0, pubblicazione attivata dall’evento di release), la tabella delle aggiunte §14 corretta (Escludi di Scan rietichettato v1.109.0) ed estesa con la chiusura CodeQL di v1.111 — così da bastare da solo come unico prompt di regressione per tutte le funzionalità. Aggiunge un test di copertura per il ramo di caricamento sovradimensionato.
Nuovo: nessuno.
[1.111.0] — 2026-07-06
Sicurezza — chiusura del backlog CodeQL. Tre rafforzamenti difesa-in-profondità che chiudono i restanti rilievi dell’analisi statica alla fonte invece di scartarli. stripDangerousMarkdown ora fa l’escape del < di qualsiasi apertura di tag pericoloso troncata (un payload che termina con <script/<iframe/…), così che il suo output non contenga in modo dimostrabile alcun tag pericoloso vivo. L’import del CV legge la dimensione del buffer caricato tramite una coercizione esplicita Number() — una barriera contro la confusione di tipi. Le righe di ruolo delle modalità ora sono stringhe template interpolate con String.replace invece di funzioni memorizzate, rimuovendo del tutto la chiamata a dispatch dinamico. Nessun cambiamento di comportamento visibile all’utente.
server/lib/security.mjs,server/lib/cv-import.mjs,server/lib/prompts.mjs. Test:tests/security-hardening-v1111.test.mjs(7) + test di guardia v1108 aggiornato. Nessun cambiamento i18n/aiuto/rotte.
Nuovo: nessuno.
[1.110.0] — 2026-07-06
Aggiornamento docs & QA (tutte le lingue). Nessuna modifica al codice. Il prompt QA dell’intero progetto è aggiornato a v1.109.0 con un nuovo §14 (v1.98→v1.109), e i prompt perenni UX-audit e design-export hanno la superficie di pagine attuale. Ogni paragrafo di aiuto aggiunto in v1.100–v1.109 è ora tradotto in tutte le 16 lingue.
Nuovo: nessuno.
[1.109.0] — 2026-07-06
Filtro Escludi in Scan + panoramica pipeline (parità layout web). Su #/scan, la casella Cerca ora tratta le virgole come OR (“ruoli da trovare”) e un nuovo campo Escludi nasconde ogni riga la cui azienda/ruolo/luogo contiene una delle parole separate da virgole (es. senior, staff); entrambi sono ricordati dalle ricerche salvate. Su #/pipeline, una striscia di panoramica compatta mostra la pipeline a colpo d’occhio — N in arrivo, N tracciati e i conteggi Applied / Responded / Interview / Offer dal tracker, ogni chip collega a #/tracker.
- Solo client (nessuna nuova rotta/scrittura).
public/js/views/scan.js+public/js/views/pipeline.js. Test:tests/scan-pipeline-ui-v1109.test.mjs(2). 4 nuove chiavi i18n ×16. Aiuto §7 + §8 estesi sul posto.
Nuovo: nessuno.
[1.108.0] — 2026-07-06
Rafforzamento della sicurezza (triage CodeQL, round 2). Corrette altre tre vulnerabilità di bassa gravità: il costruttore di prompt risolve la riga di ruolo della locale per chiave propria + typeof === function così che una locale manomessa non possa invocare un metodo del prototipo (unvalidated-dynamic-method-call); lo slug del nome file PDF è limitato a 200 caratteri prima del regex così che un input di soli trattini non torni indietro (ReDoS polinomiale); e l’importazione documenti forza un filename array (header ripetuto) a stringa (type-confusion). Nessun cambiamento di comportamento per input valido.
server/lib/prompts.mjs,server/lib/routes/runners.mjs,server/lib/cv-import.mjs+tests/security-hardening-v1108.test.mjs(3). Su v1.106–v1.108 l’arretrato dell’analisi statica è passato da 167 a ~14, con ogni risultato realmente rilevante per la sicurezza corretto e il resto (falsi positivi protetti/sanificati + lint di livello nota) respinto con motivazione.
Nuovo: nessuno.
[1.107.0] — 2026-07-06
Rafforzamento del sanitizzatore (difesa in profondità XSS a riposo). stripDangerousMarkdown — che neutralizza l’HTML pericoloso nel markdown di CV/annuncio memorizzato affinché qualsiasi consumatore che aggiri il client con escape-al-rendering resti sicuro — ora esegue la pulizia dei tag fino a un punto fisso (ripeti fino a stabilizzarsi) così che una rimozione che riforma un payload (es. <scr<script></script>ipt>) venga intercettata, corrisponde ai tag di chiusura script/style ecc. con residui finali (</script foo>) e rimuove un apertore eseguibile non chiuso (<script …>). Il comportamento per markdown valido è invariato — rimuove solo di più.
server/lib/security.mjs: ciclo a punto fisso (limitato a 8 passaggi) + pattern di chiusura[^>]*>+ rimozione dell’apertore non chiuso. +3 casi di regressione intests/cv-xss-bypasses.test.mjs. Il confine XSS autorevole resta l’escape in output (UI.md); questo rafforza la garanzia a riposo e chiude i risultati CodeQL corrispondenti.
Nuovo: nessuno.
[1.106.0] — 2026-07-06
Rafforzamento della sicurezza (triage CodeQL). Corrette tre vulnerabilità reali (seppur di bassa gravità) dopo una passata sull’arretrato dell’analisi statica: il percorso di errore del rendering ora effettua l’escape del messaggio di errore prima che raggiunga il DOM (un errore del server può riflettere input dell’utente, quindi trattato come non attendibile — confine XSS), e le scritture di proprietà di profilo/config rifiutano le chiavi __proto__ / constructor / prototype (protezioni anti prototype-pollution per sicurezza — le chiavi provengono da specifiche di campo fisse, non da input grezzo). La maggior parte degli avvisi rimanenti sono falsi positivi sulle letture/scritture legittime dello scanner in data/* e su rotte che già portano il limiter interno; respinti con motivazione.
public/js/router.jseffettua l’escape dierr.messageviaUI.escapeHtmlprima diinnerHTML;server/lib/routes/content.mjseserver/lib/routes/config.mjsproteggono le chiavi di prototipo. Nessun cambiamento di comportamento per input valido. Test:tests/security-hardening-v1106.test.mjs(3). Nessuna nuova chiave i18n.
Nuovo: nessuno.
[1.105.0] — 2026-07-06
Pagina uso e costo IA. Una nuova pagina Uso IA (barra laterale, accanto a Salute) mostra quanti token hai speso in generazioni IA live — valutazioni, report, chat — suddivisi per provider nelle ultime 24 ore, 7 giorni, 30 giorni e sempre, con un costo stimato in USD. Ogni chiamata live aggiunge un piccolo record {provider, in, out} a data/llm-usage.jsonl (nulla viene inviato da nessuna parte); le esecuzioni senza chiave (modalità manuale) non costano nulla e non vengono registrate.
- Nuovo modulo di rotta (il 30°)
server/lib/routes/usage.mjs—GET /api/usage(aggregati in sola lettura) +server/lib/llm-usage.mjs(recordUsagenormalizza le forme d’uso di Anthropic/OpenAI/Gemini e aggiunge in best-effort;readUsage/aggregateaggregano per finestra 24h/7g/30g/tutto × provider) +server/lib/llm-pricing.mjs(una tabella prezzi modificabile per provider$/1Mtoken — i token sono esatti, i dollari sono prezzi di listino approssimativi che puoi correggere; mai fatturati). La registrazione è agganciata ai punti di dispatch (runActiveProvider+routes/llm.mjs). - Nuova vista
public/js/views/usage.js(#/usage, schede finestra). Test:tests/usage-routes.test.mjs. 17 nuove chiavi i18n ×16 (usage.*+nav.usage). Aiuto §6 esteso sul posto.
Nuovo: server/lib/routes/usage.mjs; server/lib/llm-usage.mjs; server/lib/llm-pricing.mjs; public/js/views/usage.js.
[1.104.0] — 2026-07-06
Logo aziendali nella tabella di scansione (rispettosi della privacy). Un nuovo interruttore Aspetto nelle Impostazioni app — Mostra i logo delle aziende nella tabella di scansione (disattivato per impostazione predefinita) — disegna il logo di ogni azienda accanto al nome su #/scan. Il logo è la favicon dell’azienda recuperata dal suo dominio e messa in proxy lato server (GET /api/logo), così nessun servizio di logo di terze parti scopre quali datori di lavoro stai guardando. Gli annunci su un portale di lavoro condiviso (Greenhouse, Lever, Ashby, …) mostrano un badge con una lettera colorato invece dell’icona del portale, e qualsiasi logo che non si carica ricade sullo stesso badge.
- Nuovo modulo di rotta (il 29°)
server/lib/routes/logos.mjs—GET /api/logo?domain=. Valida il dominio (senza schema/percorso/loopback), recupera/favicon.icotramite ilsafeGetsicuro contro l’SSRF (una nuova modalitàbinaryrestituisce i byte grezzi + content-type; DNS-pinning, validazione dei redirect e limite di dimensione invariati), esegue uno sniffing della firma dell’immagine per non servire mai una pagina HTML di errore come immagine, mette in cache successi e fallimenti in un LRU in memoria e non scrive nulla su disco. - Nuova lib client
public/js/lib/company-logo.js(window.CompanyLogo): disattivata per impostazione predefinita tramite flag in localStorage; salta gli host ATS condivisi a favore di un avatar-lettera deterministico; ripiegoimg.onerrorsicuro per la CSP. Test:tests/logo-routes.test.mjs. 5 nuove chiavi i18n ×16 (appear.*). Aiuto §2 esteso sul posto.
Nuovo: server/lib/routes/logos.mjs; public/js/lib/company-logo.js.
[1.103.0] — 2026-07-06
Impostazioni: “Strumenti CLI IA” — quali sono installati. career-ops è basato su Claude Code ma funziona con qualsiasi CLI di agente conforme allo standard aperto di skills. Una nuova scheda Strumenti CLI IA nelle Impostazioni app (#/config) mostra quali — Claude Code, Codex, Gemini CLI, OpenCode, GitHub Copilot CLI, Qwen, Antigravity — sono installati sulla macchina che esegue il server, e i loro percorsi. È una scansione del PATH in sola lettura: verifica solo se ogni binario esiste e non lo esegue mai (nessun --version, nessuna esecuzione), non scrive nulla e non tocca dati utente.
- Nuovo modulo di rotta (il 28°)
server/lib/routes/cli-detect.mjs—GET /api/cli-detect. Il rilevamento risolve il percorso di un binario da una allowlist fissa di 7 voci tramiteprocess.env.PATH(shim.cmd/.exe/.batsu Windows; bit di esecuzione su POSIX); un file ostile sul PATH non può mai essere eseguito da questa rotta. - Nuova scheda “Strumenti CLI IA” in
public/js/views/config.js(caricamento lazy, deep-link via#/config?tab=cli). Test:tests/cli-detect-routes.test.mjs. 8 nuove chiavi i18n ×16 (cli.*+config.tabCli). Aiuto §2 esteso sul posto.
Nuovo: server/lib/routes/cli-detect.mjs.
[1.102.0] — 2026-07-05
“Chiedi alla guida” — una chat fondata sulla guida di aiuto integrata. Una nuova pagina Chiedi alla guida 💬 (barra laterale, sotto Aiuto): scrivi una domanda come “Come faccio a scansionare i portali di lavoro?” e ottieni una risposta tratta solo dalla guida di aiuto dell’app nella tua lingua — mostra quali sezioni ha usato e non legge mai il tuo CV, profilo o la tua ricerca di lavoro. Riguarda come usare l’app, non te. Con una chiave LLM risponde in tempo reale; senza chiave ti consegna un prompt pronto, già riempito con le sezioni di aiuto pertinenti.
- Nuovo modulo di rotta (il 27°)
server/lib/routes/docs-assistant.mjs—POST /api/docs-assistant/ask. Recupero senza dipendenze: la guida nella tua lingua è divisa nelle sue sezioni##e valutata per sovrapposizione di parole chiave con la domanda; le migliori vengono incluse e il modello deve rispondere da esse o dire che la guida non lo copre (nessuna funzione/rotta inventata). Cascata di provider condivisa, ripiego manuale, con limite di frequenza, senza scritture, non legge dati utente. - Nuova vista
public/js/views/docs-assistant.js. Test:tests/docs-assistant-routes.test.mjs. 14 nuove chiavi i18n ×16 (docs.*+nav.docsAssistant). Aiuto §1 esteso sul posto.
Nuovo: server/lib/routes/docs-assistant.mjs; public/js/views/docs-assistant.js.
[1.101.0] — 2026-07-05
CV Studio: adatta il tuo CV + scrivi una lettera di presentazione per un lavoro specifico, con un controllo in stile recruiter. Nuova scheda Adatta a un lavoro su #/cv-studio: incolla una descrizione di lavoro (e, facoltativamente, un ruolo/titolo target) e CV Studio produce un CV adattato a quell’annuncio più una lettera di presentazione coerente, poi li passa attraverso un controllo prima di consegnarli — gli error bloccano (corretti prima che tu veda il risultato), i warn consigliano. La meccanica è distillata dalla pratica del career coaching in regole generiche — un recruiter legge in secondi, quindi l’esperienza rilevante va in alto, il titolo corrisponde al ruolo dell’annuncio, i risultati portano numeri specifici e la lettera resta un teaser breve con un unico ponte “requisito ↔ il tuo fatto corrispondente”. Si basa solo sul tuo CV, profilo e two-pager e non inventa mai — nessuna azienda, ruolo o storia hardcoded.
- Nuovo endpoint
POST /api/cv-studio/tailor(estende il modulo cv-studio esistente — nessun 27° modulo):buildTailorPrompt+ un controllo genericoTAILOR_INSTRUCTIONS, basato subundleProjectContext, cascata di provider condivisa, ripiego manuale senza chiave, con limite di frequenza, senza scritture. Il risultato si esporta in Markdown / PDF / DOCX tramite la barra condivisareport-export.js. - Test: +3 in
tests/cv-studio-routes.test.mjs. 10 nuove chiavi i18n ×16 (cvs.tailor*). Riferimento genericodocs/prompts/resume-cover.md. Aiuto §24 esteso sul posto.
Nuovo: docs/prompts/resume-cover.md.
[1.100.0] — 2026-07-05
Two-pager: compilazione automatica con IA dal tuo CV + Anteprima + esportazione in PDF/DOCX/Markdown. Il two-pager (#/two-pager) raccoglie ciò che vuoi davvero dal prossimo ruolo, ma finora ogni campo andava scritto a mano o copiando un prompt in un altro strumento. Ora l’✨ assistente di compilazione IA viene eseguito in tempo reale con il provider configurato — legge solo il tuo CV + profilo (tramite bundleProjectContext, senza inventare nulla), redige tutti i campi (chi sono / cosa amo / irrinunciabili / cosa detesto / deal-breaker / non negoziabili / ambiente target) e compila il modulo perché tu lo riveda, modifichi e salvi. Senza chiave API torna alla finestra copia-il-prompt come prima. Un nuovo pulsante 👁 Anteprima ed esporta rende il two-pager come documento formattato con una barra Scarica .md / Salva come PDF / Salva come DOCX / Copia.
- Esportazione
.docxsenza dipendenze. Nuovoserver/lib/docx.mjsche produce un.docxOffice Open XML minimo ma valido (uno ZIP DEFLATE delle quattro parti OOXML, con CRC-32 per voce) — senza nuova dipendenza runtime (le deps restanoexpress+js-yaml). Nuova rottaPOST /api/export/docx(server/lib/routes/export.mjs, il 26° modulo di rotte; stateless, limitato a 200 KB, senza scritture / senza LLM / senza fetch di URL). Integrato nel condivisopublic/js/lib/report-export.js, quindi il report di mercato, il piano di carriera e l’orientamento professionale ottengono anch’essi l’esportazione DOCX. - La compilazione automatica in tempo reale usa la cascata di provider condivisa (
runActiveProvider/providerAvailable); lo YAML restituito viene analizzato e ricondotto alla forma limitata del two-pager (parseYamlFields+normalizeTwoPager) — chiavi sconosciute scartate, array/stringhe limitati. Modalità manuale preservata. - Test:
tests/export-routes.test.mjs. 4 nuove chiavi i18n ×16 (export.saveDocx,twoPager.preview,twoPager.aiFilling,twoPager.aiFilled).
Nuovo: server/lib/docx.mjs; server/lib/routes/export.mjs.
[1.99.0] — 2026-07-05
Pagina salute dei portali (#/portals). Lo scanner sorveglia un insieme di aziende in portals.yml; uno slug ATS può rompersi silenziosamente e quel datore di lavoro sparisce da ogni scansione futura. La nuova pagina Portals elenca ogni azienda sorvegliata e, con Check portal health, sonda ogni careers_url tramite il safeGet con DNS ancorato (anti-SSRF) e segnala quelle morte (un 404 = scartata in silenzio) — sola lettura. Rafforza anche il segnalatore di bug della v1.98.0 dopo la revisione: il buffer degli errori ora cattura i fallimenti di rete del fetch e lo scrubber oscura le chiavi provider senza etichetta.
Nuovo: server/lib/routes/portals.mjs; public/js/views/portals.js.
[1.98.0] — 2026-07-05
Segnalatore di bug integrato (parità con il web web-v0.2.0 del progetto padre). Un pulsante 🐞 Report a bug nel cassetto delle notifiche raccoglie un’istantanea diagnostica con soglia di privacy — versioni, il tuo schermo, browser, un riepilogo dei controlli di /api/health e gli ultimi 20 errori da un nuovo buffer circolare lato client — più un’impronta di deduplicazione deterministica (co-web-<base36>), ti fa rivedere il Markdown esatto e poi apre una issue GitHub precompilata. Nulla viene inviato automaticamente; non trasporta mai il tuo CV, profilo, risposte, URL di lavoro o chiavi. Nuove lib logbuf.js + bug-report.js; 11 chiavi i18n ×16; tests/bug-report.test.mjs.
Nuovo: public/js/lib/logbuf.js; public/js/lib/bug-report.js.
[1.97.1] — 2026-07-05
Corretto
- Consolidamento guidato dalla revisione e parità della documentazione (seguito della v1.97.0). Un passaggio sui log di revisione dell’IA ha fatto emergere correzioni reali:
fit-score.js(badge di fit◎della scansione).salaryFloor()non promuove più una tariffa infra-annuale a un falso minimo annuale — «at least 500 EUR/day», «$80/hr», «6000 monthly» ora restituiscononullinvece di un fattore eliminatorio da 500k/80k. La corrispondenza dei paesi è ora a parola intera (\b…\b), così «Germany» non corrisponde più all’aggettivo «German» (né «Nigeria» dentro «Nigerian») e non scatena una falsa violazione di indispensabile-altrove. +3 test intests/fit-score.test.mjs.- Parità della documentazione. Ogni README localizzato ora pubblicizza 16 lingue in modo coerente — il conteggio/l’elenco della riga Aiuto (×13) e la prosa della sezione Localizzazione più la nota «aggiungi la chiave a tutti gli N file» (×8) erano ancora sui conteggi precedenti alla v1.85 (8/9). Il conteggio degli adattatori dell’aiuto integrato §17 è corretto a 46 adattatori — 41 in inglese + 5 in russo in tutti i 16 pacchetti.
- Nessun cambiamento di comportamento oltre all’euristica del badge di fit; nessuna nuova route, chiave o aggiunta i18n.
[1.97.0] — 2026-07-05
Aggiunto
- Sorgente di scansione Dassault Systèmes + una revisione della qualità su tre fronti.
- Nuova sorgente di scansione — Dassault Systèmes (parità con il career-ops principale, #1498).
server/lib/sources/dassault.mjs+server/lib/portals/adapters/dassault.mjsrispecchiano il provider Exalead di «ricerca a schede» a costo zero in token del progetto principale (il feed pubblico dietro3ds.com/careers/jobs). È un unico endpoint globale, quindi è selezionato per provider (provider: dassault) oppure rilevato automaticamente da un host3ds.com, con l’host ancorato contro l’SSRF awww.3ds.comtramiteredirect:'error'. L’XML viene analizzato senza DOM (mappe<Meta>per ogni<Hit>), città/paese vengono estratti dalla stringa di categoria localizzata, e le offerte vengono mantenute solo quando il loro URL pubblico è su*.3ds.com. Il registro ora include 46 adattatori (41 EN + 5 RU); il conteggio diALL_ADAPTERS, le asserzioni di id ordinato e dell’insieme EN di/api/scan/sourcespassano da 40 → 41. Suitetests/sources-dassault.test.mjs(10 casi). - Correzioni di robustezza portate dal progetto principale. Il parser di Avature ora tollera due varianti di markup dei tenant in produzione (
article--resultcon un suffisso di indice di posizione + un’ancora di titolo JobDetail senza classe, #1541); Get on Board si protegge da unpublished_at0/negativo (niente più date fasulle del 1970); SuccessFactors limita l’ultima pagina in modo che non possa superareMAX_JOBS(#1528). - Correzioni dell’audit del server.
safe-fetchnon si blocca più su una risposta oltre il limite — il percorso del limite di dimensione ora risolve la promise direttamente invece di attendere un evento'end'che uno stream distrutto non emette mai (corregge i recuperi di pagine grandi su/api/pipeline/preview+ auto-pipeline). Il logging di attività SSEstream.*è di nuovo raggiungibile (il controllo di/api/stream/è stato spostato sopra la guardia generale «salta GET»). - Correzioni dell’audit della SPA. Il selettore di schede di
#/statssi protegge da una corsa di rendering asincrona — il risultato di una scheda lenta non può più sovrascrivere una scheda più recente a cui l’utente è già passato. Le conferme di eliminazione del colloquio simulato e del networking ora passano un titolo + corpo adeguati (niente più finestra di dialogo con corpo vuoto). - Correzioni di traduzione. Corretti valori del dizionario non tradotti — ucraino
config.modes*(Adaptive Framing / Exit Narrative / Location Policy), russoeval.jdLbl(«Job Description»), italianodash.quick.contactoSub(«referral» → «segnalazione») — oltre alla localizzazione del testo fisso inglese**16 locales**nei CHANGELOG di ru/uk/ja/ko/zh-CN/zh-TW. - Nuovo:
server/lib/sources/dassault.mjs;server/lib/portals/adapters/dassault.mjs.
[1.96.0] — 2026-07-04
Aggiunto
- Orientamento professionale (Epic 27). Una nuova pagina
#/orientationrisponde alla domanda «quali direzioni fanno davvero per me?» — la lettura che otterresti da un test di orientamento, ma dedotta dal tuo stesso CV e profilo anziché da un questionario. Fai clic su Genera profilo e il modello restituisce i tuoi vettori di carriera più adatti (quali degli otto archetipi — Funzionalista, Amministratore, Comunicatore, Specialista, Analista, Innovatore, Manager, Imprenditore — si adattano, con evidenze), una inclinazione di tipo professionale, ruoli consigliati, punti di forza professionali legati al tuo CV, tendenze di stile di lavoro e raccomandazioni di sviluppo. È una riflessione dell’IA su come si legge il tuo CV — non un test psicometrico: non inventa mai risultati e non riporta mai punteggi numerici come se fossero misurati. Esportalo in Markdown o PDF; nulla viene scritto sul disco.- Nuova route
server/lib/routes/orientation.mjs(24° modulo di route) —POST /api/orientation/generatecostruisce il prompt del profilo da CV+profilo+two-pager+memoria tramite la cascata di provider condivisa, con un fallback manuale da copiare e incollare e nessuna scrittura di file. - Riutilizza
report-export.jsper Markdown/PDF/copia, all’interno del gruppo di navigazione Sviluppo. - Test:
tests/orientation-routes.test.mjs(delimitazione a riflessione / nessun punteggio inventato, modo manuale con seed da CV/profilo). 7 nuove chiavi i18n ×16 lingue, Aiuto §28 ×16.
- Nuova route
- Nuovo:
#/orientation;server/lib/routes/orientation.mjs.
[1.95.0] — 2026-07-04
Aggiunto
- Piano di carriera (Epic 26). Una nuova pagina
#/career-plantrasforma il tuo CV e il tuo profilo in un piano di sviluppo concreto e personalizzato. Scegli un orizzonte (6/12/24 mesi) e un focus opzionale, e il modello — leggendo il tuo CV, il profilo, il two-pager e la nota di memoria — scrive un’istantanea del punto di partenza, una SWOT di punti di forza/crescita, obiettivi come SMART / OKR / WOOP, traiettorie alternative, un piano di competenze hard/soft, una roadmap mese per mese, metodi di monitoraggio dei progressi, insidie e mosse di supporto. Pianifica in avanti a partire da ciò che i tuoi materiali mostrano davvero e non inventa mai fatti sulla tua storia. Modificalo inline, Salvalo nel livello utente (config/career-plan.md) ed esportalo in Markdown o PDF.- Nuova route
server/lib/routes/career-plan.mjs(23° modulo di route) —GET/PUT /api/career-plan(scriveconfig/career-plan.md) +POST /api/career-plan/generate(cascata di provider condivisa, fallback manuale, nessuna invenzione).PATHS.careerPlan. - Riutilizza l’helper condiviso
report-export.js(v1.94.0) per Markdown/PDF/copia, e un nuovo gruppo di navigazione Crescita. - Test:
tests/career-plan-routes.test.mjs(delimitazione, round-trip GET/PUT, prompt consapevole dell’orizzonte e con seed da CV/profilo). 20 nuove chiavi i18n ×16 lingue, Aiuto §27 ×16.
- Nuova route
- Nuovo:
#/career-plan;server/lib/routes/career-plan.mjs;PATHS.careerPlan.
[1.94.0] — 2026-07-04
Aggiunto
- Statistiche, rielaborate (Epic 25). La pagina
#/statsè ora una sezione Statistiche a tre schede, con grafici veri e molti più dati. Una nuova scheda Report di mercato chiede al modello un’analisi delle retribuzioni e del mercato del lavoro per i tuoi ruoli target in una regione e valuta che scegli — sintesi esecutiva, retribuzione per livello con percentili P10/P25/P75/P90, principali datori di lavoro, una tabella delle competenze richieste, frequenza dei benefit, la ripartizione ufficio/ibrido/remoto, tendenze a 12–24 mesi e indicazioni per la negoziazione. Ogni cifra è etichettata come una stima orientativa dalla conoscenza del modello, mai presentata come dati estratti. Una nuova scheda La mia pipeline rappresenta il tuo tracker: distribuzione dei punteggi, imbuto degli stati, principali aziende e ruoli, candidature nel tempo e tassi di conversione. La vista originale dei ruoli target (posti vacanti/retribuzione per paese + tendenza degli snapshot salvati) si sposta in una terza scheda, ora con un selettore di valuta e una panoramica annunci-per-ruolo.- Esporta qualsiasi report in Markdown o PDF, oppure copialo — tramite l’helper condiviso
report-export.js(download del blob Markdown; PDF tramite l’esistente runner inline-PDF). - Nuova route
server/lib/routes/market.mjs(22° modulo di route) —POST /api/stats/marketcostruisce un prompt di analisi di mercato dal tuo CV/profilo (così conosce i tuoi ruoli target), regione e valuta, lo esegue attraverso la cascata di provider condivisa e ripiega su un prompt copia-e-incolla senza chiave. Nessuna scrittura di file. - Test:
tests/market-routes.test.mjs(delimitazione regione/valuta, prompt etichettato per onestà, modalità manuale con seed da CV/profilo). 36 nuove chiavi i18n ×16 lingue, Aiuto §26 ×16.
- Esporta qualsiasi report in Markdown o PDF, oppure copialo — tramite l’helper condiviso
- Nuovo:
#/statsrielaborata in schede;server/lib/routes/market.mjs;public/js/lib/report-export.js.
[1.93.0] — 2026-07-04
Aggiunto
- Livello di memoria (Epic 24). Una nuova pagina
#/memoryconserva una breve nota modificabile «ricorda questo su di me» che l’assistente tiene a mente in ogni attività:- Una nota, ovunque — poiché è inserita in
bundleProjectContext, la nota raggiunge automaticamente ogni richiesta AI (valutazione, colloquio simulato, networking, CV Studio) su tutti i provider. Scrivila una volta; orienta tutto. - Orientamento, non fatti — cattura le tue preferenze e il modo in cui ti piace lavorare (tono, formato, deal-breaker, cadenza), mai nuove affermazioni fattuali sulla tua esperienza — quelle vivono ancora solo nel tuo CV, nel tuo profilo e nel tuo two-pager. Salvata nel livello utente in
config/memory.md, mai sovrascritta dagli aggiornamenti. - Suggerisci dai tuoi dati —
POST /api/memory/suggestesamina il tuo tracker delle candidature alla ricerca di schemi comportamentali e abbozza punti elenco che puoi rivedere e modificare. Legge il tuo tracker; non inventa mai fatti e non effettua alcuna chiamata live.
- Una nota, ovunque — poiché è inserita in
- Nuovo:
server/lib/routes/memory.mjs(21° modulo di route —GET/PUT /api/memory+POST /api/memory/suggest),public/js/views/memory.js,PATHS.memorye un bloccoconfig/memory.mdaggiunto abundleProjectContext. 11 nuove chiavi i18n in tutte le 16 lingue. Test:tests/memory-routes.test.mjs.
[1.92.0] — 2026-07-04
Aggiunto
- CV Studio (Epic 21). Una nuova pagina
#/cv-studiooffre al tuo CV tre strumenti onesti e per lo più locali:- Diagnostica del curriculum — un punteggio deterministico da 0 a 100 con spiegazioni per ogni controllo (impatto quantificato, verbi deboli, buzzword, lunghezza, sezioni fondamentali, informazioni di contatto). Puramente lato client (
window.CvDiagnostics) — nessun LLM, nulla di inventato, ogni riscontro spiegato così che tu decida cosa cambiare. - Maschera privacy — oscura i PII (email, telefono, link/handle, indirizzo civico e facoltativamente il tuo nome → iniziali) prima di condividere il tuo CV come campione o screenshot. Gira interamente nel browser (
window.CvPrivacy); segnala esattamente cosa ha oscurato e non conserva mai l’originale. - Rendilo umano / abbina la voce — incolla una riga o un paragrafo rigido e riscrivilo nella tua voce, ancorato lato server a
voice-dna.mdewriting-samples/. Guardrail rigido: può riordinare, snellire e rimodulare la voce, ma non introduce mai un fatto, una metrica o un risultato non già presente nel testo. Gira live tramite la cascata condivisa dei provider, oppure restituisce un prompt da copiare-incollare senza chiave.
- Diagnostica del curriculum — un punteggio deterministico da 0 a 100 con spiegazioni per ogni controllo (impatto quantificato, verbi deboli, buzzword, lunghezza, sezioni fondamentali, informazioni di contatto). Puramente lato client (
- Nuovo:
server/lib/routes/cv-studio.mjs(20° modulo di route —POST /api/cv-studio/humanize),public/js/views/cv-studio.js,public/js/lib/cv-diagnostics.js,public/js/lib/cv-privacy.js,PATHS.voiceDna+PATHS.writingSamplesDir. 29 nuove chiavi i18n in tutte le 16 lingue. Test:tests/cv-diagnostics.test.mjs,tests/cv-studio-routes.test.mjs. (La galleria di modelli, l’esportazione Word e l’archivio PDF degli annunci sono tracciati come lavoro successivo di CV Studio.)
[1.91.0] — 2026-07-04
Aggiunto
- Networking e ricerca approfondita sulle aziende (Epic 16). Una nuova pagina
#/networkingtrasforma un’azienda in un piano attuabile per ottenere un colloquio, ancorato al tuo CV, al profilo e al two-pager:- Dossier aziendale — un brief conciso su cosa fa l’azienda, i segnali recenti degni di citazione e gli agganci “perché sono adatto” tratti dal tuo percorso reale.
- Chi contattare — 3–5 persona target (hiring manager, recruiter interno, un IC senior del team, un contatto caldo/ex compagno di studi) con una stringa di ricerca LinkedIn concreta per trovare ciascuno. Non inventa mai nomi reali.
- La via di presentazione più calda — il percorso caldo più realistico per il tuo profilo (datore di lavoro/scuola/community in comune, un percorso di secondo grado o un DM a freddo ad alto segnale) e il perché.
- Bozze di contatto — messaggi brevi e specifici per le persona principali, ancorati ai tuoi punti di prova reali.
- Live o manuale — gira live tramite la cascata condivisa dei provider con una chiave qualsiasi, oppure restituisce un prompt pronto da copiare-incollare (ripiego onesto, nulla di inventato). Salva piano conserva un piano concluso nel livello utente (
networking/net-{company}-{role}-{date}.md); la pagina elenca, apre ed elimina i piani salvati.
- Nuovo:
server/lib/routes/networking.mjs(19° modulo di route),public/js/views/networking.js,PATHS.networkingDir. Riutilizza la cascataserver/lib/llm-dispatch.mjsdella v1.90.0. 24 nuove chiavi i18n in tutte le 16 lingue. Test:tests/networking-routes.test.mjs.
[1.90.0] — 2026-07-04
Aggiunto
- Mock Interview 2.0 (Epic 15). Una nuova pagina
#/mock-interviewtrasforma il tuo CV, il profilo, il two-pager e la story bank in una simulazione di colloquio turno per turno:- Pratica conversazionale — indica un ruolo target (+ azienda / descrizione dell’annuncio opzionali) e l’intervistatore apre con una domanda mirata. Ogni risposta inviata riceve una replica strutturata: Feedback (punti di forza + la lacuna STAR+R), un Score (
N/5) e una Prossima domanda che sonda la parte più debole della tua ultima risposta. Ancorato lato server ai tuoi materiali reali — non inventa mai esperienze che non hai. - Consapevole della story bank —
interview-prep/story-bank.mdè integrato nel prompt (stesso livello di fiducia dicv.md), così il feedback può indirizzarti verso le tue storie migliori. - Live o manuale — con una chiave del provider il turno gira live tramite la cascata condivisa (Anthropic → Gemini → OpenAI → Qwen → OpenRouter → GitHub Models); senza chiave ottieni un prompt pronto da copiare-incollare (ripiego onesto, nessuna risposta inventata).
- Sessioni salvate — clicca Salva trascrizione per conservare un colloquio concluso nel livello utente (
interview-prep/mock-{company}-{role}-{date}.md); la pagina elenca, apre ed elimina le sessioni salvate.
- Pratica conversazionale — indica un ruolo target (+ azienda / descrizione dell’annuncio opzionali) e l’intervistatore apre con una domanda mirata. Ogni risposta inviata riceve una replica strutturata: Feedback (punti di forza + la lacuna STAR+R), un Score (
- Nuovo:
server/lib/routes/interview.mjs(18° modulo di route),public/js/views/mock-interview.js,server/lib/llm-dispatch.mjs(cascata di provider condivisa),PATHS.storyBank,bundleProjectContext({ extraFiles }). 30 nuove chiavi i18n in tutte le 16 lingue. Test:tests/interview-routes.test.mjs.
[1.89.0] — 2026-07-04
Aggiunto
- Fit di mercato del candidato — il two-pager (Epic 14). Una nuova pagina
#/two-pagerti permette di catturare ciò che tu vuoi davvero dal tuo prossimo ruolo, modellata sul “two-pager di Mnookin” da Never Search Alone:- Builder guidato — una narrazione in prima persona “Chi sono”, una nota “Ambiente di destinazione” e cinque editor a chip: cosa amo, must-have, cosa detesto, deal-breaker e non negoziabili. Salvato nel livello utente del progetto padre (
config/two-pager.yml) viaPUT /api/two-pager— mai sovrascritto dagli aggiornamenti di sistema. - Assistente di compilazione AI (
POST /api/two-pager/draft) — costruisce un prompt Mnookin pronto all’uso con il tuo CV + profilo inline, da eseguire in qualsiasi LLM e reincollare. Usa solo i tuoi materiali; nulla è inventato. - Badge di fit — ogni annuncio su
#/scanmostra ora un punteggio di fit◎ N(lato client, viawindow.FitScore) che confronta tipo di lavoro, paese, soglia salariale e trasferimento dell’annuncio con il tuo two-pager. Onesto per progetto: quando un annuncio non offre alcun segnale confrontabile, nessun badge viene mostrato (mai un numero inventato). Le violazioni dei deal-breaker pesano più delle semplici avversioni. - Alimenta ogni valutazione — il two-pager salvato è inline in
bundleProjectContext, così tutte le valutazioni LLM a valle fondono le tue preferenze dichiarate con il match CV-vs-JD.
- Builder guidato — una narrazione in prima persona “Chi sono”, una nota “Ambiente di destinazione” e cinque editor a chip: cosa amo, must-have, cosa detesto, deal-breaker e non negoziabili. Salvato nel livello utente del progetto padre (
- Nuovo:
server/lib/routes/two-pager.mjs,public/js/views/two-pager.js,public/js/lib/fit-score.js,PATHS.twoPager. 27 nuove chiavi i18n su tutti i 16 locale. Test:tests/two-pager-routes.test.mjs,tests/fit-score.test.mjs.
[1.88.0] — 2026-07-04
Modificato
- Rifinitura dell’issue #29 — lacune i18n nella Scansione + igiene dell’API.
- Localizzate le ultime stringhe di Scansione hardcoded (roadmap v1.69.4): le pillole di riepilogo per fonte (
N nuove / M corrispondenti), i toastN nuove offertee il badgerelocora passano pert()— 4 nuove chiavi (scan.pillNew,scan.pillMatching,scan.newOffers,scan.relocBadge) su tutti i 16 locale. Gli utenti non anglofoni non vedono più inglese sparso nel flusso di scansione principale. - Disabilitato l’header
X-Powered-By(roadmap v1.69.5):app.disable('x-powered-by')increateApp()— il server non pubblicizza più Express. (Il resto di quell’epica era già stato consegnato:parentVersionrimuove il suo commento release-please, l’interruttore del tema in modalità chiara, la chiusura delle modali al cambio di rotta e la localizzazione di «Score» (rep.score) nei Report.) - Test:
tests/scan-i18n-gaps.test.mjs+ un’asserzione di assenza diX-Powered-Byintests/security-headers.test.mjs.
[1.87.0] — 2026-07-04
Aggiunto
- 4 nuovi provider di scansione senza autenticazione (parità con il career-ops padre v1.16.0). Il registro dello scanner cresce da 41 → 45 adattatori (40 EN + 5 RU) — tutti pubblici, senza autenticazione, con host fissato,
redirect:'error'(sicuri da SSRF), ciascuno con un test isolato per la CI:- Get on Board (
getonbrd) — JSON:API pubblico dell’intero portale (tecnologia LATAM/remoto), selezionato per provider, paginato.server/lib/sources/getonbrd.mjs. - Amazon (
amazon) — JSON di ricerca pubblico diamazon.jobs, rilevato per host oprovider: amazon, paginato per offset.server/lib/sources/amazon.mjs. - Avature (
avature) — ATS*.avature.netper tenant, analizzato da HTML, rilevato per host oprovider: avature.server/lib/sources/avature.mjs. - SAP SuccessFactors (
successfactors) — elenco di riquadri RMK per tenant (*.successfactors.eu/.com,jobs2web.com), analizzato da HTML.server/lib/sources/successfactors.mjs.
- Get on Board (
- Ciascuno fornisce un
sources/<slug>.mjs(conmetaauto-rilevato → menu a discesa#/scan) e unportals/adapters/<slug>.mjsinALL_ADAPTERS(la regola dei due registri) +tests/sources-<slug>.test.mjs. Il conteggio diALL_ADAPTERSe le asserzioni di id ordinato e dell’insieme EN di/api/scan/sourcessono saliti da 36→40;GET /api/scan/sourcesora elenca 45.
[1.86.0] — 2026-07-03
Aggiunto
- Statistiche per ruoli target (
#/stats) — statistiche di mercato su offerte e retribuzioni per i TUOI ruoli target. Una nuova pagina Analytics legge i tuoi ruoli target dal profilo (config/profile.yml→ non hardcoded) e le offerte dell’ultima scansione, quindi mostra, per ruolo e paese: offerte per paese e retribuzione mediana per paese (USD) — aggregate lato client (public/js/lib/role-stats.js, riutilizzandowindow.Countries) a partire dai dati sparsi che gli scanner già raccolgono. - Le retribuzioni in qualsiasi valuta vengono normalizzate in USD tramite una tabella FX esplicitamente approssimativa, con un avviso sulla dimensione del campione — mai inventate. Inoltre filtri per ruolo e paese e grafici a barre e di tendenza in SVG inline scritti a mano (nessuna nuova dipendenza, sicuri per la CSP — solo
addEventListener). - Salva snapshot (
POST /api/stats/snapshot) persiste l’aggregato corrente indata/role-stats.jsonl; il grafico di tendenza (GET /api/stats/trend) traccia il numero di offerte nel tempo — la vista «dinamica». Ibrido onesto: gli snapshot provengono da dati di scansione locali, aggiornati su richiesta. - Completamente localizzato in tutti i 16 locale (26 nuove chiavi i18n). Novità:
server/lib/routes/stats.mjs(16° modulo di rotte),public/js/lib/role-stats.js,public/js/views/stats.js,PATHS.roleStats; testrole-stats.test.mjs(7) +stats-routes.test.mjs(5).
[1.85.0] - 2026-07-03
Aggiunto
- Localizzazione tedesca (
de), italiana (it) e turca (tr) — l’interfaccia, la guida integrata, il README e il CHANGELOG sono ora disponibili anche in queste tre lingue (portate dal set di locale di career-ops 1.16.0). L’interfaccia supporta ora 16 lingue. - Il selettore della lingua ora elenca Deutsch 🇩🇪, Italiano 🇮🇹 e Türkçe 🇹🇷; il rilevamento automatico della lingua del browser riconosce
de,it,tr. - Le impalcature dei prompt (
server/lib/prompts.mjs) sono state localizzate per le tre nuove lingue.