diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9b399a4 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,374 @@ +# Arcrun 桌面版(daemon)版本說明 + +> 🔴 **這份就是版本號的單一真相源。**(2026-08-18 從 `docs-site/src/content/docs/help/changelog.md` 搬來,D95 第一輪) +> +> 為什麼搬:`daemon-version.py` 從這份檔案算出版本號, +> 而它原本住在 repo 根的 `docs-site/` 底下——`collector/` 得往上伸手才拿得到自己的版本。 +> 那條臍帶讓 `collector/` **沒有資格被搬成獨立 repo**,也讓「源碼→產出物」多了一道要記得的扭曲。 +> 現在它與 `DAEMON_LINE`、`daemon-version.py` 住在同一棵樹底下,`collector/` 自己就算得出自己的版本。 +> +> 🔴 **雲端(Arcrun 引擎 1.4.x)的版本說明不在這裡**,它留在 +> `docs-site/src/content/docs/help/changelog.md`——兩條版本線本來就是兩個產品, +> 混在一個檔裡是 D95 要拆掉的那種扭曲。 + +## 怎麼出新版(不要手打版本號) + +在**本檔最上面**(第一個 `v0.18.x` 段落之上)加一個二級標題,標題文字就是 +`下一版(未發佈)`,底下條列白話更新內容: + + <二級標題> 下一版(未發佈) + + - 🔴 **(用戶看得懂的白話標題)**:(以前會怎樣、現在會怎樣) + +> ⚠️ 上面刻意**不寫成真的標題**(用 `<二級標題>` 代替兩個井字號)。 +> `daemon-version.py` 判斷「有沒有待發佈內容」用的是**純字串比對**, +> 本檔任何位置只要出現那一行的字面值,它就會以為要升版—— +> 連寫在說明或程式碼區塊裡都算。這是機制的已知邊角,別把它變成陷阱。 + +打包時 `daemon-version.py --stamp` 會把它戳成正式版號+今天日期。 +**版本號從頭到尾沒有人打過**,而且「更新內容」與「版本」天生綁在一起。 +沒有這一段 ⇒ `changelog-section.sh --check` 擋下打包。 + +⚠️ 寫給用戶看,不是寫給工程師看:說「以前會怎樣、現在會怎樣」,不要寫函式名或 commit 編號。 + +--- + +## v0.18.29(2026-08-16) + +**建議更新**——這版修好兩件「你以為它在做的事,其實它沒在做」。 + +- 🔑 **「移除資料夾」現在真的會收回**:先前你把一個資料夾從清單移除,它**只是停止繼續看**——已經整理進雲端的知識**一筆都不會消失**,照樣搜得到、AI 照樣拿它回答。現在移除時會問你要不要**連同雲端的知識一起收回**,預設是收回;只想停止監看也可以選。 +- 🔑 **不會再把別人的套件當成你的知識**:如果你掛的是一個軟體專案資料夾,先前它會連 `node_modules` 那類**依賴目錄**裡的說明文件一起讀進去——實測有一次產出的 27 張卡裡,**21 張是第三方套件的 API 文件與授權條款**,你自己的東西只有 5 張。現在那些不會再被收。 +- 🔑 **有沒有用 git 不再影響收哪些檔**:先前「這個資料夾要怎麼收」會看它有沒有版控——所以**只要你在自己的筆記資料夾開一次版控,收進去的東西就會大幅減少,而畫面上不會有任何提示**。現在改成看資料夾**實際裝了什麼**。 +- 這版**不影響你已經同步的資料**,只改今後怎麼收、以及移除時多一個選擇。 + +## v0.18.28(2026-08-16) + +- ⚡ **一指到資料夾,馬上就能問「裡面有什麼」**:小幫手掃完資料夾(幾秒鐘)就會先把「有哪些檔案、最近改了什麼」做成一頁總覽送上知識庫,不用等 AI 逐份整理——就算當天的 AI 額度用完了,這類問題照樣答得出來。內容的深度整理照常在背景進行,一份都不會少;檔案有增刪時總覽會自動更新,不會堆出重複資料。 +- 📚 **整理出來的不再是一檔一張流水卡,而是一份看得懂的 wiki**:每份文件變成「一張總覽卡+幾張概念卡」,放在資料夾裡隱藏的 `.wiki/` 目錄——就算不裝任何軟體,用檔案總管也能點著連結讀。每一層都有自動維護的目錄(00-INDEX):你的每一份檔案都列在上面,沒有可整理內容的(例如發票、清單)也會誠實標「空」,你分得出「沒東西」和「被漏掉」。卡片之間有雙向連結、每張卡都寫明出處原稿;你的原稿一個字都不會被動到。這次也把沒接 Gemini 金鑰的免費路一起補齊,不用自己接金鑰也拿到一樣完整的整理結果。 +- 🔗 **卡片之間的關聯終於連得起來**:上一版換了新的卡片格式後,小幫手其實抓不到卡片彼此的關係,於是每張卡都變成孤零零的一張,知識庫裡搜到一張也連不到相關的其他卡。這次修好,同一份資料裡的概念會正確地互相連結。 +- 📍 **搬動或改名資料夾後,舊卡片不會被誤刪**:以前判斷「這份卡片是不是已經被你刪掉」只認標題不認路徑——不同資料夾裡剛好同名的檔案,其中一份可能被誤判成「已經不在了」而被清掉。現在改成認路徑,不會再誤殺。 +- 🛡️ **小幫手不會再動到你自己用 git 管理的檔案**:如果你監看的資料夾本身是一個 git 專案(例如你自己維護的筆記或 wiki),小幫手現在只讀不改——不會把你自己手寫的卡片搬走改名,也不會去碰你倉庫裡的程式碼,只整理你的文件內容。 + +## v0.18.27(2026-08-13) + +- 🛡️ **監看的是筆記庫「裡面的某個資料夾」時,保護不會再失效**:上一版的筆記庫保護只認「你監看的那一層本身是不是庫」——如果你監看的其實是庫底下的子資料夾(例如 `KB/docs`),保護會誤判成「這不是筆記庫」,整理結果照樣被 Logseq/Obsidian 當成你自己的新頁面收編。現在改成往上找最近的庫根,不管你監看哪一層,保護都認得出來。 +- 🏷️ **機器寫的檔案,一眼就看得出不是你自己的**:就算檔名不撞名,資料夾裡冒出一堆陌生新檔案還是會讓人心裡發毛。現在寫進筆記庫的整理產物一律帶固定前綴,你打開資料夾能馬上分辨哪些是自己寫的、哪些是小幫手整理出來的。 + +## v0.18.26(2026-08-11) + +- 🗂️ **不會再把整理結果寫進你的 Logseq/Obsidian 筆記庫**:以前小幫手不會分辨你選的資料夾是 + 不是筆記庫,整理出來的卡片會被 Logseq/Obsidian 當成「你自己寫的新頁面」顯示出來。 + 現在會先認出這是不是筆記庫,是的話卡片改放到你看不到、筆記軟體也不會掃描的位置; + 一般資料夾的行為不變。 +- 🛡️ **不會再無聲蓋掉你原本就有的檔案**:以前如果整理出來的檔名剛好跟你資料夾裡既有的檔案 + 同名,會直接覆蓋、你的原稿就沒了。現在遇到同名會先把你原本的檔案備份一份, + 新內容才會寫進去,不會再有東西不見了都不知道。 + +## v0.18.25(2026-08-09) + +- 🌱 **第一次打開改成兩步引導**:以前打開小幫手只有兩行字兩顆按鈕,看不懂在幹嘛。 + 現在第一步先用三句話說清楚 Arcrun 是什麼,第二步用兩張卡片問你「已經有知識庫」還是 + 「還沒有」——選「還沒有」會直接告訴你回來要按哪顆鈕連線,不會丟出去就找不到路回來。 + +## v0.18.24(2026-08-08) + +- 🔴 **修好「失敗原因每幾秒就消失」**:以前一個檔案沒送上去,展開看原因會看到 + 「這個檔是在舊版失敗的,當時沒有記下原因」——即使你完全沒有更新過。 + 現在展開失敗檔案,看得到真正的失敗原因。 +- 📊 **首頁改成看總量,不用逐檔猜**:以前首頁只顯示「這一輪」處理了幾份, + 如果你的資料夾有幾千份檔案,會搞不清楚「雲端只有一百多張卡,是壞了還是 + 還在跑」。現在首頁直接告訴你:共幾份、已經送上去幾份、還在排隊幾份、 + 送不上去幾份——四個數字相加就是你資料夾裡的總檔案數。「送不上去」的 + 分類預設收起來,想看細節再展開。 +- 🩺 **「疑難排解」按鈕搬進小幫手本體**:以前雲端網頁上的診斷按鈕看不到你電腦上的 + 真實狀況(總檔案數、失敗分類、小幫手版本)——因為那顆按鈕跑在瀏覽器裡, + 碰不到你電腦的檔案。現在小幫手裡有一顆匯出診斷檔的按鈕,把雲端與本機的 + 狀況合併成一份檔案,還看得出小幫手本身是不是還活著在跑(不會誤以為當機); + 回報問題時附上這份檔案,我們就能一次看懂發生什麼事。 +- 🔒 **診斷檔不會洩漏你電腦上的資料夾路徑**:以前如果同步引擎剛好停住, + 診斷檔裡的錯誤訊息會帶出你電腦完整的資料夾路徑(例如你的使用者名稱、 + 桌面上的資料夾結構)。現在只會保留到資料夾名稱本身,你可以放心把診斷檔 + 分享出來讓我們協助排查,不會連帶交出你電腦的目錄結構。 + +## v0.18.23(2026-08-08) + +- 🔴 **修好「按了檢查更新,卻更新不了」**:以前按下「檢查更新」會跳出 + `解壓失敗:ditto: Couldn't read PKZip signature` 這種看不懂的錯誤, + 然後就卡在那裡——**只能自己去網站手動下載安裝**。 + 原因是我們從 v0.18.5 起改用 `.dmg` 發佈 Mac 版,但更新程式還在用舊的解壓方式, + **這個問題已經存在七個版本沒被發現**。現在按下去會真的更新完成。 +- 🔁 **已經是最新版就不會再叫你重新啟動**:以前手動裝好新版之後, + 設定頁還是會顯示「新版已下載完成,重新啟動就會套用」, + 明明新舊版號一模一樣,卻一直催你重開。 + 現在會實際比對版號,已經是最新版就把提示和殘留的暫存檔一起清掉。 +- 🪟 **Windows 版現在也會自己更新**:以前 Windows 按檢查更新只會幫你開啟下載資料夾, + 要自己把檔案換掉。現在會直接完成更新並重新啟動。 + +## v0.18.22(2026-08-08) + +- 🔴 **修好「一次丟一大批檔案,額度會爆掉」**:以前把幾百份檔案一次丟進資料夾, + 小幫手會逐一觸發雲端整理,短時間內衝爆你雲端帳號的免費額度, + 結果大部分檔案都失敗、只有前面幾份成功。 + 現在改成分批慢慢消化:每一輪只處理一部分,**今天新寫的檔案永遠優先**, + 積壓的舊檔案不會擋住你正在用的東西。 +- 💬 **額度用完時講人話**:偵測到雲端額度用完後,畫面會直接告訴你 + 「今天已經整理幾份、明天早上會自動恢復、不花錢也沒關係」, + 不會再讓你看到一串看不懂的錯誤代碼。 +- 🔁 **中斷後不用從頭來過**:以前同步到一半被強制關閉或電腦重開機, + 已經處理好的檔案也會被視為沒做過,得整批重跑。 + 現在每處理完一份就立刻記住進度,下次啟動只會接著做真正還沒完成的部分。 +- 🧹 **同一批資料的多種格式不會重複佔用額度**:如果你的資料夾裡有同一份筆記 + 的好幾種格式(例如同時有 `.md`/`.json`/`.html`),現在只會挑一種送去整理, + 不會把同一份內容當三份處理。 +- 🌱 **全新安裝會自動準備一個可以隨時刪除的示範資料夾**:第一次連上知識庫時, + Documents 底下會出現一個「Arcrun 範例庫(可刪除)」,裡面已經放了幾份說明 + Arcrun 的筆記——不用自己找資料夾,馬上就能看到「丟檔案 → 變成知識卡 → 搜得到」 + 的完整過程。看不順眼可以直接刪掉,刪掉就真的沒了;一旦你自己加了資料夾, + 這個示範資料夾也不會再自動出現。 + +## v0.18.21(2026-08-06) + +- 🏪 **開始提供 Microsoft Store 版本(`.msix`)**:除了原本的 `.exe`, + 現在也一併發佈商店格式的安裝包,正在送審。 + 上架後 Windows 就不會再把 Arcrun 當成可疑程式攔下來(商店版由微軟簽章)。 + +## v0.18.20(2026-08-06) + +- 📋 **失敗清單改成「點開才看細節」**:以前把失敗原因直接攤在檔名旁邊, + 中文檔名被擠成一行一個字,很難讀。 + 現在只列**檔名**,旁邊一個三角形,需要時點開才看原因(預設收合)—— + 重點是先讓你看到「**哪幾個檔沒過**」。 + +## v0.18.19(2026-08-06) + +- ✏️ **失敗訊息改成人話**:上一版雖然列出了失敗的檔案,但寫的是 + 「上次失敗(第 5 次),5h38m38s 後重試(上面是自動重試的排程,真正的原因是前一次失敗)」 + ——**那是寫給工程師看的**。現在改成: + 「已經自動試過 5 次都沒成功,約 5 小時後會再試一次。」 + 知道原因時則直接講原因(例如「今天的免費 AI 額度用完了」),把重試排程放在後面當補充。 + 時間也不再精確到秒——你需要知道的是「大概多久」,不是 `5h38m38s`。 + +## v0.18.18(2026-08-06) + +- 🔴 **失敗時會告訴你「是誰的錯、你要不要做什麼」**:以前只寫「⚠ N 份失敗」, + 你分不出「等一下就會自己好」跟「這個檔永遠不會成功」——而這兩件事的處理方式完全相反。 + 現在會列出**檔名 + 原因**,例如: + - 「今天的免費 AI 額度用完了 ⇒ 明天會自動恢復,這些檔案會自己補上,你不用做什麼」 + - 「這份 PDF 看起來是掃描的圖片 ⇒ 需要先做文字辨識(OCR),或換一份有文字的版本」 +- 🧹 **不再把開發用的檔案放進你的資料夾**:以前小幫手會自動鋪一套開發者用的範本 + (`CLAUDE.md`、`scripts/`、`system-dev/` 共 37 個檔)到你看守的資料夾裡, + 把你的資料夾弄亂,而且**那些檔還會被當成你的知識收進知識庫**(於是多出你沒建過的「庫」)。 + 現在完全不放了;已經放進去的那些也**不會再被當成知識**。 + > 已經多出來的庫,可以在 Portal 的「庫目錄管理」自己按「移除」。 +- ✏️ 「有 N 個檔案沒有被整理」不再重複列出同一個檔名。 + +## v0.18.17(2026-08-06) + +**如果你看到「⚠ N 份失敗」卻不知道是哪一份,請更新這一版。** + +- 🔴 **失敗的檔案現在會列出檔名與原因**:以前首頁只寫「⚠ 3 份失敗」, + 你完全不知道是哪幾份、為什麼失敗,也就無從判斷要不要處理。 + 現在會直接列出**檔名 + 失敗原因**(最多 8 份,總數照實講)。 +- 🗂️ **出問題時可以一鍵打開紀錄檔資料夾**:同步引擎有狀況時, + 首頁會多一張「需要回報這個問題?」的卡,按一下就開啟存放紀錄檔的資料夾。 + (上一版說有這個功能,但按鈕**實際上沒做進畫面**,這版才真的有。) + +## v0.18.16(2026-08-06) + +**如果你看到「還沒連上知識庫」但明明已經連上了,請更新這一版。** + +- 🔴 **不再誤報「還沒連上知識庫」**:明明已經新增好知識庫帳號、側邊欄也看得到, + 首頁卻跳出「需要你處理一下 ⇒ 還沒連上知識庫」。 + 原因是判斷時看錯了地方(看的是舊版單一帳號的欄位,而你的是新版多帳號設定)。 +- 🔴 **順便修好那句話指錯路**:它叫你「點托盤選單的『+ 新增帳號…』」, + 但托盤選單早就只剩「結束 Arcrun」了,那個入口根本不存在。 + 現在會正確指向左邊「知識庫」下方的**新增知識庫帳號**。 + +## v0.18.15(2026-08-06) + +- 🪵 **診斷紀錄不再印出不存在的網址**:啟動訊息以前會寫成 + `→ /webhooks/named//rag_ingest/trigger`(沒有網域、還有雙斜線), + 看起來像設定壞掉——其實功能完全正常,只是那行字用錯了資料來源。 + 誤導性的紀錄比沒有紀錄更麻煩,會害人往錯的方向找問題。現在印的是真正會用到的網址。 + +## v0.18.14(2026-08-06) + +**第一次安裝的人請用這一版**——修好「剛裝好、連上知識庫之後卻不會開始整理」。 + +- 🔴 **連上知識庫後會立刻開始,不必重開程式**:以前**第一次安裝**的人會遇到—— + 打開程式、連上知識庫、加了資料夾,畫面卻一直說「同步引擎沒有在跑」, + 要**自己想到把程式關掉再打開**才會動。 + 原因是程式啟動時還沒有設定,就沒有把同步引擎準備好;之後設定好了也不會補啟動。 + 現在設定一完成就會自己開始。 +- ✏️ 順帶把「請結束 Arcrun 再重新開啟」這句話拿掉了—— + 那是把該由程式做的事推給你。現在會直接說「正在啟動同步引擎,請稍候」, + 還沒連知識庫時則說「按『新增知識庫帳號』就會開始」。 + +## v0.18.13(2026-08-06) + +**如果你更新到 v0.18.12 仍然看到「同步引擎一直啟動失敗」,請更新這一版。** + +- 🔴 **已經裝過舊版的人,設定檔現在會自動修好**:上一版只修好了「全新安裝」, + 但**已經存在的設定檔不會被修復**——程式打開時只是讀取它,不會重寫, + 所以錯誤原封不動(畫面仍寫「缺必填欄位:manifest」)。 + 現在程式一打開就會檢查並自動補齊,不需要你做任何事、也不用重裝。 + +## v0.18.12(2026-08-06) + +- 🗂️ **出問題時,一鍵打開紀錄檔資料夾**:同步引擎有狀況時,首頁會多出一張 + 「需要回報這個問題?」的卡片,按一下就直接開啟存放紀錄檔的資料夾 + (裡面的 `collector.log` 和 `app.log` 傳給我們就能查)。 + 卡片上也會直接寫出資料夾路徑,方便自己找。 + > 紀錄檔**一直都在寫**,不需要開啟任何「偵錯模式」;這次只是補上找得到的入口。 + +## v0.18.11(2026-08-06) + +**Windows 使用者請務必更新**——這版修好「裝了完全不會動」。 + +- 🔴 **修好「一直在看守中和沒有在跑之間閃、重開也沒用」**: + 這是**全新安裝**才會撞到的問題——設定檔少了一個必要欄位, + 同步引擎每次啟動都立刻失敗,程式就一直重試、畫面跟著閃。 + 連帶的症狀是**加了資料夾也完全沒反應**(因為同步引擎根本沒跑起來過)。 + 現在全新安裝就能正常運作。 + > ⚠️ **這一版只修好「全新安裝」**;已經裝過舊版的人**不會**被自動修好(見 v0.18.13)。 +- 🔍 順帶:同步引擎出問題時,畫面會**顯示真正的錯誤訊息**, + 而不是只寫「exit status 2」這種看不懂的東西。 + +## v0.18.10(2026-08-06) + +**Windows 使用者請更新**——這版讓「同步引擎沒在跑」說得出原因。 + +- 🔍 **「同步引擎沒有在跑」現在會說出原因**:以前畫面只會在「看守中」和「沒有在跑」 + 之間閃、叫你重新開啟(但重開沒有用,因為原因沒變)。 + 現在會直接寫「**同步引擎一直啟動失敗,已自動重試 N 次**」並附上錯誤訊息, + 而且畫面不再閃爍。同樣的訊息也會寫進記錄檔(`.arcrun-rag\app.log`),方便回報問題。 + +> ⏳ **還沒做到**:正式的安裝程式(開始功能表/解除安裝)**這版沒有**,仍是單一 `.exe`。 +> 打包工具在我們的機器上壞掉了,正在換路。上架 Microsoft Store 後會一併解決。 + +## v0.18.9(2026-08-06) + +**Windows 使用者請更新**——這版把「防毒軟體把 Arcrun 當成病毒刪掉」的機會降到最低。 + +- 🛡️ **不再有第二個檔案被寫到你的電腦裡**:上一版雖然只下載一個 `Arcrun.exe`, + 但它執行時會**自己解出一支同步引擎程式**放到硬碟再執行。 + 這個動作在防毒軟體眼中跟惡意程式很像,Windows Defender 因此把整個檔案隔離 + (封測者實際遇到,訊息是「檔案包含病毒或潛在的垃圾軟體」)。 + 現在同步引擎**直接內建在同一支程式裡**,不會再產生任何額外檔案。 +- 📦 檔案也順帶變小了(26MB → 22MB)。 +- 🔍 **「有些檔案沒有被整理」現在會說出是哪一個**:以前只寫「有 1 個不是文件的檔案」, + 你根本無從判斷那是什麼、也不知道自己是不是存錯格式了。現在會直接列出檔名(最多 5 個)。 +- ✏️ 同一張卡不再把同一件事講兩次(以前會先寫「看起來不是文件,所以跳過了」, + 底下空的,然後又寫「另外有 1 個…」)。 + +> ℹ️ 這版**不保證**防毒軟體不再誤判——真正的解法是上架 Microsoft Store +> (商店版由微軟簽章,不會被誤判),我們正在辦。 +> 如果還是被擋,請在「Windows 安全性 → 保護歷程記錄」把它選為「允許在裝置上」。 + +## v0.18.8(2026-08-06) + +**Mac 使用者請務必更新**——這版修好「關不掉、所以新版裝不上去」。 + +- 🔴 **「結束 Arcrun」現在真的會結束**:以前在選單列圖示按右鍵選「結束 Arcrun」, + 程式其實**還活著**,只是視窗被藏起來——唯一關得掉的方法是「強制結束」。 + 最直接的後果是**下載新版時蓋不過舊版**,不知道怎麼強制結束的人就卡在那裡了。 +- 🖥️ **Dock 不再出現重複的圖示**:常駐小工具照慣例只在選單列出現一個圖示, + 但 Arcrun 之前**選單列和 Dock 兩邊都有**。現在只留選單列那一個。 + (順帶:它也不會再出現在「強制結束」清單裡了——那是同一個原因造成的。) +- 🔳 **視窗可以全螢幕了**:Mac 版左上角的綠色按鈕之前是灰的、按不動(Windows 版正常)。 +- 📐 **視窗預設大小改成螢幕的 70%**(上一版的 50% 太小)。 + +## v0.18.7(2026-08-06) + +**Windows 使用者請務必更新**——這版把 Windows 版幾個「根本不能用」的問題一次修掉。 +Mac 使用者這版沒有功能變化,不更新也沒關係。 + +- 🪟 **下載下來只有一個檔案了**:以前是 zip,解開還有兩個 exe, + 很容易只拿走其中一個——而少拿那個,同步就永遠不會動,畫面還不會告訴你原因。 + 現在**直接下載一個 `Arcrun.exe`,雙擊就跑**,不必解壓縮、不必找第二個檔。 +- 🔴 **Windows 上「同步引擎沒有在跑」修好了**:以前不管同步引擎有沒有在跑, + 首頁**一律**顯示「同步引擎沒有在跑」——因為判斷方式用了一個 Windows 上根本不存在的指令。 + 現在會誠實反映真實狀態。 +- 🔴 **托盤不會再開出一堆圖示**:以前每點一次程式就多開一個, + 托盤會出現好幾個一模一樣的圖示,分不清該點哪個。 + 現在**第二次點只會把既有視窗叫回來**。 +- 🎨 **圖示換成 Arcrun 的標誌**:Windows 版的視窗與托盤圖示之前一直是開發工具的預設「W」, + 現在是正確的 Arcrun 標誌。 +- 📐 **視窗大小改成螢幕的一半**:以前寫死的尺寸在部分筆電上會高到超出畫面, + 現在會依你的螢幕自動調整並置中。 + +## v0.18.6(2026-08-06) + +**建議更新**——如果你曾經丟了檔案進資料夾、卻完全看不出發生什麼事,這版會直接告訴你原因。 + +- 🔴 **讀不了的檔案,現在會當場說出來**:以前丟一份**舊版 Word(.doc)、Keynote、Pages** + 這類檔案進資料夾,小幫手會**默默跳過、一個字都不說**——你只看到「我丟了檔,然後什麼都沒發生」, + 完全無從判斷是自己弄錯了、還是程式壞了。 + 現在首頁會直接列出**哪幾個檔讀不了、分別是什麼格式**,並告訴你不用重丟、支援之後會自動補上。 + 急著要的話,先用原本的軟體另存成 PDF 或 Word(.docx)放回同一個資料夾就行。 +- 📄 目前讀得了的格式:Word(.docx)、PowerPoint(.pptx)、Excel(.xlsx)、PDF、CSV、純文字、Markdown。 + +## v0.18.5(2026-08-05) + +**強烈建議更新**——如果你發現「語意搜尋什麼都搜不到」,或「同步明明做完了、首頁卻一直說等待中」,這版都修好了。 + +- 🔴 **語意搜尋搜不到東西,修好了**:上一版換了看得懂中文的新 AI 模型,但**分數門檻沒跟著調**, + 結果把正確答案全部擋掉——新上傳的檔案一律 0 筆,只有標題幾乎一字不差的舊檔偶爾才中。 + 現在門檻改成跟著模型走,搜得到了。 + (這是雲端的修復,請到 portal 設定頁按「檢查更新」。) +- 🔴 **首頁狀態不再說謊**:以前把檔案拖進資料夾,整個整理+上傳的過程首頁都顯示「等待中」, + 做完了也看不出來。現在**整理中會真的顯示「同步中」**,做完會顯示「幾點整理了幾份」, + 而且**不會過十幾秒就被清空**。 +- 🍎 **Mac 安裝畫面補上拖曳版面**:打開 DMG 會看到 Arcrun 和「應用程式」左右並排的大圖示, + 照著把左邊拖到右邊就裝好了。 + (**請務必拖進「應用程式」再開**——直接在「下載」資料夾裡開,之後會更新不了。) + +## v0.18.4(2026-08-05) + +**強烈建議更新**——如果你遇過「同步卡住不動」或「同一份檔案在知識庫裡出現很多次」,這版都修好了。 + +- 🔴 **一個檔案失敗不再拖住整個資料夾**:以前某個檔上傳失敗,小幫手會**不停重試同一個檔** + (實測撞了 1387 次、連續 11 小時),害其他檔全部排在後面。 + 現在失敗會**隔一段時間再試**(1 分鐘 → 5 分鐘 → 15 分鐘…),其他檔照常同步。 +- 🔴 **同一份檔不再重複堆積**:以前每同步一次就在知識庫多存一份副本 + (搜尋會出現 20 筆一模一樣的結果)。現在會先清掉舊的再存新的。 +- 🔴 **刪掉的檔案真的從知識庫移除**:以前只是「標記為已刪除」但資料還留著,越積越多。 +- 🎨 **全新桌面 App**:點小幫手圖示**直接開視窗**、每個知識庫**各自一頁**、可切換深/淺色。 +- 🍎 Mac 改用 DMG 安裝(開啟後把 Arcrun 拖進「應用程式」再開)。 +- 🪟 Windows 版同步更新(新介面 + 上述所有修復)。 + +> 💡 **雲端也要更新**:這版搭配的雲端修復包含「中文搜尋準確度大幅提升」 +> (換了看得懂中文的 AI 模型)。請到 portal 設定頁按「檢查更新」。 + +## v0.15.7(2026-08-04) + +**建議更新**——如果你之前更新失敗過,這版修好了。 + +- 🔴 **修好「更新完又跳回舊版」**:以前只有把 App 放在「應用程式」資料夾才更新得了; + 現在**放哪裡都能更新** +- 🍎 **Mac 改用 DMG 安裝**:打開就是「把 App 拖進應用程式」的標準畫面 +- 🪟 **Windows 新增 MSIX**:可以像正式軟體那樣安裝 +- App 更名為 `Arcrun` + +## v0.15.6(2026-08-04) + +**強烈建議更新**——這版把「要申請 Gemini 金鑰」這道門檻整個拿掉了。 + +- 🔴 **整理檔案改用雲端 AI,不再需要任何 API 金鑰** +- 舊設定會自動切換過去;原本填的 Gemini 金鑰會保留,想用可以在「AI 設定…」切回去 +- 整理速度快約 5 倍(約 3 秒/檔,之前約 17 秒) +- AI 問答也不再需要金鑰 + +## v0.15.5(2026-08-04) + +- 嘗試把預設改成雲端 AI(**但有 bug,實際沒生效**,請直接用 v0.15.6 以上) + +## v0.15.4(2026-08-04) + +- 托盤點多下不再產生多個圖示 +- 錯誤訊息改成白話,並指名去哪裡設定 + +## 更早的版本 + +封測初期版本,建議直接更新到最新版。 diff --git a/DAEMON_LINE b/DAEMON_LINE new file mode 100644 index 0000000..a4d2ace --- /dev/null +++ b/DAEMON_LINE @@ -0,0 +1 @@ +0.18 diff --git a/check-standalone.sh b/check-standalone.sh new file mode 100755 index 0000000..d0a5c22 --- /dev/null +++ b/check-standalone.sh @@ -0,0 +1,142 @@ +#!/usr/bin/env bash +# check-standalone.sh — 證明 `collector/` 沒有任何「往上伸」的相依(D95 第一輪②的驗收閘) +# +# 🔴 leo 2026-08-17:「身為管理者,你要從頭到尾**不要有很多扭曲**,因為你根本不記得 +# 你做的這些扭曲,**每次都要查**,很直接,源碼、產出物,從 stage 到 prod。」 +# +# ── 為什麼主證明不是 grep ────────────────────────────────────────────────── +# grep 只證明「我想得到的那幾種寫法沒出現」,證明不了「真的自足」。 +# 少想到一種寫法(`$(dirname $0)/../../..`、環境變數繞路、執行期才拼出來的路徑…) +# 就會拿到一個**假綠**——而假綠比紅還糟,因為它讓人停止檢查。 +# +# ⇒ **② 才是判準**:把 collector/ 的檔案單獨複製到一個臨時目錄 +# (那裡**沒有** docs-site/installer/landing/repo 根的任何東西, +# 也不是原 repo 的子目錄,`git rev-parse --show-toplevel` 只會指到它自己), +# 在那裡跑真正的版本計算與打包前置閘。 +# **跑得起來=真的自足**;有任何一條偷偷伸手到 repo 根,在那裡就會當場斷掉。 +# +# ① 只是**快篩**,抓兩個「出現在可執行位置就一定是錯」的字樣,讓回歸早一步紅。 +# +# ⚠️ 快篩必須避開一個假警報:collector 是**處理使用者知識庫**的程式, +# 它的原始碼與測試裡到處是 `system-dev/wiki/...` 這種字串——那是**使用者 vault 裡的路徑**, +# 不是本 repo 的目錄。拿目錄名當樣式掃會掃出上百筆雜訊,然後沒有人再看這份輸出。 +# ⇒ 快篩只認語法構造(`git rev-parse --show-toplevel`/`REPO_ROOT`),且**先剝掉註解與說明字串**。 +# +# 用法: +# bash collector/check-standalone.sh # 全部檢查 +# KEEP=1 bash collector/check-standalone.sh # 保留臨時目錄以便查看 +set -uo pipefail +cd "$(dirname "$0")" +COLLECTOR="$(pwd)" +FAIL=0 +ok(){ printf " ✅ %s\n" "$1"; } +ng(){ printf " ❌ %s\n" "$1"; FAIL=1; } + +# 受控檔案清單(含未提交、排除 gitignore)——與 daemon-version.py 算指紋的口徑一致。 +files_list() { git -C "$COLLECTOR" ls-files -co --exclude-standard . ; } + +echo "━━━ ① 快篩:可執行位置不得出現往 repo 根定位的構造 ━━━" +HITS="$(files_list | python3 -c ' +import io, sys, tokenize, re +from pathlib import Path + +BAD = [ + (re.compile(r"rev-parse\b[^|;)]*--show-toplevel"), "git rev-parse --show-toplevel"), + (re.compile(r"\bREPO_ROOT\b"), "REPO_ROOT"), +] +SELF = {"check-standalone.sh", "CHANGELOG.md"} + +def strip_py(src): + """用 tokenize 剝掉 Python 的註解與獨立字串(docstring)——說明文字裡提到不算違規。""" + out = {} + try: + toks = list(tokenize.generate_tokens(io.StringIO(src).readline)) + except Exception: + return {i + 1: l for i, l in enumerate(src.split("\n"))} + for t in toks: + if t.type in (tokenize.COMMENT, tokenize.STRING, tokenize.NL, tokenize.NEWLINE): + continue + out.setdefault(t.start[0], "") + out[t.start[0]] += t.string + " " + return out + +def strip_hash(src): + """`#` / `//` / ` * ` 行註解剝掉(sh、js、mjs、go)。""" + out = {} + for i, l in enumerate(src.split("\n"), 1): + s = l.split("#", 1)[0] if l.lstrip().startswith("#") else l + if s.lstrip().startswith(("//", "*", "/*")): + s = "" + s = re.sub(r"#.*$", "", s) if l.lstrip().startswith("#") else s + out[i] = s + return out + +for rel in (x.strip() for x in sys.stdin): + if not rel or Path(rel).name in SELF: + continue + p = Path(rel) + if not p.is_file() or p.suffix in (".png", ".ico", ".sum", ".md", ".json"): + continue + try: + src = p.read_text(errors="ignore") + except Exception: + continue + lines = strip_py(src) if p.suffix == ".py" else strip_hash(src) + for n, code in lines.items(): + for rx, why in BAD: + if rx.search(code): + print(f"{rel}:{n}: [{why}] {code.strip()[:100]}") +')" +if [ -n "$HITS" ]; then + ng "有往 repo 根定位的構造(下列都在可執行位置,不是註解)" + printf '%s\n' "$HITS" | sed 's/^/ /' +else + ok "沒有 git rev-parse --show-toplevel、沒有 \$REPO_ROOT" +fi + +echo +echo "━━━ ② 行為證明:把 collector/ 單獨搬出去,看它還算不算得出自己的版本 ━━━" +TMP="$(mktemp -d)" +trap '[ "${KEEP:-}" = "1" ] || rm -rf "$TMP"' EXIT +DEST="$TMP/collector-standalone" +mkdir -p "$DEST" +# 只複製受控檔案——build 產物、node_modules、dist 一律不帶(它們本來就不該影響自足性) +N=0 +while IFS= read -r f; do + [ -f "$COLLECTOR/$f" ] || continue + mkdir -p "$DEST/$(dirname "$f")" + cp "$COLLECTOR/$f" "$DEST/$f" + N=$((N+1)) +done < <(files_list) +echo " ℹ️ 臨時樹:$DEST($N 個檔,不在原 repo 底下)" +# 這裡是全新的 git repo——`git rev-parse --show-toplevel` 只會指到 $DEST 自己, +# 任何原本靠 repo 根才拿得到的東西都會在這裡消失。 +git -C "$DEST" init -q +git -C "$DEST" add -A +git -C "$DEST" -c user.email=x@y -c user.name=z commit -qm standalone + +APP="$DEST/cmd/arcrun-app" +VER="$("$APP/daemon-version.py" 2>/dev/null)" +if [ -n "$VER" ]; then ok "daemon-version.py 在獨立樹裡算得出版本:$VER" +else ng "daemon-version.py 在獨立樹裡算不出版本(還有東西往上伸):$("$APP/daemon-version.py" 2>&1 | head -2)"; fi + +FP="$(cd "$APP" && python3 -c " +import importlib.util +s=importlib.util.spec_from_file_location('dv','daemon-version.py') +m=importlib.util.module_from_spec(s); s.loader.exec_module(m) +print(m.source_fingerprint())" 2>/dev/null)" +[ -n "$FP" ] && ok "原始碼指紋算得出來:$FP" || ng "原始碼指紋算不出來" + +if (cd "$APP" && ./changelog-section.sh "$VER" --check >/dev/null 2>&1); then + ok "changelog-section.sh --check 通過(三支 build 腳本靠它擋『忘了寫更新內容』)" +else + ng "changelog-section.sh --check 失敗:$(cd "$APP" && ./changelog-section.sh "$VER" --check 2>&1 | head -2)" +fi + +NOTES="$(cd "$APP" && ./changelog-section.sh "$VER" 2>/dev/null | head -1)" +[ -n "$NOTES" ] && ok "更新說明投影得出來:${NOTES:0:36}…" || ng "更新說明投影不出來(daemon-notes.mjs 還在往上找)" + +echo +[ "$FAIL" = 0 ] && echo "✅ collector/ 自足:搬出去照樣算得出自己的版本號" \ + || echo "❌ collector/ 還有往上伸的相依(見上)" +exit "$FAIL" diff --git a/cmd/arcrun-app/__pycache__/daemon-version.cpython-314.pyc b/cmd/arcrun-app/__pycache__/daemon-version.cpython-314.pyc new file mode 100644 index 0000000..b17b4c5 Binary files /dev/null and b/cmd/arcrun-app/__pycache__/daemon-version.cpython-314.pyc differ diff --git a/cmd/arcrun-app/build-mac.sh b/cmd/arcrun-app/build-mac.sh index febeea0..2c155c0 100755 --- a/cmd/arcrun-app/build-mac.sh +++ b/cmd/arcrun-app/build-mac.sh @@ -14,7 +14,8 @@ VERSION="${VERSION:-$(./daemon-version.py --stamp)}" # 🔴 2026-08-06 機械閘:這一版的「更新內容」沒寫進 changelog ⇒ 不准打包。 # leo:「給版本號、本版更新內容、打包產品、顯示在前端,這一整串都應該是機械化」。 -# 單一真相源=docs-site/.../help/changelog.md;manifest.notes 與前端全部投影自它。 +# 單一真相源=collector/CHANGELOG.md;manifest.notes 與前端全部投影自它。 +# (2026-08-18 D95 第一輪:從 docs-site 搬進 collector/,daemon 才算得出自己的版本。) # 理由與格式見 changelog-section.sh。 ./changelog-section.sh "$VERSION" --check diff --git a/cmd/arcrun-app/build-msix.sh b/cmd/arcrun-app/build-msix.sh index 5e1be6d..8ce82c7 100755 --- a/cmd/arcrun-app/build-msix.sh +++ b/cmd/arcrun-app/build-msix.sh @@ -79,8 +79,25 @@ echo "🏷 msix 版本:${MSIX_VERSION}(來源 ${VERSION_RAW})" # 我卻每次都當成「只有他有」而開口問。**腳本自己去讀,不要再問人。** # (對應鐵律:`system-dev/wiki/credentials-map.md` —— 用任何金鑰前先查那張表; # 查得到就自己拿,不要說「我沒有」。) -ENV_FILE="${ENV_FILE:-$(cd "$(dirname "$0")" && git rev-parse --show-toplevel 2>/dev/null)/../../.env}" -if [[ -z "${IDENTITY_NAME:-}" && -f "$ENV_FILE" ]]; then +# +# 🔴 2026-08-18(D95 第一輪):原本是 `git rev-parse --show-toplevel`/../../.env +# ——那是**用本 repo 在磁碟上的位置**去推 InkStoneCo 頂層, +# collector/ 一旦搬成獨立 repo(或只是被 clone 到別處)就指到不存在的路徑。 +# 改成**往上找那個帶著這把鍵的 .env**:leo 08-06 的要求原樣保留 +# (腳本自己去讀、不再問人,金鑰的家仍是頂層那一份), +# 但**不再依賴目錄結構長什麼樣**。找不到就照舊落到 PLACEHOLDER 並印警告, +# 不會安靜出一顆送不了審的 msix。 +if [[ -z "${ENV_FILE:-}" ]]; then + _d="$(pwd)" # 腳本開頭已 cd 到自己的目錄 + while [[ "$_d" != "/" ]]; do + if [[ -f "$_d/.env" ]] && grep -qE '^MS_STORE_ARCRUN_APP_NAME=' "$_d/.env" 2>/dev/null; then + ENV_FILE="$_d/.env"; break + fi + _d="$(dirname "$_d")" + done +fi +ENV_FILE="${ENV_FILE:-}" +if [[ -z "${IDENTITY_NAME:-}" && -n "$ENV_FILE" && -f "$ENV_FILE" ]]; then # 只取這三個鍵,不把整包 .env 灌進環境(其他都是不該碰的金鑰) IDENTITY_NAME="$(grep -E '^MS_STORE_ARCRUN_APP_NAME=' "$ENV_FILE" | cut -d= -f2-)" PUBLISHER="$(grep -E '^MS_STORE_ARCRUN_APP_PUBLISHER=' "$ENV_FILE" | cut -d= -f2-)" diff --git a/cmd/arcrun-app/build-win.sh b/cmd/arcrun-app/build-win.sh index 34aeb69..6a131db 100755 --- a/cmd/arcrun-app/build-win.sh +++ b/cmd/arcrun-app/build-win.sh @@ -23,7 +23,7 @@ export PATH="$PATH:$(go env GOPATH)/bin" # 🔴 2026-08-06:版本號**不再手打、也不再靠 git describe**。 # 舊寫法 `git describe --tags` 在這個 repo 永遠拿不到 v0.18.x(一個 tag 都沒有) # ⇒ 版本只能靠打包時人工 `VERSION=v0.18.4 ./build-win.sh` 敲 ⇒ manifest 說謊的病根。 -# 現在由 daemon-version.py 從 DAEMON_LINE + collector/ 的 commit 數算出來, +# 現在由 daemon-version.py 從 collector/DAEMON_LINE + collector/CHANGELOG.md 算出來, # 兩條打包線(win/mac)在同一個 commit 上必得同一個號碼。理由全文見該腳本。 VERSION="${VERSION:-$(./daemon-version.py --stamp)}" BUILD_TIME="$(date '+%Y%m%d-%H%M')" @@ -31,7 +31,8 @@ echo "🏷 版本:${VERSION}(build ${BUILD_TIME})" # 🔴 2026-08-06 機械閘:這一版的「更新內容」沒寫進 changelog ⇒ 不准打包。 # leo:「給版本號、本版更新內容、打包產品、顯示在前端,這一整串都應該是機械化」。 -# 單一真相源=docs-site/.../help/changelog.md;manifest.notes 與前端全部投影自它。 +# 單一真相源=collector/CHANGELOG.md;manifest.notes 與前端全部投影自它。 +# (2026-08-18 D95 第一輪:從 docs-site 搬進 collector/,daemon 才算得出自己的版本。) # 理由與格式見 changelog-section.sh。 ./changelog-section.sh "$VERSION" --check diff --git a/cmd/arcrun-app/changelog-section.sh b/cmd/arcrun-app/changelog-section.sh index da75bd4..a641fe6 100755 --- a/cmd/arcrun-app/changelog-section.sh +++ b/cmd/arcrun-app/changelog-section.sh @@ -5,7 +5,11 @@ # # 答:改的是「**寫在哪**」,不是「誰來寫」。 # 文字一定得人寫(機器編不出「語意搜尋什麼都搜不到」這種人話), -# 但**只准寫一個地方**= docs-site/src/content/docs/help/changelog.md(用戶語言那份)。 +# 但**只准寫一個地方**= collector/CHANGELOG.md(用戶語言那份)。 +# 🔴 2026-08-18(D95 第一輪):這份檔案原本住在 repo 根的 +# `docs-site/src/content/docs/help/changelog.md`,而且**同時裝著兩條版本線** +# (桌面版 v0.18.x + 雲端引擎 1.4.x)。已拆開:桌面版的搬進 `collector/`, +# 雲端那條原地不動。理由:daemon 得往上伸手才拿得到自己的版本號=搬不成獨立 repo。 # 其餘全是投影、不准手改(=頂層 principles「投影不可手改,要改去改源頭」): # · manifest.daemon.notes ← 打包時由本腳本抽出 # · docs-site 的「版本說明」頁 ← 本來就是它自己 @@ -20,8 +24,10 @@ # ./changelog-section.sh v0.18.7 --check # 只檢查存不存在 set -euo pipefail cd "$(dirname "$0")" -REPO_ROOT="$(git rev-parse --show-toplevel)" -FILE="$REPO_ROOT/docs-site/src/content/docs/help/changelog.md" +# 🔴 一律以**自己的位置**定位(往上兩層=collector/),不問 git、不問 repo 根—— +# `git rev-parse --show-toplevel` 就是往上伸手的入口,剪掉它才搬得成獨立 repo。 +COLLECTOR="$(cd ../.. && pwd)" +FILE="$COLLECTOR/CHANGELOG.md" VERSION="${1:?用法:changelog-section.sh <版本,例 v0.18.7> [--check]}" CHECK_ONLY="${2:-}" @@ -39,7 +45,7 @@ if [ -z "$(printf '%s' "$SECTION" | tr -d '[:space:]')" ]; then cat >&2 <${esc(u.notes)}`, + * ① `esc()` ⇒ 任何 markdown 符號都會原樣露出來(leo 看到的 `**` 就是這樣來的) + * ② HTML 不保留換行(`.d` 沒有 white-space:pre)⇒ 塞 `\n` 進去也**不會**變成分行 + * ⇒ 唯一能讀的形狀就是**一行短句**。而 changelog 每條的 `**粗體標題**` 本來就是 + * 那條的一句話摘要——直接拿它,不必另外維護第二份文案(第二份必然漂移)。 + * + * ── 🔴 2026-08-18(D95 第一輪):搬進 collector/,且不再往上伸手 ────────── + * 本檔原本住在 `installer/scripts/`,讀的是 repo 根的 `docs-site/.../changelog.md`。 + * 那讓 **daemon 的更新說明投影器住在 daemon 之外**,而它讀的檔也在 daemon 之外 + * ⇒ `collector/` 沒辦法自己交出「這一版對用戶意味什麼」這句話。 + * + * 現在:實作住這裡,讀的是**同一棵樹裡的** `collector/CHANGELOG.md`(自我定位,不問 git)。 + * `installer/scripts/daemon-notes.mjs` 變成薄殼,轉呼叫本檔—— + * **根可以往內伸手,collector 不可以往外伸手**,方向是單向的。 + * + * 用法(collector 內部): + * import { notesForVersion } from './daemon-notes.mjs'; + * notesForVersion('v0.18.24') // → 一行字,或 null(changelog 沒這版) + */ +import { readFileSync, existsSync } from 'node:fs'; +import { join, resolve } from 'node:path'; + +/** daemon 的 changelog=`collector/CHANGELOG.md`。由本檔位置往上兩層推出來,不問 git、不問 repo 根。 */ +export const CHANGELOG_PATH = join(import.meta.dirname, '..', '..', 'CHANGELOG.md'); +/** 畫面上一行讀得完的上限。超過就截,並改叫使用者去看說明文件。 */ +export const NOTES_MAX = 100; +const TAIL = '(細節見說明文件)'; + +/** 把 markdown 行內語法剝成純文字——畫面不渲染 markdown,留著就是雜訊。 */ +export function stripMarkdown(s) { + return String(s) + .replace(/\[([^\]]*)\]\([^)]*\)/g, '$1') // [字](連結) → 字 + .replace(/[*_`]+/g, '') // 粗體/斜體/行內碼標記 + .replace(/\s+/g, ' ') // 換行與連續空白 → 單一空白 + .trim(); +} + +/** + * 從 changelog 取某一版的「一句話摘要」清單。 + * 規則:只認**頂層條目**(行首 `- `)的第一個粗體片段——那就是該條的標題。 + * 沒有粗體的條目退而取整行(截短),因為「有寫總比漏掉好」。 + */ +export function headlinesFor(changelogText, version) { + const lines = changelogText.split('\n'); + const start = lines.findIndex((l) => new RegExp(`^##\\s+${version.replace(/[.\\]/g, '\\$&')}(\\D|$)`).test(l.trim())); + if (start < 0) return null; + const out = []; + for (let i = start + 1; i < lines.length; i++) { + const l = lines[i]; + if (/^##\s/.test(l)) break; // 下一版開始 + if (!/^-\s/.test(l)) continue; // 只取頂層條目(續行、巢狀一律略過) + const body = l.replace(/^-\s*/, ''); + const bold = body.match(/\*\*([^*]+)\*\*/); + let h = stripMarkdown(bold ? bold[1] : body); + h = h.replace(/^[^\p{L}\p{N}「((]+/u, ''); // 去掉開頭的 emoji/符號 + h = h.replace(/[::,,。.]+$/, ''); // 去掉尾標點(要串接) + if (h) out.push(h); + } + return out; +} + +/** + * 組成畫面上那一行。回傳 null=changelog 裡沒有這一版(呼叫端該當成錯誤)。 + * `changelogPath` 只給測試/薄殼覆寫用;正常呼叫不帶,走 collector 自己的 CHANGELOG.md。 + */ +export function notesForVersion(version, changelogPath = CHANGELOG_PATH) { + if (!existsSync(changelogPath)) return null; + const heads = headlinesFor(readFileSync(changelogPath, 'utf8'), version); + if (!heads || !heads.length) return null; + + let line = heads.join('・'); + if (line.length > NOTES_MAX) { + // 截到「最後一個完整條目」為止,再掛尾巴——不要把句子切一半給使用者看。 + const kept = []; + for (const h of heads) { + if ([...kept, h].join('・').length + TAIL.length > NOTES_MAX) break; + kept.push(h); + } + line = (kept.length ? kept.join('・') : heads[0].slice(0, NOTES_MAX - TAIL.length)) + TAIL; + } + return line; +} + +/** + * 機械閘用:這一行本身可不可以送到使用者眼前? + * 回傳問題清單(空=通過)。這道閘存在的理由=**手寫的那一行沒有任何人檢查**。 + */ +export function checkNotes(notes) { + const problems = []; + const s = String(notes ?? ''); + if (!s.trim()) return ['manifest.daemon.notes 是空的——使用者按「檢查更新」看不到這版改了什麼']; + if (/[*_`#]|\]\(/.test(s)) { + problems.push(`manifest.daemon.notes 裡有 markdown 符號,畫面是純文字會原樣露出來:${JSON.stringify(s.slice(0, 60))}`); + } + if (/\n/.test(s)) { + problems.push('manifest.daemon.notes 有換行——畫面不保留換行(.d 沒有 white-space:pre),會擠成一坨'); + } + if (s.length > NOTES_MAX + TAIL.length) { + problems.push(`manifest.daemon.notes 太長(${s.length} 字,上限 ${NOTES_MAX + TAIL.length})——leo 08-08:「不要這麼長的散文,簡短講改了什麼,細節去 docs 讀」`); + } + return problems; +} + +// CLI:印出某版會顯示的那一行(出貨前想先看一眼時用) +// 🔴 2026-08-18:判斷「是不是直接跑本檔」要比**絕對路徑**,不能比檔名尾綴—— +// `installer/scripts/daemon-notes.mjs` 薄殼同名,用尾綴比會讓兩支 CLI 一起開火 +// (實撞:問雲端版號時本檔先 process.exit(1),薄殼根本沒機會查 docs-site)。 +if (process.argv[1] && resolve(process.argv[1]) === import.meta.filename) { + const v = process.argv[2]; + if (!v) { console.error('用法:node collector/cmd/arcrun-app/daemon-notes.mjs <版本,例 v0.18.24>'); process.exit(2); } + const line = notesForVersion(v); + if (!line) { console.error(`❌ changelog 沒有 ${v} 這一版(${CHANGELOG_PATH})`); process.exit(1); } + // stdout **只有那一行**——它會被別的腳本(changelog-section.sh)直接取用, + // 多印一個字就會被塞進 manifest。其餘一律走 stderr。 + console.log(line); + console.error(`(${line.length} 字)`); + const probs = checkNotes(line); + if (probs.length) { probs.forEach((p) => console.error('❌ ' + p)); process.exit(1); } + console.error('✅ 可以送到使用者眼前'); +} diff --git a/cmd/arcrun-app/daemon-version.py b/cmd/arcrun-app/daemon-version.py index 156c8bd..048f493 100755 --- a/cmd/arcrun-app/daemon-version.py +++ b/cmd/arcrun-app/daemon-version.py @@ -15,8 +15,24 @@ 但**每個 commit 都變成一版**(一天 5 個 commit 就跳 5 版), 而且每跳一版就要補一段 changelog——把機械化變成新的手工活。 +── 🔴 2026-08-18(D95 第一輪):所有輸入都搬進 collector/ 了 ──────────── + leo 08-17:「身為管理者,你要從頭到尾**不要有很多扭曲**,因為你根本不記得 + 你做的這些扭曲,**每次都要查**,很直接,源碼、產出物,從 stage 到 prod。」 + + 本腳本原本往 **repo 根**伸手拿三樣東西: + ① docs-site/src/content/docs/help/changelog.md(版本與更新說明) + ② ROOT/DAEMON_LINE(版本線) + ③ `git ls-files collector`(原始碼指紋,從 repo 根算) + ⇒ `collector/` 算不出自己的版本,也**沒有資格被搬成獨立 repo**。 + + 現在三樣全在 `collector/` 底下:`CHANGELOG.md`/`DAEMON_LINE`/指紋以 collector 為根。 + 本檔一律以**自己的位置**定位(`__file__` 往上兩層=collector/), + **不再呼叫 `git rev-parse --show-toplevel`**——那是往上伸手的入口。 + (三道既有修補全部原樣保留:指紋演算法版本作廢重記、帳本自我參照排除、 + 沒有產物的版號可重戳。只有「以什麼為根」變了。) + ── 現在的做法:版本由 changelog 決定,且不用手打數字 ────────────── - 單一真相源= docs-site/src/content/docs/help/changelog.md(用戶語言那份)。 + 單一真相源= collector/CHANGELOG.md(用戶語言那份)。 · 要出新版 ⇒ 在檔案最上面加一段標題 `## 下一版(未發佈)`,底下寫白話更新內容。 · 打包時本腳本把它**戳成正式版號**(上一版 patch + 1)並補上今天日期。 @@ -40,17 +56,21 @@ import subprocess import sys from pathlib import Path -HERE = Path(__file__).resolve().parent -ROOT = Path(subprocess.check_output( - ["git", "rev-parse", "--show-toplevel"], cwd=HERE, text=True).strip()) -CHANGELOG = ROOT / "docs-site/src/content/docs/help/changelog.md" -LINE_FILE = ROOT / "DAEMON_LINE" +HERE = Path(__file__).resolve().parent # collector/cmd/arcrun-app +# 🔴 daemon 的根=`collector/`,用**自己的位置**推出來,不問 git 也不問 repo 根。 +# 這一行就是「臍帶剪掉了」的本體:往上兩層剛好是 collector/,再往上一步都不走。 +COLLECTOR = HERE.parents[1] +CHANGELOG = COLLECTOR / "CHANGELOG.md" +LINE_FILE = COLLECTOR / "DAEMON_LINE" UNRELEASED = "## 下一版(未發佈)" # 版本 → 當時原始碼指紋。用來擋「同一個版號、不同的執行檔」。 SOURCE_LOCK = HERE / ".version-source.json" # 指紋演算法版本:改算法時 +1,帳本會自動作廢重記(見 check_or_record)。 -FINGERPRINT_ALGO = 3 +# 🔴 3 → 4(2026-08-18):指紋的**根**從 repo 根換成 collector/,被雜湊的相對路徑 +# 因此全部改變(`collector/cmd/…` → `cmd/…`)⇒ 舊帳本是「用不同單位量出來的數字」, +# 比對沒有意義。照既有設計改號讓它自動整本作廢重記,不要手改 JSON。 +FINGERPRINT_ALGO = 4 RELEASED_RE = re.compile(r"^## v(\d+)\.(\d+)\.(\d+)(", re.M) @@ -72,11 +92,17 @@ def source_fingerprint(): 會給出不同指紋、即使內容一模一樣——戳版號時檔案還沒提交(在 diff 裡), 提交後樹變了、diff 空了。結果只改 wiki 也被判「版號已對應另一份原始碼」。 改成對工作區檔案內容取值,與有沒有提交無關。 + + 四修(2026-08-18,D95 第一輪): + 改成**以 `collector/` 為根**列檔(cwd=COLLECTOR、pathspec `.`), + 不再從 repo 根列 `collector` 這個子目錄。涵蓋的檔案集合完全相同, + 差別只有相對路徑前綴——而路徑有進雜湊,所以 FINGERPRINT_ALGO 跟著 +1。 + 這樣 collector/ 搬成獨立 repo 之後,同一段程式碼算出的仍是同一個值。 """ try: files = subprocess.check_output( - ["git", "ls-files", "-co", "--exclude-standard", "collector"], - cwd=ROOT, text=True).split("\n") + ["git", "ls-files", "-co", "--exclude-standard", "."], + cwd=COLLECTOR, text=True).split("\n") except subprocess.CalledProcessError: return "" # 不在 git 裡就不擋(例如從 tarball 解出來 build) # 🔴 2026-08-06 三修:**帳本自己不能算進指紋**(經典的自我參照)。 @@ -86,11 +112,11 @@ def source_fingerprint(): # dist/ 也早就 gitignore,不在名單裡。真兇只有帳本。) # 只排除帳本一個檔——**不要順手把 build/ 整個排掉**, # 那會讓「換 app icon」不算原始碼變更,等於把閘挖個洞。 - skip = {str(SOURCE_LOCK.relative_to(ROOT))} + skip = {str(SOURCE_LOCK.relative_to(COLLECTOR))} h = hashlib.sha256() for rel in sorted(f for f in files if f.strip() and f not in skip): - fp = ROOT / rel + fp = COLLECTOR / rel if not fp.is_file(): continue # 已刪除的檔案 h.update(rel.encode())