更新日誌

GitHub

每一項重大變更,逐版記錄。同一份檔案隨儲存庫發布,並已翻譯為全部 19 種語言。

[1.231.2] — 2026-09-06

修復 — 行動版頂欄折成兩列、伺服器執行較舊的上游、以及 cvstart.ru 的轉址對任何已含語言的路徑都回傳 404。

修復

  • 行動版頂欄重新回到一列,操作按鈕改為純圖示。 操作按鈕此前被強制放到整寬的第二列,手機上呈現為 [☰ · 搜尋] 上方、[🔔 🌙 Doctor 開啟 Scan] 下方——兩枚寬膠囊獨佔一列。現在它們是鈴鐺與主題切換旁的 36 px 方塊(🩺 / ⚡),在 320 px 下全部裝進一列。是實測而非目測:文件寬度在 320、360、390、430 px 以及按鈕從前會蓋住搜尋框的 560–760 px 整個區間內都與視區相等。語言不再對行動版版面產生任何影響,因為標籤被隱藏、按鈕是固定方塊;超過 900 px 時標籤原樣回歸。
  • 發行版是依上游 1.32.0 建置的,伺服器卻執行上游 1.31.0。 正式環境伺服器的 /api/health 回傳 parentVersion: 1.31.0。已同步 95 個執行期檔案並刪除 2 個。由針對線上環境的瀏覽器 QA 發現。
  • cvstart.ru 會為任何路徑前置 /ru/,完全不看裡面已經有什麼。 /ru/help 變成 /ru/ru/help,/en/help 變成 /ru/en/help,兩者皆 404。前者是從主站複製網址就會遇到的;後者更嚴重,直接讓英語訪客無法抵達英文版。現在已帶有 16 個語言前綴之一的路徑原樣通過,en 被移除(英文位於根目錄),其餘仍然取得 /ru/。

變更

  • 98 份被取代的 QA 提示已移入 qa/archive/superseded-prompts/。 qa/README.md 自 v1.137.0 起就寫明頂層只保留當前發行版的提示——但該規則連續 95 個版本未被執行。
  • 又有五份提示離開頂層——它們自稱當前,實則不是。 REGRESSION-FINAL.md 自稱「唯一權威、與版本無關的回歸提示」,但其 §§11–15 是五份已關閉的週期台帳(v1.55.x → v1.59.7),所宣告的說明基線為 16 個語言下 28 H2 / 103 H3,而今天是 32 / 122 與 17:照它執行的 QA 代理會在 §9 第一步就失敗。QA-REGRESSION-PROMPT.md 頂著無版本的名字,內容卻停在 v1.226.0。QA-REGRESSION-PROMPT-v1.76.0-FULL.md 被釘在 155 個版本之前,REGRESSION-PROMPT-FINAL.md 收束的是 v1.58.52 → v1.59.10 週期,而 QA-FULL-REGRESSION.md 以 v1.131.1 基線把它們統統重複了一遍。PARENT-SYNC-WORKLIST-v1.26.0.md 一併歸檔(上游已到 1.32.0),帶日期的 UX-AUDIT-2026-07-06.md 移入 reports/。留下四份驅動,其自身基線已更新為 v1.231.2 的實測值。qa/README.md 重寫為描述真實存在的目錄。

說明

  • 頂欄修復過程中踩到的兩個優先序陷阱現已在 app.css 中註明:桌面端規則宣告在 max-width: 900px 區塊之後,因此在優先序相同時勝出。
  • applyI18n() 會指派 el.textContent,因此 i18n 鍵被移到內層 <span>;留在按鈕上會在每次切換語言時抹掉圖示。
  • 上游的第一次 rsync 在一個本地已不存在的檔案處中途中斷,卻仍回報成功。上游部署現在以總和檢查碼驗證,絕不依據結束碼。

[1.231.1] — 2026-09-06

修正 — v1.231.0 的計數替換改寫了所有連結中的帳號名。

修復

  • 每一個 Fighter90 連結都變成了 Fighter92。 v1.231.0 的來源計數替換在數字邊界保護下把 90 換成 92;該保護能正確跳過 290 與 901,卻會比對到 Fighter90 裡的 90。17 個 README、17 套說明文件以及網站說明鏡像中的 562 處連結,加上維基裡的 138 處,都指向不存在的帳號。是一位使用者因為圖片沒顯示而發現的。
  • 五處歷史版本引用發生偏移。 同一版本的 85 → 87 替換把 v1.85.0 變成 v1.87.0,憑空造出一個從未發布的版本。

說明

  • 數字邊界並不夠:它保護數字不受相鄰數字影響,卻不能防止它是識別碼或版本字串的一部分。計數本身正確:92 個來源、87 個 EN 轉接器、3009 項測試。
  • 上游儲存庫未受影響。

[1.231.0] — 2026-09-05

新增 — 來自上游 career-ops v1.32.0 的兩個掃描來源(Collage、Telegram 嚴格)、Gem 的 REST 模式,以及移植過程在上游自身程式碼中發現的一處缺陷。

新增

  • 兩個新來源,90 → 92(87 EN + 5 RU),ALL_ADAPTERS 85 → 87。皆無需權杖。
    • Collage(provider: collage)— Collage HR 的公開職缺網站 API。路徑片段是租戶自己選定的網站位址,而非由公司名產生的 slug,因此只從 api: 或 secure.collage.co/jobs/<位址> 的職缺頁位址讀取,絕不猜測:猜出的位址會去掃描別人的職缺板。
    • Telegram(嚴格)(provider: telegram-channel)— 讀取與現有 telegram 來源相同的 t.me/s/<頻道> 預覽,但做的是相反的取捨:只有當貼文點名雇主並且連結到職缺頁時才成為一列。摘要貼文、隱藏雇主的貼文與僅留聯絡方式的貼文都會被丟棄。涵蓋率低是刻意為之——上游量得 HR 經營頻道有 32%–77% 的貼文通過——但每一列都帶著真實的雇主與真實的連結。兩者與上游一樣同時提供。
  • Gem 新增了選用的 REST 模式,與 GraphQL 路徑並存(api: https://api.gem.com/job_board/v0/<board>/job_posts)。它絕不由職缺板 id 推導,GraphQL 路徑仍為預設。移植自上游 #3783。

修復

  • telegram-channel 的地點篩選看不見西里爾字母,會把城市當作雇主。 上游的 LOCATIONISH_RE 使用了僅適用於 ASCII 的 \b,它在西里爾字母旁邊永遠不會觸發,因此其中所有俄語備選項都不可達,Senior Engineer | Москва 會把「Москва」當作雇主回傳。這正是整套歸屬策略要防止的「把錯誤欄位當作事實」,而且恰好打中這個來源的主要受眾。web-ui 的移植改用 Unicode 判定,與上游在下一個常數 ROLE_WORD_RE 中已有的做法一致。以一個在 ASCII 形式下會失敗的測試固定。上游尚未修正。

說明

  • 上游的 Workday 分面復原工作(#3851、#3874)無需移植 — web-ui 根本不對 Workday 分頁,只有 offset: 0 的一次 POST。
  • providers/_types.js 僅文件變更。
  • 測試:2962 → 3009(+47)。

[1.230.0] — 2026-09-05

變更 — 來自上游 career-ops v1.32.0 的三項提供方修正,以及移植過程本身暴露的一處缺陷。

新增

  • Welcome to the Jungle 接受伺服器端 Algolia filters 運算式(wttj: { filters: "offices.country_code:FR AND contract_type:full_time" })。單靠關鍵字無法收窄一個全球職缺板:決定哪 max_hits 筆結果回傳的是 Algolia 的相關度排序,而不是掃描器的篩選條件——我們的篩選在其後才生效,作用於已經抵達的內容,因此超出上限的部分無論多麼契合都看不見。篩選器縮小的是結果集合本身而非重新排序,所以單次查詢的上限從 200 提高到 1000。設定篩選器後 queries 變為選用:空查詢意味著「凡通過篩選的一切」。移植自上游 #3757。

修復

  • 當租戶只是提供的職缺比其自家橫幅所宣稱的更少時,Radancy 不再警告「truncated」。 上游將 data-total-results 與九個真實租戶逐一核對:五個吻合,但四個——既有小租戶也有超大租戶——在沒有任何重複列的情況下,交付的唯一職缺比公告值少 10%–56%。橫幅誇大了租戶自身搜尋索引實際提供的數量,因此拿累計計數與之比較,在多數職缺板上都是誤報。警告現在只在我們自己的 max_jobs/max_pages 終止走訪時出現,那是呼叫方唯一能採取行動的情形;totalResults 作為脈絡保留在訊息中,永不參與判斷。移植自 #3839。
  • Radancy 為每一次 JSON 片段請求繞過快取。 該路由前的快取層在部分租戶上重播過期回應:同一筆職缺在數頁之後再次出現,而另一筆從未被回傳——上游九次執行中九次重現。Cache-Control/Pragma 請求標頭毫無作用,而每次請求附帶一個隨機參數解決了問題。它必須按呼叫唯一,絕不能由頁碼推導:確定性的參數仍會給快取一個穩定的鍵,從而保留同樣穩定而錯誤的對應。移植自 #3839。
  • Greenhouse 的辦公地點清單具有確定順序。 Greenhouse 並不保證兩次回應之間 offices[] 的順序,而從 /offices 樹中還原出的集合遵循其走訪順序。兩者都會進入職缺的 location 欄位,因此職缺板重排辦公地點就會改寫這個字串——於是毫無變動的職缺被讀成新職缺。上游修正了集合(#3839);web-ui 還存在同一缺陷的第二處實例,而上游沒有——當職缺自身沒有地點時啟用的 offices[0] 回退——現在兩處都已排序。

說明

  • 上游的 iCIMS 修正(#3728)無需移植。 它從詳細頁的 JSON-LD 中讀取每一個 jobLocation 項目,因為部分租戶會在真實地址之前放置一筆全為 UNAVAILABLE 的項目。web-ui 的 iCIMS 從列表頁的 HTML 中取得地點,從不開啟詳細頁。
  • 上游對 _http.mjs 的改動無需移植。 它抽出 isRefusedRedirectError 供第二個使用者共享判斷,而 web-ui 沒有那個使用者;isRetryableError 的行為不變。
  • 位置感知去重(#3751)未做鏡像——它位於上游 CLI 的 scan.mjs 管線中。
  • 登錄檔不變:90 個來源(85 EN + 5 RU)。測試:2956 → 2962(+6)。

[1.229.0] — 2026-09-03

新增 —— 來自父專案 career-ops v1.31.0 的四個掃描來源:Built In、Feishu Jobs、Garena 與 MokaHR。

新增

  • 四個新來源,86 → 90(EN 85 + RU 5),ALL_ADAPTERS 81 → 85,四者皆無需權杖。Built In 涵蓋美國科技職缺板的九個市場,且不攜帶預設查詢:既無 queries: 也無 categories: 的項目什麼都不掃描,並直說這一點 —— 共用來源不該背負某個人的搜尋詞;不在白名單上的市場會被拒絕,絕不悄悄換成全國板。Feishu Jobs 只接受兩種主機形式,而兩者通往職缺頁的路徑不同。Garena 是單一公司:office 塑造的是連結,不是列表。MokaHR 回傳加密回應(金鑰隨行的 AES-128-CBC 信封)—— 這是混淆而非安全,但不解密就什麼也讀不到。

修復

  • htmlToText 可能把不完整的標籤開頭洩漏進內文。 safe &lt;img src=x onerror=1 會輸出為 safe <img src=x onerror=1。現在每次解碼之後都跟一次清除,末尾的開頭字元會失去角括號。移植自父專案(#3491)。

說明

  • 父專案的 Workday 修復一點也沒有移植。 它拯救的是後端在位移 2000 處卡住分頁的租戶 —— 而 web-ui 根本不分頁 Workday(offset: 0 上一次 POST,100 列)。
  • verify-cv-facts 與 merge-tracker 的修復無需移植:web-ui 從父專案檢出中執行這些指令碼。
  • 測試:2909 → 2956(+47)。

[1.228.5] — 2026-08-30

修復 —— 上報的版本描述的是磁碟上的檔案,而不是正在執行的程式碼。

修復

  • /api/health 在每個請求上重新讀取 package.json,因此陳舊的行程會上報一個它並未執行的版本。 QA 發現一個本機實例回答 version: 1.228.4,同時對 1.228.3 新增的 /api/ping 回傳 404:它在部署之前啟動,提供著舊程式碼,卻上報新檔案的版本。一次只複製檔案、從未重啟的部署,正是這樣看起來像成功的 —— 維運唯一會檢查的那個字串,恰恰是看不見問題的那個。這與 v1.228.1 的符號連結同屬一類,當時 rsync 在傳輸中途中斷,卻依然回報成功。

    版本現在在模組載入時擷取,因而描述正在執行的程式碼。陳舊的行程會上報舊版本 —— 那是真相,第一個請求就能看見。parentVersion 刻意保留每請求讀取:它描述的是父專案檢出,本行程並不載入,且在執行中的伺服器下合法地變化。

[1.228.4] — 2026-08-30

修復 —— 無需認證的存活探針在每個請求上都做磁碟工作。

修復

  • GET /api/ping 每次命中都讀取並解析 package.json。 它是唯一無需憑證即可到達的端點,因此每請求一次磁碟讀取加一次 JSON 解析,等於把拒絕服務的槓桿遞給任何人 —— CodeQL 將其標記為缺少速率限制。版本現在只在註冊路由時讀取一次;不重啟本就無法改變,因此處理函式完全不做 I/O。速率限制能給傷害封頂;移除這項工作則移除了槓桿本身。
  • 迴環之外對個人資料擁有者真實姓名的遮罩現在由測試固定。 /api/health 早已將其替換為 hidden——但只是因為那一行與專案根目錄共用 hidden ?? value 這道防線。沒有任何測試驗證它,因此一次給該行賦予自身值的改動,會把真實人名悄無聲息地洩漏到區域網路。

[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. buildAskPrompt did if (ctx.length + sec.body.length > MAX_CONTEXT) break — a break, 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 added telegram_channels to 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, so telegram_channels is 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 (продуктовая разработка, высоконагруженные системы) became FinCore 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 (so Coca-Cola survives), 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/health reports 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 single includes(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-design was 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.mjs also 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 only refero matches in the repo are the Polish word preferowany.

[1.228.0] — 2026-08-29

新增 —— Telegram 頻道作為掃描來源,以及 /api/scan-results 的分頁查詢模式。

新增

  • Telegram 頻道現在可被掃描。 在 portals.yml 的 telegram_channels: 區塊中列出,每個頻道透過公開網頁預覽 https://t.me/s/<頻道> 讀取。註冊表:85 → 86 個來源(EN 81 + RU 5),ALL_ADAPTERS 80 → 81。

    Telegram 不提供 RSS,Bot API 也無法讀取機器人未管理的頻道 —— 兩條顯而易見的路都被堵死。/s/ 預覽是伺服器渲染的純 HTML:無需登入、無需 JS、無需 Cookie。已在 16 個頻道驗證:15 個在裸 GET 下各回傳 20 則。

    channel: 接受任意寫法。解析器錨定在 Telegram 改版最不可能挪動的三處:data-post、內文容器、ISO 日期 <time datetime>。

  • GET /api/scan-results 新增分頁查詢模式。 ?q=<文字>&limit=50[&region=ru|en][&set=filtered|fresh][&offset=N] 回傳 { total, returned, offset, limit, set, rows },其中 total 是分頁之前的計數。不帶參數時該路由仍回傳完整快照 —— #/scan 表格依賴那個形狀。

修復

  • Telegram 助理只憑一小部分資料作答。 它的技能早已指向 /api/scan-results,但該路由只會回傳整份快照 —— 實機上約 2 MB —— 塞不進代理的上下文。面對「找出所有 Продуктовый менеджер」,它回報 9 筆,而快照裡有數百筆,並說自己「在用一個受限的本地索引」。這是實話,原因是它看得見端點卻消化不了。有了分頁模式,同一個問題只花 2 KB,並回傳誠實的 total。技能也一併重寫:原文要求總結新增貼文,也就是指向 fresh(僅上次執行)而非 filtered(全部保留項)。

備註

  • 頻道貼文是散文,不是職缺記錄。 沒有結構化欄位:標題取自第一行實質內容,公司/地點/薪資取自明確標籤,內文放入描述。把真實職缺與廣告、摘要區分開的工作交給現有的 title_filter —— 就是你已為其他來源調好的那個。這些是線索,不是清單。
  • 公司名絕不猜測。 沒有標籤時該列歸屬於頻道(@rabotaphp):錯誤的雇主會作為事實進入追蹤表。
  • 解析為空是錯誤,而不是「沒有職缺」。 對於私有或不存在的頻道,t.me 以重新導向作答;此時回傳零列會被讀成風平浪靜的一天,並永久掩蓋設定裡的拼字錯誤。原清單中的 jobGeeks 正是如此(HTTP 302),因此刻意未設定。
  • 遠端判定使用 Unicode 詞邊界而非 \b:\b 僅支援 ASCII,西里爾詞前的空格並不構成邊界 —— 與 v1.227.3 標題過濾器踩中的是同一個陷阱,這次在發布前被測試逮住。
  • 已設定並實測 15 個頻道:299 則貼文,15/15 頻道。新增 28 個測試。測試: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 Total row 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

修復 —— 區域掃描耗盡伺服器記憶體,另有一個 Playwright 套件因自身拆解而隨機失敗。

修復

  • 區域掃描以 OOM 拖垮了伺服器。 ru-scanner 會累積所有查詢的全部原始結果,直到最後才去重與過濾。查詢清單刻意由近義詞構成(Golang、Go разработчик、Golang разработчик、Senior Go ……),因此同一職缺每個查詢都會回傳一次,陣列持有的物件是唯一數量的數倍 —— 還帶著職缺描述,而描述佔物件的絕大部分體積,且幾乎全會被過濾器丟棄。實測:21 個查詢的真實執行佔用堆積 742 MB。伺服器將 Node 堆積上限設為 490 MB(V8 依機器 956 MB 記憶體推算),於是掃描以 FATAL ERROR: Reached heap limit 拖垮服務 —— 一天四次。現在去重與過濾按查詢進行,同一次執行降至 177 MB,減少 76%。所回報的數字不變:唯一總數由 URL 字串的 Set 承載。
  • net::ERR_SOCKET_NOT_CONNECTED 隨機拖垮 CI。 三個 Playwright 套件在斷言無主控台錯誤前呼叫 window.stop() 中止在途請求。Chromium 會依 socket 所處狀態,用三種不同名稱回報這同一個動作:取消時為 ERR_ABORTED,在途時為 ERR_EMPTY_RESPONSE,socket 已消失時為 ERR_SOCKET_NOT_CONNECTED。共用過濾器只列了前兩種。它在語言巡檢的 tr 上暴露,而同樣呼叫 window.stop() 的 playwright-forms 與 playwright-smoke 也只差一次倒楣的執行。

備註

  • 為何現在才失敗。 加入產品經理相關搜尋後,查詢從 14 條增至 21 條。舊實作的記憶體隨結果總數成長,查詢 +50% 便衝破了機器上限。
  • 去重改動保持行為不變,這一點是測過的而非宣稱的。 tests/ru-scanner-dedup-order.test.mjs 鎖定了那個彆扭的方向 —— 後來的重複項若未通過過濾,必須把先前通過的那條擠掉 —— 並對舊實作做了 200 次隨機等價校驗。
  • 主控台過濾器仍然狹窄。 只有被取消的請求這一族是良性的;連線被拒、DNS 失敗、連線重設、任何 5xx 和未捕捉的 JS 例外仍會使斷言失敗。
  • 兩個假設經檢驗後被否決而非草率發布:Caddy 逾時(設定乾淨,SSE 所需的 flush_interval -1 正確)與 Node 預設的 5 分鐘 requestTimeout(縮比重現表明它不會中斷長時間串流回應)。真正的證據是日誌裡的 Reached heap limit。測試:2860 → 2865。

[1.227.4] — 2026-08-29

修復 —— content_filter 仍忽略 word: / stem:;v1.227.3 只修了標題過濾器。

修復

  • content_filter 未遵循這兩個前綴。 v1.227.3 讓 title_filter 學會了上游的 word: / stem: 前綴,卻把 content_filter 留在它自己的裸 lower.includes(k) 上,因此那裡帶前綴的條目仍被當成字面文字 "word:java" 來比對 —— 同一個靜默空操作,只是挪到了下一個過濾器。content_filter 讀取職缺描述,其預設一直是簡單子字串比對:正因如此,一個裸的否定項 java 會排除所有僅僅提到「JavaScript」的職缺;前綴就是讓單一條目跳脫該預設的手段。已移植上游的 compileContentKeyword:它共用前綴機制,但刻意不自動錨定短關鍵字 —— 標題過濾器錨定 2–3 字母縮寫,是因為「Coordinator」中的「COO」永遠是錯的;而描述段落裡的短字串通常是刻意為之(aws、gcp、sql、go)。

備註

  • 在 v1.227.3 PR 的 AI 審查中發現:審查注意到程式碼並未涵蓋其自身註解所宣稱的範圍 —— 註解寫著「title_filter / content_filter」,但接上的只有標題路徑。現在註解已明確區分:兩個過濾器都遵循前綴,只有標題過濾器會自動錨定縮寫。
  • 同樣依該審查核實且未變: 把 2–3 字母縮寫規則從 \b 換成 Unicode 邊界,在 ASCII 上行為完全一致,僅在應當從嚴處更嚴 —— COO_lead 與 coo1 仍保留(底線與數字在兩種寫法下都是單字字元),Coordinator 保留,「Директор COO」與「VP «Продукт»」被正確排除。已核對 14 個邊界案例。
  • 不帶前綴的 content_filter 條目逐位元組保持原有比對,空的或缺失的描述仍會通過,空的 word: / stem: 不比對任何內容而非全部比對。tests/content-filter.test.mjs 新增 6 個案例。測試:2852 → 2858。

[1.227.3] — 2026-08-29

修復 —— 標題過濾器的 word: / stem: 前綴被靜默忽略,使那些過濾行形同虛設。

修復

  • web-ui 未實作上游的 word: / stem: 前綴。 title_filter(以及 content_filter)預設以不分大小寫的子字串比對,這正是為何一個裸的否定項 intern 連「International Product Manager」與「Internal Tools Engineer」也一併排除。與其翻轉該預設值(會破壞所有已設定的安裝),上游選擇讓精確比對逐條選用:word:intern 只比對完整單字,stem:agent 必須位於詞首且可延續(藉此區分「Agentforce」與「Reagents」)。web-ui 兩者皆無,於是把該條目當成字面文字 "word:intern" 來比對 —— 而沒有任何職缺標題含有這串字元。該行因而成為靜默的空操作:同一份 portals.yml 在 CLI 中過濾正常,在這裡完全不過濾,使用者排除掉的實習職缺依舊全數出現在 #/scan 表中。已自上游 title-keywords.mjs 移植,包含支援 Unicode 的詞邊界(\p{L}\p{M}\p{N}_),而非僅支援 ASCII、會在帶重音或西里爾字母標題中於詞中命中的 \b。

備註

  • 在檢查一份真實 portals.yml 時發現,其中剛引入 word:intern,正是為了讓裸的 intern 不再丟棄「International Product Manager」。該前綴在 CLI 中生效,web-ui 卻悄悄保留了所有實習職缺。現在兩者行為一致。
  • 預設行為未變。 不帶前綴的關鍵字維持原有比對方式:片語以及任何含非字母字元的條目(.NET、SAP 、L&D)仍按子字串比對,2–3 個字母的縮寫仍錨定詞邊界。空的 word: / stem: 視為筆誤,不比對任何內容 —— 作為否定項,空樣式會比對所有標題,僅因一個多餘的冒號就否決整次掃描。
  • 2–3 字母縮寫規則也改用同一 Unicode 邊界:先前使用 \b,導致 coo 與 word:coo 在非 ASCII 標題上表現不一致 —— 同一條規則卻有兩種寫法。
  • tests/title-filter.test.mjs 新增 6 個案例。測試:2846 → 2852。

[1.227.2] — 2026-08-29

修復 —— 窄螢幕手機上 #/config 會橫向溢出。

修復

  • MOBILE-1:在 ≤ 352 px 的視口下 #/config 橫向溢出。 每個 select 類欄位都帶著行內的 min-width: 300px。行內樣式優先於樣式表,因此控制項無法隨容器收縮:在 320 px 視口下 select 的右邊緣落在 353 px,頁面出現橫向捲軸,控制項被擠出右邊界。受影響的是全部 18 個供應商/模型下拉選單(cfg-llm-provider 及所有 cfg-*-model)。現改為 min-width: min(300px, 100%) + max-width: 100%,只有空間足夠時才套用 300 px 的舒適寬度。處於臨界值及以下的裝置:iPhone SE(一代,320 px)、老舊 Android、Galaxy Fold 外螢幕。

備註

  • 先前所有行動端檢查為何都沒發現。 360 px 與 375 px 恰好有足夠餘裕掩蓋溢出,而 Playwright 套件執行在 1280 px 寬的視窗中。CI 裡從未有任何測試量測過窄於 375 px 的視口。新增的 tests/playwright-narrow-viewport.mjs 在 320 px 下巡覽帶表單的路由,出現任何橫向溢出即失敗,並在斷言訊息中指出最寬的元素;第二個案例鎖定 #/config 的任何下拉選單都不得越出視口。提交前已先驗證它能重現該缺陷(scrollWidth 353 > clientWidth 320,18 個右邊緣為 353 的下拉選單)。Playwright:99 → 101。
  • 已知且刻意暫未納入門檻:280 px(Galaxy Fold 外螢幕)仍會溢出約 5 px,但成因不同 —— 是設定卡片 <details> 區塊內的長內容,以及 256 px 側邊欄對 280 px 視口,而非本次修復的行內 min-width。為一個主流手機都不會回報的寬度重新排版屬於另一項工作;套件的 WIDTHS 清單已為此就緒。
  • 同時更正了 v1.227.1 的 QA 提示:其中把跟進路由寫成 #/modes/followup。mode 頁面以裸 slug 註冊(Router.register(cfg.slug)),因此路由是 #/followup,而 #/modes/followup 正確回傳 404。僅文件變更。
  • 單元測試套件維持 2846(新增涵蓋屬瀏覽器層級)。

[1.227.1] — 2026-08-29

修復 —— 說明中的來源拆分與自身對不上,且 GitHub 橫幅在應用內被當成原始標記印出。

修復

  • §17 給出的總數與拆分不相加。 所有說明包都寫著「server/lib/sources/ 註冊表內建 85 個 adapter —— 78 個英文 + 5 個俄文」。78 + 5 = 83,而非 85;ja/ko 偏得更遠,為 77(= 82)。每加一個來源只更新總數、不動加數,兩個數字已經錯開好幾個版本。現在 17 種語言的拆分統一為 80 EN + 5 RU。這不只是觀感問題:docs/help/<lang>.md 是「Ask the docs」唯一的依據語料,因此詢問支援多少來源的使用者會從同一句話得到兩個互相矛盾的數字。
  • 同一句話釘死了一個過期的版本錨點。 「截至 v1.213.0,註冊表 …」在 16 種語言中存活,而計數已變動兩次(v1.219.0 為 83,v1.226.0 為 85)。該錨點在 v1.210.1 已手工修過一次,隨後又漂移。現改為不含版本的措辭並移除 —— 計數本身對照活的註冊表校驗,不會過期。
  • 供應商橫幅在 #/help 頂部被當成跳脫後的標記印出。 v1.225.0/v1.225.1 記錄稱它「因 UI.md() 不支援圖片而在應用內被剝離」。這混淆了兩件事:UI.md() 是跳脫優先的 —— 它在任何 markdown 轉換之前跳脫來源的每個位元組,這是正確的 XSS 邊界,但與移除恰恰相反。沒有人移除橫幅,因為從來沒有程式碼嘗試過;於是 17 種語言的 #/help 都以 268 個字元的字面 <p align="center"><img src="https://… 開頭。新的共用入口(server/lib/help-markdown.mjs::stripGithubOnlyBlocks)在讀取時丟棄獨立的 HTML 圖片區塊 —— GET /api/help/:lang 與 docs-assistant 語料兩者皆然。檔案中仍保留橫幅,因此 GitHub 上的展示不受影響。
  • 節奏看板的「#」欄自 v1.117.0 起一直是空的。 它讀取 entry.appNum,而 followup-cadence.mjs 輸出的是 num —— appNum 從未出現在酬載中。現在兩者都會讀取。v1.227.0 的「Next up」行繼承了同一處錯誤,一併修復。

備註

  • 四處問題都因為無人把關而長期未被發現。 說明的門檻(canonical-docs-coverage, help-ui, help-ru-config-section)統計的是標題 —— 32 H2 / 121 H3 —— 從來沒有測試讀過段落內部的數字,也沒有測試看過說明入口實際回傳什麼。兩個新測試套件補上這一環:tests/help-source-counts.test.mjs(所宣告的總數等於 SOURCES.length;加數等於活的 EN/RU 劃分且相加等於總數;段落不釘死版本)與 tests/help-banner-strip.test.mjs(橫幅為 GitHub 保留在磁碟上、絕不在剝離後存活、每個說明包的首個渲染行是其標題、剝離範圍保持狹窄、兩個消費方都經過它)。兩者在提交前都已驗證會針對這些具體缺陷失敗。測試套件:2837 → 2846。
  • 註冊表、掃描器與路由契約均未變動;來源仍為 85 個(80 EN + 5 RU),ALL_ADAPTERS 仍為 80。

[1.227.0] — 2026-08-28

新增 —— SEEK 香港(JobsDB)、跟進的僅待辦篩選,以及保密雇主顯示代為投遞的仲介機構。

新增

  • 透過既有 Jobstreet/SEEK 來源接入 JobsDB 香港。 香港以 JobsDB 品牌營運,但運行在同一 v5 端點背後的同一 SEEK 平台上,因此無需獨立來源——只需在主機白名單中加入 hk.jobsdb.com 並設定 siteKey: HK-Main。登錄檔維持不變:85 個來源(80 EN + 5 RU),ALL_ADAPTERS 80——這是既有配接器上的新市場,而非新配接器。
  • 節奏看板的僅待辦篩選(#/modes/followup)。新增核取方塊把看板收窄到此刻需要行動的項目。是否待辦取自上游 followup-cadence.mjs 已算出的 urgency(urgent / overdue),絕不取自追蹤表的 status——後者是 applied/responded/interview,不會比對到任何內容。當沒有待辦時,看板會指出最近一筆已排程的跟進,而非顯示空白。邏輯位於純模組 public/js/lib/followup-view.js,對應上游的 followup-view.mjs。
  • 保密雇主歸因到代為投遞的仲介機構。 隱去雇主的職缺在追蹤表中記為 ?,招募方則寫入 Via 欄。#/tracker 先前只顯示一個孤零零的 ?,多筆保密紀錄因此無法區分,標誌解析也無從比對。現在這類紀錄顯示為 「保密 · 經由 <仲介機構>」,標誌依仲介機構名稱解析,並可在搜尋框中用該名稱找到。這只是呈現層變更——規範欄位 company 絕不會被招募方覆寫。新增純模組 public/js/lib/company-presentation.js,對應上游的 company-presentation.mjs。

修復

  • Jobstreet/SEEK 先前在呼叫已淘汰的端點。 該來源仍指向 SEEK 已下線的 chalice-search v4(/api/chalice-search/v4/search);上游已遷移到 /api/jobsearch/v5/search,而 web-ui 未跟進。現在所有 Jobstreet/SEEK 項目都查詢 v5。v5 的項目結構被原生讀取(由 id 建構職缺 URL、locations[0].label、advertiser.description、salaryLabel),v4 專有的投影參數 solrFields 已移除;若某項目的 portals.yml 仍固定著失效的 v4 路徑,其市場主機名會保留,路徑則在 v5 上重建——無需使用者改動。

備註

  • 與 career-ops 1.30.0 對齊(上游 HEAD 8d64f65 → 6cc46a4;其 VERSION 仍為 1.30.0——release-please 落後於 main)。未移植及理由:iCIMS 的 JSON-LD 地點回填——web-ui 的 iCIMS 來源根本沒有 enrichDate() 詳情頁掛鉤(僅列表頁),無處可改;非致命 CLI stderr(#1974)——web-ui 的執行器僅憑結束代碼判定失敗,從未使用該啟發式;Turbopack 路徑追蹤——web-ui 沒有打包器;cv-sync-check 修復——以唯讀、fail-soft 方式轉送;scan.mjs 中 Playwright 串行化、Block H 答案、updater/doctor/update-system 防護、遞迴語法檢查、Go 版儀表板,以及 jd-skill-gap/verify-cv-facts 的自我測試新增——web-ui 不會呼叫的純 CLI 面,或不改變行為的自我測試。測試套件:2818 → 2837。

[1.226.0] — 2026-08-27

新增 —— 兩個越南招聘來源: ITviec 與 CareerViet。

新增

  • ITviec + CareerViet(越南)。 兩個主機鎖定的越南看板經 provider: itviec / provider: careerviet 項目加入掃描器:ITviec(itviec.com,技術向)與 CareerViet(careerviet.vn,廣覆蓋)。兩者皆為零 token 的 HTML 抓取器,帶 SSRF 主機鎖定、僅 HTTPS、依公司容錯與頁數上限。註冊表現有 85 個來源(80 EN + 5 RU),ALL_ADAPTERS 80 —— 兩者皆出現在 #/scan 的來源篩選中。

備註

  • career-ops 1.30.0 對齊(parentVersion 1.29.0 → 1.30.0)。未移植(僅 CLI/社群或已涵蓋):Hired Wall、/calibrate、clean-markers、範本包、doctor、pdf/LaTeX,以及 web-ui 不呼叫的 eval/tracker/scan CLI;_html-to-text 修復與 detect-reposts roleFuzzyMatch 已存在;title-filter stem: 前綴延後。測試套件:2818。

[1.225.1] — 2026-08-27

修復 —— 供應方標誌橫幅現在能在 README 與說明中顯示。

修復

  • v1.225.0 的橫幅在 GitHub 上不顯示:獨立的 images/providers.svg 有一個無效的 font-family(巢狀的雙引號),被 GitHub 拒絕;<img> 使用了儲存庫相對 src(GitHub 不解析 raw HTML <img> 的相對路徑);<p> 區塊缺少結尾空行,導致與下一行合併。現在它透過絕對 raw URL 指向已算繪的 images/providers.png,並以空行分隔;SVG 也已修正為有效 XML。

備註

  • 僅文件;無程式碼/測試改動。cvstart.org 展示區與應用內磚貼先前已正確(內嵌 SVG,非此檔案)。 2784

[1.225.0] — 2026-08-27

新增 —— README 與說明指南頂部的供應方標誌橫幅。

新增

  • 供應方標誌橫幅。 一個自包含的 images/providers.svg —— 18 個 LLM 供應方,發布圖示的 11 個用真實品牌標誌(simple-icons,CC0),其餘 7 個用品牌配色字母標 —— 現位於 README(×17)與應用內說明指南頂部。(應用自身渲染真實標誌磚貼;橫幅在 GitHub 渲染,應用的 XSS 安全渲染器會將其移除。)

備註

  • 僅文件 —— 無程式碼/測試改動;SVG 為內嵌(無遠端資源)。掃描來源不變:83。測試套件:2784。

[1.224.0] — 2026-08-26

變更 —— LLM 供應方磚貼現在顯示真實的品牌標誌。

變更

  • 真實的供應方標誌。 供應方磚貼 —— 設定中的 API 金鑰欄位、用量列、儀表板晶片、設定的「使用中」摘要、⚡ 評估結果與歡迎橫幅 —— 現在為發布了開源圖示的 11 個供應方(Anthropic · Gemini · OpenAI · Qwen · OpenRouter · GitHub · DeepSeek · Kimi · MiniMax · Mistral · Ollama;來自 simple-icons 的單路徑 SVG,CC0)呈現真實的品牌標誌。沒有發布圖示的 7 個(Hermes · GLM/Z.ai · Grok · Together · Fireworks · BytePlus Ark · Volcengine Ark)保留品牌配色的字母標。仍然在建構上對 CSP 安全:標誌路徑是內嵌的靜態常數 —— 無遠端資源,無 innerHTML。

備註

  • 無路由或行為變化;相同的 ProviderLogo.el(slug) API。provider-logo.js 變大了(內嵌的標誌路徑)。掃描來源不變:83。測試套件:2784。

[1.223.0] — 2026-08-26

修復 —— 成功的 ⚡ 即時評估被標為錯誤;另外可從 GitHub URL 或 .zip 直接安裝 OpenWorker coworker。

修復

  • ⚡ 評估結果徽章配色。 成功的即時評估不再被著色為失敗。行程內供應方(OpenRouter、DeepSeek、…)不回傳子行程結束 code,因此嚴格的 code === 0 為假,所有成功都顯示為紅色(卻仍寫著 “exit 0”)。配色現在跟隨結果 —— 僅非零結束的子行程標紅;無結束碼的即時結果為 badge-ok,“· exit N” 後綴僅在有真實結束碼時顯示。

變更

  • 從 GitHub URL / .zip 安裝 OpenWorker coworker。 coworker 文件現已在 docs/integrations/openworker.md、應用內說明 §32(×17)與 README 中說明一條命令安裝器(curl … | bash,冪等 —— 重用既有的 career-ops / web-ui)和 OpenWorker 的三種安裝途徑(GitHub URL · .zip · 單一檔案匯入)。

備註

  • 補上一個長期的涵蓋缺口:Recruitee 來源現在有針對 v1.214.2 引號內角括號修復的端對端測試(標籤屬性中的 > 不得洩漏到描述)。掃描來源不變:83。測試套件:2783。

[1.222.0] — 2026-08-26

變更 —— 擴充供應方(DeepSeek … Volcengine Ark)的欄位提示現已本地化為全部 17 種語言。

變更

  • 供應方欄位提示本地化。 v1.216.0–v1.217.0 新增的 11 個 OpenAI 相容供應方(DeepSeek · GLM/Z.ai · Kimi · MiniMax · Mistral · Grok · Together · Fireworks · Ollama · BytePlus Ark · Volcengine Ark)的每個 config.<slug>Hint —— 設定 → API 金鑰下的 API 金鑰、模型與基礎 URL 提示 —— 現已翻譯為全部 17 種語言,不再回退到英文。註冊 URL、模型 id 與 ⚡ 即時評估標記皆原樣保留。

備註

  • 行為無變化 —— field-specs.js 中的英文 hintFallback 仍在金鑰缺失時兜底,新增的契約測試斷言每個欄位描述子 hintKey 都能在全部 17 種語言中解析。掃描來源不變:83。測試套件:2779。

[1.221.0] — 2026-08-26

安全 —— 掃描器 fetch 路徑的 DNS 重新繫結縱深防禦。

安全

  • 掃描器 HTTP 核心(server/lib/http-json.mjs 的 fetchJson/fetchText)現在會在真實網路路徑上解析每個來源的主機,並在其解析到私有/回送/連結本地/CGNAT/雲端中繼資料位址時拒絕連線 —— 封堵 DNS 重新繫結向量。掃描器主機本已鎖定到公共網域,故為縱深防禦;使用者 URL 路徑已有更強的連線鎖定(safe-fetch.mjs)。守衛僅在真實 fetch 傳輸上執行 —— 測試注入的 fetchImpl 從不被解析。

備註

  • 正常掃描行為不變。掃描來源不變:83。測試:2775。

[1.220.0] — 2026-08-26

新增 —— 用一個項目掃描多個 Get on Board 分類。

新增

  • getonbrd 項目現在可設定 categories: [programming, operations-management, machine-learning-ai](或單一 category:),不再只是預設的 programming 來源;職缺在各分類間依 URL 去重,上限 12 個。現有項目保持不變(預設仍為 programming)。

備註

  • 僅伺服端;SSRF 主機鎖定(www.getonbrd.com)與 redirect:'error' 不變,無效分類 slug 在請求前即被拒。掃描來源不變:83。測試:2771。

[1.219.0] — 2026-08-26

新增 —— Torre 加入掃描器,現在每個供應方標籤都正確。

新增

  • Torre(provider: torre)—— torre.ai 的泛拉美人才市集,一個免驗證的公開職缺搜尋,包含 Greenhouse/Lever/Ashby 上沒有的遠端職缺;每個項目一次受限請求。註冊表現在提供 83 個來源(英文 78 + 俄文 5)。此外,歡迎橫幅上顯示目前供應方的字母標。

修復

  • a16z Speedrun 掃描在會輪換的看板上過早停止(把掃描中途的短頁當成最後一頁,截斷為約 300 中的 149);現在信任資料來源本身的 total_pages。歡迎橫幅原先從 4 條對應解析供應方(其餘 14 個顯示原始 slug),現改用 18 個的標籤+字母標;設定裡的供應方說明從 7 改為 18。

備註

  • Get on Board 每項目多分類的新選項尚未接入(需要來源層重構)。測試:2768。

[1.218.0] — 2026-08-26

新增 —— 在顯示供應方的每個位置都加上標誌(與正確名稱)。

新增

  • 儀表板小標籤、設定「目前」摘要、⚡ 評估結果標題處的供應方字母標。

修復

  • 過時標籤:三處介面從 5 條對應解析名稱(評估結果把所有非 Anthropic 供應方誤標為「Gemini」);現改用共享的 ProviderStatus.label(18)。金鑰計數顯示「/ 7」→18。LLM_PROVIDER 提示的 slug 清單在全部 17 種語言中新增 ark / arkcn。

備註

  • 僅用戶端,CSP 安全。無伺服器改動。掃描來源:82。測試:2758。

[1.217.1] — 2026-08-26

測試加固 —— 涵蓋全部供應方的端點。

新增

  • 一個參數化的 run<Provider> 測試,校驗 Kimi、MiniMax、Mistral 與 Fireworks 的端點、預設模型與 Bearer 認證。

[1.217.0] — 2026-08-26

新增 —— Ark 加入 LLM 名單(18 個供應方)。 字節跳動的 Doubao 模型 BytePlus Ark 與 Volcengine Ark 現為 ⚡ 即時供應方——採用同一 OpenAI 相容核心與按區域的基礎 URL。

新增

  • BytePlus Ark(ARK_API_KEY,國際)與 Volcengine Ark(ARK_CN_API_KEY,中國)—— 透過 runOpenAICompatible() 的 OpenAI 相容 Chat Completions。設定 ARK_MODEL / ARK_CN_MODEL(Doubao 模型名或你的 ep-… 端點 id);ARK_BASE_URL / ARK_CN_BASE_URL 切換區域。兩者均加入 auto 順序(末位),並在設定與用量中顯示字母標。Web UI 現已涵蓋 18 個即時供應方。

備註

  • 與其餘相同的受信任設定 + http(s) 協定檢查路徑(絕不經過職缺 URL 的 SSRF 驗證器)。掃描來源不變:82。測試:2755。

[1.216.0] — 2026-08-26

新增 —— 再增九個 LLM 供應方,每個都有自己的品牌標。 ⚡ 即時評估現可透過 DeepSeek、GLM (Z.ai)、Kimi (Moonshot)、MiniMax、Mistral、Grok (xAI)、Together、Fireworks 或完全本機的 Ollama 執行——只需一個金鑰,設定中每個欄位旁都帶有字母標。

新增

  • 九個 OpenAI 相容供應方。 DeepSeek、GLM (Z.ai)、Kimi (Moonshot)、MiniMax、Mistral、Grok (xAI)、Together AI、Fireworks AI 與 Ollama(完全本機、免金鑰)。在設定中填入金鑰(Ollama 則填 OLLAMA_BASE_URL),即會加入 Hermes 之後的 auto 順序;用 LLM_PROVIDER 可固定任一個。Together 亦託管 Thinking Machines 的 Inkling(thinkingmachines/Inkling)。Web UI 現已涵蓋 16 個即時供應方。
  • 供應方字母標。 一個 CSP 安全、品牌配色的首字母方塊在設定的金鑰欄位旁及用量頁面的列內標記每個供應方——內聯 SVG,無外部標誌,任何資料都不離開你的裝置。

備註

  • 供應方的基礎 URL 屬受信任設定,經由 runOpenAICompatible() 的 http(s) 協定檢查(允許 Ollama 回送),絕不經過職缺 URL 的 SSRF 驗證器。掃描來源不變:82。測試:2752。

[1.215.0] — 2026-08-26

新增 —— 從 OpenWorker 執行你的整個求職流程。 一個新的 coworker 從 Andrew Ng 的開源 AI coworker 應用驅動本流水線,應用內說明現已涵蓋它,因此「向文件提問」助手可以帶你完成。

新增

  • OpenWorker coworker。 一個免程式碼的 OpenWorker coworker —— Fighter90/career-ops-coworker —— 執行整個求職流程(掃描、評分、客製、追蹤、追進),並可啟動本儀表板,全部在你的桌面完成。它是一個以 17 種語言提供的單檔 persona,已用 OpenWorker 自帶載入器驗證可安裝。
  • 說明 §32 “Run it from OpenWorker”(全部 17 種語言)對其進行說明,以便應用內的向文件提問助手與浮動說明按鈕回答相關問題。到達頁頁尾、README、wiki 以及 docs/integrations/openworker.md 也均已連結。

說明

  • 僅文件 + 網站 —— 無應用程式碼改動。掃描來源維持 82 不變。測試套件:2742。

[1.214.2] — 2026-08-25

修復 — 共享的 HTML→文字 管線(Greenhouse / Recruitee 描述)在屬性值含 > 字元時不再把標籤屬性洩漏進描述。

修復

  • 屬性內含 > 的 HTML 描述現在被乾淨地剝離。 Greenhouse、Recruitee 描述背後的標籤剝離器用的是在第一個 > 就停下的樸素比對,於是像 <a title="salary > 100k"> 這樣的標籤會把尾部屬性文字(100k">)留在描述裡。現在它尊重帶引號的屬性值(單引號或雙引號),在解碼實體之前剝離標記(這樣屬性內被編碼的引號不會成為假分隔符),並且仍在純文字中保留字面的 <>。

說明

  • 測試套件:2742。掃描來源維持 82 不變。本輪父專案沒有其他新增:其餘更改以唯讀方式中繼(follow-up / weekly-digest / rejection-latency / salary-gap 修復無需 web-ui 改動)或不屬於 web-ui 範疇(一個外部 X/Xquik 自帶金鑰外掛、CLI/verify 修復)。可選項 —— SmartRecruiters 詳情描述、detect-reposts 聚合器跳過、stem: / word: 標題前綴、DNS 重繫結加固 —— 仍在排隊。

[1.214.1] — 2026-08-25

修復 — Ashby 的職缺描述現在與 Greenhouse、Recruitee 截斷到相同長度,因此內容篩選在三個板塊上表現一致,且單一板塊無法撐大掃描快取。

修復

  • Ashby 描述現在遵守與其他板塊相同的長度上限。 v1.214.0 把 Ashby 的職缺內文送入內容篩選,但與截斷到 4000 字元的 Greenhouse、Recruitee 不同,Ashby 的內文是無上限直接映射(有些長 4 倍)。於是同一個 content_filter 關鍵字僅因它落在文字的位置不同,就可能在 Ashby 比對到而在 Greenhouse 比對不到,而單一板塊可能給 #/scan 載入的掃描快取增加數百 KB。Ashby 現在也截斷到相同的 4000 字元(其文字已是純文字,因此只截斷,絕不剝離標籤)。

說明

  • 測試套件:2738。掃描來源維持 82 不變。

[1.214.0] — 2026-08-25

修復 — Greenhouse、Ashby 與 Recruitee 的職缺描述現在會進入內容篩選,因此關鍵字 / 國家 / 簽證篩選能真正比對這些板塊;並且「Associate <資深職稱>」(含學術職級)不再被誤判為初階。

修復

  • 內容篩選現在在 Greenhouse、Ashby 與 Recruitee 上生效。 這些板塊現在把每則職缺的完整內文作為其描述,因此讀取描述的 content_filter(或國家 / 簽證關鍵字篩選)會比對職缺內文,而非讓板塊盲目通過。Ashby 與 Recruitee 在清單負載裡免費帶上內文;Greenhouse 早已如此。三者都經由同一個共享的 HTML→文字 管線處理。
  • 「Associate Director」/「Associate Professor」是資深,不是初階。 資歷分類器(供 skip_tiers: 使用)即便 「associate」 修飾的是資深職稱,也把它當作初階標記。現在它把 「Associate <資深名詞>」——含學術職級(Associate Professor、Associate Dean)——讀作資深,同時把真正的初階變體(Associate Attorney、Associate Editor)保留為初階,並且仍把開頭的 「Intern, Associate Dean」 歸為實習。

說明

  • 測試套件:2736。掃描來源維持 82 不變。排隊待辦:選用的 SmartRecruiters 描述增強(逐則職缺的詳情請求)、detect-reposts 的 aggregator: true 板塊跳過,以及錨定關鍵字的 stem: / word: 標題前綴 —— 各為選用設定項,而非預設行為變更。DNS 重繫結加固仍排隊等待其專門的安全版本。

[1.213.0] — 2026-08-22

新增 — 將新加坡國家招聘銀行 MyCareersFuture 作為掃描來源。修復 — Greenhouse 職缺現在攜帶完整內文以便內容篩選生效,遠端 Ashby 職缺不再被僅城市的地點隱藏。

新增

  • MyCareersFuture(新加坡)(mycareersfuture.gov.sg)— 面向新加坡國家公共招聘銀行(由 Workforce Singapore 營運)的新零 token 掃描來源。在 #/scan 的來源篩選中選取,或新增 provider: mycareersfuture 的公司並附選用的 keywords 清單(與 Job Bank 一樣,省略時回退到你個人檔案的目標角色)。讀取公開搜尋 API,釘選主機,不需金鑰。

修復

  • Greenhouse 職缺現在可依內容篩選。 抓取 Greenhouse 板塊時會帶上職缺完整內文,解碼為純文字作為職缺描述 —— 因此讀取描述的 content_filter(或國家/簽證關鍵字篩選)現在會真正比對 Greenhouse 職缺,而非讓它們盲目通過。
  • 遠端 Ashby 職缺不再被城市篩選丟棄。 Ashby 把工作模式(Remote/Hybrid/Onsite)與辦公城市分開保存,所以一個完全遠端的職缺仍讀作例如「San Francisco」—— 而封鎖該城市的地點篩選會隱藏你本可勝任的職缺。現在職缺為遠端時會在地點後附加「Remote」,並且 workplaceType 優先於過時的 isRemote 旗標,以免把辦公室錨定的混合職缺錯誤標記。

說明

  • 掃描來源:82 個(英文 77 + 俄文 5)。測試套件:2724。一項 DNS 重繫結加固(連線前校驗主機解析出的位址)已排入專門版本 —— 它需要 web-ui 專屬設計,而非直接移植。

[1.212.1] — 2026-08-21

修復 — cvstart.org 到達頁少算了掃描器的職缺來源(顯示 80 並遺漏 Job Bank(加拿大));現在重新與應用的 81 一致,若兩者不符,網站建置會大聲失敗。

修復

  • 到達頁的「職缺來源」計數重新與應用同步。 在 v1.212.0 之後,cvstart.org 顯示 80 個職缺板並缺少新的 Job Bank(加拿大) 晶片,而應用、掃描下拉選單與說明指南都列出 81。到達頁透過載入即時掃描器登錄來建立清單,而某個來源因其引入 YAML 相依的方式在該建置中載入失敗,於是被悄悄丟棄。現在 Job Bank 以延遲載入方式引入該相依,與應用其餘部分在掃描時的做法一致,所以它一定會出現。
  • 網站建置現在拒絕發佈不相符的來源計數。 若登錄列舉出的來源少於磁碟上實際存在的數量(來源載入失敗的特徵),建置會以清楚的訊息失敗,而不是悄悄發佈錯誤的數字。

說明

  • 應用行為不變——掃描器始終擁有全部 81 個來源,只有到達頁受影響。掃描來源:81 個(英文 76 + 俄文 5)—— 不變。測試套件:2687。

[1.212.0] — 2026-08-21

新增 — Job Bank(加拿大)聯邦國家職缺板。移除 — EchoJobs(其來源已被反爬攔截)。修復 — 基於 Consider 的職缺板重新回傳結果,多地點 Lever 職缺不再隱藏一半地點。

新增

  • Job Bank(加拿大)(jobbank.gc.ca)— 面向加拿大聯邦國家就業服務的新零 token 掃描來源,這是任何聚合器都覆蓋不好的高流量職缺板。在 #/scan 的來源篩選中選取,或新增 provider: jobbankca 的公司並附選用的 keywords 清單(省略時回退到你個人檔案的目標角色)。讀取公開 ATOM 來源,釘選主機,不需金鑰。

移除

  • EchoJobs — 已退役。其公開來源現已位於反爬保護之後,不回傳任何內容,保留它只會浪費一個掃描槽。

修復

  • 基於 Consider 的職缺板重新回傳結果。 Consider 現在要求在接受搜尋請求前進行匿名交握(一次 GET,用於種下工作階段 cookie + CSRF token);沒有它,請求會被悄然拒絕,職缺板看起來是空的。
  • 多地點 Lever 職缺不再隱藏一半地點。 Lever 把一個主要城市放在 location,其餘放在 allLocations;只讀主要城市會讓一個同時在巴塞隆納與蒙特維多開放的職缺看起來只在巴塞隆納(並被地點篩選錯誤丟棄)。現在兩者合併。

說明

  • 分頁職缺板的翻頁間隔更溫和(由 150 ms 提高到 250 ms),以禮待單主機招聘網站。掃描來源:81 個(英文 76 + 俄文 5)—— 不變(Job Bank 加入、EchoJobs 移除)。測試套件:2685。

[1.211.0] — 2026-08-19

新增 — 台灣科技職缺板 Yourator。修復 — 標題/公司名中的重音實體現在處處都能解碼,名稱含重音的公司不再被誤標。

新增

  • Yourator(yourator.co)— 面向台灣科技與數位職缺市場的新零 token 掃描來源。在 #/scan 的來源篩選中選取,或新增 provider: yourator 的公司。它讀取公開 JSON API(不需金鑰、不需瀏覽器),走訪職缺板每一頁,並輸出每筆職缺的真實雇主連結(其自有 ATS),去除追蹤參數。

修復

  • 重音命名實體現在處處都能解碼。 共享 HTML 解碼器新增了 Latin-1 字母(&eacute; → é、&ccedil; → ç、……),因此書寫 D&eacute;veloppeur 或 Fran&ccedil;ais 的歐洲職缺板不再把該字面量留在標題、追蹤器或產生的文件中。(大寫保持大寫——&Eacute; 是 É 而非 é——像 &constructor; 這樣的查找現在解析為其自身。)
  • 名稱含重音的公司不再因位於自有網域而被誤標。 「Işık」現在摺疊為「isik」並比對 isik.com.tr;「Société Générale」比對 societegenerale.com。舊檢查會刪除重音字母,而非將其摺疊為 ASCII 基字母。

說明

  • 掃描來源:81 個(英文 76 + 俄文 5)。測試套件:2667。

[1.210.1] — 2026-08-19

修復 — 含「&」或引號的 Habr Career 職缺標題與公司名不再亂碼。

修復

  • Habr Career 來源現在會在標題與公司名繼續往下流轉之前解碼 HTML 實體。伺服器渲染的卡片以轉義形式抵達(“Changellenge >>”、“Demand Forecasting & Inventory Optimization”、“ООО "М-ТЕХ"”),因此未解碼的「&」會悄然通不過你自己的「&」標題篩選(正是上個版本在另外五個板塊上關閉的症狀),公司名也會以亂碼形式抵達追蹤器與報告。實體解碼現已在全部六個受影響來源上完成。

說明

  • 測試套件:2644。

[1.210.0] — 2026-08-19

新增 — Senjob,掃描器的首個非洲職位板(塞內加爾);另有五個職位板的標題比對更精準。

新增

  • Senjob(senjob.com)— 面向塞內加爾的新零 token 掃描來源,也是掃描器的首個非洲職位板。在 #/scan 的來源篩選中選取,或新增 provider: senjob 的公司。它以純 HTTP 讀取公開清單(不需金鑰、不需瀏覽器),將每個請求釘選到 senjob.com,並且——因為解析 HTML——把突然解析為空的清單視為損壞的職位板(顯式錯誤),而非一個沒有職缺的國家。

修復

  • 含「&」的標題不再於五個職位板上遺失職缺 — 在 beesite、Cornerstone(csod)、Hacker News「Who is hiring」、Phenom 與 TKMS 上,標題以 HTML 轉義形式抵達,因此像 “R&D Engineer” 這類職位中被轉義的「&」無法通過你自己的 “r&d” 關鍵字,職缺便悄然消失(“sales & marketing” 的否定篩選也從未觸發)。現在標題——以及 Phenom 的地點——會在篩選前解碼。

說明

  • 掃描來源:80 個(英文 75 + 俄文 5)。測試套件:2643。

[1.209.0] — 2026-08-17

新增 —— 應用內說明現在講解如何記錄應徵結果,「問問文件」也能帶你過去。

新增

  • 追蹤器說明(§11)在全部 17 種語言中新增了「記錄結果」小節,講解 結果 按鈕:選擇發生了什麼(被拒 / offer / 錄取 / 拒絕 / 已讀不回 / 進入面試),預覽它將做什麼,然後記錄 —— 這會記下結果、歸檔你送出的履歷和求職信,並替你同步該列的狀態。浮動的「問問文件」助手會讀取該指南,所以現在會把你引到那個按鈕,而不再只是建議你手動編輯狀態。

說明

  • 每個說明套件現在為 31 H2 / 119 H3(原為 118);已同步提升一致性守衛。僅文件 —— 無程式碼或行為變更。測試套件:2625。

[1.208.2] — 2026-08-16

修復 — 手機上通知與主題按鈕不再蓋在搜尋框上。

修復

  • v1.208.1 已讓頂列按鈕不再蓋住頁面標題,但在不算最窄的窄螢幕上——尤其是按鈕文字較長的語言——整條頂列仍會擠在一列,導致 🔔 與 🌙 按鈕蓋到搜尋框上。現在動作按鈕(通知、主題、診斷、開啟 Scan)在手機上一律換到自己整寬的第二列,搜尋框完整可見,互不重疊。

說明

  • 手機上頂列動作按鈕移到整寬的第二列,消除了脆弱的「幾乎佔滿一列」區段——先前版面會把剩餘的負空間當成重疊來分配。Playwright 守衛現在重現了確切的觸發條件——長文字語言 × 565–640px 區段——並斷言頂列控制項絕不共用像素。測試套件:2621。

[1.208.1] — 2026-08-16

修復 — 手機上頂列按鈕不再蓋住頁面。

修復

  • v1.208.0 在窄螢幕上把頂列動作按鈕(診斷、開啟 Scan、通知、主題)換到第二列,但頂列高度固定,導致換行的一列溢出並壓在頁面標題上。現在頂列會隨內容長高,內容排在其下方。

說明

  • 頂列固定的 height 改為 min-height,因此在任意寬度都會隨內容長高(桌面不變)。Playwright 守衛現在也檢查頂列不會溢出到頁面上。測試套件:2621。

[1.208.0] — 2026-08-16

修復 — 應用程式現在能適配手機螢幕:不再左右橫向捲動。

修復

  • 在窄螢幕上整個應用程式會向右溢出——頂列、表格、說明文章和設定分頁都超出右邊緣。現在每個頁面都能適配任意寬度:頂列按鈕換到第二列,寬表格和程式碼區塊在各自的框內捲動,說明把目錄疊在文章上方,按鈕/分頁列換行,長路徑或 URL 會換行而不是撐大頁面。

說明

  • 根因是經典的 flex/grid min-width: auto 陷阱,加上幾個未包裹的寬元素;透過給網格項加 min-width: 0、給 markdown/標題加 overflow-wrap、可捲動的 markdown 表格,以及在行動斷點處讓說明網格縱向堆疊來修復。Playwright 守衛在主要路由上驗證 375px 時 0 橫向溢出。tests/playwright-smoke.mjs。測試套件:2621。

[1.207.2] — 2026-08-16

修復 — AI 計畫和職涯方向輪廓不再顯示為原始程式碼傾印。

修復

  • 有些模型會把整個回答包在 markdown … 程式碼圍欄裡。發生時,發展計畫和方向輪廓會顯示為等寬程式碼區塊,而不是帶標題和清單的文件。現在會移除這層外圍欄——僅當它包住整個回答且語言明確為 markdown/md 時,因此真正的 python/js/無語言 ``` 程式碼回答保持不變。

說明

  • 在共用的 LLM 清理步驟(cleanLlmMarkdown)裡一次處理,所有 AI 路由都受益,被包住回答內部的程式碼區塊也會保留。tests/llm-output.test.mjs(+3)。測試套件:2621。

[1.207.1] — 2026-08-16

修復 — 在小螢幕手機上著陸頁不再向右溢出。

修復

  • 在窄螢幕手機上,首屏(標題、簡介行和安裝終端)可能被右邊緣裁掉,因為過長的安裝指令和版面欄位不會縮小到螢幕。現在它們能適配任意寬度;安裝指令在其終端框內橫向捲動。

說明

  • 也加固了一項不穩定的端對端冒煙檢查,它可能因資源的瞬時 404 而失敗:現在它像相鄰檢查一樣忽略無害的網路雜訊(favicon/連線/資源載入失敗),同時仍能捕捉真正的腳本錯誤。應用行為未變。測試套件:2618。

[1.207.0] — 2026-08-15

新增 — 直接在追蹤表中記錄一次應徵的結果。

新增

  • 每一列追蹤記錄都新增了結果動作:選擇發生了什麼(被拒、收到 Offer、錄用、拒絕 Offer、無回覆、進入面試),加一則選填備註,預覽結果狀態,然後記錄。記錄會封存已提交的履歷與求職信檔案,並把追蹤表同步到規範狀態——一次確定性的動作,而不是手動編輯追蹤表。

說明

  • 新的 POST /api/outcome 中繼結果 CLI:dryRun:true 是唯讀預覽(比對該列、回報結果狀態、不寫入任何內容),實際呼叫才會記錄。寫入安全:結果類型被限制在已知集合內,且每個文字欄位在呼叫外部命令前都會拒絕控制字元(陣列參數、spawn——無 shell)。tests/outcome-route.test.mjs。測試套件:2618。

[1.206.0] — 2026-08-15

文件 — 應用程式內說明指南現已用全部 17 種語言涵蓋最新的五項功能。

新增

  • 內建說明指南,以及據其作答的「問說明」助手,現在記錄了最近上線的五項功能:設定體檢(設定 — 檢查你的履歷與檔案是否有缺漏和殘留的範例資料)、探索 ATS 招募板(入口 — 自動找到某公司的招募板)、「還在招嗎?」檢查(追蹤 — 職缺是否仍開放)、「重用舊履歷?」提示(履歷工作室 — 當已客製的履歷適配新職缺時提醒)以及技能日誌(分析 — 記錄自評分數)。五個新小節,已翻譯為全部 17 種語言。

說明

  • 說明結構增至 31 個 H2 / 118 個 H3,各語言均有一致性檢查。參考文件已更新:docs/architecture/API.md 記錄了這些功能的五個路由,CLAUDE.md 與 docs/sdd/CONVENTIONS.md 中的路由/版本計數已是最新(36 個路由模組)。測試套件:2610。

[1.205.0] — 2026-08-15

新增 — 記錄練習測驗/評估結果的技能日誌。

新增

  • 新增技能日誌(分析 → 技能日誌),可記錄一次自評——公司、平台、技能、分數 % 及可選備註——附加到 data/assessments.tsv,並依最新在前列出歷史記錄。零 token、確定性;檔案格式由父專案 CLI 掌管。

說明

  • 新增 GET /api/assessments(中繼 assessment-log.mjs 的預設 JSON 清單;柔性失敗 {available:false})+ POST /api/assessments(顯式寫入:欄位作為陣列參數傳給 assessment-log.mjs add)。寫入安全:任何含控制字元的文字欄位都會被拒(TAB 會破壞欄、換行會注入列)→ 寫入前 400;分數/門檻限定 0–100,長度受限。tests/assessments-route.test.mjs。測試套件:2610。

[1.204.0] — 2026-08-15

新增 — 設定中的「設定體檢」面板,標記不完整或殘留範例資料的 CV/資料。

新增

  • 設定 → 設定體檢現在零 token 檢查你的 cv.md 與 config/profile.yml,列出阻斷問題(缺少的檔案/欄位)與警告(殘留的範例/佔位資料、硬編碼指標)——讓你在不完整的設定削弱掃描與客製之前發現它。唯讀,一鍵重跑。

說明

  • 新增唯讀的 GET /api/cv-sync-check,中繼父專案的 cv-sync-check.mjs(輸出人類可讀文字 + 結束碼,無 --json);路由對其穩定的 ERROR: / WARN: 行做輕量解析為 {ok, errors[], warnings[]}——由橫幅而非結束碼判定成功。獨立安裝時以 {available:false} 柔性失敗。tests/cv-sync-check-route.test.mjs。測試套件:2602。

[1.203.0] — 2026-08-15

新增 — CV Studio 中的「重用過往履歷?」提示。

新增

  • 在 CV Studio 開啟一份已儲存的職缺描述時,應用現在會將其與你其他已儲存的職缺比對(確定性的詞語重疊,零 token),並告訴你最相近的一個是否足以重用那份客製履歷、改一改再用,還是重新客製 —— 免得為已投過的職缺從頭再來。

說明

  • 新增唯讀的 GET /api/jds/:name/reuse,中繼父專案的 jd-similarity.mjs(Jaccard 重疊 + 資歷守衛;JSON {decision, score, reason}),對每個過往職缺各呼叫一次(扇出上限 25,取最佳匹配);指令碼或過往職缺缺失時以 {available:false} 柔性失敗。tests/jd-similarity-reuse-route.test.mjs。測試套件:2594。

[1.202.0] — 2026-08-15

新增 — 在 #/portals 中發現公司的 ATS 招聘板並開始追蹤。

新增

  • 在 #/portals 輸入公司名,應用會在 Greenhouse、Ashby、Lever 探測其公開招聘板——零 LLM、無瀏覽器——並顯示存在且目前列有 ≥1 個職缺的招聘板。點一下即可把所選招聘板加入掃描器監視的公司。探測為唯讀;僅當點擊新增時才寫入 portals.yml。

說明

  • 新增 server/lib/discover-ats.mjs(固定主機、字元集驗證的 slug 經 DNS 固定的 safeGet 探測,每次請求 ≤12 次)+ POST /api/portals/discover(唯讀)與 POST /api/portals/track(顯式寫入:withFileLock + 文字拼接 + 重新解析保護 + 原子重新命名;僅已知 ATS 主機、冪等)。複用掃描器的配接器註冊表確認招聘板並統計職缺。i18n ×17。tests/discover-ats-resolver.test.mjs + tests/discover-ats-route.test.mjs。測試套件:2588。

[1.201.0] — 2026-08-15

修復 — 使用在地化或變體欄標題的追蹤器不再顯示為空白。

修復

  • 如果你的 data/applications.md 使用非英文或變體標題——西班牙文 empresa / puesto / estado / fecha / enlace,或 position / stage / link——追蹤器會用錯誤的鍵讀取,導致 公司 / 職位 / 狀態 / 日期 / 連結欄為空。現在這些標題會摺疊到規範欄位名,追蹤器正常顯示。全英文追蹤器的解析與先前完全一致。

說明

  • 在 parseApplications(server/lib/parsers.mjs)中新增 HEADER_ALIASES 對應與正規化摺疊;未知或已規範的標題原樣通過。tests/tracker-header-aliases.test.mjs。測試套件:2563。

[1.200.0] — 2026-08-15

新增 — 在追蹤器中一鍵查看 ATS 職缺「是否仍在招」。

新增

  • 在 #/tracker 中,URL 為 Greenhouse / Lever / Ashby / Workday / SmartRecruiters 職缺的應徵現在會顯示 「是否仍在招?」 按鈕。點一下即查詢該 ATS 自己的公開 JSON——零 token、無瀏覽器——並顯示 在招 / 已下架 / 未知,讓你無需逐一開啟就能發現失效職缺。保守設計:僅明確的 404/410 判為已下架,含糊情況保持未知(絕不誤報已下架)。

說明

  • 新增 server/lib/liveness-core.mjs + liveness-api.mjs,以及唯讀的 GET /api/liveness?url=(不寫入、無 LLM)。防 SSRF:URL 先經 isValidJobUrl 驗證,ATS API 僅透過 DNS 固定的 safeGet 以固定主機 + 字元集驗證的路徑段存取。tests/liveness-core.test.mjs + tests/liveness-route.test.mjs。測試套件:2557。

[1.199.0] — 2026-08-15

修復 — 過寬的表格現在可橫向捲動,而不再被截斷。

修復

  • 在 Scan 頁面(以及其他所有表格 — 追蹤器、統計、用量、儀表板)中,比視窗更寬的表格會被截斷且沒有捲軸,導致最右側的欄位無法存取。現在過寬的表格會依需要顯示橫向捲軸,因此在任何寬度下每一欄都可存取。

說明

  • public/css/components.css 中的 .table-wrap 由 overflow: hidden 改為 overflow-x: auto(與現有的 .reports-scroll 容器一致);圓角邊框保持不變。tests/table-wrap-scroll.test.mjs。測試套件:2540。

[1.198.0] — 2026-08-15

新增 —— 掃描重試現在採用指數退避、抖動,並尊重限流方的 Retry-After。

新增

  • 當徵才看板在掃描途中短暫限流或出錯(HTTP 429 / 5xx)時,重試現在以指數退避 + 抖動等待,而非固定的短延遲 —— 這樣繁忙的看板不會被以相同節奏反覆敲打,並行重試也不會同步再次衝撞。看板送來的 Retry-After 會被尊重(但有上限,使惡意的 Retry-After: 86400 無法拖住整次掃描)。永久性錯誤(404、被拒絕的重新導向)仍然立即失敗 —— 無變化。

說明

  • 在 server/lib/http-json.mjs 中新增 parseRetryAfterMs() 與純函式 computeRetryDelayMs();fetchJson 現在在非 ok 回應上擷取 .retryAfter,fetchJsonWithRetry 接受可選的 maxDelayMs(預設 8000)。tests/http-json.test.mjs (+9)。測試套件:2536。

[1.197.0] — 2026-08-14

新增 — 僅憑 careers_url 即可追蹤 Getro 的 VC 徵才看板,集合 id 會自動解析。

新增

  • 被追蹤的 Getro 看板(b2venture、Earlybird、Point Nine …)不再需要手動查找的數字 getro_collection。只需提供看板自己的 careers_url,首次掃描時 id 便會從該頁面自動解析 —— 一次防 SSRF 的安全 GET 直接從頁面內嵌資料中讀取數字 network.id。明確的 getro_collection 仍然優先,並會完全跳過該抓取。

說明

  • 在 server/lib/sources/getro.mjs 中新增 httpsCareersUrl()、extractCollectionId() 與非同步的 resolveCollectionId();看板頁面透過 DNS 固定、大小受限的 safeGet 抓取,解析出的 id 仍由 assertGetroUrl 將主機鎖定為 api.getro.com。轉接器現在即使沒有 id,也能比對帶有 https careers_url 的 provider: getro 項目。tests/sources-getro.test.mjs (+8)。測試套件:2527。

[1.196.0] — 2026-08-14

修復 (安全) — Workday 轉接器以主機名而非子字串驗證 api 端點。

修復

  • portals.yml 中的 Workday api: 值現在只有在其主機名為 myworkdayjobs.com(或 .myworkdayjobs.com 子網域)時才被接受。舊檢查是子字串比對,因此任何僅包含該字串的 URL——例如 https://example.com/?x=myworkdayjobs.com——都會通過並被當作端點使用。真實的 Workday 端點不受影響。(由 CodeQL 回報,#443。)

說明

  • 新增 isWorkdayApi() 解析 URL 並檢查主機(server/lib/portals/adapters/workday.mjs)。tests/workday-adapter-endpoint.test.mjs(+1)。測試套件:2522。

[1.195.0] — 2026-08-14

效能 (掃描器) — 在大掃描歷史上重複職位偵測依然很快。

效能

  • 在大的 scan-history.tsv 上,重複職位偵測不再退化為 O(N²)。此前按公司的標題分組是一個巢狀迴圈,對每一對都執行完整的 roleFuzzyMatch;現在改為倒排索引——一次遍歷按精確標題分桶,然後僅對共享判別性(非基線)詞元的不同桶做模糊比對。輸出完全相同——相同的重複職位叢集——透過對舊演算法在 200+ 隨機歷史上的差分測試證明。

說明

  • server/lib/detect-reposts.mjs 中的 groupRowsByTitle(為差分測試匯出)。tests/detect-reposts-grouping.test.mjs(+2)。測試套件:2521。

[1.194.0] — 2026-08-14

修復 (掃描器) — 單段 URL 的 Workday 招聘頁面現在能正確掃描。

修復

  • Workday 轉接器現在能解析路徑為單一段的招聘 URL——例如 https://parsons.wd5.myworkdayjobs.com/Search、.../KBR_Careers、.../Careers。此前站點會退回 External,轉接器打到錯誤的 CXS 端點,探測看似正常卻什麼都不回傳。現在取路徑第一個非空段作為站點(丟棄 en-US 之類的語言前綴);文件中的 /en-US/External 情形不變。(見 #255。)

說明

  • server/lib/portals/adapters/workday.mjs 中的結構化路徑解析。tests/workday-adapter-endpoint.test.mjs(+5)。測試套件:2519。

[1.193.0] — 2026-08-14

新增 (統計) — 一個顯示值得提醒的面試的「面試後無回應」分頁。

新增

  • #/stats 中的面試後無回應分頁:超過禮貌等待期(預設 30 天)後陷入沉默的面試,彙整你進行中的面試與追蹤表——附上每個已沉默多久、最近一次面試日期和原因。一份溫和的提醒/收尾清單;僅為建議,絕不斷言被拒。零 token。

說明

  • 新增 GET /api/stats/rejection-latency 中繼(指令稿缺失時 {available:false})。tests/stats-rejection-latency-route.test.mjs(+2)。i18n 鍵 +10 ×17;#/stats help-hint 由 7→8 個分頁。測試套件:2510。

[1.192.0] — 2026-08-14

新增 (cv-studio) — 一個抓出你從未有過的數字的「核對履歷事實」關卡。

新增

  • #/cv-studio 中的核對你的履歷事實卡片:貼上適配的履歷或求職信,將其中每個斷言的指標和事實與你真實的履歷、檔案和 two-pager 核對。你會得到 pass / warn / block 判定,以及確切的編造指標、無據事實和禁用/警示片語——讓產生的履歷無法悄悄宣稱一個不屬於你的數字。零 LLM;不寫入任何內容。

說明

  • 新增 POST /api/cv-studio/verify-facts 中繼:將文字寫入一次性暫存檔並執行 verify-cv-facts.mjs,即便指令稿在 block 時以 1 退出也信任 JSON 判定。tests/cv-studio-verify-facts-route.test.mjs(+4)。i18n 鍵 +15 ×17。測試套件:2508。

[1.191.0] — 2026-08-14

新增 (統計) — 「接下來學什麼」分頁,為優先學習的技能排序。

新增

  • #/stats 中的接下來學什麼分頁:跨整個追蹤表的技能缺口彙整——最常拖垮低匹配的缺失技能,按加權(每份評估報告的 5−匹配分)與 Critical / High / Medium 層級排序——外加你 CV/檔案已涵蓋的技能。唯讀、僅建議、零 token。

說明

  • 新增 GET /api/stats/upskill 中繼(資料不足時帶 { error } 欄位;指令稿缺失時 {available:false})。tests/stats-upskill-route.test.mjs(+3)。i18n 鍵 +15 ×17。測試套件:2504。

[1.190.0] — 2026-08-14

新增 (追蹤表) — 「公司歷史」面板,告訴你哪些公司真的會回應你。

新增

  • #/tracker 上的公司歷史卡片:選擇一家公司即可取得唯讀證據——它對你的回應程度(對你無回應 / 混合 / 此前有回應),以及同一職位是否反覆重新發布——彙整你的追蹤表、跟進與掃描歷史。零 token;不呼叫掃描器。

說明

  • 新增 GET /api/stats/company-history[?company=] 中繼(指令稿缺失時 {available:false})。tests/stats-company-history-route.test.mjs(+3)。i18n 鍵 +18 ×17。測試套件:2501。

[1.189.0] — 2026-08-14

修復 (掃描器) — 用羅馬數字表示的資歷等級現在在非拉丁標題中也能辨識。

修復

  • skip_tiers 背後的層級分類器現在能在任何文字系統中讀取職位詞後的羅馬數字等級後綴(I / II / III / IV / V)——「Инженер III」、「エンジニア I」、「Ingénieur IV」——不僅限於 ASCII 詞之後。此前,非拉丁詞後的等級數字會被忽略,職位落入 mid,因此 skip_tiers: [senior] 或 [entry] 會漏掉這些職位。

說明

  • server/lib/classify-tier.mjs 中與文字系統無關的後行斷言;移除了一個無效的重複 Sr. 匹配器。tests/classify-tier.test.mjs(+1)。測試套件:2498。

[1.188.0] — 2026-08-14

修復 (UI) — 主要操作按鈕不再緊貼頁面副標題。

修復

  • 每週面試摘要、已募資公司、入口、職涯規劃 和 職涯定位 頁面的主要操作/控制列現在有了適當的上邊距,按鈕在副標題下方留有空間,而非緊貼其上。

說明

  • 回歸護欄 tests/lead-row-top-margin.test.mjs(+5)。測試套件:2497。

[1.187.0] — 2026-08-14

修復(掃描器)——skip_tiers 設定重新生效:你要求按資歷跳過的職位會被剔除。

修復

  • portals.yml 中的 skip_tiers: 清單(如 skip_tiers: [intern, entry])現在會被掃描採納。每個職位標題被分類為資歷層級(intern / entry / mid / senior),若其層級在清單中則被剔除。此前掃描執行標題 / 地點 / 內容 / 可信度過濾,但沒有層級過濾,因此 skip_tiers 被默默忽略。沒有明確級別詞的標題歸入 mid(所以 skip_tiers: [mid] 也會剔除大多數普通職位),分類器讀取最左側的級別詞。

說明

  • 新增純模組 server/lib/classify-tier.mjs(classifyTier + buildTierFilter),接入 EN 與 RU 掃描器的過濾鏈。tests/classify-tier.test.mjs(+7)。套件:2492。

[1.186.0] — 2026-08-14

新增(CV 工作室)——「技能差距」面板:職位要求的技能中,你的履歷已列出、隱含或缺失的部分。

新增

  • CV 工作室中新增的技能差距面板。選擇一份已儲存的職位描述,它會把每項要求技能分類為履歷已列出、履歷中隱含或缺失——無 LLM 的詞語比較,不寫入任何內容。若職位沒有明確的要求部分,會出現低信心提示。

說明

  • 新增 GET /api/jds/:name/skill-gap(職位名先經路徑清洗並確認位於 jds/ 下,才作為參數;無腳本時軟回退為 {available:false})。i18n 鍵 +13 ×17。測試:tests/jds-skill-gap-route.test.mjs(+4,含路徑穿越拒絕)。套件:2485。

[1.185.0] — 2026-08-14

新增(統計)——「漏斗與速度」分頁:你的漏斗與市場相比如何,以及你在各階段間推進的速度。

新增

  • 統計中新增的漏斗與速度分頁,在市場基準區間旁顯示你的回覆率與面試率(保留小樣本與選擇偏差提示)、超過典型首次回覆視窗的進行中申請的等待清單,以及每階段中位天數(申請 → 回覆 → 面試 → Offer)——推進慢的列做右設限,以免拉偏中位數。唯讀、零 token;僅讀取你自己的追蹤器。

說明

  • 新增 GET /api/stats/funnel(無腳本時軟回退為 {available:false})。i18n 鍵 +18 ×17。測試:tests/stats-funnel-route.test.mjs(+2)。套件:2481。

[1.184.0] — 2026-08-14

修復(UI)——儀表板的快捷操作磁貼現在排成整齊的格線。

修復

  • 在儀表板(指揮中心)中,3 個磁貼的一組比 4 個的一組更寬,使各區塊右邊緣參差不齊。現在每組都使用等寬欄(寬螢幕 4 欄,視窗變窄時降為 3 / 2 / 1),因此所有磁貼大小一致、右邊緣對齊。

說明

  • 僅 CSS(.qa-grid:用固定的 repeat(N, minmax(0,1fr)) 取代 auto-fill)。由 tests/dashboard-grid-align.test.mjs 守護(+2)。套件:2479。

[1.183.0] — 2026-08-14

新增(掃描器)——更聰明的去重:帶追蹤連結重新發布的同一職位不再出現兩次。

新增

  • 掃描器現在透過正規化 URL 鍵辨識職位,因此帶追蹤參數(?utm_…、gclid 等)、http 與 https、或帶結尾斜線 / #片段重新發布的同一職位會被當作同一個——掃描結果與管道中不再有重複列,也不會對已看過的職位浪費一次評估。真正不同的職位(保留的功能性 id 如 gh_jid)仍分別計數。

說明

  • 新增 server/lib/url-key.mjs,接入兩個掃描器的去重與管道寫入。刻意欠正規化——絕不合併兩個不同職位。測試:tests/url-key.test.mjs(+5)、tests/parsers.test.mjs(+1)。套件:2477(+6)。

[1.182.0] — 2026-08-14

修復(掃描器)——薪資區間現在在所有語言下顯示一致。

修復

  • 掃描與追蹤器列中的薪資數字改用地區中立符號 ≥ 與 ≤(如 ≥ 120000 EUR、≤ 90000),取代未翻譯洩漏到非英文介面的英文詞 “from” / “up to”。適用於所有回報單邊區間的看板(Getro、Remotli、Manfred、Agentic Jobs、JustJoin、Jobicy);雙邊區間(100000–150000 USD)本就中立。

說明

  • 僅顯示——用戶端薪資過濾器無視前綴解析數字,過濾不變。套件:2471。

[1.181.0] — 2026-08-14

新增(掃描器)——Getro 招聘板現在顯示薪資、全部地點和遠端職位。

新增

  • Getro 掃描器(基金人才網絡招聘板)現在為每個職位顯示薪資(年薪範圍 + 幣別),列出全部地點而非僅第一個,並標記遠端職位。掃描與追蹤器中的 Getro 職位現在擁有與其他招聘板相同的薪資 + 地點資訊。

說明

  • 僅掃描器;無新依賴,無路由 / CSP / SSRF 變更。測試:tests/sources-getro.test.mjs(+5)。套件:2470(+5)。

[1.180.0] — 2026-08-14

修復(中,報告)——#/reports 清單現在是表格,並復原了被 Machine Summary 佔位符隱藏的真實評分。

修復

  • #/reports 清單是一個表格(報告 · 日期 · 可信度 · 評分),而不是 4 卡片格線。 過長的「未偵測到評分」標籤會把標題欄擠到接近零,卡片標題的 overflow-wrap: anywhere 隨即把報告名逐字元換行。現在每個欄位都有自己的欄,名稱儲存格依詞換行,窄螢幕時表格橫向捲動(新增 .reports-scroll 容器)。新增 i18n 鍵 rep.colReport ×17。
  • 內文中的真實評分(**Итоговый балл:** 1.8 / 5)不再被 Machine Summary 佔位符(score: —)隱藏。 當 ## Machine Summary 區塊帶有非數字或超出範圍的評分時,它會佔據已解析評分的位置並阻斷粗體值形式回退,於是即使內文有真實的 X / 5,報告仍顯示「未偵測到評分」。現在只要沒有可用數字殘留,parseReportHeader 就會復原內文的值形式(步驟 4.5)。

說明

  • 僅用戶端 + 解析器;無路由 / CSP / SSRF / 父寫入變更。測試:tests/reports-table.test.mjs(+5)、tests/report-header-locale.test.mjs(+2)。套件:2465(+7)。

[1.179.0] — 2026-08-13

變更 (LOW, 掃描器) — 將 20 個重複的 HTML 實體解碼器合併到共用模組(對齊後續,關閉 worklist)。

變更

  • 20 個抓取型掃描來源各自帶有 decodeEntities/decodeXmlEntities(+ fromCodePoint 輔助)——這些副本已漂移(其中三個可能擲出 RangeError,已在 v1.172.0 修復;其餘允許 NUL/C0 或錯誤解析 &#1a2;)。現在全部經由單一的 server/lib/html-entities.mjs(符合 XML 1.0 Char 的安全解碼器),移除約 237 行重複。8 個 RSS 型來源新增 &nbsp; 解碼(此前只處理 5 個實體);cryptocurrencyjobs 刻意的雙重解碼透過別名保留。hh 保留自有解碼器(處理 &mdash;/&ndash;,在共用 6 個之外)。新的守護測試在任何來源重建本地解碼器時失敗。

說明

  • 保持行為的重構;無路由 / CSP / SSRF / 父寫入變更。測試:tests/decoder-consolidation.test.mjs(+2)。套件:2458(+2)。

[1.178.0] — 2026-08-13

修復 (LOW, 父專案對齊) — 將兩個過時常數更新為與父專案一致(PARENT-SYNC GAP #4 + #5)。

修復

  • 瀏覽器 User-Agent(GAP #4) — BROWSER_LIKE_USER_AGENT(workable/workday/oraclecloud/a16z/eightfold 用於通過 WAF/機器人門)從 Chrome 131 升到 151,與父專案 user-agent.mjs 一致;過時版本更易被攔截。由 Chrome major ≥ 151 測試守護。
  • 追蹤器狀態 FALLBACK(GAP #5) — states.mjs 的最後備用 FALLBACK(僅當即時 templates/states.yml 不可讀時使用 — 全新克隆 / CI 隔離根)新增父專案的土耳其語狀態別名(#2615):değerlendirildi、başvuruldu、yanıt verildi、mülakat、teklif、reddedildi、iptal edildi、uygun değil、kabul edildi/işe alındı。生產中即時檔案已提供這些。

說明

  • 僅兩個常數;無路由 / CSP / SSRF / 父寫入變更。測試:tests/http-json.test.mjs(+1) + tests/states.test.mjs(+1)。套件:2456(+2)。

[1.177.0] — 2026-08-13

修復 (MEDIUM, 掃描器) — 對用工作階段 Cookie 保護搜尋 API 的租戶,csod(Cornerstone)回傳 0 個職缺(parent #2769,PARENT-SYNC GAP #1)。

修復

  • 部分 Cornerstone 租戶在啟動招聘網站首頁設定工作階段 Cookie,若這些 Cookie 不隨匿名 bearer 權杖一起回傳,搜尋 API 就回傳 401 CSOD Unauthorized。sources/csod.mjs 現在用新的 fetchResponse 輔助函式讀取啟動頁,從其 Set-Cookie 值建立 Cookie 標頭(cookieHeaderFrom — 僅 name=value,jar 語意)並在搜尋 POST 上重放。僅同源(主機固定 + redirect:'error'),工作階段 Cookie 絕不會到達第三方;不設定 Cookie 的租戶與之前完全相同。

說明

  • 新增 server/lib/http-json.mjs::fetchResponse(純新增;不影響現有來源)。無路由 / CSP / SSRF / 父寫入變更。測試:tests/sources-parity-v1118a.test.mjs(+1)。套件:2454(+1)。

[1.176.0] — 2026-08-13

修復 (MEDIUM, 報告) — RU 表未列出的粗體標籤下的評分仍顯示 “Score not detected”(FIND-5)。

修復

  • 兩份 RU 報告把評分寫成 **Итоговый балл:** 1.8 / 5 / **Скор:** 1.8 / 5 — 這是 REPORT_LABELS.ru 未列舉的粗體標籤(只識別 “Оценка”/“Балл”),故評分未被解析。parseReportHeader 不再擴充同義詞清單,而是回退到值的形態:任意粗體標籤下按 /5 量表的分數。它與語言無關,對標題免疫(無 **、無 /5 值),並拒絕像 5/5/2026 這樣的日期(對分母做否定前瞻)。

說明

  • 僅伺服器解析器;無路由 / CSP / SSRF / 父寫入變更。測試:tests/report-header-locale.test.mjs(+2)。套件:2453(+2)。

[1.175.0] — 2026-08-13

修復 (LOW, 加固) — 為 FIND-3 SEO 描述加回歸護欄 + 空值安全的合法性剝離(AI 評審跟進)。

修復

  • SEO 描述一致性護欄 — v1.174.0 將各語言 meta.desc 中硬編碼的 “~55” 換成註冊表衍生的 {adapters} 佔位符,但沒有測試,下次編輯某個語言時可能悄悄回退。新的 CI 隔離測試 tests/site-meta-desc-parity.test.mjs 在 17 個 site/src/i18n/*.json 中任一遺失佔位符、重新硬編碼計數,或 Landing.astro 停止把它插入三個描述中繼標籤時失敗。
  • 空值安全的合法性剝離 — stripEmphasis 對空值輸入回傳 '' 而非字串 “undefined”(欄位以字串初始化,屬縱深防禦)。

說明

  • 測試 + 解析器一行護欄;無路由 / CSP / SSRF / 父寫入變更。測試:tests/site-meta-desc-parity.test.mjs(+3)。套件:2451(+3)。

[1.174.0] — 2026-08-13

修復 (HIGH, 報告) — 本地化報告顯示 “Score not detected”;SEO 描述過時。

修復

  • 評分解析 (FIND-1) — H1 中包含評分標籤詞的非英文報告(# Оценка вакансии: <標題>)不再把該標題誤當作評分。parseReportHeader 現在錨定到本地化的粗體標籤(**Оценка:** 1.5 / 5),跳過標題行,並要求標籤緊鄰冒號 — 因此顯示 “Score not detected” 的 RU 報告會展示真實評分。
  • 合法性徽章 (FIND-2) — 從值中移除 Markdown 強調,徽章顯示 “High Confidence” 而非 “** High Confidence”。
  • 評分溢出 — 帶尾隨狀態文字的評分行(“1.8, Status: Evaluated, …”)被壓縮為僅評分;.score-pill 新增不換行/溢出上限,標題欄可收縮,因此彩色徽章不會溢出卡片邊緣。
  • SEO 描述 (FIND-3) — cvstart.org 的 meta / OG / Twitter 描述(全部 17 種語言)硬編碼 “Scan ~55 job boards”,而內文依真實註冊表計數(“~75”)。描述現在插入註冊表衍生的計數,不會再漂移。

說明

  • 伺服器解析器 + 用戶端渲染/CSS + 網站 i18n;無路由 / CSP / SSRF / 父寫入變更。測試:tests/report-header-locale.test.mjs(+4)。套件:2448(+4)。

career-ops-ui v1.267.1 · page 3/9 · mirror of the project changelog for cvstart.org